Terrence Parr presented an introduction to virtual machines. He implemented a simple virtual machine and put the code in both Java and C varieties on GitHub:
I decided to implement the same virtual machine in one of the worst languages for such a task: Lua. Lua itself uses a virtual machine. Now we have a virtual machine inside another virtual machine. Furtheremore, Lua doesn’t have an integer type or the switch
statement. This means the default word size is actually a double and the dispatch loop is fairly verbose in comparison to the C-inspired languages. I tried a number of dispatch methods, including a dispatch table of functions and a giant series of if ... else
statements. While the dispatch table is more idiomatic Lua, it’s a bit slower.
Computed Goto
Then I forked the C implemention, added the CALL/RET opcodes and converted the giant switch statement into a computed goto. I expect the computed goto to offer a significant improvement in performance. Behold the hackish glory:
Performance
Compare the time to calculate factorial(12) one million times. Run on a Intel i5-3570K, average of five runs.
Simple virtual machine implementations:
luajit: 10.940s [13.7x]
java: 3.114s [3.90x]
c switch: 2.045s [2.56x]
c goto: 0.798s [1.00x]
Other virtual machines:
luajit 0.047s [0.06x]
java 0.331s [0.41x]
lua 1.591s [1.99x]
ruby 2.569s [3.22x]
python 4.175s [5.23x]
Performance is as expected. LuaJIT, which seems to compile every opcode, was not as fast as Java. But for a dynamic language, 3.5x slower isn’t bad. While Java has a world famous JIT compiler, this non-representative case shows it to be half the speed of the equivalent C. The computed goto is ugly and non-portable but, as expected, it is very fast. The code used to time the other languages is on gist.