2009-12-29 235 views
13

我得到了一些我需要解析的大文件,而且人們一直在推薦mmap,因爲這應該避免必須在內存中分配整個文件。mmap問題,分配大量內存

但看着'頂部'它看起來好像我打開整個文件到內存中,所以我認爲我必須做錯了什麼。 '熱門節目> 2.1演出'

這是一個代碼片斷,顯示我在做什麼。

感謝

#include <stdio.h> 
#include <stdlib.h> 
#include <err.h> 
#include <fcntl.h> 
#include <sysexits.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <sys/mman.h> 
#include <cstring> 
int main (int argc, char *argv[]) { 
    struct stat sb; 
    char *p,*q; 
    //open filedescriptor 
    int fd = open (argv[1], O_RDONLY); 
    //initialize a stat for getting the filesize 
    if (fstat (fd, &sb) == -1) { 
    perror ("fstat"); 
    return 1; 
    } 
    //do the actual mmap, and keep pointer to the first element 
    p =(char *) mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); 
    q=p; 
    //something went wrong 
    if (p == MAP_FAILED) { 
    perror ("mmap"); 
    return 1; 
    } 
    //lets just count the number of lines 
    size_t numlines=0; 
    while(*p++!='\0') 
    if(*p=='\n') 
     numlines++; 
    fprintf(stderr,"numlines:%lu\n",numlines); 
    //unmap it 
    if (munmap (q, sb.st_size) == -1) { 
    perror ("munmap"); 
    return 1; 
    } 
    if (close (fd) == -1) { 
    perror ("close"); 
    return 1; 
    } 
    return 0; 
} 
+0

@monkeyking,code-pre的正確關閉是/ pre/code,不是post :-)修復了代碼標籤。 – paxdiablo 2009-12-29 03:34:34

+0

啊,萬分感謝! #include我不能把它們放到代碼示例中 – monkeyking 2009-12-29 03:37:54

+0

標記整個塊然後使用CTRL-K - 這會縮進四個空格。我現在已經這樣做了,你應該可以看到一個stdio包含。 – paxdiablo 2009-12-29 03:44:06

回答

39

不,你在做什麼是映射文件到內存中。這與將文件實際讀入內存不同。

如果您要閱讀它,則必須將整個內容傳輸到內存中。通過映射它,您可以讓操作系統處理它。如果您嘗試讀取或寫入該內存區域中的某個位置,操作系統將首先爲您加載相關部分。它會而不是加載整個文件,除非需要整個文件。

這就是你獲得性能增益的地方。如果你映射整個文件,但只改變一個字節然後取消映射,你會發現根本沒有太多的磁盤I/O。

當然,如果你觸摸文件中的每個字節,那麼是的,它將全部在某一時刻加載,但不一定在物理RAM中一次加載。但即使您預先加載整個文件,情況也是如此。如果系統中沒有足夠的物理內存來容納所有數據,操作系統將交換部分數據以及其他進程。

內存映射的主要優點是:

  • 你推遲讀取文件部分在需要直到他們(如果永遠都不會需要他們,他們沒有得到加載)。所以在加載整個文件時沒有大的前期成本。它攤銷加載的成本。
  • 寫入是自動的,你不必寫出每個字節。只需關閉它,操作系統將寫出更改後的部分。我認爲這也發生在內存換出時(在低物理內存的情況下),因爲你的緩衝區只是一個文件窗口。

請記住,您的地址空間使用情況和物理內存使用情況之間極有可能斷開連接。您可以在只有1G內存的32位機器上分配4G的地址空間(理想情況下,儘管可能存在操作系統,BIOS或硬件限制)。操作系統處理與磁盤的分頁。

並回答您的澄清進一步要求:

只是爲了澄清。所以如果我需要整個文件,mmap會實際加載整個文件?

是的,但它可能不在物理一次記憶。操作系統會將位退回到文件系統,以便引入新的位。

但它也會這樣做,如果你已經手動讀取整個文件。這兩種情況的區別如下。

手動將文件讀入內存時,操作系統會將部分地址空間(可能包含數據或不可用)交換到交換文件。當你完成它時你需要手動重寫這個文件。

通過內存映射,您已經有效地告訴它使用原始文件作爲額外的交換區域,該文件/內存僅爲。並且,當數據被寫入到那個交換區域時,它立即影響實際文件。所以,當你完成後不必手動重寫任何東西,也不會影響正常的交換(通常)。

這真的僅僅是一個窗口,文件:

                                        memory mapped file image

+0

只是爲了澄清。所以如果我需要整個文件,mmap會實際加載整個文件? – monkeyking 2009-12-29 03:47:38

+0

是的,查看更新。 – paxdiablo 2009-12-29 04:01:52

+0

@paxdiablo,你能否澄清一下:「手動將文件讀入內存時,操作系統會將部分地址空間(可能包含數據或不可用)交換到交換文件中」。如果我們讀取(2)整個文件到內存 - >寫(2)一些數據 - >關閉(2)它(fsync(2),如果需要的話)是否意味着該文件將不包含最新的更改? 還是應該使用下面的方案?讀(2) - >一些更改 - >寫(2)整個文件。 – dshil 2018-02-21 03:11:37

0

系統肯定會嘗試將所有數據放入物理內存。你會保存的是交換。

+0

錯誤。 VM將使用RAM來使文件可用;但只要存在一定的內存壓力,它就會被換出。這幾乎就像使用RAM作爲文件的緩存一樣。 – Javier 2009-12-29 03:39:08

+0

錯了。它永遠不會使用交換空間進行只讀映射。它會做I/O來交換它,但你不會使用空間。 – bmargulies 2009-12-29 13:29:28

3

top有許多與內存相關的列。他們中的大多數是基於映射到進程的內存空間的大小;包括任何共享庫,交換出的RAM和mmap空間。

檢查RES列,這與當前正在使用的物理RAM有關。我認爲(但不確定)它將包括用於「緩存」mmap文件的RAM

1

「在內存中分配整個文件」將兩個問題混爲一談。一個是你分配多少虛擬內存;另一個是將文件的哪些部分從磁盤讀取到內存中。在這裏你分配足夠的空間來容納整個文件。但是,只有您觸摸的頁面實際上會在磁盤上進行更改。而且,無論進程發生什麼情況,只要您更新了mmap爲您分配的內存中的字節,它們就會被正確更改。通過使用mmap的「size」和「offset」參數,您可以一次只映射文件的一部分,從而分配更少的內存。然後,您必須通過映射和取消映射來自己管理一個窗口,或許通過文件移動窗口。分配一大塊內存需要相當長的時間。這可能會在應用程序中引入意外的延遲。如果您的進程已經佔用大量內存,則虛擬內存可能已經變得碎片化,並且在您提出要求時可能無法爲大文件找到足夠大的塊。因此可能需要儘可能早地進行映射,或者使用某種策略來保持足夠大的內存空間,直到您需要爲止。

但是,當您指定需要解析文件時,爲什麼不通過組織解析器來操作數據流來完全避免這種情況?然後,最需要的是一些預見性和一些歷史記錄,而不需要將文件的離散塊映射到內存中。

2

您可能被提供了錯誤的建議。

內存映射文件(mmap)在您解析它們時將使用越來越多的內存。當物理內存變低時,內核將根據其LRU(最近最少使用)算法從物理內存中取消部分文件的映射。但是LRU也是全球性的。LRU也可能會強制其他進程將頁面交換到磁盤,並減少磁盤緩存。這會對其他流程和整個系統的性能造成嚴重的負面影響。

如果你是線性讀取文件,比如計算行數,mmap是一個不錯的選擇,因爲在將內存釋放回系統之前,mmap會填滿物理內存。最好使用傳統的I/O方法,一次在一個塊中進行流或讀。這樣內存可以在之後立即釋放。

