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 }