Если вы хотите измерить пропуски доступа к кешу, вызов clflu sh напрямую возможен из java, но в итоге вы пишете библиотеку JNI с ASM intrinsi c. Нельзя сказать, что вы, вероятно, не можете сделать это надежным способом, поскольку вам нужно предоставить виртуальный адрес, и G C может переместить ваш буфер в любое время.
Вместо этого я предлагаю вам следующее:
- Используйте эталонный снимок одного снимка, как и вы
- Измерение одной операции не было бы хорошей идеей (измерение наносекунд имеет высокую погрешность). Вместо этого создайте миллионов таких идентичных буферов и выполните ту же операцию для миллионов буферов. Каждый раз, когда вы получаете доступ к следующему буферу, которого нет в кэше
- Вы также можете выполнять некоторые вычисления между итерациями. Например, чтение 32+ мегабайт памяти, чтобы исключить строки кэша из вашего кэша. Но с миллионами буферов он не показывает никакой прибыли
Полученный код:
@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();
}
}
Как видите, я сам являюсь держателем состояния padd, поэтому чтение ссылок на буфера всегда в разных строках кэша (класс Padder имеет 24 длинных поля). О, и я также сам добавляю буфер, JMH не сделает это за вас.
Я реализовал эту идею, и у меня есть среднее значение 100 нс для простой операции, такой как чтение первого элемента буфера. Чтобы прочитать первый элемент, вам нужно прочитать две строки кэша (ссылка на буфер + первый элемент). Полный код здесь