MYK*_*MYK 13 java performance microbenchmark branch-prediction jmh
在参考这个问题时,答案指出未排序的数组花费更多时间,因为它未通过分支预测测试.但如果我们对程序进行微小改动:
import java.util.Arrays;
import java.util.Random;
public class Main{
public static void main(String[] args) {
// Generate data
int arraySize = 32768;
int data[] = new int[arraySize];
Random rnd = new Random(0);
for (int c = 0; c < arraySize; ++c) {
data[c] = rnd.nextInt() % 256;
}
// !!! With this, the next loop runs faster
Arrays.sort(data);
// Test
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < 100000; ++i) {
// Primary loop
for (int c = 0; c < arraySize; ++c) {
if (data[c] >= 128) {
sum = data[c];
}
}
}
System.out.println((System.nanoTime() - start) / 1000000000.0);
System.out.println("sum = " + sum);
}
}
Run Code Online (Sandbox Code Playgroud)
在这里我已经更换(来自原始问题)
if (data[c] >= 128)
sum += data[c];
Run Code Online (Sandbox Code Playgroud)
同
if (data[c] >= 128)
sum = data[c];
Run Code Online (Sandbox Code Playgroud)
未排序的数组给出约.同样的结果,我想问为什么分支预测在这种情况下不起作用?
Mar*_*nik 10
我用jmh来分析这个.这是我的代码:
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 3, time = 1)
@State(Scope.Thread)
@Fork(2)
public class Comparison
{
static final int SIZE = 1<<15;
final int[] data = new int[SIZE];
@Setup
public void setup() {
int i = 1;
for (int c = 0; c < SIZE; ++c) data[c] = (i*=611953);
for (int c = 0; c < SIZE; ++c) data[c] = data[c] >= 128? 128 : 127;
}
@GenerateMicroBenchmark
public long sum() {
long sum = 0;
for (int c = 0; c < SIZE; ++c) if (data[c] >= 128) sum += data[c];
return sum;
}
}
Run Code Online (Sandbox Code Playgroud)
注意我不使用排序或随机数生成; 它们是不必要的并发症.使用上面代码中使用的公式:
data[c] = (i*=611953);
Run Code Online (Sandbox Code Playgroud)
我的运行时间为132μs.如果我注释掉涉及的行
data[c] = data[c] >= 128? 128 : 127;
Run Code Online (Sandbox Code Playgroud)
时间不会改变.这消除了所有算术考虑,并侧重于分支预测.如果我使用
data[c] = 127;
Run Code Online (Sandbox Code Playgroud)
我得到13μs,如果我使用
data[c] = 128;
Run Code Online (Sandbox Code Playgroud)
我得到16μs.这是"基本案例",强调不断分支决策之间的区别.
我的结论:这绝对是低级分支预测的结果.
我们现在分析你的干预.如果我使用上面代码中提供的公式,但更改
if (data[c] >= 128) sum += data[c];
Run Code Online (Sandbox Code Playgroud)
至
if (data[c] >= 128) sum = data[c];
Run Code Online (Sandbox Code Playgroud)
然后时间确实从132μs下降到27μs.
这是我对解释下降的猜测:JIT编译器可以做的优化技巧是反转循环的方向.现在你的代码变成了
for (int c = SIZE-1; c <= 0; --c) if (data[c] >= 128) { sum = data[c]; break; }
Run Code Online (Sandbox Code Playgroud)
循环已经短路到达到与原始循环相同结果所需的最小迭代次数.
我加了这个
data[SIZE-1] = 128;
Run Code Online (Sandbox Code Playgroud)
到setup()
方法结束,但它没有改变时间.这似乎使"循环逆转"猜想的天真版本无效.
cmovl
在分析程序集时,我发现:
cmp edx, 0x80
cmovl eax, ebx
Run Code Online (Sandbox Code Playgroud)
cmovl
是一个条件移动指令,它将执行在then
分支中发生的赋值的效果,但不涉及任何跳转,因此消除了与分支预测失败相关的任何惩罚.这是对实际效果的一个很好的解释.
归档时间: |
|
查看次数: |
597 次 |
最近记录: |