2009-07-29 51 views
5

在解決我們應用中的一些性能問題時,我發現C的stdio.h函數(至少對於我們的供應商,C++的fstream類)是線程安全的。因此,每次我執行一些簡單的操作時,RTL必須獲取一個鎖,讀取一個字節並釋放該鎖。C/C++中的非線程安全文件I/O

這不利於性能。

在C和C++中獲得非線程安全文件I/O的最佳方式是什麼,以便我可以管理鎖定自己並獲得更好的性能?

  • MSVC提供_fputc_nolock,和GCC提供unlocked_stdioflockfile,但我無法找到我的編譯器(CodeGear的C++ Builder的)任何類似的功能。
  • 我可以使用原始的Windows API,但這不是可移植的,我認爲比一次性字符輸入/輸出的解鎖fgetc要慢。
  • 我可以切換到類似Apache Portable Runtime的東西,但這可能會有很多工作。

其他人如何解決這個問題?

編輯:由於有些人想知道,我在發佈之前已經測試過了。 fgetc如果能夠滿足從其緩衝區中讀取數據,則不會執行系統調用,但它仍然會執行鎖定,因此鎖定會花費大量時間(從磁盤中讀取數據的單個數據塊需要幾百個鎖才能獲取和釋放)。不是每次一個字符都會是一個解決方案,但C++ Builder的fstream類不幸地使用fgetc(所以如果我想用iostream類,我堅持使用它),而且我有很多的遺留代碼使用fgetc和朋友從記錄式文件中讀取字段(如果不是鎖定問題,這將是合理的)。

+0

C的stdio.h函數不是線程安全的;那也是你的供應商。 – MSalters 2009-07-29 13:50:06

+0

不只是我的供應商;例如,POSIX需要它。 – 2009-07-29 13:56:22

回答

3

我只是不會做一次一個字符,如果它是明智的表現明智。

+0

所有對流的操作都會導致char IO。您必須使用流緩衝區。我在下面發佈瞭如何... – ovanes 2009-07-29 17:48:18

1

fgetc幾乎肯定不會在每次調用它時讀取一個字節(其中「讀取」的意思是調用系統調用來執行I/O)。看看其他地方是否存在性能瓶頸,因爲這可能不是問題,並且使用不安全的函數當然不是解決方案。你所做的任何鎖定處理都可能比標準例程處理效率低。

1

最簡單的方法是讀取內存中的整個文件,然後將自己的類似fgetc的界面提供給該緩衝區。

1

爲什麼不只是內存映射文件?內存映射是可移植的(除了在Windows Vista中,它需要你跳過現在使用它的希望,dumbasses)。無論如何,將你的文件映射到內存中,你是否自己鎖定/不鎖定所產生的內存位置。

操作系統處理實際從磁盤讀取所需的所有鎖定 - 您永遠無法消除這種開銷。但是另一方面,你的處理開銷不會受到除你自己以外的外部鎖定的影響。

1

多平臺方法非常簡單。在標準規定他們應該使用哨兵時避免使用功能或操作員。 sentry是iostream類中的內部類,它確保每個輸出字符的流一致性,並且在多線程環境中,它鎖定輸出的每個字符的流相關互斥鎖。這避免了在低級別的比賽條件,但仍使輸出不可讀,因爲從兩個線程的字符串可能是同時輸出如下面的例子指出:

線程1應該寫:ABC
線程2應該寫的:def

輸出可能如下所示:adebcf而不是abcdef或defabc。這是因爲實施了哨兵來鎖定和解鎖每個角色。

該標準定義了它的所有功能和處理的IStream或ostream的運算符。避免這種情況的唯一方法是使用流緩衝區和您自己的鎖定(例如,每個字符串)。

我寫一個應用程序,其中的一些數據輸出到文件中並mesures速度。如果你在這裏添加一個函數,直接使用fstream輸出,而不使用緩衝區並刷新,你將看到速度差異。它使用提升,但我希望它不是你的問題。嘗試刪除所有的流緩衝區,看看有沒有它們的區別。我的情況是性能缺陷是2-3倍左右。

following article由N.邁爾斯將解釋如何語言環境和哨兵在C++輸入輸出流工作。當然,你應該查看ISO C++標準文檔,哪些功能使用哨兵。

好運,
Ovanes

#include <vector> 
#include <fstream> 
#include <iterator> 
#include <algorithm> 
#include <iostream> 
#include <cassert> 
#include <cstdlib> 

#include <boost/progress.hpp> 
#include <boost/shared_ptr.hpp> 

double do_copy_via_streambuf() 
{ 
    const size_t len = 1024*2048; 
    const size_t factor = 5; 
    ::std::vector<char> data(len, 1); 

    std::vector<char> buffer(len*factor, 0); 

    ::std::ofstream 
    ofs("test.dat", ::std::ios_base::binary|::std::ios_base::out); 
    noskipws(ofs); 

    std::streambuf* rdbuf = ofs.rdbuf()->pubsetbuf(&buffer[0], buffer.size()); 

    ::std::ostreambuf_iterator<char> oi(rdbuf); 

    boost::progress_timer pt; 

    for(size_t i=1; i<=250; ++i) 
    { 
    ::std::copy(data.begin(), data.end(), oi); 
    if(0==i%factor) 
     rdbuf->pubsync(); 
    } 

    ofs.flush(); 
    double rate = 500/pt.elapsed(); 
    std::cout << rate << std::endl; 
    return rate; 
} 

void count_avarage(const char* op_name, double (*fct)()) 
{ 
    double av_rate=0; 
    const size_t repeat = 1; 
    std::cout << "doing " << op_name << std::endl; 
    for(size_t i=0; i<repeat; ++i) 
     av_rate+=fct(); 

    std::cout << "average rate for " << op_name << ": " << av_rate/repeat 
      << "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n" 
      << std::endl; 
} 


int main() 
{ 
    count_avarage("copy via streambuf iterator", do_copy_via_streambuf); 
    return 0; 
} 
1

有一點要考慮的是建立一個自定義的運行時間。大多數編譯器爲運行時庫提供源代碼(如果它不在C++ Builder包中,我會感到驚訝)。

這可能最終會被大量的工作,但也許他們已經本地化的線程支持,使這樣的事情很容易。例如,使用我正在使用的嵌入式系統編譯器,它就是爲此設計的 - 它們已經記錄了添加鎖定例程的鉤子。但是,這可能是一個維護頭痛,即使它最初相對容易。

另一個類似的路線將是交談的人喜歡Dinkumware關於使用第三方的運行時,提供你所需要的功能。

相關問題