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

        };

    };

此处未定义的所有变量都在我的代码中的其他地方定义,并且它正在工作。那么有没有可能加快这段代码的速度呢?
非常感谢!

4

4 回答 4

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 );

}

第一个函数只是制作测试数据集的丑陋事情(我知道它可以做得更好,但老实说我没有时间)

第一个实验 - 顺序读取 第二个实验 - 并行读取

结果:

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

结论:

答案是:

您的阅读代码是错误的,您阅读文件的速度如此之慢以至于速度显着提高,然后您使任务并行运行。在上面的代码中,阅读实际上产生线程的费用要快

于 2013-08-20T15:20:36.387 回答
4

您的主要瓶颈是从硬盘物理读取。

除非您将文件放在不同的驱动器上,否则驱动器一次只能从一个文件中读取数据。最好的办法是读取每个文件作为一个整体,而不是读取一个文件的一部分,告诉驱动器定位到另一个文件,从那里读取,然后重复。将驱动器头重新定位到其他位置,尤其是其他文件,通常比让驱动器完成读取单个文件更昂贵。

下一个瓶颈是处理器和硬盘之间的数据通道。如果您的硬盘驱动器共享任何类型的通信通道,您将看到瓶颈,因为来自每个驱动器的数据必须通过通信通道到达您的处理器。您的处理器将通过此通信通道(PATA、SATA、USB 等)向驱动器发送命令。

下一步的目标是减少程序内存和硬盘驱动器通信接口之间的“中间人”的开销。最高效的是直接访问控制器;使用操作系统功能的效率较低;“C”函数(fread和家族),最少的是 C++ 流。随着效率的提高,与平台的耦合更紧密,安全性(和简单性)降低。

我建议如下:

  1. 在内存中创建多个缓冲区,大到足以节省时间,小到足以防止操作系统将内存分页到硬盘驱动器。
  2. 根据需要创建一个将文件读入内存的线程。在网上搜索“双缓冲”。只要缓冲区有空间,这个线程就会读取数据。
  3. 创建多个“传出”缓冲区。
  4. 创建第二个线程,从内存中删除数据并“处理”它,然后插入“传出”缓冲区。
  5. 创建第三个线程,将“传出”缓冲区中的数据发送到数据库。
  6. 在内存限制内调整缓冲区的大小以获得最佳效率。

如果您可以访问 DMA 通道,请使用它们从硬盘驱动器读取到“读取缓冲区”。

接下来,您可以优化代码以有效地使用处理器的数据缓存。例如,设置您的“处理”,使数据结构不超过缓存中的数据行。此外,优化您的代码以使用寄存器(指定register关键字或使用语句块,以便编译器知道何时可以重用变量)。

其他可能有帮助的优化:

  • 将数据与处理器的本机字长对齐,必要时填充。例如,更喜欢使用 32 字节而不是 13 或 24 字节。
  • 以处理器字长的数量获取数据。例如,在 32 位处理器上一次访问 4 个八位字节(字节)而不是 1 个字节的 4 次访问。
  • 展开循环 - 循环内有更多指令,因为分支指令会减慢处理速度。
于 2013-08-20T18:28:59.797 回答
1

您可能已达到磁盘的读取限制,这意味着您的选择有些有限。如果这是一个持续存在的问题,您可以考虑使用不同的 RAID 结构,这将为您提供更大的读取吞吐量,因为多个读取头可以同时访问数据。

要查看磁盘访问是否真的是瓶颈,请使用 time 命令运行您的程序:

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

在输出中,您将看到您使用的 CPU 时间与磁盘访问等所需时间的比较。

于 2013-08-20T14:48:22.267 回答
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;
于 2013-08-20T14:52:12.080 回答