It's well known that an assignment expression results in a value which can in turn be used as the right-hand side of another assignment. This gives us the convenient abbreviation:
public class test {
public int x, y, z ;
public void f() {
x = y = z = 0 ;
}
}
for which the compiler produces this code:
aload_0 aload_0 aload_0 iconst_0 dup_x1 putfield thing/z I dup_x1 putfield thing/y I putfield thing/x I return
The sequence
aload_0 aload_0 iconst_0 dup_x1
can be replaced with
aload_0 iconst_0 dup2
where dup2 is used to duplicate the top two words on the stack,
with a saving of one byte. This results in the following code:
aload_0 aload_0 iconst_0 dup2 putfield thing/z I dup_x1 putfield thing/y I putfield thing/x I return
Applying the same trick again results in a further reduction to:
aload_0 iconst_0 dup2 putfield thing/z I dup2 putfield thing/y I putfield thing/x I return
The same technique could be applied for longer chains of assignments, but
I expect that chains of more than two assignments are likely to be rare.
As before rules have been included for other types of instructions to
push an integer on the stack: iconst, bipush,
sipush, ldc and iload. Also,
boolean variables have been handled as well as ints.
Testing these optimisations with a million executions of the assignments gives the following results:
Original Bytes Unoptimised Optimised
value saved time (ms) time (ms)
0 1 547 468
12 1 554 486
250 1 562 485
250,000 1 554 554
local int 1 572 499