簡體   English   中英

Java,為什么從MappedByteBuffer讀取比從BufferedReader讀取慢

[英]Java, why reading from MappedByteBuffer is slower than reading from BufferedReader

我試圖從一個可能很大的文件中讀取行。

為了獲得更好的性能,我嘗試使用映射文件。 但是當我比較性能時,我發現映射文件的方式甚至比我從BufferedReader讀取的速度還要慢一點

public long chunkMappedFile(String filePath, int trunkSize) throws IOException {
    long begin = System.currentTimeMillis();
    logger.info("Processing imei file, mapped file [{}], trunk size = {} ", filePath, trunkSize);

    //Create file object
    File file = new File(filePath);

    //Get file channel in readonly mode
    FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel();

    long positionStart = 0;
    StringBuilder line = new StringBuilder();
    long lineCnt = 0;
    while(positionStart < fileChannel.size()) {
        long mapSize = positionStart + trunkSize < fileChannel.size() ? trunkSize : fileChannel.size()  - positionStart ;
        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, positionStart, mapSize);//mapped read
        for (int i = 0; i < buffer.limit(); i++) {
            char c = (char) buffer.get();
            //System.out.print(c); //Print the content of file
            if ('\n' != c) {
                line.append(c);
            } else {// line ends
                processor.processLine(line.toString());
                if (++lineCnt % 100000 ==0) {
                    try {
                        logger.info("mappedfile processed {} lines already, sleep 1ms", lineCnt);
                        Thread.sleep(1);
                    } catch (InterruptedException e) {}
                }
                line = new StringBuilder();
            }
        }
        closeDirectBuffer(buffer);
        positionStart = positionStart + buffer.limit();
    }

    long end = System.currentTimeMillis();
    logger.info("chunkMappedFile {} , trunkSize: {},  cost : {}  " ,filePath, trunkSize, end - begin);

    return lineCnt;
}

public long normalFileRead(String filePath) throws IOException {
    long begin = System.currentTimeMillis();
    logger.info("Processing imei file, Normal read file [{}] ", filePath);
    long lineCnt = 0;
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        String line;

        while ((line = br.readLine()) != null) {
            processor.processLine(line.toString());
            if (++lineCnt % 100000 ==0) {
                try {
                    logger.info("file processed {} lines already, sleep 1ms", lineCnt);
                    Thread.sleep(1);
                } catch (InterruptedException e) {}
            }            }
    }
    long end = System.currentTimeMillis();
    logger.info("normalFileRead {} ,   cost : {}  " ,filePath, end - begin);

    return lineCnt;
}

在Linux中讀取文件大小為537MB的測試結果:

MappedBuffer方式:

2017-09-28 14:33:19.277 [main] INFO  com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :14804 , lines per seconds: 861852.0670089165

BufferedReader的方式:

2017-09-28 14:27:03.374 [main] INFO  com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :13001 , lines per seconds: 981375.1249903854

就是這樣:文件IO並不簡單明了。

您必須記住,您的操作系統對即將發生的事情具有巨大的影響。 從這個意義上講,沒有適用於所有平台上所有JVM實現的可靠規則。

當您真的需要擔心性能的最后一點時,在目標平台上進行深度分析是主要的解決方案。

除此之外,您將“性能”方面弄錯了。 含義:內存映射IO不會神奇地提高一次讀取應用程序內單個文件的性能。 它的主要優點是:

如果您有多個進程以只讀方式從同一文件訪問數據,那么mmap非常有用,這在我編寫的服務器系統中很常見。 mmap允許所有這些進程共享相同的物理內存頁面,從而節省了大量內存。

(從使用C mmap()系統調用的答案中引用)

換句話說:您的示例是關於讀取文件內容的。 最后,操作系統仍然必須轉向驅動器以從那里讀取所有字節。 含義:它讀取光盤內容並將其放入內存。 當您一次進行此操作時...在此基礎上再做一些“特殊”的事情真的沒關系。 相反,由於與“普通”讀取相比開銷較大,因此在執行“特殊”操作時,內存映射方法甚至可能更慢。

回到我的第一條記錄:即使您有5個進程讀取同一文件,內存映射方法也不一定更快。 正如Linux可能顯示的那樣:我已經將該文件讀入內存,並且它沒有改變-因此,即使沒有顯式的“內存映射”,Linux內核也可能緩存信息。

內存映射實際上並沒有提供任何優勢,因為即使您將文件批量加載到內存中,您仍然仍在一次處理一個字節。 如果以適當大小的byte[]塊處理緩沖區,則可能會提高性能。 即使這樣, BufferedReader版本的性能也可能更好或至少幾乎相同。

您的任務的本質是按順序處理文件。 BufferedReader已經很好地做到了這一點,並且代碼很簡單,所以如果必須選擇,我會選擇最簡單的選項。

另請注意,除了單字節編碼外,您的緩沖區代碼不起作用。 一旦每個字符獲得多個字節,它將嚴重失敗。

GhostCat是正確的。 除了您選擇操作系統之外,其他可能影響性能的因素也是如此。

  • 映射文件將對物理內存提出更大的要求。 如果物理內存“緊張”,可能會導致分頁活動和性能下降。

  • 如果您使用read系統調用讀取文件而不是將其映射到內存,則操作系統可以使用不同的預讀策略。 預讀(進入緩沖區高速緩存)可以使文件讀取快得多。

  • BufferedReader的默認緩沖區大小和OS內存頁面大小可能會有所不同。 這可能導致磁盤讀取請求的大小不同。 (更大的讀取次數通常會導致更大的吞吐量I / O。至少到一定程度。)

基准測試的方式也可能導致“偽影”。 例如:

  • 第一次讀取文件時,部分或全部文件的副本將落入緩沖區高速緩存中(在內存中)
  • 第二次讀取同一文件時,它的部分內容可能仍在內存中,並且表觀的read時間將縮短。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM