2014-02-22 43 views
1

我有一個動態分配的數組,其中包含有1700萬個元素的struct。將其保存到磁盤上,我寫fwrite()性能遠低於磁盤容量

fwrite(StructList, sizeof(Struct), NumStructs, FilePointer) 

在後面的步驟我用等效fread聲明讀它,就是使用sizeof(Struct)NumStructs計數。我期望得到的文件將大約3.5 GB(這是所有x64)。

是否有可能通過sizeof(Struct) * NumStructs作爲大小和1作爲計數來加速?我爲什麼寫入操作可能需要分鐘在32 GB RAM(大量寫入緩存)的快速計算機上寫着我的腦袋。我已經運行了自制基準測試,並且緩存足夠積極,第一個800 MB到1 GB的典型值爲400 MB /秒。 PerfMon顯示它在fwrite期間佔用了一個內核的100%。

我看到here這個問題,所以我問的是,fwrite中是否存在一些循環,可以通過告訴它寫入1個大小爲n * s的元素而不是n個元素來「欺騙」大小爲s。

編輯

我在釋放模式和兩次我放棄了等待跑這兩次。然後我以調試模式運行它,知道通常運行時間會更長。要寫入的數據的確切大小是4,368,892,928字節。在所有這三種情況下,PerfMon都會顯示兩次間隔30秒的磁盤寫入活動,然後CPU達到100%的一個內核。該文件在那個點上是73,924,608字節。我在fwrite的任一側都有斷點,所以我知道它就在那裏。看起來似乎有些東西卡住了,但我會讓它在一夜之間運行並看看。

編輯

離開這個過夜,在fwrite肯定掛,文件從來沒有過去的70 MB。

+0

你試過了嗎?爲了防止緩存阻礙,您可以使用其他應用程序佔用其他內存。 – Mysticial

+3

更改這樣的參數不會影響性能;如果寫入很短,它只會影響錯誤報告。國際海事組織將不會有太多的事情可以改善性能。您只需要一次呼叫即可將系統呼叫開銷降至最低;從那裏,這是o/s能夠多快分配3.5 GiB磁盤空間的問題。您可以查找系統調用以預配置文件大小並儘可能接近地進行分配,但這非常適合平臺。我看到你的標籤中有Windows;我不能幫助更多...... –

+2

如果你的程序正在燃燒100%的核心,那麼它不會被磁盤陷入困境。這使得你認爲它與fwrite()有很大關係。不信任任何反惡意軟件並使用分析器。 –

回答

2

這絕對是一個問題fwrite(我試過VS2012和2010)。

從一個標準的C++項目開始,我只在靜態鏈接中更改了使用多字節字符集x64 target和標準庫的多線程調試版本的設置。

下面的代碼成功(沒有錯誤的簡潔檢查):

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
    FILE *fp; 
    long long n; 
    unsigned char *data; 

    n = 4LL * 1024 * 1024 * 1024 - 1; 

    data = (unsigned char *)malloc(n * sizeof(unsigned char)); 

    fp = fopen("T:\\test.bin", "wb"); 

    fwrite(data, sizeof(unsigned char), n, fp); 

    fclose(fp); 
} 

在我的機器上調試版本,該程序完成在1分鐘左右(malloc的只需要幾秒鐘,所以這是主要是fwrite),平均消耗30%的CPU。 PerfMon顯示寫入完全發生在最後是一個4 GB(寫入緩存)的「閃存」。

在賦值n中將- 1更改爲+ 1,並且重現該問題:瞬時100%的CPU使用率,並且沒有任何內容被寫入。幾分鐘後,文件的大小仍然是0字節(在我的實際代碼中回憶它設法轉儲70 MB左右)。

這絕對是fwrite問題,如下面的代碼可以寫入文件就好了:

int main() 
{ 
    FILE *fp; 
    long long n; 
    long long counter = 0; 
    long long chunk; 
    unsigned char *data; 

    n = 4LL * 1024 * 1024 * 1024 + 1; 

    data = (unsigned char *)malloc(n * sizeof(unsigned char)); 

    fp = fopen("T:\\test.bin", "wb"); 

    while (counter < n) 
    { 
     chunk = min(n - counter, 100*1000); 
     fwrite(data+counter, sizeof(unsigned char), chunk, fp); 
     counter += chunk; 
    } 

    fclose(fp); 
} 

在我的機器,這花了45秒而不是1分鐘。 CPU使用率不是恆定的,它以突發形式出現,並且報告的IO寫入比「單塊」方法分佈得更多。

如果速度增加是錯誤的(即由於緩存),我會感到非常驚訝,因爲在編寫包含所有相同數據和包含隨機數據和報告寫入速度的文件之前,與緩存)是相同的。所以我敢打賭,至少這個fwrite的實現不喜歡一次傳遞給它的大塊。

我還測試了fread在關閉4 GB + 1格式的寫入文件後立即進行讀取,並及時返回 - 最多幾秒(這裏沒有真實數據,因此我沒有檢查它) 。

編輯

我跑了一些測試與塊寫入方法和4 GB-1文件的單一fwrite的調用(這兩種方法都可以做的最大尺寸)。多次運行程序(使用代碼打開文件,使用多個fwrite調用寫入,關閉,然後再次打開,單次調用並關閉),毫無疑問,塊寫入方法返回的速度更快。在最壞的情況下,單次呼叫所需時間爲68%,最多隻有20%。

+0

是的看起來像是一個32位的包裝錯誤。請注意,如果爲大塊保留虛擬機空間,但在內存被觸摸之前不會初始化交換,則可以向前付出「malloc」成本。我不知道VC malloc是否這樣做。但如果是這樣,這可能會導致寫入和尋呼機之間的奇怪交互。在寫入之前嘗試初始化內存會很有趣。 – Gene

0

這不是一個問題fwrite但意圖(但無可否認)行爲:

fwrite()功能必須寫,從陣列指向ptr,至多nitems元件,其尺寸由size指定給流指向的流。 對於每個對象,size呼叫須作出的fputc()功能,從數組取的值(按順序)[...]

因此,基本上,通過正確使用fwrite作弊,你正在請求撥打fputc的數十億電話。
考慮到上述要求,很明顯你如何作弊才能使其正常工作。

+1

我不明白你說什麼作弊?併網站您的來源。 – pelesl

+0

如果您正在編寫大小爲Y的X結構,並且您告訴C標準庫改爲寫入一個大小爲X * Y的對象,那實際上就是「作弊」。你沒有說出你在做什麼的真相。標準報價來自http://pubs.opengroup.org/onlinepubs/9699919799/functions/fwrite.html,但您可以參考ISO 9899(C99)17.9.8.2中的相同措辭,如果您願意的話。 – Damon

+0

我不會像你這樣讀。對我來說,這表示fwrite有兩個嵌套循環,其中是對fputc的調用。如您所說,作弊行爲除了返回值的粒度之外沒有任何區別(即知道實際寫入了多少項目) – pelesl