Should a local variable be introduced for an array element at a specific index which is accessed repeatedly?

Viewed 97

If an array element at a specific index is accessed repeatedly in a loop, should a local variable be introduced, in sense of performance ? Aka. does the array access by index bring overhead?


e.g

public void test(int[] arr) {
    for (int i = 0; i < (1 << 20); i++) {
        System.out.println(arr[0]);
    }
}
public void test2(int[] arr) {
    int first = arr[0];
    for (int i = 0; i < (1 << 20); i++) {
        System.out.println(first);
    }
}

Is test2() better than test(), in the sense of performance ?


Update - languages of interests

Golang, C, Java

2 Answers

The question is entirely dependent of the language used and more specifically of the toolchain used (compilers, JIT, interpreters, etc.). Since the provided code is in Java, I will consider the case of Java using a mainstream JVM like HotSpot for example.

Mainstream JVM implementations can optimize this themselves easily as long as the loop is a hot loop. Indeed, JVMs can know that arr[0] is a constant here. This is the case here, especially if the function is executed multiple times. Thus, like most of the time, it is not a problem and you should not care about such micro-optimizations unless you get a benchmark that shows it is actually a problem. The proposed optimization does not matter here because the println call will be several orders of magnitude slower than anything else in the loop.

Note however, when you have a lot of small loops and the code is executed only few time or very rarely, then the second code can be slightly faster. This reason is that the second code results in a bit less efficient bytecode that may not be directly optimized by the JVM due to the cost of compiling the bytecode to a fast native code (it as to find a trade-off).

Jérôme Richard's answer has the most important part in it: don't worry about this kind of micro-optimization unless/until you have a benchmark showing that it's important.

I'll answer from the Go and C sides in a different way though. The two bits of code have different meanings here. (I'm not really a Java programmer so I'll just refer to Aliasing in Java with Arrays and Reference Types for the Java variant of this point.) Let's also change the code so that we have a mystery function, rather than some known-to-do-nothing-but-print function:

/* C */
extern void f(int);
void test(int *arr) {
    int i;
    for (i = 0; i < (1 << 20); i++) {
        f(arr[0]);
    }
}
// Go
func test(arr []int, f func(int)) {
    for i := 0; i < (1 << 20); i++ {
        f(arr[0])
    }
}

Now let's consider a valid call to test. Here's part of the C-language implementation of f:

extern int A[];
void f(int arg) {
    /* do something with arg */
    A[0]++;
}

The call to test reads:

test(A);

That is, arr in test is A, and f() modifies A[0]. So each call to f() needs to pass a different integer value.

If you modify test to read:

/* C */
extern void f(int);
void test(int *arr) {
    int i;
    int arg = arr[0];
    for (i = 0; i < (1 << 20); i++) {
        f(arg);
    }
}

then suddenly each call to f passes the original value only from A[0]. So these programs have different meanings.

Go and C have similar aliasing rules. However, Go compilers can often "see further" than C compilers (because the compiler usually gets a better chance to do function inlining, if nothing else) and hence detect whether or not some aliasing may be taking place. It's easier, in a sense, for a Go compiler to grab the arr[0] value once outside the loop, if that's possible, than it is for the C compiler. That's not a function of the language itself: it's a function of the traditional ways that C and Go compilers have been written.

Still, the upshot of all this is that if you intend to pass the same value to your function every trip through the loop, you can write that as code by copying arr[0] to a local variable before running the loop. If you intend to allow arr[0] to be modified each trip through the loop, you can write that by writing the variant without a local variable—but it might also be wise to put in a comment, noting that the called function is intended to be able to modify the array element.

Write the code so that the reader can understand the intent first. Then, if and when it proves to be a bottleneck, write the code in some more-obscure-but-faster form, if that's possible and appropriate.

Related