- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我决定使用不同的锁定策略来衡量增量,并为此目的使用 JMH。我正在使用 JMH 检查吞吐量和平均时间以及检查正确性的简单自定义测试。有六种策略:
基准代码:
@State(Scope.Benchmark)
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
public class UnsafeCounter_Benchmark {
public Counter unsync, syncNoV, syncV, lock, atomic, unsafe, unsafeGA;
@Setup(Level.Iteration)
public void prepare() {
unsync = new UnsyncCounter();
syncNoV = new SyncNoVolatileCounter();
syncV = new SyncVolatileCounter();
lock = new LockCounter();
atomic = new AtomicCounter();
unsafe = new UnsafeCASCounter();
unsafeGA = new UnsafeGACounter();
}
@Benchmark
public void unsyncCount() {
unsyncCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void unsyncCounter() {
unsync.increment();
}
@Benchmark
public void syncNoVCount() {
syncNoVCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void syncNoVCounter() {
syncNoV.increment();
}
@Benchmark
public void syncVCount() {
syncVCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void syncVCounter() {
syncV.increment();
}
@Benchmark
public void lockCount() {
lockCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void lockCounter() {
lock.increment();
}
@Benchmark
public void atomicCount() {
atomicCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void atomicCounter() {
atomic.increment();
}
@Benchmark
public void unsafeCount() {
unsafeCounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void unsafeCounter() {
unsafe.increment();
}
@Benchmark
public void unsafeGACount() {
unsafeGACounter();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public void unsafeGACounter() {
unsafeGA.increment();
}
public static void main(String[] args) throws RunnerException {
Options baseOpts = new OptionsBuilder()
.include(UnsafeCounter_Benchmark.class.getSimpleName())
.threads(100)
.jvmArgs("-ea")
.build();
new Runner(baseOpts).run();
}
}
和基准测试结果:
JDK 8u20
Benchmark Mode Samples Score Error Units
o.k.u.u.UnsafeCounter_Benchmark.atomicCount thrpt 5 42.178 ± 17.643 ops/us
o.k.u.u.UnsafeCounter_Benchmark.lockCount thrpt 5 24.044 ± 2.264 ops/us
o.k.u.u.UnsafeCounter_Benchmark.syncNoVCount thrpt 5 22.849 ± 1.344 ops/us
o.k.u.u.UnsafeCounter_Benchmark.syncVCount thrpt 5 20.235 ± 2.027 ops/us
o.k.u.u.UnsafeCounter_Benchmark.unsafeCount thrpt 5 12.460 ± 1.326 ops/us
o.k.u.u.UnsafeCounter_Benchmark.unsafeGACount thrpt 5 39.106 ± 2.966 ops/us
o.k.u.u.UnsafeCounter_Benchmark.unsyncCount thrpt 5 93.076 ± 9.674 ops/us
o.k.u.u.UnsafeCounter_Benchmark.atomicCount avgt 5 2.604 ± 0.133 us/op
o.k.u.u.UnsafeCounter_Benchmark.lockCount avgt 5 4.161 ± 0.546 us/op
o.k.u.u.UnsafeCounter_Benchmark.syncNoVCount avgt 5 4.440 ± 0.523 us/op
o.k.u.u.UnsafeCounter_Benchmark.syncVCount avgt 5 5.073 ± 0.439 us/op
o.k.u.u.UnsafeCounter_Benchmark.unsafeCount avgt 5 9.088 ± 5.964 us/op
o.k.u.u.UnsafeCounter_Benchmark.unsafeGACount avgt 5 2.611 ± 0.164 us/op
o.k.u.u.UnsafeCounter_Benchmark.unsyncCount avgt 5 1.047 ± 0.050 us/op
大多数测量结果如我所料,除了 UnsafeCounter_Benchmark.unsafeCount
,它使用 sun.misc.Unsafe.compareAndSwapLong
和 while
循环。它是最慢的锁定。
public void increment() {
long before = counter;
while (!unsafe.compareAndSwapLong(this, offset, before, before + 1L)) {
before = counter;
}
}
我认为低性能是因为 while 循环和 JMH 引起了更高的争用,但是当我通过 Executors
检查正确性时,我得到了我预期的数字:
Counter result: UnsyncCounter 97538676
Time passed in ms:259
Counter result: AtomicCounter 100000000
Time passed in ms:1805
Counter result: LockCounter 100000000
Time passed in ms:3904
Counter result: SyncNoVolatileCounter 100000000
Time passed in ms:14227
Counter result: SyncVolatileCounter 100000000
Time passed in ms:19224
Counter result: UnsafeCASCounter 100000000
Time passed in ms:8077
Counter result: UnsafeGACounter 100000000
Time passed in ms:2549
正确性测试代码:
public class UnsafeCounter_Test {
static class CounterClient implements Runnable {
private Counter c;
private int num;
public CounterClient(Counter c, int num) {
this.c = c;
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
c.increment();
}
}
}
public static void makeTest(Counter counter) throws InterruptedException {
int NUM_OF_THREADS = 1000;
int NUM_OF_INCREMENTS = 100000;
ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);
long before = System.currentTimeMillis();
for (int i = 0; i < NUM_OF_THREADS; i++) {
service.submit(new CounterClient(counter, NUM_OF_INCREMENTS));
}
service.shutdown();
service.awaitTermination(1, TimeUnit.MINUTES);
long after = System.currentTimeMillis();
System.out.println("Counter result: " + counter.getClass().getSimpleName() + " " + counter.getCounter());
System.out.println("Time passed in ms:" + (after - before));
}
public static void main(String[] args) throws InterruptedException {
makeTest(new UnsyncCounter());
makeTest(new AtomicCounter());
makeTest(new LockCounter());
makeTest(new SyncNoVolatileCounter());
makeTest(new SyncVolatileCounter());
makeTest(new UnsafeCASCounter());
makeTest(new UnsafeGACounter());
}
}
我知道这是非常糟糕的测试,但在这种情况下,Unsafe CAS 比 Sync 变体快两倍,一切都按预期进行。有人可以澄清所描述的行为吗?有关详细信息,请参阅 GitHub 存储库:Bench , Unsafe CAS counter
最佳答案
大声思考:人们通常会完成 90% 的乏味工作,而将 10%(乐趣开始的地方)留给其他人,这很了不起!好吧,我正在享受所有的乐趣!
让我先在我的 i7-4790K、8u40 EA 上重复这个实验:
Benchmark Mode Samples Score Error Units
UnsafeCounter_Benchmark.atomicCount thrpt 5 47.669 ± 18.440 ops/us
UnsafeCounter_Benchmark.lockCount thrpt 5 14.497 ± 7.815 ops/us
UnsafeCounter_Benchmark.syncNoVCount thrpt 5 11.618 ± 2.130 ops/us
UnsafeCounter_Benchmark.syncVCount thrpt 5 11.337 ± 4.532 ops/us
UnsafeCounter_Benchmark.unsafeCount thrpt 5 7.452 ± 1.042 ops/us
UnsafeCounter_Benchmark.unsafeGACount thrpt 5 43.332 ± 3.435 ops/us
UnsafeCounter_Benchmark.unsyncCount thrpt 5 102.773 ± 11.943 ops/us
确实,unsafeCount
测试似乎有些可疑。确实,在验证之前,您必须假设所有数据都是可疑的。对于 nanobenchmarks,您必须验证生成的代码,看看您是否真的测量了您想要测量的东西。在 JMH 中,使用 -prof perfasm
可以非常快速地实现。事实上,如果您查看 unsafeCount
中 HitTest 的区域,您会注意到一些有趣的事情:
0.12% 0.04% 0x00007fb45518e7d1: mov 0x10(%r10),%rax
17.03% 23.44% 0x00007fb45518e7d5: test %eax,0x17318825(%rip)
0.21% 0.07% 0x00007fb45518e7db: mov 0x18(%r10),%r11 ; getfield offset
30.33% 10.77% 0x00007fb45518e7df: mov %rax,%r8
0.00% 0x00007fb45518e7e2: add $0x1,%r8
0.01% 0x00007fb45518e7e6: cmp 0xc(%r10),%r12d ; typecheck
0x00007fb45518e7ea: je 0x00007fb45518e80b ; bail to v-call
0.83% 0.48% 0x00007fb45518e7ec: lock cmpxchg %r8,(%r10,%r11,1)
33.27% 25.52% 0x00007fb45518e7f2: sete %r8b
0.12% 0.01% 0x00007fb45518e7f6: movzbl %r8b,%r8d
0.03% 0.04% 0x00007fb45518e7fa: test %r8d,%r8d
0x00007fb45518e7fd: je 0x00007fb45518e7d1 ; back branch
翻译:a) offset
字段在每次迭代时都会被重新读取——因为 CAS 内存效应意味着 volatile 读取,因此需要悲观地重新读取该字段; b) 有趣的是 unsafe
字段也被重新读取以进行类型检查——出于同样的原因。
这就是为什么高性能代码应该是这样的:
--- a/utils bench/src/main/java/org/kirmit/utils/unsafe/concurrency/UnsafeCASCounter.java
+++ b/utils bench/src/main/java/org/kirmit/utils/unsafe/concurrency/UnsafeCASCounter.java
@@ -5,13 +5,13 @@ import sun.misc.Unsafe;
public class UnsafeCASCounter implements Counter {
private volatile long counter = 0;
- private final Unsafe unsafe = UnsafeHelper.unsafe;
- private long offset;
- {
+ private static final Unsafe unsafe = UnsafeHelper.unsafe;
+ private static final long offset;
+ static {
try {
offset = unsafe.objectFieldOffset(UnsafeCASCounter.class.getDeclaredField("counter"));
} catch (NoSuchFieldException e) {
- e.printStackTrace();
+ throw new IllegalStateException("Whoops!");
}
}
如果这样做,unsafeCount
的性能会立即提升:
Benchmark Mode Samples Score Error Units
UnsafeCounter_Benchmark.unsafeCount thrpt 5 9.733 ± 0.673 ops/us
...考虑到错误范围,现在已经非常接近同步测试了。如果您现在查看 -prof perfasm
,这是一个 unsafeCount
循环:
0.08% 0.02% 0x00007f7575191900: mov 0x10(%r10),%rax
28.09% 28.64% 0x00007f7575191904: test %eax,0x161286f6(%rip)
0.23% 0.08% 0x00007f757519190a: mov %rax,%r11
0x00007f757519190d: add $0x1,%r11
0x00007f7575191911: lock cmpxchg %r11,0x10(%r10)
47.27% 23.48% 0x00007f7575191917: sete %r8b
0.10% 0x00007f757519191b: movzbl %r8b,%r8d
0.02% 0x00007f757519191f: test %r8d,%r8d
0x00007f7575191922: je 0x00007f7575191900
这个循环很紧凑,似乎没有什么能让它走得更快。我们大部分时间都在加载“更新的”值并实际对它进行 CAS 处理。但是我们争吵很多!为了弄清楚争用是否是主要原因,让我们添加退避:
--- a/utils bench/src/main/java/org/kirmit/utils/unsafe/concurrency/UnsafeCASCounter.java
+++ b/utils bench/src/main/java/org/kirmit/utils/unsafe/concurrency/UnsafeCASCounter.java
@@ -20,6 +21,7 @@ public class UnsafeCASCounter implements Counter {
long before = counter;
while (!unsafe.compareAndSwapLong(this, offset, before, before + 1L)) {
before = counter;
+ Blackhole.consumeCPU(1000);
}
}
...运行:
Benchmark Mode Samples Score Error Units
UnsafeCounter_Benchmark.unsafeCount thrpt 5 99.869 ± 107.933 ops/us
瞧。我们在循环中做了更多的工作,但这使我们免于争吵。我之前在 "Nanotrusting the Nanotime" 中尝试过对此进行解释,返回那里并阅读更多有关基准测试方法的信息可能会很好,尤其是在测量重量级操作时。这突出了整个实验中的陷阱,而不仅仅是 unsafeCount
。
针对 OP 和感兴趣的读者的练习:解释为什么 unsafeGACount
和 atomicCount
比其他测试执行得更快。您现在拥有了工具。
附言在具有 C (C < N) 个线程的机器上运行 N 个线程是愚蠢的:您可能认为您与 N 个线程“争用”,但实际上您只是在运行和“争用”C 个线程。当人们在 4 核机器上做 1000 个线程时,特别有趣...
附言时间检查:10 分钟做分析和额外的实验,20 分钟写下来。您浪费了多少时间手工复制结果? ;)
关于java - 通过 JMH 进行的 sun.misc.Unsafe.compareAndSwap 测量中的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26995856/
我正在使用 Web 服务在 Android 应用程序和 SOAP Web 服务之间发送数据。此 Web 服务仅接受序列化对象,而执行此操作的唯一方法是使用: import sun.misc.BASE6
我正在将 JDK 版本从 8 更新到 11,以解决某些并发数据结构的问题。 error: type Contended is not a member of package sun.misc [ERR
scipy.misc.logsumexp函数的输入参数有(a, axis=None, b=None, keepdims=False, return_sign=False),具体配置可参见这里,返回的
我刚刚安装了 scipy(通过 easy_install scipy),但由于某些原因 scipy.misc 丢失了。 看看这个: >>> import scipy >>> scipy >>> sc
在 kate(或 QtCreator)中,我有一个名为“Fixed[Misc]”的字体。我想检索字体文件,但没有找到:在我的字体目录中,我有一个“misc”目录,但我无法准确找到我在 kate 中使用
我在 python 脚本中导入 scipy.misc 时遇到问题。现在我知道其他人也提出了与此相关的问题,但他们的解决方案对我不起作用。 我正在编写的程序是这样开始的: import matplotl
我一直在使用 sun.misc 中的 BASE64Encoder 和 BASE64Decoder;我正在使用 Eclipse 并且不得不求助于警告,因为默认情况下访问权限仅限于它。 这些类工作得很好,
前言 unsafe类在jdk 源码的多个类中用到,这个类的提供了一些绕开jvm的更底层功能,基于它的实现可以提高效率。但是,它是一把双刃剑:正如它的名字所预示的那样,它是unsafe的,它所分配的
我正在使用具有以下导入的 scipy 1.3.1 运行旧代码: from scipy.misc import bytescale 出现以下错误: ImportError: cannot import
10 分钟前,我尝试连接 phppgadmin 5.1,但我在 Web 服务器错误日志中发现了此错误消息:“Misc 在/usr/share/phppgadmin/classes/Misc.php 第
我想调整表面法线“图像”(H * W * 3)的大小。问题在于数组中存在可取数字。如何使用scipy.misc.resize或cv2.resize调整大小? 最佳答案 cv2.resize支持负数。
jdk1.8.0_144中的src.zip包含Float.java 这又指的是 sun.misc.FloatingDecimal。我在 src.zip 中找不到它?谁能告诉我它在哪里?我可以找到 gr
我正在开发一个供其他团队使用的库,在库中有一些公开的类/方法,但我不希望其他人使用它们。 像java中的sun.misc包这样的东西,虽然所有的类都是公共(public)的,但编译器在使用它时会抛出“
10 分钟前,我尝试连接 phppgadmin 5.1,但我在 Web 服务器错误日志中发现了此错误消息:“Misc 在/usr/share/phppgadmin/classes/Misc.php 第
虽然也有类似的问题(例如 A 、 B 和 C ),但他们的答案并不能解决我的问题。 我使用的是针对 Android API 18 的 Android Studio 1.5.1(Android KitK
我将在我的代码中使用 sun.misc.BASE64Decoder 的 decodeBuffer(String inputString) 。多个线程将在同一个解码器对象上调用此函数。 这个线程安全吗?
我知道 sun.* 包不是官方 Java API 的一部分。但是,我需要使用一些类似于 Perf 提供的功能:特别是基本 JVM 指标(堆、线程、PermGen 等)、GC 的 JvmStat 计数器
我有一个选项卡设置为使用 API8 max 的 Activity 扩展。在此选项卡中,我正在初始化一些 TextView、EditText 和 SeekBar 对象,如下所示。 我正在寻找一种更通用的
sun.misc.Unsafe 或 theUnsafe 实例线程安全吗? 最佳答案 Unsafe 的方法不是线程安全的。您需要像往常一样同步访问您想要自己操作的数据。然而,访问实例 theUnsafe
我想知道我在这里做错了什么...... 我正在试验一个简单而人为的函数,它对某些 x 值求导: f(x) = x^3,然后计算导数 f'(x) = 3x^2 对于 x 在 1、2、3 处的值 >>>
我是一名优秀的程序员,十分优秀!