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 напрямую, так как здесь есть проверка границ, чтобы увидеть, насколько это важно.

Другие вопросы по тегам