JDK-8366043 : [lworld] (LIFE = Legacy Idiom For Equality) causes performance regressions
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: repo-valhalla
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2025-08-24
  • Updated: 2025-12-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
repo-valhallaUnresolved
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
LIFE (Legacy Idiom For Equality) is an optimization that makes a reference equality check before the "equals" method invocation to speed up the equality check.
Like it's implemented in the Objects.equals method:
 public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
 }

In Valhalla, such equality optimization causes performance regression. Reference check on value classes (and/or migrated classes) is expensive and really unnecessary.

1. SPECjbb2005 has -7% regressions.
The source: equality check for j.l.Integer inside j.u.HashMap.

2. A simple microbenchmark like:
    @Benchmark
    public OffsetDateTime odt_now() {
        return OffsetDateTime.now();
    }
has -30% performance regression.
The source: ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache
Comments
Separate comment: I would expect some regression risk from JDK-8366214 that has nothing to do with acmp or values. The risk comes from this source code replacement: a == b || a != null && a.equals(b) => Objects.equals(a,b) //runs a == b || a != null && a.equals(b) “Wait, that’s not a change!" you might say. But it is sometimes, because the expression a.equals(b) collects a type profile for a. A particular caller might be able to inline the call to .equals, if the call is effectively monomorphic. But the utility method Objects.equals cannot collect a useful profile (except in micros) since many types of objects will show up at the .equals call in that utility method. This problem can be helped, perhaps, by collecting argument typing information for either or both arguments of Objects.equals. It needs to be inlined to profit from such information, so a @ForceInline would not go amiss. We don’t do ad hoc argument profiling AFAIK, yet, but we should, for JDKJ methods like Objects.equals that we know about. They are (morally) built-in bytecodes. So they should get bytecode-like profiles. That means argument profiles, for particular named methods. To track this, I filed JDK-8373467 (some JDK methods should have ad hoc argument profiling).
10-12-2025

Fuller solution (both faster and more compatible) is probably JDK-8255024. If you either remove the acmp, or strength-reduce the acmp (to Unsafe::sameReference), you are putting pressure on the .equals invocation to DTRT. Only the VM knows if the .equals invocation DTRT. The idea of JDK-8255024 is to give a reward to .equals methods that DTRT (99% of them) by secretly spinning a version that omits the acmp call, and uses this special version only when the caller has previously called acmp on the same operands. That would be a JIT analysis and IR transform. The problem with Unsafe::safeReference is, what happens when it fails? Before you call .equals, you still have to check the value-bit and do the full acmp. Unless you trust .equals to DTRT. (Relatedly, acmp should get a vtable slot.)
10-12-2025

In the Option 2 direction, can C2 use available information to insert or use aacmp or oop comparison to speed up `equals` method? It has the refs and can easily compare them. Even value objects might have binary equal memory representations that would speed up simple cases.
26-08-2025

Ideal Solution (in terms of performance). 1. Create a new intrinsic: Unsafe.maybeEquals(Object x, Object y). If the intrinsic returns true, x and y are equal. Otherwise, the equality of x and y is unknown. The intrinsic performs oops comparison without paying attention to whether the class has an identity or not. 2. Rewrite classlib to use Unsafe.maybeEquals(x,y) instead of x==y in LIFE patterns. In such case the performance regression of the existing benchmarks will be 0%. There is another way to implement this: 1. Makes Objects.equals an intrinsic where this behavior is hidden inside. 2. Force all classlib classes to use Objects.equals (important for HashMap and ConcurrentHashMap)
24-08-2025

Clean Solution. Get rid of this optimization inside JDK classlib. Experiments have shown that if remove the reference equality check in the HashMap, SPECJbb2005 regression reduced to -1% value, which is within normal benchmark variance and could be considered as "acceptable" regression. Classlib cleanup could be implemented in two steps: 1. The first and most important areas: Objects.equals, HashMap, and ConcurrentHashMap. Cleaning these classes can fix 95%-98% of all known and potential regressions. 2. Cleaning the rest of classlib.
24-08-2025