I spent the last two years more or less full time trying out ways of making interpreters of similar complexity go faster without making the same mistake.
One method that seems really promising so far is AOT tracing [0], making guarantees and removing redundant code ahead of time based on incomplete semantic models. It's different from a full compiler in that it only tries to improve the situation and will back out and leave code alone where the semantic model is insufficient. And since it acts as a transformation on VM code, it's fairly transparent and easy to debug. The complexity of the models is a three way compromise between faster tracing, faster code and implementation complexity.
Once the VM code is reasonably optimal, further compiling to C by generating the interpreter code inline becomes even more attractive. Which also leads to native executables as a bonus feature.