Some ideas, most depending on the fact that there is at least some algorithmic relation between the objects
- Using indexed drawing. Maybe you can have a "big" predefined VBO, and simply need to update the indices.
- Using glBufferSubData(). Maybe some of the vertex data is the same, and some is not.
- Is it a setup similar to animation? Animation can be greatly improved by using bones. Vertices are then attached to bones, and you only have to move a few bones. The shader will then compute the new vertices from the bones data, which is a much smaller set. The technique can be extended to arbitrary complex transformations, if the number of vertices is fairly constant but have a mathematical relation to a smaller subset of data.
- Use a geometry shader to create the vertices you need.
Not everything is entirely accurate.
- Indexed drawing is very useful, but you should be careful not to abuse it. In order for a pipeline stage to run efficiently most data should be cacheable. Abusing indexed drawing may actually backfire by causing a lot of cache misses in the vertex shader (obviously this will happen only for relatively large vertex buffers).
- glBufferSubData is a double edged sword. It's really nice to be able to update parts of the buffer with one API call, but in many cases it actually ruins performance, because many synchronizations have to occur (basically you'll be stalling the hardware). The right way to do partial buffer updates is more complicated, and it involves using glMapBufferRange with GL_MAP_UNSYNCHRONIZED_BIT, while manually managing fences and of course doing it on two threads. Basically, avoid partial buffer updates whenever you can, and when you can't then try to split your buffers so you can. And if that fails then I don't envy you.
- Bones are great. Use them whenever you can. They have their limitations, and some coding is required to do them right, but supporting them is practically a must.
- If performance is your concern, avoid GS like the plague. There are some cases in which you won't loose performance by using GS. However, there are no cases in which it'll give you better performance than using the alternatives. This is mostly a hardware limitation, but (unfortunately) it's here to stay.
One good thing to note about GS is that it makes your code neater, shorter and cleaner. So if you can't really be bothered by some annoying performance issues, GS is awesome.