【发布时间】:2015-09-22 19:57:24
【问题描述】:
我有一个这样的 StressTester 类:
public abstract class StressTest {
public static final int WARMUP_JIT_COMPILER = 10000;
public interface TimedAction {
void doAction();
}
public static long timeAction(int numberOfTimes, TimedAction action) {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
for (int i = 0; i < WARMUP_JIT_COMPILER; i++) {
action.doAction();
}
long currentTime = bean.getCurrentThreadCpuTime();
for (int i = 0; i < numberOfTimes; i++) {
action.doAction();
}
return (bean.getCurrentThreadCpuTime() - currentTime)/1000000;
}
}
main 方法看起来像这样:
private static boolean isPrime1(int n) { ... }
private static boolean isPrime2(int n) { ... }
private static boolean isPrime3(int n) { ... }
private static boolean isPrime4(int n) { ... }
private static final int NUMBER_OF_RUNS = 1000000;
public static void main(String[] args) {
long primeNumberFinderTime1 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
for (int i = 0; i < 100; i++) {
isPrime1(i);
}
});
long primeNumberFinderTime2 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
for (int i = 0; i < 100; i++) {
isPrime2(i);
}
});
long primeNumberFinderTime3 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
for (int i = 0; i < 100; i++) {
isPrime3(i);
}
});
long primeNumberFinderTime4 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
for (int i = 0; i < 100; i++) {
isPrime4(i);
}
});
}
当我这样设置时,结果与预期的差不多,我可以按预期交换测试和结果。 isPrime3 比 isPrime1 快大约 200 倍。
我的真实代码有点复杂。我有几个类可以找到这样的素数:
class PrimeNumberFinder1 {
@Override
bool isPrime(i) { /* same code as in static isPrime1() */ };
}
class PrimeNumberFinder2 extends PrimeNumberFinder1 {
@Override
bool isPrime(i) { /* same code as in static isPrime2() */ };
}
class PrimeNumberFinder3 extends PrimeNumberFinder1 {
@Override
bool isPrime(i) { /* same code as in static isPrime3() */ };
}
class PrimeNumberFinder4 extends PrimeNumberFinder1 {
@Override
bool isPrime(i) { /* same code as in static isPrime4() */ };
}
我有这样的课程:
class SomeClassWithPrimeNumberFinder {
PrimeNumberFinder1 _pnf;
void setPrimeNumberFinder(PrimeNumberFinder1 pnf) {
_pnf = pnf;
}
void stressTest() {
StressTest.doAction(10000000, () -> {
for (int i = 0; i < 100; i++) {
_pnf.isPrime(i);
}
});
}
}
还有我的主要方法:
public static void main(String() args) {
SomeClassWithPrimeNumberFinder sc = new SomeClassWithPrimeNumberFinder();
sc.setPrimeNumberFinder(new PrimeNumberFinder1());
sc.stressTest();
sc.setPrimeNumberFinder(new PrimeNumberFinder2());
sc.stressTest();
sc.setPrimeNumberFinder(new PrimeNumberFinder3());
sc.stressTest();
sc.setPrimeNumberFinder(new PrimeNumberFinder4());
sc.stressTest();
}
使用此设置PrimeNumberFind1 与第一次测试中的 isPrime1() 一样快。但是PrimeNumberFind3 在第一次测试中比 isPrime3() 慢了大约 200 倍。
如果我移动 PrimeNumberFind3 使其首先运行,我在第一次测试中得到与 isPrime3() 相同的时间。其余时间也稍慢一些 (5-10%),但没有像 PrimeNumberFind3 那样。
前 3 个 PrimeNumberFind 只是循环和 if。不涉及任何国家。最后一个具有创建查找列表的构造函数,但也只是一个简单的循环。如果我从构造函数中取出代码并使用数组文字创建查找列表,则时间是相同的。
任何想法为什么会发生这种情况?
【问题讨论】:
-
您是否尝试过将结果累积到
volatile字段中而不是将其丢弃? -
你也忘了给我们看相关代码...
-
我尝试将结果累积到
volatile字段中,但没有任何区别。我是 Java 新手,但 volatile 与缓存自己的值版本的线程有关,对吧?这个程序是纯单线程的。 -
@carlsb3rg 没错,
volatile与多线程有关,防止对字段的读/写重新排序(粗略地说)。但作为上述的副作用,它也阻止了读/写被完全优化。这就是为什么即使在单线程微基准测试中也使用它们来累积结果的原因。 -
@biziclop 积累是关键,但我不必积累到易变的领域。 @peter-lawrey 明白了,但还是谢谢你。我相信
volatile稍后会派上用场;)