如何更快地读取多个文件?

时间:2013-08-20 14:21:11

标签: c++ multithreading file

在我的程序中我想读取几个文本文件(超过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 个答案:

答案 0 :(得分: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

结论:

答案是:

您的阅读代码错误,您正在阅读文件这么慢,速度显着提高,然后您可以并行运行任务。在上面的代码中,阅读实际上是更快,然后费用来生成线程

答案 1 :(得分: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)

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

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

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

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

我建议如下:

  1. 在内存中创建多个缓冲区,大小足以节省时间,小巧 足以阻止操作系统将内存分页到硬盘驱动器。
  2. 根据需要创建一个将文件读入内存的线程。 在网上搜索“双缓冲”。只要有空间 缓冲区,这个线程将读取数据。
  3. 创建多个“传出”缓冲区。
  4. 创建第二个线程,从内存和“进程”中删除数据 它,并插入“传出”缓冲区。
  5. 创建第三个线程,该线程获取“传出”缓冲区中的数据 并发送到数据库。
  6. 调整缓冲区的大小以获得最佳效率 记忆的局限性。
  7. 如果您可以访问DMA通道,请使用它们从硬盘驱动器读取“读取缓冲区”。

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

    可能有所帮助的其他优化:

    • 将数据与处理器本机字大小对齐,必要时填充。对于 例如,更喜欢使用32个字节而不是13个或24个。
    • 以处理器字长的数量获取数据。例如, 在32位处理器上一次访问4个八位字节(字节)而不是4个 访问1个字节。
    • 展开循环 - 循环内的更多指令,作为分支 说明减慢了处理速度。

答案 3 :(得分:0)

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

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

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

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