Well, a high-performance Ruby VM will perform inline caching to mitigate this. And if the VM isn't a high-performance VM (i.e. it's interpreted), then the overhead of the interpreter dispatch loop dominates everything.
Well, a high-performance Ruby VM will perform inline caching to mitigate this. And if the VM isn't a high-performance VM (i.e. it's interpreted), then the overhead of the interpreter dispatch loop dominates everything.
Earlier indirect branch predictors didn't take much execution history into account, mostly associating a predicted branch target with the address of a branch instruction. With a predictor of this kind, using a distinct branch instruction for distinct execution paths dramatically improves the amount of context available to the branch predictor. The same remains true if you add a small amount of context to the predictor.
Over time, conditional branch predictors incorporated more branch history, e.g. the various designs like OGEHL / TAGE based on associating a branch instruction with multiple branch histories whose lengths form a geometric series. The same trick was quite successful when applied to indirect branches, although it is more difficult to implement because the result of a prediction is an address rather than a single bit indicating the branch direction.
You can see evaluations of various benchmarks against both actual Intel processors and simulations of the leading academic branch predictor designs here:
https://hal.inria.fr/hal-01100647/document
On Haswell and later (and with their model of ITTAGE) the mispredictions encountered by a switch-based interpreter loop are negligible.
That being said, I would still write an interpreter in assembly myself, because the code generators found in traditional compilers do a poor job of generating code for interpreters, and you can do a better job of deciding what work goes on the inline fast path and what goes on a slow path. When you're writing an interpreter by hand, you might as well use separate indirect branches for dispatch because there's no impedance mismatch like there is in C/C++.
Are you referring to the assembler in the GNU toolchaine or? Could you elaborate on this sentiment? Thanks.
For example, if the JIT has the code "function(a, b) { return a+b; }", and it sees that the function has been called 50 times with a and b as integers, it can generate a machine code implementation that is just a couple of instructions adding two registers.
If you keep the abstractions wide enough to support other values, you can never achieve this efficiency.
The language designer.
> If you're the JIT developer and you define a value encoding that supports all possible values (so that the JITed function never has to be rolled back), then you can't really optimize.
Obviously, the solution is to know the types of values ahead of time. This is precisely where a language with more precise abstractions can make the lives of implementors easier.
(0) They increase the effectiveness of code as a communication medium between programmers. To use an analogy: If I tell you a story, you don't need to hire actors to enact it, just to understand the plot. You can just use your knowledge of the English language, right? Imagine if we could do the same with code: programmers conversing in code, because they understand it natively, without having to “interpret” it (in a REPL, using a test suite, etc.).
(1) They decrease the burden of anticipating, preventing and handling things that may go wrong. Again, to use an analogy: Defensive programming in a language with imprecise abstractions is like spending 75% of a construction project's budget on buying insurance.
The fact that precise abstractions usually lend themselves to simpler and more elegant implementations than imprecise abstractions, well, it is just a nice additional benefit.