Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does using an intermediate variable instead of array.length make your for-loop faster?

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:

Method one

6: iload_2 7: aload_1 8: arraylength 9: if_icmpge     18 12: iinc          2, 1 15: goto          6 18: return 

Method two

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?

like image 614
Jeroen Vannevel Avatar asked Aug 14 '15 13:08

Jeroen Vannevel


People also ask

What is the use of the length method in looping?

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.)

How do you find the length of an array in a for loop?

var len = nodes. length; for (var i = 0; i < len; i++) { ... }


1 Answers

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).

like image 137
Dragan Bozanovic Avatar answered Oct 01 '22 11:10

Dragan Bozanovic