Re: Java (bytecode) execution speed
Lee wrote:
[...]
Compilation of the byte code would be nothing more or less than
re-implementing a portion of the string virtual machine. So that makes
no sense to me, as presumably you've done it right the first time when
you implemented the string virtual machine for that hardware in the
first place.
For one thing, the virtual-to-native compilation can
eliminate all the decoding of the virtual instructions. A
straightforward interpreter will fetch a virtual instruction,
fiddle with it for a while, and dispatch to an appropriate
sequence of actual instructions that accomplish the virtual
instruction's mission. It may amount to only a few masks, a
few tests, and a big switch construct, but the interpreter
goes through it on every virtual instruction. Once the code
is compiled to native instructions, all the decoding and
dispatching simply vanishes: it was done once, by the compiler,
and need never be done again.
Another effect is that the virtual instructions are quite
often more general than they need to be for particular uses.
Stepping away from your two-instruction string machine for a
moment, let's suppose you've got a virtual instruction that adds
two integers to form their sum. The interpreter probably fetches
operand A, fetches operand B, adds them, and stores the sum in
target C. Well, the virtual-to-native compiler might "notice"
that A,B,C are the same variable, which the program adds to itself
in order to double it. The generated native machine code is then
quite unlikely to do two fetches: one will suffice, followed by
a register-to-register add or a left shift or some such. Not only
that, but the compiler may further notice that C is immediately
incremented after doubling, so instead of storing C and fetching
it back again for incrementation, the native machine code says
"Hey, I've already got it in this here register" and eliminates
both the store and the subsequent fetch.
[...]
So, a JVM could invoke a JIT to translate frequently-executed code
into a suitable binary format that the OS can execute.
Which means that the implementation of any given Java machine language
primitive is dynamically altered at run time. Eek! Can that be true?
I see no problem there.
You dont? The native hardware instructions that find the head of a
string are re-invented every time somebody does the "head" operation?
Can that be right?
Could be. The virtual-to-native compiler has the advantage
of being able to see the context in which a virtual instruction
is used, and may be able to take shortcuts, as in the instruction-
combining example above. As an example of a JVM-ish application
of this sort of thing, consider compiling `x[i] += x[i];', our
familiar doubling example but this time with arrays. Formally
speaking, each array reference requires a range check -- but the
JIT may notice that if the left-hand side passes the range check,
there is no need to do it a second time on the right-hand side.
Even better, the JIT may notice common patterns like
for (int i = 0; i < x.length; ++i)
x[i] += x[i];
.... and skip the range checking entirely.
A viewpoint you may find helpful, if a little wrenching at
first, is to think of the virtual instruction set as the elements
of a low-level programming language. You could, with sufficient
patience, write Java bytecode by hand, but it might be easier to
write Java and use javac to generate bytecode from it. Either
way, the bytecode is just an expression of a program, written in
a formal language, and there's no reason a translator couldn't
accept that formal language as its "source" for compilation.
--
Eric Sosman
esosman@acm-dot-org.invalid