How can I read multiple files faster?
在我的程序中,我想读取几个文本文件(超过约800个文件),每个文件有256行,文件名从1.txt到n.txt,并在几个处理步骤后存储到数据库中。 我的问题是数据的读取速度。 通过使用OpenMP多线程读取循环,我可以将程序速度提高到以前的两倍。 有没有办法加快速度? 我的实际代码是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 << ' '; //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 }; }; |
此处未定义的所有变量都在我的代码中的其他位置定义,并且它正在工作。 那么有可能加快这个代码的速度吗?
非常感谢你!
一些实验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #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 ", GetTickCount() - t1 ); t1 = GetTickCount(); readFiles(256); printf("Read: %li ms ", 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 ", GetTickCount() - t1 ); } |
第一个函数只是制作一个测试数据集的丑陋的东西(我知道它可以做得更好,但我老实说没有时间)
第一个实验 - 顺序读取
第二个实验 - 并行读取所有内容
结果:
256个文件:
1 2 3 | Write: 250 ms Read: 140 ms Read (threaded): 78 ms |
1024个文件:
1 2 3 | Write: 1250 ms Read: 547 ms Read (threaded): 843 ms |
我认为第二次尝试清楚地表明,从长远来看,"愚蠢"的线程创建只会让事情变得更糟。当然,它需要在预分配工作者,某些线程池等方面进行改进,但我认为通过从磁盘读取100-200k这样快速的操作,将此功能转移到线程中并没有什么好处。我没有时间编写更"聪明"的解决方案,但我怀疑它会更快,因为你必须为互斥锁等添加系统调用......
走极端,你可以想到预分配内存池等..但正如在代码之前提到你发布的错误..这是几毫秒的问题,但肯定不是秒
800个文件(每行20个字符,256行)
1 2 3 | Write: 250 ms Read: 63 ms Read (threaded): 500 ms |
结论:
答案是:
您的阅读代码是错误的,您阅读文件的速度非常慢,以至于速度显着提高,然后您就可以并行运行任务。在上面的代码中,读取实际上比生成线程的开销更快
您的主要瓶颈是从硬盘上物理读取。
除非您将文件放在不同的驱动器上,否则驱动器一次只能读取一个文件中的数据。最好的办法是将每个文件作为一个整体读取,而不是读取一个文件的一部分,告诉驱动器找到另一个文件,从那里读取,然后重复。将驱动器头重新定位到其他位置,尤其是其他文件,通常比让驱动器完成读取单个文件更昂贵。
下一个瓶颈是处理器和硬盘之间的数据通道。如果您的硬盘驱动器共享任何类型的通信通道,您将看到瓶颈,因为每个驱动器的数据必须通过通信通道连接到您的处理器。您的处理器将通过此通信通道(PATA,SATA,USB等)向驱动器发送命令。
接下来的步骤的目的是减少程序的内存和硬盘驱动器通信接口之间的"中间人"的开销。最有效的是直接访问控制器;效率较低的是使用OS功能;"C"函数(
我建议如下:
足以阻止操作系统将内存分页到硬盘驱动器。
在网上搜索"双缓冲"。只要有空间
缓冲区,这个线程将读取数据。
它,并插入"传出"缓冲区。
并发送到数据库。
记忆的局限性。
如果可以访问DMA通道,请使用它们从硬盘驱动器读取"读取缓冲区"。
接下来,您可以优化代码以有效地使用处理器的数据缓存。例如,设置"处理",使数据结构不超过缓存中的数据行。此外,优化代码以使用寄存器(指定
其他可能有帮助的优化:
-
将数据与处理器本机字大小对齐,必要时填充。对于
例如,更喜欢使用32个字节而不是13个或24个字节。 -
以处理器字长的数量获取数据。例如,
在32位处理器上一次访问4个八位字节(字节)而不是4个
访问1个字节。 -
展开循环 - 循环内的更多指令,作为分支
说明减慢处理速度。
我会尝试使用C代码来读取文件。我怀疑它会更快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; |
您可能达到了磁盘的读取限制,这意味着您的选项有限。如果这是一个常见问题,您可以考虑使用不同的RAID结构,这将为您提供更高的读取吞吐量,因为多个读取头可以同时访问数据。
要查看磁盘访问是否确实是瓶颈,请使用time命令运行程序:
1 | >> /usr/bin/time -v <my program> |
在输出中,您将看到您使用的CPU时间与磁盘访问所需的时间相比。