Производительность Java MappedByteBuffer становится все хуже и хуже, когда вы постоянно меняете размер карты

Недавно у меня было несколько тестов о Java MappedByteBuffer. Я обнаружил, что если я постоянно сопоставляю один и тот же файл и читаю его, время, затрачиваемое на чтение, становится все больше и больше. Но если бы я не изменил размер карты, это было бы быстрее, чем если бы я использовал тот же размер карты в тесте изменения размера карты.

У меня есть файл «dataFile» размером 1 ГБ, который заполнен целыми числами.

private final File dataFile = new File("~/testfile");
private final int intNum = 1024 * 1024 * 1024 / 4; // 1GB Integers

@Test
public void writeFile() throws Exception {
    DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
    for (int i = 0; i < intNum; i++) {
        dos.writeInt(RandomUtils.nextInt());
    }
    dos.close();
} 

И метод о его чтении

// read this dataFile in a loop with fixed map size
private void bufferSizePerformanceTest(final int buffSize) throws Exception {
    Stopwatch stopwatch = Stopwatch.createStarted();
    FileChannel fc = new RandomAccessFile(dataFile, "r").getChannel();
    MappedByteBuffer buffer;
    final int readPerLoop = buffSize / 4;
    int currentLen = 0;
    int readCount = 0;
    for (int i = 1; ; i++) {
        int i1 = i * buffSize;
        if (i1 >= dataFile.length()) {
            buffer = fc.map(FileChannel.MapMode.READ_ONLY, currentLen, dataFile.length() - currentLen);
            for (int j = 0; j < readPerLoop; j++) {
                buffer.getInt();
                readCount++;
            }
            break;
        } else {
            buffer = fc.map(FileChannel.MapMode.READ_ONLY, currentLen, buffSize);
            currentLen = i1;
        }

        for (int j = 0; j < readPerLoop; j++) {
            buffer.getInt();
            readCount++;
        }
    }
    fc.close();
//        ByteBufferUtil.releaseByteBuffer(buffer);
//        System.gc();
    System.out.println("readCount : " + readCount + " raf buffer size " + getMBytes(buffSize) + " MB : " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}

Вариационный тест buffSize

private static int getMBytes(int bytes) {
    return bytes / 1024 / 1024;
}

// get the power of 2 by n
private static int getM(int n) {
    return (int) (Math.log10(n) / Math.log10(2));
}

@Test
public void testBuffSizeReadPerformance() throws Exception {
    System.out.println(ManagementFactory.getRuntimeMXBean().getName());
    for (int i = 0; i <= getM(1024); i++) {
        Thread.sleep(1000);
        bufferSizePerformanceTest((int) (Math.pow(2, i) * 1024 * 1024));
    }
}

Выходы вариаций:

[email protected]
readCount : 268435456 raf buffer size 1 MB : 122
readCount : 268435456 raf buffer size 2 MB : 133
readCount : 268435456 raf buffer size 4 MB : 29
readCount : 268435456 raf buffer size 8 MB : 35
readCount : 268435456 raf buffer size 16 MB : 38
readCount : 268435456 raf buffer size 32 MB : 124
readCount : 268435456 raf buffer size 64 MB : 241
readCount : 268435456 raf buffer size 128 MB : 456
readCount : 268435456 raf buffer size 256 MB : 1086
readCount : 268435456 raf buffer size 512 MB : 2458
readCount : 268435456 raf buffer size 1024 MB : 4952

Исправлен тест buffSize:

@Test
public void testBuffSizeReadPerformance2() throws Exception {
    System.out.println(ManagementFactory.getRuntimeMXBean().getName());

    for (int i = 0; i < 10; i++) {
        bufferSizePerformanceTest(1024 * 1024 * 1024);
    }
}

Выход

[email protected]
readCount : 268435456 raf buffer size 1024 MB : 127
readCount : 268435456 raf buffer size 1024 MB : 111
readCount : 268435456 raf buffer size 1024 MB : 20
readCount : 268435456 raf buffer size 1024 MB : 17
readCount : 268435456 raf buffer size 1024 MB : 23
readCount : 268435456 raf buffer size 1024 MB : 19
readCount : 268435456 raf buffer size 1024 MB : 21
readCount : 268435456 raf buffer size 1024 MB : 22
readCount : 268435456 raf buffer size 1024 MB : 20
readCount : 268435456 raf buffer size 1024 MB : 33

Как показывают 2 теста, время, затрачиваемое на чтение с одним и тем же buffSize (1024 МБ), сильно различается в 2 тестах. Тест с фиксированным buffSize намного быстрее, чем тест с вариациями.

У меня вопрос: 1. Как так получилось, почему будет быстрее? 2. Занимает ли MappedByteBuffer физическую память? Как я вижу в ActivityMonitor, он не будет занимать физическую память.

Спасибо

----- Обновлять -----

Код буфера выпуска:

public static void releaseByteBuffer(ByteBuffer buffer) throws NoSuchFieldException, IllegalAccessException {
    Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
    cleaner.clean();
}

Я не думаю, что причиной этой проблемы является использование памяти. Потому что он имеет тот же результат, даже если я включу код выпуска и код gc. В любом случае, если речь идет об использовании памяти, я установил время цикла на 100 во втором тесте, он должен использовать больше памяти, чем первый тест, но он быстрее, чем первый.

----- Обновление 2 -----

Если я уменьшу buffSize вместо увеличения в тесте 1, проблема исчезнет.

@Test
public void testBuffSizeReadPerformance3() throws Exception {
    System.out.println(ManagementFactory.getRuntimeMXBean().getName());
    for (int i = getM(1024); i >= 0; i--) {
        bufferSizePerformanceTest((int) (Math.pow(2, i) * 1024 * 1024));
    }
}

Выход:

[email protected]
readCount : 268435456 raf buffer size 1024 MB : 101
readCount : 268435456 raf buffer size 512 MB : 187
readCount : 268435456 raf buffer size 256 MB : 31
readCount : 268435456 raf buffer size 128 MB : 30
readCount : 268435456 raf buffer size 64 MB : 36
readCount : 268435456 raf buffer size 32 MB : 37
readCount : 268435456 raf buffer size 16 MB : 37
readCount : 268435456 raf buffer size 8 MB : 32
readCount : 268435456 raf buffer size 4 MB : 44
readCount : 268435456 raf buffer size 2 MB : 34
readCount : 268435456 raf buffer size 1 MB : 55

person Alexis    schedule 25.05.2016    source источник


Ответы (1)


Вы не «постоянно меняете размер карты». Вы продолжаете создавать новые карты, и нет механизма, с помощью которого эти сопоставления освобождаются, включая GC, поэтому вы используете все больше и больше памяти.

Вы должны стремиться использовать как можно меньше MappedByteBuffers, что может означать, что вам также нужно увеличить размеры.

Я не знаю, что делает ByteBufferUtil.releaseByteBuffer(buffer) и откуда оно берется, но эти вещи по своей природе не могут быть надежными.

person user207421    schedule 25.05.2016
comment
Я должен заявить, что не понимаю цели вашего тестового кода, но если вы не измените размер карты, вы, вероятно, всегда получаете одну и ту же базовую отображаемую память, а не другой фрагмент, который экономит виртуальную память. В любом случае, я не знаю, с какой целью его менять. - person user207421; 25.05.2016
comment
Я хочу выяснить, влияет ли какое-то влияние на эффективность чтения файла при использовании карты разного размера, поэтому я пишу этот код. И я хочу знать, если я читаю файл в 1 ГБ, какой размер карты мне следует использовать. - person Alexis; 25.05.2016
comment
Ответ на это всегда один и тот же. Как можно больше. Ваш тест полностью признан недействительным, если он запускается из одного и того же процесса, поэтому вы получаете все виды внутрипроцессных/кэш-эффектов, которые не произошли бы в отдельных процессах. Проблема здесь в основном в вашей методологии тестирования, а не в результатах. - person user207421; 25.05.2016