mmap() против производительности Java MappedByteBuffer?

Я разрабатывал проект C++ из существующего кода Java. У меня есть следующий код C++ и чтение кода Java из того же тестового файла, который состоит из миллионов целых чисел.

C++:

    int * arr = new int[len]; //len is larger than the largest int from the data
    fill_n(arr, len, -1);  //fill with -1
    long loadFromIndex = 0;
    struct stat sizeResults;
    long size;
    if (stat(fileSrc, &sizeResults) == 0) {
        size = sizeResults.st_size; //here size would be ~551950000 for 552M test file
    }
    mmapFile = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, pageNum*pageSize);
    long offset = loadFromIndex % pageSize;
    while (offset < size) {
        int i = htonl(*((int *)(mmapFile + offset)));
        offset += sizeof(int);
        int j = htonl(*((int *)(mmapFile + offset)));
        offset += sizeof(int);
        swapElem(i, j, arr);
    }
    return arr;

Джава:

    IntBuffer bb = srcFile.getChannel()
                    .map(MapMode.READ_ONLY, loadFromIndex, size)
                    .asIntBuffer().asReadOnlyBuffer();
    while (bb.hasRemaining()) {
        int i = bb.get();
        int j = bb.get();
        swapElem(i, j, arr); //arr is an int[] of the same size as the arr in C++ version, filled with -1
    }
    return arr;

void swapElem(arr) в C++ и Java идентичны. Он сравнивает и изменяет значения в массиве, но оригинальный код довольно длинный для публикации здесь. В целях тестирования я заменил его следующей функцией, чтобы цикл не был мертвым кодом:

void swapElem(int i, int j, int * arr){   // int[] in Java
    arr[i] = j;
}

Я предполагал, что версия C++ должна превосходить версию Java, но тест дает противоположный результат - код Java почти в два раза быстрее, чем код C++. Есть ли способ улучшить код C++?

Я чувствую, может быть, mmapFile+offset в C++ повторяется слишком много раз, так что это O(n) дополнений для этого и O(n) дополнений для offset+=sizeof(int) где n - количество целых чисел для чтения. Для Java IntBuffer.get(), он просто непосредственно считывает из индекса буфера, поэтому не требуется никакой операции сложения, кроме O(n) приращений индекса буфера на 1. Поэтому, включая приращения индекса буфера, C++ принимает O(2n) дополнений, в то время как Java принимает O(n)) дополнения. Когда речь идет о миллионах данных, это может привести к значительной разнице в производительности.

Следуя этой идее, я изменил код C++ следующим образом:

    mmapBin = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, pageNum*pageSize);
    int len = size - loadFromIndex % pageSize;
    char * offset = loadFromIndex % pageSize + mmapBin;
    int index = 0;
    while (index < len) {
        int i = htonl(*((int *)(offset)));
        offset += sizeof(int);
        int j = htonl(*((int *)(offset)));
        offset += sizeof(int);
        index+=2*sizeof(int);
    }

Я предполагал, что будет небольшой прирост производительности, но это не так.

Кто-нибудь может объяснить, почему код C++ работает медленнее, чем код Java? Благодарю.

Обновить:

Я должен извиниться, что когда я сказал, что -O2 не работает, возникла проблема с моей стороны. Я испортил Makefile, поэтому код C++ не перекомпилировался с использованием -O2. Я обновил производительность, и версия C++ с использованием -O2 превзошла версию Java. Это может решить вопрос, но если кто-то захочет поделиться тем, как улучшить код C++, я последую. Обычно я ожидаю, что он будет в 2 раза быстрее, чем код Java, но в настоящее время это не так. Спасибо всем за ваш вклад.

Компилятор: g++

Флаги: -стена -c -O2

Версия Java: 1.8.0_05

Размер файла: 552 МБ, все 4-байтовые целые

Процессор: 2,53 ГГц Intel Core 2 Duo

Память 4 ГБ 1067 МГц DDR3

Обновленный бенчмарк:

Время версии (мс)

C++: ~ 1100

Ява: ~ 1400

C++ (без цикла while): ~35

Java (без цикла while): ~40

У меня есть кое-что перед этим кодом, которое вызывает производительность ~35 мс (в основном, заполнение массива -1), но это не важно здесь.

1 ответ

У меня есть некоторые сомнения в правильности эталонного метода. Оба кода являются "мертвыми" кодами. На самом деле вы нигде не используете i и j, поэтому компилятор gcc или Java JIT могут решить фактически удалить цикл, видя, что он не влияет на будущий поток кода.

В любом случае, я бы изменил код C++ на:

mmapFile = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, pageNum*pageSize);
long offset = loadFromIndex % pageSize;
int i, j;
int szInc = 2 * sizeof(int);
while (offset < size) {
    scanf(mmapFile, "%d", &i);
    scanf(mmapFile, "%d", &j);
    offset += szInc; // offset += 8;
}

Это было бы эквивалентно коду Java. Кроме того, я бы продолжил использовать -O2 в качестве флагов компиляции. Имейте в виду, что htonl это дополнительное преобразование, которое Java-код, похоже, не делает.

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