So why am I getting such a low frame rate? If everything has to be redrawn anyway then there's another problem in my code that I'm not seeing
You're making 40,000 draw calls every frame! The GPU can do a lot of stuff very fast, but pushing data from the CPU to the GPU is not one of them. Typically even a large AAA game would make no more than a few hundred draw calls in a frame. 40,000 is absolutely insane.
If you put all your cubes into one vertex buffer, you could get away with a single draw call per frame, and I'm sure your frame rate would be fine. Or you could use instancing.
But first, you should really find a book/website that teaches about the basic performance behaviors of modern GPUs.