Alexis
Alexis

Reputation: 1162

Java MappedByteBuffer performance getting worse and worse when you change the map size continuously

Recently I had some tests about Java MappedByteBuffer. I found that if I continuously map the same file and read it, the time spend in reading getting longer and longer. But if I didn't change the map size, It would be faster than the I use the same map size in the map size variation test.

I hava a file "dataFile" in 1GB which filled with Integers.

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();
} 

And a method about reading it

// 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));
}

The variation buffSize test

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));
    }
}

Variation Outputs:

[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

The fixed buffSize test:

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

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

Output

[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

As the 2 tests shows, the time spent in reading with the same buffSize(1024MB) is quite different in 2 tests. The test with fixed buffSize is much faster than the variation test.

My question is: 1. How does this happened, why it will be faster? 2. Does the MappedByteBuffer occupy physical memory? As I see in the ActivityMonitor, it will not occupy physical memory.

Thanks

----- Update -----

The release buffer code:

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

I don't think the cause of this problem is memory usage. Because it has the same output even if I turn on the release code and gc code. Anyway, if is about the memory usage, I set the loop times to 100 in the second test, it should use more memory than the first test, but it's faster than the first one.

----- Update 2 -----

If I turn the buffSize to decrease instead of increase in test 1, the problem disappear.

@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));
    }
}

Output:

[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

Upvotes: 1

Views: 183

Answers (1)

user207421
user207421

Reputation: 311052

You aren't 'changing the map size continuously'.You keep creating new maps, and there is no mechanism whereby the mappings are released, including GC, so you are using more and more memory.

You should aim to use as few MappedByteBuffers as possible, which may mean you need to make the sizes larger as well.

I don't know what ByteBufferUtil.releaseByteBuffer(buffer) does, or where it comes from, but these things of their nature cannot be reliable.

Upvotes: 1

Related Questions