如果你是隨機訪問一個文件,mmap是一個好的選擇。但這並不是最優的,因爲你仍然依賴內核的一般LRU算法,但使用它的速度比編寫緩存機制快。

一般來說,我絕不會建議任何人使用mmap,除了一些極端的性能邊緣情況 - 例如同時從多個進程或線程訪問文件,或者文件與免費量有效內存。

+1

呃。您可以在使用mmap進行約10次樹查找的過程中,逐塊地預先生成B +樹結構。 – 2010-07-15 20:37:13

+0

不一定正確。第一次讀取的IO的性能將在mmap和pread之間幾乎相同(對於所有實際目的而言) - 兩者都必須從媒體中讀取它。問題在於隨後的讀取。 Mmap將使用內核的內存驅逐LRU算法來決定映射哪些頁面。通過Pread,IO子系統可以決定從緩存中刪除哪些塊(如果有的話)。在釋放未使用的內存資源方面,這兩種方法都不是高效的。因此,依賴於mmap的應用程序可能會通過佔用內存資源來降低整個系統的性能和效率。 – tgiphil 2010-07-22 03:43:38

+1

您不計算每個系統調用浪費的幾千個CPU週期。 mmap加載速度更快。 – 2011-07-11 21:58:55

0

如果不希望整個文件一次映射到內存中,則需要指定小於mmap調用中文件總大小的大小。使用偏移參數和較小的尺寸,您可以一次一個地映射較大文件的「窗口」。

如果您的解析只是單次傳遞文件,並且回看或向前看的次數最少,那麼使用mmap代替標準庫緩衝I/O,實際上並不會獲得任何結果。在你用來計算文件中換行符的例子中,使用fread()這樣做會更快。不過,我認爲你的實際解析更復雜。

如果您需要一次讀取文件的多個部分,則必須管理多個mmap區域,這可能會很快變得複雜。

0

有點偏題。

我不完全同意馬克的回答。其實mmapfread快。

儘管利用了系統的磁盤緩衝區,fread也有一個內部緩衝區,此外,數據將被複制到用戶提供的緩衝區中,因爲它被調用。

恰恰相反,mmap只是返回指向系統緩衝區的指針。所以有一個雙存儲器拷貝節省

但使用mmap有點危險。您必須確保指針永遠不會離開文件,否則會出現段錯誤。雖然在這種情況下fread只是返回零

+0

我實際上已經完成了基準測試,結果顯示(在Mac OS X上),窗口化mmap和fread之間的直通式讀取吞吐量幾乎沒有差異。是的,使用高級庫時,數據會被複制(最多三次),但與實際的I/O時間相比,複製數據的時間可以忽略不計。我通常使用適當的最高級別界面。 – 2010-01-02 20:44:28

+1

@Mark:在第一次閱讀文件時與您同意。但是,如果程序不止一次讀取文件,或者程序反覆運行(例如Web服務器),則會有很大差異。 (將'fread'改爲'mmap'使得我的經驗中整個程序速度提高了50%) – iamamac 2010-01-03 05:00:46

+0

特別是當考慮到'fseek' +'fread'總是讀取緩衝區的完整大小以獲得任意大小。 – 2010-11-04 13:44:31

4

您也可以使用fadvise(2)(和madvise(2),另請參閱posix_fadvise & posix_madvise)將mmaped文件(或其部分)標記爲只讀。

#include <sys/mman.h> 

int madvise(void *start, size_t length, int advice); 

的建議是可以在其中

MADV_SEQUENTIAL 

期望順序頁引用的建議參數指示。 (因此,在給定範圍內的頁面可以積極地提前閱讀, ,並且可能會在訪問它們後立即釋放。)

可移植性: posix_madvise和posix_fadvise是IEEE標準1003.1,2004年和常量的先進的實時選項將POSIX_MADV_SEQUENTIAL和POSIX_FADV_SEQUENTIAL的一部分。