2014-01-25 98 views
3
string str1, str2; 
vector<string> vec; 

ifstream infile; 

infile.open("myfile.txt"); 
while (! infile.eof()) 
{ 
    getline(infile,str1); 
    istringstream is; 
     is >> str1; 
    while (is >> str2) 
    { 
     vec.push_back(str2); 
    } 
} 

代碼的作用是從文件中讀取字符串並將其存儲到向量中。優化文件讀取C++

性能需要優先考慮。我如何優化此代碼,以使讀取性能更快?

+9

第一件事,第一:[爲什麼的iostream ::內EOF循環條件考慮錯誤?](http://stackoverflow.com/q/5605125/1870232) – P0W

+1

什麼格式?似乎你正在試圖解析文件,在這裏我沒有看到太多的優化空間。其實**測量**,這是一個瓶頸? – 2014-01-25 10:14:22

回答

4

正如其他人已經指出的那樣(例如參見herohuyongtao's answer),環路條件以及如何將str1放入istringstream必須固定。

但是,這裏有一個重要的問題,大家都錯過了目前爲止:你根本不需要istringstream

vec.reserve(the_number_of_words_you_exptect_at_least); 

    while (infile >> str1) { 

    vec.push_back(str1); 
    } 

它擺脫了內循環,你並不需要擺在首位,並且不產生在每個迭代的istringstream的。

如果您需要進一步分析每一行,你確實需要一個istringstream,創建循環,並通過istringstream::str(const string& s)設置其字符串緩衝區。

我可以很容易地想象你的循環速度非常慢:Windows上的堆分配非常慢(與Linux相比);我被咬了一次。

Andrei Alexandrescu在他的演講Writing Quick Code in C++, Quickly中介紹了(某種意義上)的一個類似的例子。令人驚訝的是,在像上面那樣的緊密循環中執行不必要的堆分配可能比實際的文件IO慢。我很驚訝地看到這一點。


您沒有將您的問題標記爲C++ 11,但這裏是我在C++ 11中所要做的。

while (infile >> str1) { 

    vec.emplace_back(std::move(str1)); 
    } 

此舉構建在載體的背面,沒有複製的字符串。我們可以做到這一點,因爲我們不需要的str1的內容,我們已經把它變成矢量之後。換句話說,不需要將其複製到向量背後的全新字符串中,只需將其內容移動到該向量就足夠了。第一個循環與vec.push_back(str1);可能可能複製str1這是真的不必要的內容。

gcc 4.7.2中的字符串實現當前爲copy on write,所以兩個循環具有相同的性能;你使用哪一個並不重要。目前。

不幸的是,現在寫入字符串的拷貝現在被標準禁止了。我不知道gcc開發人員何時會改變實現。如果實施更改,無論您移動(emplace_back(std::move(s)))還是複製(push_back(s)),都可能會影響性能。

如果C++ 98兼容性對您很重要,那麼請使用push_back()。即使未來發生最糟糕的事情,並且您的字符串被複制(它現在不被複制),該副本可以變成快速發展的memmove()/memcpy(),這可能比從文件中讀取文件的內容更快硬盤所以文件IO很可能仍然是瓶頸。

+0

+1 - 很好很多http://en.wikipedia.org/wiki/Golden_mean_%28philosophy%29 – bobah

+0

我的文件包含大量數據,我不知道要預留多少,我該如何去abt這個? – lily

+0

@aayat然後對您期望的金額*至少*給出一個合理的估計。如果這個估計值太低,這個矢量將不會自動調整;我不會爲此擔心。你正在使用哪種編譯器,gcc,Visual Studio或其他? – Ali

2

任何優化之前,您需要更改

while (! infile.eof())  // problem 1 
{ 
    getline(infile,str1); 
    istringstream is; 
     is >> str1;   // problem 2 
    while (is >> str2){ 
     vec.push_back(str2); 
     } 
} 

while (getline(infile,str1)) // 1. don't use eof() in a while-condition 
{ 
    istringstream is(str1); // 2. put str1 to istringstream 
    while (is >> str2){ 
     vec.push_back(str2); 
     } 
} 

使如你預期它的工作。


P.S.對於優化部分,除非它成爲瓶頸,否則不需要考慮太多。 Premature optimization is the root of all evil。但是,如果您想加快速度,請查看@ Ali的答案以獲取更多信息。

+0

從downvoters的任何建議? – herohuyongtao

+0

我只是做了這個變化.. – lily

+0

@herohuyongtao我**不是** downvoter,但我* gue ss * downvote是因爲錯過了一個重要的細節。請閱讀我的答案。 – Ali

1

Loop condition is wrong.不是性能問題。假設這個IO循環確實是你的應用程序的瓶頸。但即使不是,它也可以是一個很好的教育活動,或者只是一個週末的樂趣。

在循環中有很多臨時表和動態內存分配情況。在環路前調用std::vector::reserve()會改善一點。手動重新分配它以模擬x1.2增長因子,在一定大小後反對2x將有所幫助。如果文件大小不可預測,則std::list可能更合適。

使用std::istringstream作爲分詞器是不理想。切換到基於迭代器的「查看」分詞器(Boost has one)應該可以提高速度。

如果你需要它是非常快有足夠的RAM你可以在讀取它之前的內存映射文件。 Boost::iostreams可以讓你快速到達那裏。但是,一般來說,如果沒有Boost,你可以快兩倍(Boost並不差,但它必須是通用的,並且可以在十幾個編譯器上工作,這就是爲什麼)。

如果您是使用Unix/Linux作爲開發環境的幸運者,請在valgrind --tool=cachegrind下運行您的程序,您將看到所有有問題的地方以及它們相對於另一個地方有多糟糕。此外,valgrind --tool=massif可讓您識別高性能代碼中通常不可容忍的小塊堆分配對象。

+0

當然,正如阿里指出的那樣,標記器根本不需要,因爲這些行對結果沒有任何意義。但總的來說,增強鏈接是有用的,與valgrind的鏈接更加有用。 –

+0

@JanHudec - 同意,我贊成阿里的回答,但我不認爲有效地標記字符串的技巧,我在我的答案中共享是浪費本頁空間 – bobah

+0

我同意。這裏絕對有用的信息。 –

1

最快的,雖然不是完全可移植的,方法是將文件加載到內存映射區(參見維基mmap

既然你知道文件的大小,你現在可以定義前向迭代器(可能指向const char)在該內存區域,您可以使用它來查找將文件分隔爲「字符串」的令牌。

本質上,您重複獲取指向第一個字符的指針對分別指向每個「字符串「。從那對迭代器創建std::string

這種方法有微妙的問題,但:

  • 你需要採取文件的字符編碼的照顧,可能從此字符編碼轉換到用於由您std::string(推測UTF-8所需的編碼)。

  • 的「令牌」,以單獨的字符串(通常\n,可能是與平臺相關的,或者可能取決於該程序創建的文件

+0

http://www.boost.org/doc/libs/1_55_0/libs/iostreams/doc/index.html以便攜的方式完成所有操作(可承受的性能損失) – bobah