2013-08-20 41 views
3

我想讀取幾個文本文件(超過800個文件),每個文件有256行,文件名從1.txt開始到n.txt,並且在幾個處理步驟後將它們存儲到數據庫中。我的問題是數據的閱讀速度。通過使用OpenMP多線程進行讀取循環,我可以將程序速度提高到之前的兩倍。有沒有辦法加快它的速度?我的實際代碼是如何更快地讀取多個文件?

std::string CCD_Folder = CCDFolder; //CCDFolder is a pointer to a char array 
int b = 0; 
int PosCounter = 0; 
int WAVENUMBER, WAVELUT; 
std::vector<std::string> tempstr; 
std::string inputline; 
//Input 
omp_set_num_threads(YValue); 
#pragma omp parallel for private(WAVENUMBER) private(WAVELUT) private(PosCounter) private(tempstr) private(inputline) 
    for(int i = 1; i < (CCD_Filenumbers+1); i++) 
    { 
     //std::cout << omp_get_thread_num() << ' ' << i << '\n'; 
     //Umwandlung und Erstellung des Dateinamens, Öffnen des Lesekanals 
     std::string CCD_Filenumber = boost::lexical_cast<string>(i); 
     std::string CCD_Filename = CCD_Folder + '\\' + CCD_Filenumber + ".txt"; 
     std::ifstream datain(CCD_Filename, std::ifstream::in); 
     while(!datain.eof()) 
     { 
      std::getline(datain, inputline); 
      //Processing 

     }; 

    }; 

這裏沒有定義的所有變量都是在我的代碼的其他地方定義的,並且它正在工作。那麼是否有可能將這些代碼加快一點?
非常感謝!

+6

'while(!datain.eof())'Argggggghhhhhhhhhh –

+0

您關心什麼平臺,文件系統,硬件等? – Useless

+3

嗯,你很快就會遇到磁盤訪問限制:你的硬盤基本上是一個連續的機制。這就是SQL這樣的數據庫管理者使用優化的存儲子系統的原因 – lucasg

回答

0

您可能正在讀取磁盤的讀取限制,這意味着您的選項有所限制。如果這是一個常見的問題,您可以考慮使用不同的RAID結構,這將爲您提供更高的讀取吞吐量,因爲多個讀取頭可以同時訪問數據。

要查看是否接盤確實是瓶頸,隨着時間的命令運行程序:

>> /usr/bin/time -v <my program> 

在輸出你會看到你有多少CPU時間利用所相比,所需的時間量像磁盤訪問的東西。

+0

我無法在Windows PC上使用這些命令... –

+0

根據您的Windows版本,還有其他選擇:http:// stackoverflow。com/questions/673523/how-to-measure-execution-time-of-command-in-windows-command-line – guyrt

1

我會試着用C代碼閱讀文件。我懷疑它會更快。

FILE* f = ::fopen(CCD_Filename.c_str(), "rb"); 
if(f == NULL) 
{ 
    return; 
} 

::fseek(f, 0, SEEK_END); 
const long lFileBytes = ::ftell(f); 
::fseek(f, 0, SEEK_SET); 

char* fileContents = new char[lFileBytes + 1]; 
const size_t numObjectsRead = ::fread(fileContents, lFileBytes, 1, f); 
::fclose(f); 

if(numObjectsRead < 1) 
{ 
    delete [] fileContents; 
    return; 
} 

fileContents[lFileBytes] = '\0'; 

// assign char buffer of file contents here 

delete [] fileContents; 
+2

-1'懷疑'C API主要比C++ API更快是一個搖搖欲墜的理由優化。 – ComicSansMS

+0

我懷疑它更快,因爲我的解決方案預先分配了足夠大的緩衝區來容納文件的全部大小。看起來海報的解決方案沒有。我的回答不是純粹的猜測。 –

+1

@PaulDardeau:你可以用'fstream'做同樣的事情。這個C++代碼中不需要C語言。 – Cornstalks

8

一些實驗:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <Windows.h> 

void generateFiles(int n) { 
    char fileName[32]; 
    char fileStr[1032]; 

    for (int i=0;i<n;i++) { 
     sprintf(fileName, "c:\\t\\%i.txt", i); 
     FILE * f = fopen(fileName, "w"); 
     for (int j=0;j<256;j++) { 
      int lineLen = rand() % 1024; 
      memset(fileStr, 'X', lineLen); 
      fileStr[lineLen] = 0x0D; 
      fileStr[lineLen+1] = 0x0A; 
      fileStr[lineLen+2] = 0x00; 
      fwrite(fileStr, 1, lineLen+2, f);   
     } 
     fclose(f); 
    } 
} 

void readFiles(int n) { 
    char fileName[32]; 

    for (int i=0;i<n;i++) { 
     sprintf(fileName, "c:\\t\\%i.txt", i); 
     FILE * f = fopen(fileName, "r"); 
     fseek(f, 0L, SEEK_END); 
     int size = ftell(f); 
     fseek(f, 0L, SEEK_SET); 
     char * data = (char*)malloc(size); 
     fread(data, size, 1, f); 
     free(data); 
     fclose(f); 
    } 
} 

DWORD WINAPI readInThread(LPVOID lpParam) 
{ 
    int * number = (int *)lpParam; 
    char fileName[32]; 

    sprintf(fileName, "c:\\t\\%i.txt", *number); 
    FILE * f = fopen(fileName, "r"); 
    fseek(f, 0L, SEEK_END); 
    int size = ftell(f); 
    fseek(f, 0L, SEEK_SET); 
    char * data = (char*)malloc(size); 
    fread(data, size, 1, f); 
    free(data); 
    fclose(f); 

    return 0; 
} 


