我想运行一个SingleShot
JMH基准测试,将与正在处理的内存相关的所有缓存层次结构可靠地清除。
该基准大致如下:
@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刷新缓存的方法?
答案 0 :(得分:1)
如果要测量高速缓存未命中的访问,可以从Java直接调用clflush,但是最终使用ASM内部函数编写了JNI库。更不用说,您可能无法以可靠的方式执行此操作,因为您需要提供虚拟地址,GC可能会随时移动缓冲区。
相反,我向您提供:
结果代码:
@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不会为您这样做。
我已经实现了这个想法,对于简单的操作(例如读取缓冲区的第一个元素),我的平均结果为100 ns。要读取第一个元素,您需要读取两个缓存行(缓冲区引用+第一个元素)。完整的代码是here