For benchmarks it is generally recommended to use JMH [1] instead of coding your
own warm-up, timing & GC profiling implementation. Playing around with a quick
JMH set-up [2], there are a few new findings though:
| Benchmark | Count | Preallocate | Time (ns/op) | Allocation (B/op) | Allocation (op) |
|-----------+-------+-------------+--------------+-------------------+---------------------|
| OnHeap | 10^6 | true | 6231838 | 16000020 | ~ 1 |
| OffHeap | 10^6 | true | 6819142 | 54 | Should also be ~ 1 |
| OnHeap | 10^6 | false | 9719988 | 33554598 | ~ 17 |
| OffHeap | 10^6 | false | 19450448 | 773 | Should also be ~ 17 |
1. The difference between on/off-heap Python-like implementation is less
significant (~ 10%) provided that we preallocate things correctly.
2. The off-heap (MemorySegment) implementation slows down as the number of
allocation increasing.
EDIT: Profiling with JMH (using -prof async) shows that the off-heap
implementation spent quite some time on jdk.internal.misc.Unsafe.getIntUnaligned
in its resizeIfNeeded method, which should be the culprit here - Java array are
guaranteed to be aligned (at least in HotSpot JVM), and aligned operations are
much more efficient than unaligned ones, which should be able to explain the
performance hit.
--- 55482970432 ns (18.53%), 5548 samples
[ 0] jdk.internal.misc.Unsafe.getIntUnaligned
[ 1] jdk.internal.misc.ScopedMemoryAccess.getIntUnalignedInternal
[ 2] jdk.internal.misc.ScopedMemoryAccess.getIntUnaligned
[ 3] java.lang.invoke.VarHandleSegmentAsInts.get
[ 4] java.lang.invoke.VarHandleGuards.guard_LJ_I
[ 5] jdk.internal.foreign.AbstractMemorySegmentImpl.get
[ 6] gc_less.python_like.IntHashtableOffHeap.resizeIfNeeded
[ 7] gc_less.python_like.IntHashtableOffHeap.put
[ 8] gc_less.benchmarks.OffHeapBenchmark.testPutOffHeap
[1]
https://github.com/openjdk/jmh[2] https://github.com/gudzpoz/gc_less/commit/17dcc3eb57b89f2642...