The "Performance Tips" section in the Android documentation has a pretty bold claim:
one()
is faster. It pulls everything out into local variables, avoiding the lookups. Only the array length offers a performance benefit.
where it refers to this code snippet:
int len = localArray.length; for (int i = 0; i < len; ++i) { sum += localArray[i].mSplat; }
This surprised me a lot because localArray.length
is just accessing an integer and if you'd use an intermediate variable, you'd have to do that exact same step again. Are we really saying that an intermediate variable that only has to go to x
instead of y.x
is faster?
I took a look over at this question which is about the same idea but uses an arraylist and its subsequent .size()
method instead. Here the concensus seemed to be that there would be no difference since that method call is probably just going to be inlined to an integer access anyway (which is exactly the scenario we have here).
So I took to the bytecode to see if that could tell me anything.
Given the following source code:
public void MethodOne() { int[] arr = new int[5]; for (int i = 0; i < arr.length; i++) { } } public void MethodTwo() { int[] arr = new int[5]; int len = arr.length; for (int i = 0; i < len; i++) { } }
I get the following bytecode:
public void MethodOne(); Code: 0: iconst_5 1: newarray int 3: astore_1 4: iconst_0 5: istore_2 6: iload_2 7: aload_1 8: arraylength 9: if_icmpge 18 12: iinc 2, 1 15: goto 6 18: return public void MethodTwo(); Code: 0: iconst_5 1: newarray int 3: astore_1 4: aload_1 5: arraylength 6: istore_2 7: iconst_0 8: istore_3 9: iload_3 10: iload_2 11: if_icmpge 20 14: iinc 3, 1 17: goto 9 20: return
They differ in the following instructions:
6: iload_2 7: aload_1 8: arraylength 9: if_icmpge 18 12: iinc 2, 1 15: goto 6 18: return
9: iload_3 10: iload_2 11: if_icmpge 20 14: iinc 3, 1 17: goto 9 20: return
Now, I'm not 100% sure how I have to interpret 8: arraylength
but I think that just indicates the field you're accessing. The first method loads the index counter and the array and accesses the arraylength
field while the second method loads the index counter and the intermediate variable.
I benchmarked the two methods as well with JMH (10 warmups, 10 iterations, 5 forks) which gives me the following benchmarking result:
c.m.m.Start.MethodOne thrpt 50 3447184.351 19973.900 ops/ms c.m.m.Start.MethodTwo thrpt 50 3435112.281 32639.755 ops/ms
which tells me that the difference is negligible to inexistant.
On what is the Android documentation's claim that using an intermediate variable in a loop condition, based?
length property per loop will introduce a slight amount of additional processing. On the flip side, the alternative introduces another variable. Also, if there's any chance the array changes during the iterations (e.g., the loop calls functions internally, etc.)
var len = nodes. length; for (var i = 0; i < len; i++) { ... }
You misunderstood the documentation. They are not referring to what you have described (although I don't blame you, they should've put more effort into those docs :)).
It pulls everything out into local variables, avoiding the lookups.
By avoiding the lookups they refer to field vs local variable access cost. Accessing the field (mArray
in the example in the docs) requires loading this
first, then loading the field based on the fixed offset from this
.
After a while, JIT will probably figure out what's going on and optimize the field access as well (if the field is not volatile
or some other form of synchronization happens in the loop) and rewrite the code so that all of the variables participating in the loop are accessed/changed in the CPU registers and caches until the end of the loop.
Generally, it could be potentially much more work for JIT to figure out whether it is safe to optimize the access to the length of the array referenced from a field compared to the reference stored in a local variable. Let's say we have the following loop:
for (int i = 0; i < array.length; ++i) { process(array[i]); }
If array
is a field and process
invokes thousands of lines of complex code, then JIT may find it difficult to check whether the array
field is changed somewhere in the loop to reference some other array which has a different length.
Obviously, it is much easier to check whether the local variable is changed in this case (three lines of code).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With