javagarbage-collectionjvmloop-unrolling

Counted/Uncounted loops and Safepoints - is `while (++i < someInt)` considered uncounted loop?


I was reading this post on Counted/Uncounted loops and Safepoints.
What it tells me is that there will be safepoint polls for Uncounted loop, meaning Uncounted loop has worse performance than Counted loop.

In the blog there's this intersting code:

// 4. Should be counted, but treated as uncounted
int i = 0;
while (++i < reps) {
  // ...
}

// 5. Should be counted, but treated as uncounted
while (i++ < reps) {
  // ...
}

// 6. Should be counted, and is!
while (i < reps) {
  // ...
  i++;
}

Which basically says doing variable increment in while (be it i++ or ++i) will make your loop be treated as uncounted loop.
Given this, I suppose for performance critical code, I should avoid doing this?

Additional question, is the fact that JVM won't do safepoint polls to Counted loop because loop unrolling? Or JVM is smart enough to not do it even without loop unrolling?


Solution

  • The article looks outdated.
    In the modern JDK, all 6 loops mentioned in the post are considered counted:

    // 1
    for (int i = 0; i < reps; i++) { ... }
    
    // 2
    for (int i = 0; i < int_reps; i+=2) { ... }
    
    // 3
    for (long l = 0; l < int_reps; i++) { ... }
    
    // 4
    int i = 0;
    while (++i < reps) { ... }
    
    // 5
    while (i++ < reps) { ... }
    
    // 6
    while (i < reps) { ...; i++; }
    

    The loop #3 with the long variable became counted in JDK 16 (as a result of JDK-8223051). All other loops are treated as counted since JDK 8.

    I used the following program to verify this:

    public class LoopTest {
    
        public static void main(String[] args) throws Exception {
            int[] array = new int[1000];
            fill(array);
            System.out.println(Arrays.stream(array).sum());
        }
    
        static void fill(int[] array) {
            int reps = array.length;
    
            int i = 0;
            while (++i < reps) {
                array[i] = i * i;
            }
        }
    }
    

    Run debug build of JDK with the following options:

    java -Xcomp -XX:-TieredCompilation -XX:+TraceLoopOpts
         -XX:CompileCommand=compileonly,LoopTest::fill
         -cp ../test/out/production/test/ LoopTest
    

    The very first line of the TraceLoopOpts output will confirm that the loop is Counted:

    Counted        Loop: N140/N120  limit_check predicated counted [1,int),+1 (-1 iters)
    

    Additional question, is the fact that JVM won't do safepoint pools to Counted loop because loop unrolling?

    No, safepoint polls have nothing to do with loop unrolling.

    The statement that counted loops have no safepoint poll inside is also outdated. Loop strip mining implemented in JDK 10 allows JIT compiler to split large counted loops into two nested loops: the inner (hot) one without a safepoint poll instruction, and the outer - with a safepoint poll.

    E.g.

    for (int i = start; i < end; i++) {
       // loop
    }
    

    is transformed to something like

    for (int p = start; p < end; p += 1000) {
        for (int q = 0; q < 1000; q++) {
            int i = p + q;
            // loop
        }
        safepoint_poll();
    }
    

    This optimization decreases the overhead of safepoint polling, while keeping the time-to-safepoint latency small for an arbitrary large counted loop.

    The up-to-date HotSpot JVM is smart enough about optimizations of most kinds of counted loops, so you don't usually need to care about this yourself.