Skip to content

Conversation

@bishabosha
Copy link
Member

Returning anonymous classes mean that virtual calls are always made, with specialised type the specialised method is immediately selected.

fixes #24501

@bishabosha bishabosha requested a review from a team as a code owner November 21, 2025 12:52
Returning anonymous classes mean that virtual calls are always made,
with specialised type the specialised method is immediately selected.

fixes scala#24501
@bishabosha bishabosha force-pushed the varargbuilder-specialisation branch from cd33dcc to 5c5d6a0 Compare November 21, 2025 12:57
Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point of returning anonymous classes (or private classes in the Scala.js version) is so that they don't become part of the public ABI. It is quite intentional.

These boxes will disappear after a trivial round of JITting. I want to see actual benchmarks before considering making the public ABI more resistant to change.

@bishabosha
Copy link
Member Author

bishabosha commented Nov 22, 2025

I took up the challenge, it seems for the first 10 million iterations it is 43% the time of the old implementation but yes then JIT kicks in https://gist.github.com/bishabosha/5f693912173a371c1e561861506f876b

The `//> using jmh` directive is experimental
Please bear in mind that non-ideal user experience should be expected.
If you encounter any bugs or have feedback to share, make sure to reach out to the maintenance team at https://github.com/VirtusLab/scala-cli
Compiling project (Scala 3.7.4, JVM (21))
Compiled project (Scala 3.7.4, JVM (21))
Processing 27 classes from .scala-build/varargs-bench_fa08f591a7/classes/main with "reflection" generator
Writing out Java source to .scala-build/varargs-bench_fa08f591a7_jmh/sources and resources to .scala-build/varargs-bench_fa08f591a7_jmh/resources
Compiling project (Scala 3.7.4, JVM (21))

# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 20 iterations, 10 ms each
# Measurement: 50 iterations, 10 ms each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: bench.Benchmarks.newVarArgs

# Run progress: 0.00% complete, ETA 00:00:01
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration   1: 1441501125.000 ns/op
# Warmup Iteration   2: 906896375.000 ns/op
# Warmup Iteration   3: 743975583.000 ns/op
# Warmup Iteration   4: 749568834.000 ns/op
# Warmup Iteration   5: 744106083.000 ns/op
# Warmup Iteration   6: 748333041.000 ns/op
# Warmup Iteration   7: 744819541.000 ns/op
# Warmup Iteration   8: 746948667.000 ns/op
# Warmup Iteration   9: 746322625.000 ns/op
# Warmup Iteration  10: 743689416.000 ns/op
# Warmup Iteration  11: 752257750.000 ns/op
# Warmup Iteration  12: 743875667.000 ns/op
# Warmup Iteration  13: 747473084.000 ns/op
# Warmup Iteration  14: 743449209.000 ns/op
# Warmup Iteration  15: 746948584.000 ns/op
# Warmup Iteration  16: 743649250.000 ns/op
# Warmup Iteration  17: 743372250.000 ns/op
# Warmup Iteration  18: 748407750.000 ns/op
# Warmup Iteration  19: 746134417.000 ns/op
# Warmup Iteration  20: 746080250.000 ns/op
Iteration   1: 747218875.000 ns/op
Iteration   2: 743284542.000 ns/op
Iteration   3: 746029125.000 ns/op
Iteration   4: 748799417.000 ns/op
Iteration   5: 754360167.000 ns/op
Iteration   6: 760071542.000 ns/op
Iteration   7: 762859042.000 ns/op
Iteration   8: 759400417.000 ns/op
Iteration   9: 746086375.000 ns/op
Iteration  10: 745247292.000 ns/op
Iteration  11: 744491291.000 ns/op
Iteration  12: 750987708.000 ns/op
Iteration  13: 745635416.000 ns/op
Iteration  14: 747874667.000 ns/op
Iteration  15: 743220958.000 ns/op
Iteration  16: 744356334.000 ns/op
Iteration  17: 767452209.000 ns/op
Iteration  18: 746273333.000 ns/op
Iteration  19: 750353375.000 ns/op
Iteration  20: 745862458.000 ns/op
Iteration  21: 744338375.000 ns/op
Iteration  22: 744161958.000 ns/op
Iteration  23: 743264041.000 ns/op
Iteration  24: 742851583.000 ns/op
Iteration  25: 745592583.000 ns/op
Iteration  26: 747601666.000 ns/op
Iteration  27: 746337375.000 ns/op
Iteration  28: 745838458.000 ns/op
Iteration  29: 746858625.000 ns/op
Iteration  30: 744466167.000 ns/op
Iteration  31: 743541500.000 ns/op
Iteration  32: 747589209.000 ns/op
Iteration  33: 745973208.000 ns/op
Iteration  34: 743298375.000 ns/op
Iteration  35: 745249375.000 ns/op
Iteration  36: 745315542.000 ns/op
Iteration  37: 742824958.000 ns/op
Iteration  38: 745200000.000 ns/op
Iteration  39: 744898875.000 ns/op
Iteration  40: 748161125.000 ns/op
Iteration  41: 748375917.000 ns/op
Iteration  42: 744728208.000 ns/op
Iteration  43: 743635125.000 ns/op
Iteration  44: 747098584.000 ns/op
Iteration  45: 742428667.000 ns/op
Iteration  46: 749222500.000 ns/op
Iteration  47: 748327292.000 ns/op
Iteration  48: 749407792.000 ns/op
Iteration  49: 756956583.000 ns/op
Iteration  50: 746742416.000 ns/op

