On Saturday, April 8, 2017 at 9:40:46 AM UTC-7, Kirk Pepperdine wrote:
>
>
> >>> 
> >>> - Your mySleep won't actually do what you think it does. The entire 
> method can be optimized away to nothing after inking at the call site by 
> the JIT once the calls to it actually warm up enough, since it has no side 
> effects and nothing is done with its return code. 
> >> 
> >> Well, this won’t happen in OpenJDK because of the return value. 
> > 
> > The return value "saves" you only as long as the method doesn't get 
> inlined. After it is inlined, the fact that the return value isn't used 
> allows the JIT to kill the entire code… 
>
> You’d think but not in my experience. 
>

Stock OpenJDK currently inlines and completely eliminates:

  public static int wasteSomeTime(int t) {
    int x = 0;
    for(int i = 0; i < t * 10000; i++) {
      x += (t ^ x) % 93;
    }
     return x;
  }

When called like this:

  wasteSomeTime(sleepArg);


So return values demonstrably don't prevent the optimization...


The optimization will not happen if inlining the method at the call site. 


I built a small set of jmh benchmarks to demonstrate this 
<https://github.com/giltene/GilExamples/blob/master/CodeGenExample-benchmarks/src/main/java/bench/MethodInliningExampleBench.java>.
 They result in this:


Benchmark                                             (benchLoopCount)  
(sleepArg)   Mode  Cnt           Score           Error  Units
MethodInliningExampleBench.noRetValIntLoop                      100000          
 1  thrpt    5  2830940580.903 ±  52900090.474  ops/s
MethodInliningExampleBench.noRetValIntLoopNoInlining            100000          
 1  thrpt    5        5500.356 ±       245.758  ops/s
MethodInliningExampleBench.retValIntLoop                        100000          
 1  thrpt    5  2877030926.237 ± 134788500.109  ops/s
MethodInliningExampleBench.retValIntLoopNoInlining              100000          
 1  thrpt    5           0.219 ±         0.007  ops/s



Which demonstrates that when inlining is **prevented** at the caller there 
is a real difference between having return value and not (the loop in the 
method gets optimized away only if there is no return value). But that when 
inlining is not prevented at the caller and the return value is not used, 
both cases get optimized away the same way. 

And since it is "hard" to reliably disallow inlining (without e.g. using 
Aleksey's cool @CompilerControl(CompilerControl.Mode.DONT_INLINE 
annotations in jmh), inlining can bite you and wreck your assumptions at 
any time...

Interestingly, as you can see from the same jmh tests above, while stock 
OpenJDK will optimize away the above code, it *currently* won't optimize 
away this code:

  public static long mySleepL1(long t) {
    long x = 0;
    for(int i = 0; i < t * 10000; i++) {
      x += (t ^ x) % 93;
    }
    return x;
  }

Which differs only in using longs instead of ints.

The results for the longs tests are:

Benchmark                                              (benchLoopCount)  (
sleepArg)   Mode  Cnt           Score           Error  Units
MethodInliningExampleBench.noRetValLongLoop                      100000     
      1  thrpt    5  2924098828.778 ± 234409260.906  ops/s 
MethodInliningExampleBench.noRetValLongLoopNoInlining            100000     
      1  thrpt    5           0.243 ±         0.013  ops/s 
MethodInliningExampleBench.retValLongLoop                        100000     
      1  thrpt    5           0.254 ±         0.014  ops/s 
MethodInliningExampleBench.retValLongLoopNoInlining              100000     
      1  thrpt    5           0.246 ±         0.012  ops/s



So the using longs seems to defeat some of the *current* OpenJDK 
optimizations. But how much would you want to bet on that staying the same 
in the next release? 

Similarly, *current* stock OpenJDK won't recognize that System.nanoTime() 
and System.currentTimeMillis() have no side effects, so the original 
example method:
 
    public static long mySleep(long t) {
        long x = 0;
        for(int i = 0; i < t * 10000; i++) {
            x += System.currentTimeMillis() / System.nanoTime();
        }
        return x;
    }

Will not optimize away at the call site on *current* OpenJDK builds.  But 
this can change at any moment as new optimizations and metadata about 
intrinsics are added in coming versions or with better optimizing JITs.

In all these cases, dead code *might* be removed. And whether or not it 
does can depend on the length of the run, the data you use, the call site, 
the phase of the moon 🌙 , or the version of the JDK or JIT that happens to 
run your code. Any form of comparison (between call sites, versions, etc.) 
with such dead code involved is flakey, and will often lead to "surprising" 
conclusions. Sometimes those surprising conclusions happen right away. 
Sometimes the happen a year later, when you test again using your 
previously established, tried-and-tested, based-on-experience tests that no 
longer do what you think they do...

E.g. I'm fairly sure OpenJDK will at some point (soon?) need to catch up 
optimizations on longs to match optimizations on ints in many cases (who 
uses ints anymore? except for array indexing), which will probably break a 
lot of benchmarks out there that may be inadvertantly relying on longs 
optimizations to not be happening.


-- 
You received this message because you are subscribed to the Google Groups 
"mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to