ByteBuffer.putLong ~ в 2 раза быстрее с не родным ByteOrder
Вот результат, который я не могу обернуть головой, несмотря на обширное чтение источника JDK и изучение внутренних процедур.
Я тестирую очистку ByteBuffer
, выделенный с allocateDirect
с помощью ByteBuffer.putLong(int index, long value)
, Основываясь на коде JDK, это приводит к одиночной 8-байтовой записи, если буфер находится в "родном порядке байтов", или к замене байтов, за которой следует то же самое, если это не так.
Так что я бы ожидал, что порядок байтов в нативе (для меня немного порядковый) будет, по крайней мере, таким же быстрым, как и в нативном. как выясняется, однако, неродные - в 2 раза быстрее.
Вот мой тест в Caliper 0.5x:
...
public class ByteBufferBench extends SimpleBenchmark {
private static final int SIZE = 2048;
enum Endian {
DEFAULT,
SMALL,
BIG
}
@Param Endian endian;
private ByteBuffer bufferMember;
@Override
protected void setUp() throws Exception {
super.setUp();
bufferMember = ByteBuffer.allocateDirect(SIZE);
bufferMember.order(endian == Endian.DEFAULT ? bufferMember.order() :
(endian == Endian.SMALL ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN));
}
public int timeClearLong(int reps) {
ByteBuffer buffer = bufferMember;
while (reps-- > 0) {
for (int i=0; i < SIZE / LONG_BYTES; i+= LONG_BYTES) {
buffer.putLong(i, reps);
}
}
return 0;
}
public static void main(String[] args) {
Runner.main(ByteBufferBench.class,args);
}
}
Результаты:
benchmark type endian ns linear runtime
ClearLong DIRECT DEFAULT 64.8 =
ClearLong DIRECT SMALL 118.6 ==
ClearLong DIRECT BIG 64.8 =
Это соответствует. Если я поменяюсь putLong
за putFloat
, это примерно в 4 раза быстрее для родного порядка. Если вы посмотрите на то, как putLong
работает, это делает абсолютно больше работы в не родном случае:
private ByteBuffer putLong(long a, long x) {
if (unaligned) {
long y = (x);
unsafe.putLong(a, (nativeByteOrder ? y : Bits.swap(y)));
} else {
Bits.putLong(a, x, bigEndian);
}
return this;
}
Обратите внимание, что unaligned
верно в любом случае. Единственная разница между собственным и не родным порядком байтов Bits.swap
который предпочитает родной регистр (little-endian).
2 ответа
Подводя итоги обсуждения из списка рассылки "Механическая симпатия":
1. Аномалия, описанная OP, не воспроизводилась на моей установке (JDK7u40/Ubuntu13.04/i7), что приводило к стабильной производительности как для кучи, так и для прямых буферов во всех случаях, а прямой буфер предлагал огромное преимущество в производительности:
BYTE_ARRAY DEFAULT 211.1 ==============================
BYTE_ARRAY SMALL 199.8 ============================
BYTE_ARRAY BIG 210.5 =============================
DIRECT DEFAULT 33.8 ====
DIRECT SMALL 33.5 ====
DIRECT BIG 33.7 ====
Метод Bits.swap(y) встроен в одну инструкцию и поэтому не может / не должен учитывать большую часть различий / накладных расходов.
2. Вышеуказанный результат (т. Е. Противоречащий опыту ОП) был независимо подтвержден наивным ручным тестом и тестом JMH, написанным другим участником.
Это наводит меня на мысль, что вы испытываете какие-то локальные проблемы или проблемы с инфраструктурой сравнительного анализа. Было бы полезно, если бы другие могли провести эксперимент и посмотреть, смогут ли они воспроизвести ваш результат.
Значение по умолчанию - big endian, даже в системах с прямым порядком байтов. Можете ли вы попробовать ByteOrder.nativeOrder(), поскольку это должно быть быстрее для вас.
прямые байтовые буферы быстрее для ввода-вывода, так как буферы кучи должны быть скопированы в / из прямого буфера.
Кстати, вы могли бы сравнить это с использованием Unsafe напрямую, так как здесь есть проверка границ, чтобы увидеть, насколько это важно.