1

I have a class which counts events. It looks like this:

public class Counter {
    private static final long BUCKET_SIZE_NS = Duration.ofMillis(100).toNanos();
    ...

    private long nextBucketNum() {
        return clock.getTime() / BUCKET_SIZE_NS;
    }

    public void count() {
       ...
       final long num = nextBucketNum();
       ...
    }
    ...
}

If I remove static modifier from the field (intending to make it a class parameter), the counting throughput degrades more than for 25% according to JMH report.

The generated bytecode for static case:

 INVOKEINTERFACE Clock.getTime ()J (itf)
 GETSTATIC Counter.BUCKET_SIZE_NS : J
 LDIV

And for non-static one:

INVOKEINTERFACE Clock.getTime ()J (itf)
ALOAD 0
GETFIELD Counter.BUCKET_SIZE_NS : J
LDIV

Am I doing performance test wrong experiencing some sort of dead-code elimination or is it some legitimate micro-optimization at some level like JIT or Hyperthreading?

The difference exists both in single-theaded and multi-threaded benchmarks.

Environment:

JMH version: 1.34
VM version: JDK 1.8.0_161, Java HotSpot(TM) 64-Bit Server VM, 25.161-b12

macOS Monterey 12.2.1

Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
viator
  • 1,413
  • 3
  • 14
  • 25
  • static final fields are more conducive to optimisation because they're guaranteed to have one value across your whole program. Why are you initialising `BUCKET_SIZE_NS` with an expression involving a method call, instead of a literal constant that can be evaluated at compile-time? – khelwood Mar 24 '22 at 17:42
  • No, I'm 100% on board with initializing it with a method call that explains clearly what it's doing and doesn't depend on getting constants right at code writing time. The JIT should inline it at runtime after evaluating it once. – Louis Wasserman Mar 24 '22 at 17:49
  • 2
    Perhaps with `static`, it's evaluated once at compile time, and the JIT does a better job at optimizing division by it into a multiplicative inverse? ([Why does GCC use multiplication by a strange number in implementing integer division?](https://stackoverflow.com/q/41183935)) But without `static`, it ends up being a runtime-variable if the compiler doesn't fully constant-propagate through the initializer? So it might end up using an actual x86-64 `div` instruction after JITing to machine code. (A smarter JIT compiler could maybe do better in theory, but JIT missed optimizations are common) – Peter Cordes Mar 24 '22 at 19:01
  • A static field has a fixed address. An instance field's address is relative to`this`, so accessing it requires an indirection. No surprise here. – user207421 Mar 25 '22 at 01:14
  • *"Perhaps with static, it's evaluated once at compile time ... "*. Not perhaps. This applies to "constant variables" [JLS 4.12.4](https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.4) – Stephen C Mar 25 '22 at 01:33
  • 1
    But the OP's example is not a constant variable because the expression does not qualify as a constant expression (per the spec). So any optimization here is solely the work of the JIT compiler. – Stephen C Mar 25 '22 at 02:02
  • Could you share the code of the benchmark in your question? – Sergey Tsypanov Mar 28 '22 at 13:39

2 Answers2

1

The JVM optimizes static final fields as true constants, but it doesn't do the same for instance fields. In theory, the code could be analyzed and proven to show that the field is always the same, but that's more complicated. In addition, final fields aren't treated as truly final because of the reflection backdoor. There's a Jira item which tracks this issue, but I cannot find it right now. Internally, the JDK uses a special @Stable annotation to optimize accesses to final instance fields.

But even if you could use this annotation, extra analysis would still required to prove that the field is the same for all instances. In most cases, the code which assigns the field needs to be fully inlined for the analysis to work. What if the Duration.ofMillis call was implemented to return a random number? Of course it's not, but without the analysis, how could the compiler be certain?

boneill
  • 1,478
  • 1
  • 11
  • 18
  • This is a bit inaccurate. 1) The compile time "static final field" optimization only applies when the value is a "constant expression" as defined in the spec. 2) Using reflection to change a `final` field has has behavior that is not specified. Particularly in the case you are talking about; see [JLS 17.5.3](https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html#jls-17.5.3) – Stephen C Mar 25 '22 at 01:40
  • I believe you're right with respect to the specification. However, in practice the static final fields do get optimized. This is essential for optimizing things like VarHandles and MethodHandles. A simple test can be performed to see if modifying a static final field via reflection has any effect. According to the `setAccessible` docs, a static final field cannot be modified. – boneill Mar 25 '22 at 01:46
  • 3) The `@Stable` annotation is an internal class. It is unclear (and certainly unspecified) what would happen if you tried to use it in application code. – Stephen C Mar 25 '22 at 01:46
  • Yes, that's why I wrote, "...even if you could use...". The documentation of the `@Stable` annotation does say what would happen if you tried -- it won't work: "Annotations on fields of classes loaded outside of the boot loader are ignored." – boneill Mar 25 '22 at 01:56
  • *"According to the setAccessible docs, a static final field cannot be modified."* - Prior to Java 16 (I think) you *could* call `setAccessible` on a non-modifiable field ... but the effects were undefined. The rules (in the javadoc) have been being progressively made more strict (starting in Java 9). But the Java language spec is more vague about this. – Stephen C Mar 25 '22 at 01:58
  • (I just noticed that the OP is using Java 8 for his benchmarking ... so mention of Java versions is relevant.) – Stephen C Mar 25 '22 at 02:05
  • Good point regarding Java 8. It's been my experience that static final optimizations have been in place long before they have become more spelled out in the docs. – boneill Mar 25 '22 at 02:12
  • 1
    @StephenC you still can call `setAccessible`, but that doesn’t imply that you can change the field. This method serves different purposes 1) allow to ignore modifiers like `private`, etc. 2) allow to change `final` instance fields. The latter always was restricted to instance fields (implied by JLS §17.5.3.). The now-non-working hacks used by some to circumvent this restriction were much deeper. For the record (pun intended), some final instance fields are nowadays also not modifiable through Reflection. – Holger Mar 25 '22 at 08:32
1

There are 2 optimizations at play here:

  • Constant folding: The static final field is pre-computed and written into the code blob (the end result of JIT compilation). This is will translate into a performance win compared to a memory load (when reading the field).
  • Arithmetic simplification: When dividing by a potentially variable quantity, the compiler has to use a division instruction which is super expensive. When dividing by a constant, the compiler can come up with a cheaper alternative. This is particularly true when dividing (and multiplying) by powers of 2 which can be simplified into shift instructions.

To look further into this I would recommend you run your benchmark with perfasm and see where the cycles went and what assembly code was generated.

Happy hunting!

Nitsan Wakart
  • 2,841
  • 22
  • 27