1 /*
  2  * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.bench.valhalla.acmp;
 24 
 25 import org.openjdk.jmh.annotations.Benchmark;
 26 import org.openjdk.jmh.annotations.BenchmarkMode;
 27 import org.openjdk.jmh.annotations.CompilerControl;
 28 import org.openjdk.jmh.annotations.Fork;
 29 import org.openjdk.jmh.annotations.Measurement;
 30 import org.openjdk.jmh.annotations.Mode;
 31 import org.openjdk.jmh.annotations.OperationsPerInvocation;
 32 import org.openjdk.jmh.annotations.OutputTimeUnit;
 33 import org.openjdk.jmh.annotations.Scope;
 34 import org.openjdk.jmh.annotations.Setup;
 35 import org.openjdk.jmh.annotations.State;
 36 import org.openjdk.jmh.annotations.Warmup;
 37 
 38 import java.util.concurrent.TimeUnit;
 39 
 40 @Fork(value = 3, jvmArgsAppend = {"--enable-preview"})
 41 @Warmup(iterations = 3, time = 1)
 42 @Measurement(iterations = 5, time = 1)
 43 @OutputTimeUnit(TimeUnit.NANOSECONDS)
 44 @BenchmarkMode(Mode.AverageTime)
 45 @State(Scope.Thread)
 46 public class FastPath {
 47     public static final int SIZE = 100;
 48 
 49     @CompilerControl(CompilerControl.Mode.DONT_INLINE)
 50     private static int acmp(Object[] objects1, Object[] objects2) {
 51         int s = 0;
 52         for (int i = 0; i < SIZE; i++) {
 53             if (objects1[i] == objects2[i]) {
 54                 s += 1;
 55             } else {
 56                 s -= 1;
 57             }
 58         }
 59         return s;
 60     }
 61 
 62 
 63     // Homogeneous arrays, all go to fast path, all pairwise equal
 64     @Benchmark
 65     @OperationsPerInvocation(SIZE)
 66     @CompilerControl(CompilerControl.Mode.INLINE)
 67     public int homogeneous_eq(ObjStateHomogeneousAllEq st) {
 68         return acmp(st.arr1, st.arr2);
 69     }
 70 
 71     // Homogeneous arrays, all go to fast path, all pairwise unequal
 72     @Benchmark
 73     @OperationsPerInvocation(SIZE)
 74     @CompilerControl(CompilerControl.Mode.INLINE)
 75     public int homogeneous_neq(ObjStateHomogeneousAllNeq st) {
 76         return acmp(st.arr1, st.arr2);
 77     }
 78 
 79     // Homogeneous arrays, all go to fast path
 80     @Benchmark
 81     @OperationsPerInvocation(SIZE)
 82     @CompilerControl(CompilerControl.Mode.INLINE)
 83     public int homogeneous_eq_neq(ObjStateHomogeneousEqNeq st) {
 84         return acmp(st.arr1, st.arr2);
 85     }
 86 
 87     // Heterogeneous arrays, all go to fast path, all pairwise equal
 88     @Benchmark
 89     @OperationsPerInvocation(SIZE)
 90     @CompilerControl(CompilerControl.Mode.INLINE)
 91     public int heterogeneous_eq(ObjStateHeterogeneousAllEq st) {
 92         return acmp(st.arr1, st.arr2);
 93     }
 94 
 95     // Heterogeneous arrays, all go to fast path, all pairwise unequal but same type
 96     @Benchmark
 97     @OperationsPerInvocation(SIZE)
 98     @CompilerControl(CompilerControl.Mode.INLINE)
 99     public int heterogeneous_neq(ObjStateHeterogeneousAllNeq st) {
100         return acmp(st.arr1, st.arr2);
101     }
102 
103     // Heterogeneous arrays, all go to fast path, all pairwise of same type
104     @Benchmark
105     @OperationsPerInvocation(SIZE)
106     @CompilerControl(CompilerControl.Mode.INLINE)
107     public int heterogeneous_eq_neq(ObjStateHeterogeneousEqNeq st) {
108         return acmp(st.arr1, st.arr2);
109     }
110 
111     // Heterogeneous arrays, all cases with same types go to fast path, using migrated classes
112     @Benchmark
113     @OperationsPerInvocation(SIZE)
114     @CompilerControl(CompilerControl.Mode.INLINE)
115     public int numeric_classes(ObjStateNumeric st) {
116         return acmp(st.arr1, st.arr2);
117     }
118 
119     // Heterogeneous arrays, all cases with same types go to fast path, using single field user-defined classes
120     @Benchmark
121     @OperationsPerInvocation(SIZE)
122     @CompilerControl(CompilerControl.Mode.INLINE)
123     public int custom_single_field(ObjStateSingleField st) {
124         return acmp(st.arr1, st.arr2);
125     }
126 
127     // Heterogeneous arrays, all cases with same types go to fast path, using multi-field user-defined classes
128     @Benchmark
129     @OperationsPerInvocation(SIZE)
130     @CompilerControl(CompilerControl.Mode.INLINE)
131     public int custom_multi_field(ObjStateMultiField st) {
132         return acmp(st.arr1, st.arr2);
133     }
134 
135     // Heterogeneous arrays, nothing goes to fast path because the payload is too big for fast path
136     @Benchmark
137     @OperationsPerInvocation(SIZE)
138     @CompilerControl(CompilerControl.Mode.INLINE)
139     public int too_big(ObjStateTooBig st) {
140         return acmp(st.arr1, st.arr2);
141     }
142 
143     // Heterogeneous arrays, nothing goes to fast path because the payload contains an oop
144     @Benchmark
145     @OperationsPerInvocation(SIZE)
146     @CompilerControl(CompilerControl.Mode.INLINE)
147     public int with_oops(ObjStateWithOop st) {
148         return acmp(st.arr1, st.arr2);
149     }
150 
151     // Arrays of empty value objects, all equal, and fast path
152     @Benchmark
153     @OperationsPerInvocation(SIZE)
154     @CompilerControl(CompilerControl.Mode.INLINE)
155     public int empty_objects(ObjStateEmpty st) {
156         return acmp(st.arr1, st.arr2);
157     }
158 
159     // Arrays of empty value objects, all equal, and fast path
160     @Benchmark
161     @OperationsPerInvocation(SIZE)
162     @CompilerControl(CompilerControl.Mode.INLINE)
163     public int empty_objects_heterogeneous(ObjStateHeterogeneousEmpty st) {
164         return acmp(st.arr1, st.arr2);
165     }
166 
167     // All is identity objects
168     @Benchmark
169     @OperationsPerInvocation(SIZE)
170     @CompilerControl(CompilerControl.Mode.INLINE)
171     public int identity_objects(ObjStateIdentity st) {
172         return acmp(st.arr1, st.arr2);
173     }
174 
175     // Big mix of fast path value classes, no fast path value classes, identity classes and null
176     @Benchmark
177     @OperationsPerInvocation(SIZE)
178     @CompilerControl(CompilerControl.Mode.INLINE)
179     public int big_mix(ObjStateBigMix st) {
180         return acmp(st.arr1, st.arr2);
181     }
182 
183 
184     @State(Scope.Thread)
185     public abstract static class ObjStateHomogeneous {
186         Object[] arr1, arr2;
187 
188         public void setup(int mode) {
189             arr1 = new Object[SIZE];
190             arr2 = new Object[SIZE];
191 
192             for (int i = 0; i < SIZE; i++) {
193                 switch (mode) {
194                     case 0:
195                         arr1[i] = new Integer(i);
196                         arr2[i] = new Integer(i);
197                         break;
198                     case 1:
199                         arr1[i] = new Integer(i);
200                         arr2[i] = new Integer(-i);
201                         break;
202                     case 2:
203                         arr1[i] = new Integer(i);
204                         if (i % 2 == 0) {
205                             arr2[i] = new Integer(i);
206                         } else {
207                             arr2[i] = new Integer(-i);
208                         }
209                         break;
210                 }
211             }
212         }
213     }
214 
215     public static class ObjStateHomogeneousAllEq extends ObjStateHomogeneous {
216         @Setup
217         public void setup() {
218             setup(0);
219         }
220     }
221 
222     public static class ObjStateHomogeneousAllNeq extends ObjStateHomogeneous {
223         @Setup
224         public void setup() {
225             setup(1);
226         }
227     }
228 
229     public static class ObjStateHomogeneousEqNeq extends ObjStateHomogeneous {
230         @Setup
231         public void setup() {
232             setup(2);
233         }
234     }
235 
236     @State(Scope.Thread)
237     public abstract static class ObjStateHeterogeneous {
238         Object[] arr1, arr2;
239 
240         public void setup(int mode) {
241             arr1 = new Object[SIZE];
242             arr2 = new Object[SIZE];
243 
244             for (int i = 0; i < SIZE; i++) {
245                 if (i % 2 == 0) {
246                     switch (mode) {
247                         case 0:
248                             arr1[i] = new Integer(i);
249                             arr2[i] = new Integer(i);
250                             break;
251                         case 1:
252                             arr1[i] = new Integer(i);
253                             arr2[i] = new Integer(-i);
254                             break;
255                         case 2:
256                             arr1[i] = new Integer(i);
257                             if (i % 4 == 0) {
258                                 arr2[i] = new Integer(i);
259                             } else {
260                                 arr2[i] = new Integer(-i);
261                             }
262                             break;
263                     }
264                 } else {
265                     switch (mode) {
266                         case 0:
267                             arr1[i] = new Short((short) i);
268                             arr2[i] = new Short((short) i);
269                             break;
270                         case 1:
271                             arr1[i] = new Short((short) i);
272                             arr2[i] = new Short((short) -i);
273                             break;
274                         case 2:
275                             arr1[i] = new Short((short) i);
276                             if (i % 4 == 1) {
277                                 arr2[i] = new Short((short) i);
278                             } else {
279                                 arr2[i] = new Short( (short) -i);
280                             }
281                             break;
282                     }
283                 }
284             }
285         }
286     }
287 
288     public static class ObjStateHeterogeneousAllEq extends ObjStateHeterogeneous {
289         @Setup
290         public void setup() {
291             setup(0);
292         }
293     }
294 
295     public static class ObjStateHeterogeneousAllNeq extends ObjStateHeterogeneous {
296         @Setup
297         public void setup() {
298             setup(1);
299         }
300     }
301 
302     public static class ObjStateHeterogeneousEqNeq extends ObjStateHeterogeneous {
303         @Setup
304         public void setup() {
305             setup(2);
306         }
307     }
308 
309     @State(Scope.Thread)
310     public static class ObjStateNumeric {
311         Object[] arr1, arr2;
312 
313         @Setup
314         public void setup() {
315             arr1 = new Object[SIZE];
316             arr2 = new Object[SIZE];
317 
318             for (int i = 0; i < SIZE; i++) {
319                 switch (i % 6) {
320                     case 0:
321                         arr1[i] = new Integer(i);
322                         arr2[i] = new Integer(i);
323                         break;
324                     case 1:
325                         arr1[i] = new Integer(i);
326                         arr2[i] = new Integer(-i);
327                         break;
328                     case 2:
329                         arr1[i] = new Integer(i);
330                         arr2[i] = new Short((short)i);
331                         break;
332                     case 3:
333                         arr1[i] = new Short((short)i);
334                         arr2[i] = new Integer(i);
335                         break;
336                     case 4:
337                         arr1[i] = new Short((short)i);
338                         arr2[i] = new Short((short)i);
339                         break;
340                     case 5:
341                         arr1[i] = new Short((short)i);
342                         arr2[i] = new Short((short)-i);
343                         break;
344                 }
345             }
346         }
347     }
348 
349     static value class CustomShort {
350         short s;
351         CustomShort(int s) {
352             this.s = (short)s;
353         }
354     }
355     static value class CustomChar {
356         char c;
357         CustomChar(int c) {
358             this.c = (char)c;
359         }
360     }
361     @State(Scope.Thread)
362     public static class ObjStateSingleField {
363         Object[] arr1, arr2;
364 
365         @Setup
366         public void setup() {
367             arr1 = new Object[SIZE];
368             arr2 = new Object[SIZE];
369 
370             for (int i = 0; i < SIZE; i++) {
371                 switch (i % 6) {
372                     case 0:
373                         arr1[i] = new CustomShort(i);
374                         arr2[i] = new CustomShort(i);
375                         break;
376                     case 1:
377                         arr1[i] = new CustomShort(i);
378                         arr2[i] = new CustomShort(-i);
379                         break;
380                     case 2:
381                         arr1[i] = new CustomShort(i);
382                         arr2[i] = new CustomChar(i);
383                         break;
384                     case 3:
385                         arr1[i] = new CustomChar(i);
386                         arr2[i] = new CustomShort(i);
387                         break;
388                     case 4:
389                         arr1[i] = new CustomChar(i);
390                         arr2[i] = new CustomChar(i);
391                         break;
392                     case 5:
393                         arr1[i] = new CustomChar(i);
394                         arr2[i] = new CustomChar(-i);
395                         break;
396                 }
397             }
398         }
399     }
400 
401     static value class ShortByte {
402         short s;
403         byte b;
404         ShortByte(int s, int b) {
405             this.s = (short)s;
406             this.b = (byte)b;
407         }
408     }
409     static value class ByteByte {
410         byte b1;
411         byte b2;
412         ByteByte(int b1, int b2) {
413             this.b1 = (byte)b1;
414             this.b2 = (byte)b2;
415         }
416     }
417     @State(Scope.Thread)
418     public static class ObjStateMultiField {
419         Object[] arr1, arr2;
420 
421         @Setup
422         public void setup() {
423             arr1 = new Object[SIZE];
424             arr2 = new Object[SIZE];
425 
426             for (int i = 0; i < SIZE; i++) {
427                 switch (i % 6) {
428                     case 0:
429                         arr1[i] = new ShortByte(i, i);
430                         arr2[i] = new ShortByte(i, i);
431                         break;
432                     case 1:
433                         arr1[i] = new ShortByte(i, i);
434                         arr2[i] = new ShortByte(i, 0);
435                         break;
436                     case 2:
437                         arr1[i] = new ShortByte(i, 0);
438                         arr2[i] = new ByteByte(i, 0);
439                         break;
440                     case 3:
441                         arr1[i] = new ByteByte(i, i);
442                         arr2[i] = new ShortByte(i, 0);
443                         break;
444                     case 4:
445                         arr1[i] = new ByteByte(i, i);
446                         arr2[i] = new ByteByte(i, i);
447                         break;
448                     case 5:
449                         arr1[i] = new ByteByte(i, i);
450                         arr2[i] = new ByteByte(i, 0);
451                         break;
452                 }
453             }
454         }
455     }
456 
457     static value class TooBigLongInt {
458         long first;
459         int second;
460         TooBigLongInt(long first, int second) {
461             this.first = first;
462             this.second = second;
463         }
464     }
465     static value class TooBigLongLong {
466         long first;
467         long second;
468         TooBigLongLong(long first, long second) {
469             this.first = first;
470             this.second = second;
471         }
472     }
473     @State(Scope.Thread)
474     public static class ObjStateTooBig {
475         Object[] arr1, arr2;
476 
477         @Setup
478         public void setup() {
479             arr1 = new Object[SIZE];
480             arr2 = new Object[SIZE];
481 
482             for (int i = 0; i < SIZE; i++) {
483                 switch (i % 6) {
484                     case 0:
485                         arr1[i] = new TooBigLongInt(i, i);
486                         arr2[i] = new TooBigLongInt(i, i);
487                         break;
488                     case 1:
489                         arr1[i] = new TooBigLongInt(i, i);
490                         arr2[i] = new TooBigLongInt(i, 0);
491                         break;
492                     case 2:
493                         arr1[i] = new TooBigLongInt(i, 0);
494                         arr2[i] = new TooBigLongLong(i, 0);
495                         break;
496                     case 3:
497                         arr1[i] = new TooBigLongLong(i, i);
498                         arr2[i] = new TooBigLongInt(i, 0);
499                         break;
500                     case 4:
501                         arr1[i] = new TooBigLongLong(i, i);
502                         arr2[i] = new TooBigLongLong(i, i);
503                         break;
504                     case 5:
505                         arr1[i] = new TooBigLongLong(i, i);
506                         arr2[i] = new TooBigLongLong(i, 0);
507                         break;
508                 }
509             }
510         }
511     }
512 
513     static value class WithOopString {
514         String s;
515         WithOopString(String s) {
516             this.s = s;
517         }
518     }
519     static value class WithOopArray {
520         Integer[] s;
521         WithOopArray(int i) {
522             if (i % 4 == 0) {
523                 this.s = null;
524             } else {
525                 this.s = new Integer[]{i};
526             }
527         }
528     }
529     @State(Scope.Thread)
530     public static class ObjStateWithOop {
531         Object[] arr1, arr2;
532 
533         @Setup
534         public void setup() {
535             arr1 = new Object[SIZE];
536             arr2 = new Object[SIZE];
537 
538             for (int i = 0; i < SIZE; i++) {
539                 switch (i % 6) {
540                     case 0:
541                         arr1[i] = new WithOopString(String.valueOf(i));
542                         arr2[i] = new WithOopString(String.valueOf(i));
543                         break;
544                     case 1:
545                         arr1[i] = new WithOopString(String.valueOf(i));
546                         arr2[i] = new WithOopString(String.valueOf(-i));
547                         break;
548                     case 2:
549                         arr1[i] = new WithOopString(String.valueOf(i));
550                         arr2[i] = new WithOopArray(i);
551                         break;
552                     case 3:
553                         arr1[i] = new WithOopArray(i);
554                         arr2[i] = new WithOopString(String.valueOf(i));
555                         break;
556                     case 4:
557                         arr1[i] = new WithOopArray(i);
558                         arr2[i] = new WithOopArray(i);
559                         break;
560                     case 5:
561                         arr1[i] = new WithOopArray(i);
562                         arr2[i] = new WithOopArray(-i);
563                         break;
564                 }
565             }
566         }
567     }
568 
569     static value class Empty {
570     }
571     @State(Scope.Thread)
572     public static class ObjStateEmpty {
573         Object[] arr1, arr2;
574 
575         @Setup
576         public void setup() {
577             arr1 = new Object[SIZE];
578             arr2 = new Object[SIZE];
579 
580             for (int i = 0; i < SIZE; i++) {
581                 arr1[i] = new Empty();
582                 arr2[i] = new Empty();
583             }
584         }
585     }
586     static value class OtherEmpty {
587     }
588     @State(Scope.Thread)
589     public static class ObjStateHeterogeneousEmpty {
590         Object[] arr1, arr2;
591 
592         @Setup
593         public void setup() {
594             arr1 = new Object[SIZE];
595             arr2 = new Object[SIZE];
596 
597             for (int i = 0; i < SIZE; i++) {
598                 switch (i % 4) {
599                     case 0:
600                         arr1[i] = new Empty();
601                         arr2[i] = new Empty();
602                         break;
603                     case 1:
604                         arr1[i] = new Empty();
605                         arr2[i] = new OtherEmpty();
606                         break;
607                     case 2:
608                         arr1[i] = new OtherEmpty();
609                         arr2[i] = new Empty();
610                         break;
611                     case 3:
612                         arr1[i] = new OtherEmpty();
613                         arr2[i] = new OtherEmpty();
614                         break;
615                 }
616             }
617         }
618     }
619 
620     @State(Scope.Thread)
621     public static class ObjStateIdentity {
622         Object[] arr1, arr2;
623 
624         @Setup
625         public void setup() {
626             arr1 = new Object[SIZE];
627             arr2 = new Object[SIZE];
628 
629             for (int i = 0; i < SIZE; i++) {
630                 switch (i % 6) {
631                     case 0:
632                         arr1[i] = String.valueOf(i);
633                         arr2[i] = String.valueOf(i);
634                         break;
635                     case 1:
636                         arr1[i] = String.valueOf(i);
637                         arr2[i] = String.valueOf(-i);
638                         break;
639                     case 2:
640                         arr1[i] = String.valueOf(i);
641                         arr2[i] = new int[]{i};
642                         break;
643                     case 3:
644                         arr1[i] = new int[]{i};
645                         arr2[i] = String.valueOf(i);
646                         break;
647                     case 4:
648                         arr1[i] = new int[]{i};
649                         arr2[i] = new int[]{i};
650                         break;
651                     case 5:
652                         arr1[i] = new int[]{i};
653                         arr2[i] = new int[]{-i};
654                         break;
655                 }
656             }
657         }
658     }
659 
660     @State(Scope.Thread)
661     public static class ObjStateBigMix {
662         Object[] arr1, arr2;
663 
664         @Setup
665         public void setup() {
666             arr1 = new Object[SIZE];
667             arr2 = new Object[SIZE];
668 
669             for (int i = 0; i < SIZE; i++) {
670                 switch (i % 11) {
671                     case 0:
672                         arr1[i] = new Integer(i);
673                         arr2[i] = new Integer(i);
674                         break;
675                     case 1:
676                         arr1[i] = new Integer(i);
677                         arr2[i] = new Integer(-i);
678                         break;
679                     case 2:
680                         arr1[i] = new Integer(i);
681                         arr2[i] = String.valueOf(i);
682                         break;
683                     case 3:
684                         arr1[i] = new TooBigLongInt(i, 0);
685                         arr2[i] = new Integer(i);
686                         break;
687                     case 4:
688                         arr1[i] = String.valueOf(i);
689                         arr2[i] = String.valueOf(i);
690                         break;
691                     case 5:
692                         arr1[i] = String.valueOf(-i);
693                         arr2[i] = String.valueOf(i);
694                         break;
695                     case 6:
696                         arr1[i] = new TooBigLongInt(i, 0);
697                         arr2[i] = new TooBigLongInt(i, 0);
698                         break;
699                     case 7:
700                         arr1[i] = String.valueOf(i);
701                         arr2[i] = null;
702                         break;
703                     case 8:
704                         arr1[i] = null;
705                         arr2[i] = new Integer(i);
706                         break;
707                     case 9:
708                         arr1[i] = null;
709                         arr2[i] = new TooBigLongInt(i, 0);
710                         break;
711                     case 10:
712                         arr1[i] = null;
713                         arr2[i] = null;
714                         break;
715                 }
716             }
717         }
718     }
719 }