【问题标题】:Java MappedByteBuffer performance getting worse and worse when you change the map size continuously当您不断更改地图大小时,Java MappedByteBuffer 性能越来越差
【发布时间】:2016-05-25 05:47:52
【问题描述】:

最近我做了一些关于 Java MappedByteBuffer 的测试。我发现如果我不断地映射同一个文件并阅读它,阅读所花费的时间会越来越长。但是如果我不改变地图大小,它会比我在地图大小变化测试中使用相同的地图大小更快。

我有一个 1GB 的文件“dataFile”,里面填充了整数。

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

变化输出:

14071@LiuzhMacbook.local
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);
    }
}

输出

14157@LiuzhMacbook.local
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(1024MB) 进行读取所花费的时间在 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 -----

如果我在测试 1 中将 buffSize 减小而不是增大,问题就会消失。

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

输出:

16651@LiuzhMacbook.local
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

【问题讨论】:

    标签: java linux nio mappedbytebuffer


    【解决方案1】:

    您没有“不断更改地图大小”。您不断创建 地图,并且没有释放映射的机制,包括 GC,因此您使用的内存越来越多.

    您的目标应该是尽可能少地使用MappedByteBuffers,这可能意味着您还需要增大尺寸。

    我不知道ByteBufferUtil.releaseByteBuffer(buffer) 是做什么的,也不知道它来自哪里,但这些东西本质上是不可靠的。

    【讨论】:

    • 我不得不声明我不明白你的测试代码的目的,但如果你不改变映射大小,你可能总是得到相同的底层映射内存而不是另一个块,这节省了虚拟内存。反正我也不知道改的目的是什么。
    • 我想发现使用不同的map size对读取文件的效率有影响,所以我写了这段代码。我想知道如果我读取 1 GB 的文件,我应该使用什么地图大小。
    • 答案总是一样的。尽可能大。由于在同一进程中运行,您的测试完全无效,因此您将获得在单独进程中不会发生的各种进程内/缓存效果。这里的问题主要是你的测试方法,而不是结果。
    猜你喜欢
    • 2012-06-25
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 2017-08-03
    • 1970-01-01
    • 2014-05-02
    相关资源
    最近更新 更多