在JEP193中,的特定目标之一VarHandles
是提供使用FieldUpdaters
和的替代方法AtomicIntegers
(并避免与之相关的一些开销)。
AtomicIntegers
由于它们是一个单独的对象(它们每个使用大约36个字节,取决于一些因素(例如是否启用压缩的OOP等)),因此在内存方面可能特别浪费。
如果您有很多整数可能需要原子更新(在许多小对象中),则要减少浪费,实质上有三种选择:
使用 AtomicFieldUpdater
用一个 VarHandle
或重新排列代码以AtomicIntegerArray
代替对象中的字段。
因此,我决定测试替代方案,并对每种方案的性能含义有所了解。
使用整数字段的原子(易失性模式)增量作为代理,我在2014年中的MacBook Pro上获得以下结果:
Benchmark Mode Cnt Score Error Units VarHandleBenchmark.atomic thrpt 5 448041037.223 ± 36448840.301 ops/s VarHandleBenchmark.atomicArray thrpt 5 453785339.203 ± 64528885.282 ops/s VarHandleBenchmark.fieldUpdater thrpt 5 459802512.169 ± 52293792.737 ops/s VarHandleBenchmark.varhandle thrpt 5 136482396.440 ± 9439041.030 ops/s
在此基准测试中,速度VarHandles
大约慢了四倍。
我想了解的是开销来自何处?
这是由于签名多态访问方法引起的吗?我在微型基准测试中犯了错误吗?
基准细节如下。
我在2014年中的MacBook Pro上使用以下JVM运行了基准测试
> java -version openjdk version "11.0.2" 2019-01-15 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.2+9) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.2+9, mixed mode)
基准测试的源代码:
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; @State(Scope.Thread) @Fork(value = 1, jvmArgs = {"-Xms256m", "-Xmx256m", "-XX:+UseG1GC"}) @Warmup(iterations = 3, time = 3) @Measurement(iterations = 5, time = 5) @Threads(4) public class VarHandleBenchmark { // array option private final AtomicIntegerArray array = new AtomicIntegerArray(1); // vanilla AtomicInteger private final AtomicInteger counter = new AtomicInteger(); // count field and its VarHandle private volatile int count; private static final VarHandle COUNT; // count2 field and its field updater private volatile int count2; private static final AtomicIntegerFieldUpdaterCOUNT2 ; static { try { COUNT = MethodHandles.lookup() .findVarHandle(VarHandleBenchmark.class, "count", Integer.TYPE); COUNT2 = AtomicIntegerFieldUpdater.newUpdater(VarHandleBenchmark.class, "count2"); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } @Benchmark public void atomic(Blackhole bh) { bh.consume(counter.getAndAdd(1)); } @Benchmark public void atomicArray(Blackhole bh) { bh.consume(array.getAndAdd(0, 1)); } @Benchmark public void varhandle(Blackhole bh) { bh.consume(COUNT.getAndAdd(this, 1)); } @Benchmark public void fieldUpdater(Blackhole bh) { bh.consume(COUNT2.getAndAdd(this, 1)); } }
更新:应用apangin解决方案后,这些是基准测试的结果:
Benchmark Mode Cnt Score Error Units VarHandleBenchmark.atomic thrpt 5 464045527.470 ± 42337922.645 ops/s VarHandleBenchmark.atomicArray thrpt 5 465700610.882 ± 18116770.557 ops/s VarHandleBenchmark.fieldUpdater thrpt 5 473968453.591 ± 49859839.498 ops/s VarHandleBenchmark.varhandle thrpt 5 429737922.796 ± 41629104.677 ops/s
差异消失。