標籤:

C++怎樣讀取文件才有最快的速度?

大文件


文件讀取的速度極限就是硬碟的讀速度上限,想要達到硬碟的讀速度上限就要設法不讓硬碟停下來,讓它一直讀。

對於磁碟,要保證文件是連續存儲的,減少尋道時間,減少磁頭的移動次數。

充分利用緩存的機制,硬碟在響應讀請求的時候會預讀取一部分數據到緩存,下次讀取就從緩存拿不去真正訪問硬碟,所以每次讀請求的數據量要比緩存大,這樣才不會讓硬碟閑置。

如果OS支持非同步IO就用非同步的,pending一堆讀請求,目的就是不讓硬碟閑著。

在單獨的線程處理讀取到的數據,必要時開多個線程,保證處理速度遠遠快於讀取速度。

如果許可權夠,可以繞過文件系統直接讀取物理磁碟。

軟體能做的也就這些了,再提高速度只能升級硬體了。


順序讀大文件這個場景,內核優化過,你需要做的就是不要亂搞影響內核工作。老老實實用fread讀。不會madvise別隨便mmap,用了madvise也不會比fread更快。什麼非同步IO,多線程調read,direct io,都三個字,別亂來。


好些資料庫實現都是直接mmap的


大部分情況下,該怎麼讀就怎麼讀,kernel里有相應的預讀機制。

mmap雖然可以少一次內存copy,但mmap的預讀演算法是read around,也就是說,默認情況下,當你用mmap讀一個文件的時候,讀第1000個位元組,kernel事實上會讀取該文件900 - 1100個位元組(具體數字事實上是若干個page大小)。而非同步預讀則默認不啟用,因為mmap大多數時候是隨機讀寫,kernel不知道該讀取哪部分數據。

而read系統調用,其默認預讀演算法是readahead,也就是講,你讀第1000個位元組的時候,kernel會讀取1000 - 1200個位元組(具體數字也是若干個page)。並且一直非同步預讀後面的數據,直到把空閑內存填滿或達到某個閾值。所以,理論上read在順序讀取的時候效率比mmap要高。

也就是說,mmap的默認行為是為隨機讀寫優化的,read的默認行為是為順序讀寫優化的。當然,你也可以用madvise/fadvise去修改這些行為以及調優預讀參數。


參考:https://www.byvoid.com/blog/fast-readfile


充分利用操作系統的Map和Cache機制,不同系統不太一樣

順便說一下,不做任何參數改變,只使用默認方式,Windows上使用C庫的fread和C++的fstream要比單純的WinAPI Readfile慢得多。WinCE更加顯著。


這個問題要結合怎麼消費文件才能給出合理的答案。

對於相對大塊的順序讀,各種方法性能差距常常並不很大。但是,對於反序列化,夾雜本地化,不同的讀方式可能導致巨大的性能差異。

舉兩個例子。

基於fread直接實現反序列化,這導致大量的api調用。如果用mmap或每次預先fread一大塊數據到內存,基於這個內存做反序列化,出現數量級的性能提升是可能的。

在某種格式的文件中反向搜索特定的signature。直接調fseek,再fread會慢到死,必須加以處理。雖然fread和mmap都能用於讀取大塊數據到內存,但方便性上還是各有所長。


對於不大的文件(小於1M),建議一次性讀取,較大的文件建議使用內存映射。

C++有一個成例,讀取文本文件為字元串(二進位文件需要一些技巧),我認為比較快。

ifstream in("filename");

ostringstream out;

out &<&< in.buf(); //關鍵,直接讀取buf

out.str(); //輸出字元串。

內存映射可以使用boost庫。


節選並重新排版自:探尋C++最快的讀取文件的方案

為確保準確性,我又換到Windows平台上測試了一下。結果如下表:

從上面可以看出幾個問題

  • Linux 平台上運行程序普遍比 Windows 上快。

  • Windows 下 VC 編譯的程序一般運行比 MINGW(MINimal Gcc for Windows)快。

  • VC 對 cin 取消同步與否不敏感,前後效率相同。反過來 MINGW 則非常敏感,前後效率相差8倍

  • read 本是 linux 系統函數,MINGW 可能採用了某種模擬方式,read 比 fread 更慢。

  • Pascal 程序運行速度實在令人不敢恭維。

好像黑了不少東西 :) 個人意見是 大文件的話使用 fread,不考慮效率以及內存佔用的話可以考慮 fstream。example:高效地讀取解析文件(fread)


std::ifstream ifs("Title.pic", std::ios::binary);
std::vector& buffer;
buffer.assign( std::istreambuf_iterator&(ifs),
std::istreambuf_iterator&() );

std::ifstream ifs("Title.pic", std::ios::binary);
std::vector& buffer;
buffer.resize(ifs.seekg(0, std::ios::end).tellg());
ifs.seekg(0, std::ios::beg).read( buffer[0], static_cast&(buffer.size()) );

後面是前面的20倍


光靠某種語言或者某種API讀文件是快不了的,已經給出的答案,都沒有考慮到文件的存儲布局,尤其是在機械磁碟的情況下。

舉個栗子啊,你用emule下載藍光文件,如果不用預先填充零的方式先獲得一個碩大的文件,最後文件的各個數據塊分散在磁碟的各個地方,然後你拷貝到另外一個硬碟時,會發現拷貝速度慢得離譜。為毛呢?數據塊太分散,磁頭機械臂需要來回移動,導致尋道耗費的時間太多。

根據上述栗子,你明白為毛Oracle數據創建表空間時候總是填充得到若干個文件了吧?


1.順序讀取

2換ssd


問題好大啊,可以寫一本書了,呵呵。快是指延時低還是帶寬大?

c++只是一種語言,具體平台不同,實現方式好多差異。有的場景下適合併行,有的場景不適合,有的場景適合預讀,有的場景不適合。除非深入交流,才能有更具體的答案。


想問一下用C++讀取.txt的文件時如果文件比較大,速度會很慢,如何優化這個問題呢?直接用二進位讀取嗎?


推薦閱讀:

最近看到陳碩的一本書提了一個問題,「編譯器如何處理inline函數中的static變數?」
Mac系統下最好用的C++ IDE是XCode嗎 ?
關於鏈表的問題?
C/C++ 中 exit() 函數的參數到底有什麼意義?
怎樣改造一個有序的鏈表,使其能夠具有高效找到Node在鏈表中index(在鏈表中的第幾個結點)?

TAG:C | C標準 | CPrimer |