Result "bench.Benchmarks.newVarArgs":
  747603012.500 ±(99.9%) 2625963.207 ns/op [Average]
  (min, avg, max) = (742428667.000, 747603012.500, 767452209.000), stdev = 5304575.588
  CI (99.9%): [744977049.293, 750228975.707] (assumes normal distribution)


# JMH version: 1.37
# VM version: JDK 21.0.2, OpenJDK 64-Bit Server VM, 21.0.2+13-jvmci-23.1-b30
# VM invoker: /Users/jamie/Library/Caches/Coursier/arc/https/github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_macos-aarch64_bin.tar.gz/graalvm-community-openjdk-21.0.2+13.1/Contents/Home/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 20 iterations, 10 ms each
# Measurement: 50 iterations, 10 ms each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: bench.Benchmarks.oldVarArgs

# Run progress: 50.00% complete, ETA 00:00:53
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration   1: 3344266917.000 ns/op
# Warmup Iteration   2: 1553600041.000 ns/op
# Warmup Iteration   3: 743365834.000 ns/op
# Warmup Iteration   4: 747848417.000 ns/op
# Warmup Iteration   5: 751121792.000 ns/op
# Warmup Iteration   6: 755555833.000 ns/op
# Warmup Iteration   7: 749231667.000 ns/op
# Warmup Iteration   8: 746073792.000 ns/op
# Warmup Iteration   9: 746827291.000 ns/op
# Warmup Iteration  10: 745787708.000 ns/op
# Warmup Iteration  11: 746708750.000 ns/op
# Warmup Iteration  12: 750466333.000 ns/op
# Warmup Iteration  13: 759171250.000 ns/op
# Warmup Iteration  14: 747405334.000 ns/op
# Warmup Iteration  15: 745792750.000 ns/op
# Warmup Iteration  16: 747287917.000 ns/op
# Warmup Iteration  17: 750150083.000 ns/op
# Warmup Iteration  18: 764514042.000 ns/op
# Warmup Iteration  19: 755594792.000 ns/op
# Warmup Iteration  20: 754008666.000 ns/op
Iteration   1: 747377375.000 ns/op
Iteration   2: 748105250.000 ns/op
Iteration   3: 745592416.000 ns/op
Iteration   4: 746496792.000 ns/op
Iteration   5: 751514375.000 ns/op
Iteration   6: 752852375.000 ns/op
Iteration   7: 754348584.000 ns/op
Iteration   8: 745658875.000 ns/op
Iteration   9: 749784000.000 ns/op
Iteration  10: 757858125.000 ns/op
Iteration  11: 758606083.000 ns/op
Iteration  12: 769690333.000 ns/op
Iteration  13: 743920292.000 ns/op
Iteration  14: 752870000.000 ns/op
Iteration  15: 766539792.000 ns/op
Iteration  16: 748048250.000 ns/op
Iteration  17: 744704250.000 ns/op
Iteration  18: 764190833.000 ns/op
Iteration  19: 769334500.000 ns/op
Iteration  20: 748336792.000 ns/op
Iteration  21: 748474625.000 ns/op
Iteration  22: 753766542.000 ns/op
Iteration  23: 761624000.000 ns/op
Iteration  24: 753725417.000 ns/op
Iteration  25: 768837125.000 ns/op
Iteration  26: 757125583.000 ns/op
Iteration  27: 754480959.000 ns/op
Iteration  28: 751926792.000 ns/op
Iteration  29: 750774292.000 ns/op
Iteration  30: 761501750.000 ns/op
Iteration  31: 745085250.000 ns/op
Iteration  32: 749707250.000 ns/op
Iteration  33: 770499500.000 ns/op
Iteration  34: 760885167.000 ns/op
Iteration  35: 775560834.000 ns/op
Iteration  36: 742671084.000 ns/op
Iteration  37: 748527209.000 ns/op
Iteration  38: 753468625.000 ns/op
Iteration  39: 751164000.000 ns/op
Iteration  40: 745309000.000 ns/op
Iteration  41: 756949750.000 ns/op
Iteration  42: 742121584.000 ns/op
Iteration  43: 748943500.000 ns/op
Iteration  44: 750899209.000 ns/op
Iteration  45: 746217625.000 ns/op
Iteration  46: 758306042.000 ns/op
Iteration  47: 764865500.000 ns/op
Iteration  48: 747602666.000 ns/op
Iteration  49: 774361709.000 ns/op
Iteration  50: 759338625.000 ns/op

Result "bench.Benchmarks.oldVarArgs":
  754411010.120 ±(99.9%) 4306396.927 ns/op [Average]
  (min, avg, max) = (742121584.000, 754411010.120, 775560834.000), stdev = 8699134.834
  CI (99.9%): [750104613.193, 758717407.047] (assumes normal distribution)


# Run complete. Total time: 00:01:49

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.

Benchmark              Mode  Cnt          Score         Error  Units
Benchmarks.newVarArgs  avgt   50  747603012.500 ± 2625963.207  ns/op
Benchmarks.oldVarArgs  avgt   50  754411010.120 ± 4306396.927  ns/op

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VarArgsBuilder does not specialise when adding single element

2 participants