int main(int argc, char ** argv) { 
    long t1 = GetTickCount(); 
    generateFiles(256); 
    printf("Write: %li ms\n", GetTickCount() - t1); 

    t1 = GetTickCount(); 
    readFiles(256); 
    printf("Read: %li ms\n", GetTickCount() - t1); 

    t1 = GetTickCount(); 

    const int MAX_THREADS = 256; 

    int  pDataArray[MAX_THREADS]; 
    DWORD dwThreadIdArray[MAX_THREADS]; 
    HANDLE hThreadArray[MAX_THREADS]; 

    for(int i=0; i<MAX_THREADS; i++) 
    { 

     pDataArray[i] = (int) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
       sizeof(int)); 

     pDataArray[i] = i; 

     hThreadArray[i] = CreateThread( 
      NULL,     
      0,      
      readInThread,  
      &pDataArray[i],   
      0,      
      &dwThreadIdArray[i]); 
    } 

    WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE); 

    printf("Read (threaded): %li ms\n", GetTickCount() - t1); 

} 

第一功能只是醜陋的東西,使測試數據集(我知道這是可以做到更好,但老實說,我沒有時間)

實驗1 - 連續讀取 第二個實驗 - 全部並行讀取

結果:

256個文件:

Write: 250 ms 
Read: 140 ms 
Read (threaded): 78 ms 

1024文件:

Write: 1250 ms 
Read: 547 ms 
Read (threaded): 843 ms 

我認爲第二次嘗試清楚地表明,在長期運行「啞巴」線程的創建只是使事情變得更糟。當然,它需要預先分配的工作人員,某些線程池等方面的改進,但我認爲通過從磁盤讀取100-200k這樣的快速操作,將此功能移到線程中並沒有真正的好處。我沒有時間寫更多'聰明'的解決方案,但我懷疑它會快得多,因爲你將不得不添加系統調用互斥鎖等......

進入極限,你可以想到預先分配內存池但正如前面提到的代碼你的發佈錯誤..這是短短的幾毫秒,但是可以肯定不是秒

800文件(每行20個字符,256線)

Write: 250 ms 
Read: 63 ms 
Read (threaded): 500 ms 

結論:

答案是:

您的閱讀代碼錯誤,你正在讀取文件這麼慢速度顯着增加,那麼你使任務並行運行。在上面閱讀的代碼實際上是更快那麼費用生成一個線程

+0

但是爲什麼我的代碼在使用多線程時速度更快? 200個文件,256行:單線程:平均1.5秒,多線程:平均0.8秒; 800個文件,256行:單線程:平均8.5秒,多線程:平均4秒... –

+1

,因爲你的代碼是錯誤的,你在以慢速錯誤的方式讀取文件,那麼你的速度顯着提高任務並行運行。在我的代碼讀取實際上更快然後產生線程的費用 – evilruff

+0

我的代碼是錯誤的,因爲我使用.eof() - 命令?或者我的代碼也錯在哪裏? –

1

您的主要瓶頸是物理硬盤讀取。

除非您將文件放在單獨的驅動器上,否則驅動器一次只能從一個文件讀取數據。最好的辦法是將每個文件作爲一個整體讀取,而不是讀取一個文件的一部分,告訴驅動器找到另一個文件,從那裏讀取並重復。將驅動器頭重新定位到其他位置,尤其是其他文件,通常比讓驅動器讀完單個文件更昂貴。

下一個瓶頸是處理器和硬盤驅動器之間的數據通道。如果您的硬盤驅動器共享任何類型的通訊通道,您將看到一個瓶頸,因爲每個驅動器的數據必須通過通訊通道傳送到處理器。您的處理器將通過此通信通道(PATA,SATA,USB等)向驅動器發送命令。

下一步的目標是減少程序內存和硬盤通信接口之間的「中間人」的開銷。最有效的是直接訪問控制器;使用OS功能的效率較低; 「C」函數(fread和familiy),最少的是C++流。隨着效率的提高,與平臺的耦合更加緊密,並且降低了安全性(和簡單性)。

我建議如下:

  1. 在內存中創建多個緩衝區,足夠大,以節省時間,小 足以防止操作系統從內存分頁到硬盤驅動器。
  2. 根據需要創建一個將文件讀入內存的線程。 在網上搜索「雙緩衝」。只要 中有緩衝區的空間,這個線程就會讀取數據。
  3. 創建多個「傳出」緩衝區。
  4. 創建第二個線程,它從內存中刪除數據並「處理」它,並將其插入「傳出」緩衝區。
  5. 創建第三個線程,將「傳出」緩衝區中的數據傳送到數據庫中。
  6. 在內存限制內調整緩衝區的大小以獲得最佳效率。

如果您可以訪問DMA通道,請使用它們從硬盤讀取「讀取緩衝區」。

接下來,您可以優化代碼以有效使用處理器的數據緩存。例如,設置您的「處理」,以便數據結構不超過緩存中的數據行。另外,優化代碼以使用寄存器(或者指定register關鍵字或使用語句塊,以便編譯器知道何時可以重用變量)。

其他優化,可以幫助:

  • 對齊數據處理器原生字的大小,墊如果必要的。例如,對於 示例,更喜歡使用32字節而不是13或24.
  • 以處理器的字大小的數量獲取數據。例如, 在32位處理器上一次訪問4個八位字節(字節),而不是訪問1個字節。
  • 展開循環 - 循環內部有更多指令,因爲分支 指令減慢了處理速度。