在Single Shot基准测试的情况下刷新缓存行



我想运行一个SingleShotJMH基准测试,其中与正在运行的内存相关的所有缓存层次结构都被可靠地刷新。

基准大致如下:

@State(Scope.Benchmark)
public class MyBnchmrk {
public byte buffer[];
@Setup(Level.Trial)
public void generateSampleData() throws IOException {
// writes to buffer ...
}
@Setup(Level.Invocation)
public void flushCaches() {
//Perfectly I'd like to invoke here something like
//_mm_clflushopt() intrinsic as in GCC/clang for each line of the buffer
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
public void benchmarkMemoryBoundCode() {
//the benchmark
}
}

在需要单次测量或手写clflush之前,是否有Java方法来刷新缓存

如果您想测量缓存未命中访问,可以从java直接调用clflush,但最终会编写带有ASM内在的JNI库。更不用说,您可能无法以可靠的方式完成这项工作,因为您需要提供虚拟地址,GC可能会随时移动您的缓冲区。

相反,我给你这个:

  • 使用单个快照基准测试
  • 测量单个操作不是一个好主意(测量纳秒有很高的误差(。相反,创建百万个这样的相同缓冲区,并对百万个缓冲区执行相同的操作。每次访问不在缓存中的下一个缓冲区时
  • 您还可以在迭代之间运行一些计算。例如,读取32MB以上的内存,以便从缓存中逐出缓存行。但有了数百万的缓冲区,它没有任何利润

结果代码:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class BufferBenchmarkLatency {
public static final int BATCH_SIZE = 1000000;
public static final int MY_BUFFER_SIZE = 1024;
public static final int CACHE_LINE_PADDING = 256;
public static class StateHolder extends Padder {
byte buffer[];
StateHolder() {
buffer = new byte[CACHE_LINE_PADDING + MY_BUFFER_SIZE + CACHE_LINE_PADDING];
Arrays.fill(buffer, (byte) ThreadLocalRandom.current().nextInt());
}
}
private final StateHolder[] arr = new StateHolder[BATCH_SIZE];
private int index;
@Setup(Level.Trial)
public void setUpTrial() {
for (int i = 0; i < arr.length; i++) {
arr[i] = new StateHolder();
}
ArrayUtil.shuffle(arr)
}
@Setup(Level.Iteration)
public void prepareForIteration(Blackhole blackhole) {
index = 0;
blackhole.consume(CacheUtil.evictCacheLines());
System.gc();
System.gc();
}
@Benchmark
public long read() {
byte[] buffer = arr[index].buffer;
return buffer[0];
}
@TearDown(Level.Invocation)
public void move() {
index++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(BufferBenchmarkLatency.class.getSimpleName())
.measurementBatchSize(BATCH_SIZE)
.warmupBatchSize(BATCH_SIZE)
.measurementIterations(10)
.warmupIterations(10)
.build();
new Runner(opt).run();
}
}

正如您所看到的,我填充状态保持器本身,所以读取缓冲区引用总是在不同的缓存行上(Padder类有24个长字段(。哦,而且我也垫缓冲区本身,JMH不会为你做的。

我已经实现了这个想法,对于读取缓冲区的第一个元素这样的简单操作,我得到了平均100ns的结果。要读取第一个元素,您需要读取两个缓存行(缓冲区引用+第一个元素(。完整的代码在这里

最新更新