JCStress - 奇怪的 volatile 和 static 关键字行为
JCStress - strange volatile and static keyword behavior
我有这个简单的 Jcstress 测试:
package io.denery;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.IIII_Result;
@JCStressTest
@Outcome(expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class RaceIRIWTest {
volatile int x, y;
@Actor
public void actor1() {
x = 1;
}
@Actor
public void actor2() {
y = 1;
}
@Actor
public void actor3(IIII_Result r) {
r.r1 = x;
r.r2 = y;
}
@Actor
public void actor4(IIII_Result r) {
r.r3 = y;
r.r4 = x;
}
}
但是这次测试的结果是:
[OK] io.denery.RaceIRIWTest
(JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN])
Observed state Occurrences Expectation Interpretation
0, 0, 0, 0 22,180,923 ACCEPTABLE_INTERESTING
0, 0, 0, 1 721,581 ACCEPTABLE_INTERESTING
0, 0, 1, 0 13,347 ACCEPTABLE_INTERESTING
0, 0, 1, 1 456,971 ACCEPTABLE_INTERESTING
0, 1, 0, 0 344,068 ACCEPTABLE_INTERESTING
0, 1, 0, 1 36 ACCEPTABLE_INTERESTING
0, 1, 1, 0 528,641 ACCEPTABLE_INTERESTING
0, 1, 1, 1 258,265 ACCEPTABLE_INTERESTING
1, 0, 0, 0 204,088 ACCEPTABLE_INTERESTING
1, 0, 0, 1 667,580 ACCEPTABLE_INTERESTING
1, 0, 1, 1 94,877 ACCEPTABLE_INTERESTING
1, 1, 0, 0 663,159 ACCEPTABLE_INTERESTING
1, 1, 0, 1 306,251 ACCEPTABLE_INTERESTING
1, 1, 1, 0 128,608 ACCEPTABLE_INTERESTING
1, 1, 1, 1 18,838,186 ACCEPTABLE_INTERESTING
我们看到竞争条件,但如果我将 static
放入 volatile int x, y
并删除 volatile
关键字,那么 jcstress 测试的结果将是:
[OK] io.denery.RaceIRIWTest
(JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN])
Observed state Occurrences Expectation Interpretation
1, 1, 1, 1 100,299,061 ACCEPTABLE_INTERESTING
为什么 volatile 不能修复竞态条件,而 static 关键字可以修复它?还是 Jcstress 问题?
我不是 JCStress 专家。
但是您缺少的是您需要指定法律结果,请参见下面的 link 示例。
[禁忌]
在 IRIW 测试的情况下,您想要防止不同的 CPU 对不同地址的存储,可以看出顺序不正确。所以你想防止 actor 3 看到 r1=1, r2=0
(所以在 y
之前看到 x
)。演员 4 看到 r3=1, r4=0
(因此在 x
之前看到 y
)。所以禁止的情况是 1,0,1,0
[易变]
我用volatile检查结果时,并没有遇到禁止的情况。原因是带有 volatile 的示例没有数据竞争,因此它只产生顺序一致 (SC) 执行。对于 SC,总有一些解释执行的 loads/stores 的总顺序。因为有一个total order,所以它也会把store order到不同CPU发出的不同地址。由于 SC 执行需要与程序顺序 (PO) 一致,因此 SC 将阻止重新排序负载。
[没有易变的静态]
带有静态的示例包含数据竞争,因为在读取和写入之间的边缘之前没有发生。所以只有静态允许看到禁止执行1,0,1,0
一个简单的解释是 2 个加载被 JIT 重新排序,或者 CPU 如果它允许乱序加载(例如 ARM)。我的猜测是您只能看到 1,1,1,1
因为 x
和 y
没有被 JCStress 取消设置;但是我对JCStress还不够了解。
[多余]
为了使示例更有趣,我将使 2 个存储不透明存储和加载不透明加载,并在 2 个加载之间放置一个 [LoadLoad] 栅栏以确保它们按顺序执行。 Opaque 将确保 load/store 不会被优化掉,它会提供原子性(基本保证),但不会为不同地址的 loads/stores 提供任何排序保证。因此,您将测试 CPU.
的内存一致性保证
IRIW 不应在现代 CPUs 上失败,因为它们是多副本原子的,我所知道的唯一 CPU 可能表现出这种行为的是 PowerPC。
我有这个简单的 Jcstress 测试:
package io.denery;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.IIII_Result;
@JCStressTest
@Outcome(expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class RaceIRIWTest {
volatile int x, y;
@Actor
public void actor1() {
x = 1;
}
@Actor
public void actor2() {
y = 1;
}
@Actor
public void actor3(IIII_Result r) {
r.r1 = x;
r.r2 = y;
}
@Actor
public void actor4(IIII_Result r) {
r.r3 = y;
r.r4 = x;
}
}
但是这次测试的结果是:
[OK] io.denery.RaceIRIWTest (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN]) Observed state Occurrences Expectation Interpretation
0, 0, 0, 0 22,180,923 ACCEPTABLE_INTERESTING
0, 0, 0, 1 721,581 ACCEPTABLE_INTERESTING
0, 0, 1, 0 13,347 ACCEPTABLE_INTERESTING
0, 0, 1, 1 456,971 ACCEPTABLE_INTERESTING
0, 1, 0, 0 344,068 ACCEPTABLE_INTERESTING
0, 1, 0, 1 36 ACCEPTABLE_INTERESTING
0, 1, 1, 0 528,641 ACCEPTABLE_INTERESTING
0, 1, 1, 1 258,265 ACCEPTABLE_INTERESTING
1, 0, 0, 0 204,088 ACCEPTABLE_INTERESTING
1, 0, 0, 1 667,580 ACCEPTABLE_INTERESTING
1, 0, 1, 1 94,877 ACCEPTABLE_INTERESTING
1, 1, 0, 0 663,159 ACCEPTABLE_INTERESTING
1, 1, 0, 1 306,251 ACCEPTABLE_INTERESTING
1, 1, 1, 0 128,608 ACCEPTABLE_INTERESTING
1, 1, 1, 1 18,838,186 ACCEPTABLE_INTERESTING
我们看到竞争条件,但如果我将 static
放入 volatile int x, y
并删除 volatile
关键字,那么 jcstress 测试的结果将是:
[OK] io.denery.RaceIRIWTest (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=RU, -Duser.language=en, -Duser.variant, -XX:-TieredCompilation, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN]) Observed state Occurrences Expectation Interpretation
1, 1, 1, 1 100,299,061 ACCEPTABLE_INTERESTING
为什么 volatile 不能修复竞态条件,而 static 关键字可以修复它?还是 Jcstress 问题?
我不是 JCStress 专家。
但是您缺少的是您需要指定法律结果,请参见下面的 link 示例。
[禁忌]
在 IRIW 测试的情况下,您想要防止不同的 CPU 对不同地址的存储,可以看出顺序不正确。所以你想防止 actor 3 看到 r1=1, r2=0
(所以在 y
之前看到 x
)。演员 4 看到 r3=1, r4=0
(因此在 x
之前看到 y
)。所以禁止的情况是 1,0,1,0
[易变]
我用volatile检查结果时,并没有遇到禁止的情况。原因是带有 volatile 的示例没有数据竞争,因此它只产生顺序一致 (SC) 执行。对于 SC,总有一些解释执行的 loads/stores 的总顺序。因为有一个total order,所以它也会把store order到不同CPU发出的不同地址。由于 SC 执行需要与程序顺序 (PO) 一致,因此 SC 将阻止重新排序负载。
[没有易变的静态]
带有静态的示例包含数据竞争,因为在读取和写入之间的边缘之前没有发生。所以只有静态允许看到禁止执行1,0,1,0
一个简单的解释是 2 个加载被 JIT 重新排序,或者 CPU 如果它允许乱序加载(例如 ARM)。我的猜测是您只能看到 1,1,1,1
因为 x
和 y
没有被 JCStress 取消设置;但是我对JCStress还不够了解。
[多余]
为了使示例更有趣,我将使 2 个存储不透明存储和加载不透明加载,并在 2 个加载之间放置一个 [LoadLoad] 栅栏以确保它们按顺序执行。 Opaque 将确保 load/store 不会被优化掉,它会提供原子性(基本保证),但不会为不同地址的 loads/stores 提供任何排序保证。因此,您将测试 CPU.
的内存一致性保证IRIW 不应在现代 CPUs 上失败,因为它们是多副本原子的,我所知道的唯一 CPU 可能表现出这种行为的是 PowerPC。