非常快的文本文件处理 (C++)

Posted

技术标签:

【中文标题】非常快的文本文件处理 (C++)【英文标题】:very fast text file processing (C++) 【发布时间】:2011-12-28 17:09:47 【问题描述】:

我编写了一个在 GPU 上处理数据的应用程序。代码运行良好,但我的问题是输入文件的读取部分(~3GB,文本)是我的应用程序的瓶颈。 (从硬盘读取速度很快,但逐行处理很慢)。

我用 getline() 读取了一行并将第 1 行复制到一个向量,将第 2 行复制到一个向量并跳过第 3 行和第 4 行。对于其余 11 行 mio 行,依此类推。

我尝试了几种方法以尽可能在最佳时间获取文件:

我发现最快的方法是使用 boost::iostreams::stream

其他人是:

以 gzip 格式读取文件,以最小化 IO,但比直接慢 阅读它。 通过读取将文件复制到内存(文件指针、字符数组、长度) 并用循环处理它以区分行(也比 boost 慢)

有什么建议可以让它运行得更快吗?

void readfastq(char *filename, int SRlength, uint32_t blocksize)
    _filelength = 0; //total datasets (each 4 lines)
    _SRlength = SRlength; //length of the 2. line
    _blocksize = blocksize;

    boost::iostreams::stream<boost::iostreams::file_source>ins(filename);
    in = ins;

    readNextBlock();



void readNextBlock() 
    timeval start, end;
    gettimeofday(&start, 0);

    string name;
    string seqtemp;
    string garbage;
    string phredtemp;

    _seqs.empty();
    _phred.empty();
    _names.empty();
    _filelength = 0;

            //read only a part of the file i.e the first 4mio lines
    while (std::getline(in, name) && _filelength<_blocksize) 
        std::getline(in, seqtemp);
        std::getline(in, garbage);
        std::getline(in, phredtemp);

        if (seqtemp.size() != _SRlength) 
            if (seqtemp.size() != 0)
                printf("Error on read in fastq: size is invalid\n");
         else 
            _names.push_back(name);

            for (int k = 0; k < _SRlength; k++) 

                //handle special letters
                                    if(seqtemp[k]== 'A') ...
                                    else
                _seqs.push_back(5);
                                    

            
            _filelength++;
        
    

编辑:

源文件可在https://docs.google.com/open?id=0B5bvyb427McSMjM2YWQwM2YtZGU2Mi00OGVmLThkODAtYzJhODIzYjNhYTY2下下载

由于一些指针问题,我更改了函数 readfastq 来读取文件。因此,如果您调用readfastqblocksize(以行为单位)必须大于要读取的行数。

解决方案:

我找到了一个解决方案,它将读取文件的时间从 60 秒缩短到 16 秒。我删除了处理特殊字符的内循环并在 GPU 中执行此操作。这减少了读入时间,并且仅略微增加了 GPU 运行时间。

感谢您的建议。

void readfastq(char *filename, int SRlength) 
    _filelength = 0;
    _SRlength = SRlength;

    size_t bytes_read, bytes_expected;

    FILE *fp;
    fp = fopen(filename, "r");

    fseek(fp, 0L, SEEK_END); //go to the end of file
    bytes_expected = ftell(fp); //get filesize
    fseek(fp, 0L, SEEK_SET); //go to the begining of the file

    fclose(fp);

    if ((_seqarray = (char *) malloc(bytes_expected/2)) == NULL) //allocate space for file
        err(EX_OSERR, "data malloc");


    string name;
    string seqtemp;
    string garbage;
    string phredtemp;

    boost::iostreams::stream<boost::iostreams::file_source>file(filename);


    while (std::getline(file, name)) 
        std::getline(file, seqtemp);
        std::getline(file, garbage);
        std::getline(file, phredtemp);

        if (seqtemp.size() != SRlength) 
            if (seqtemp.size() != 0)
                printf("Error on read in fastq: size is invalid\n");
         else 
            _names.push_back(name);

            strncpy( &(_seqarray[SRlength*_filelength]), seqtemp.c_str(), seqtemp.length()); //do not handle special letters here, do on GPU

            _filelength++;
        
    

【问题讨论】:

“ram”是指 PC 内存,还是板载视频内存? 请注意,string::empty()vector::empty() 是对容器状态的只读测试。也许你的意思是.clear() What is the Fastest Method for High Performance Sequential File I/O in C++?的可能重复 _seqs.empty(); 只返回true。默认构造函数创建一个空字符串,bool std::string::empty() constvoid std::string::clear()不一样。 我不知道您要解决的问题,但您确定文本文件是处理如此庞大的数据集和显然时间紧迫的应用程序的方法吗? 【参考方案1】:

首先,您可以使用文件映射,而不是将文件读入内存。您只需将程序构建为 64 位以适应 3GB 的虚拟地址空间(对于 32 位应用程序,在用户模式下只能访问 2GB)。或者,您也可以按部分映射和处理您的文件。

接下来,在我看来,您的瓶颈是“将线复制到向量”。处理向量涉及动态内存分配(堆操作),这在关键循环中会非常严重地影响性能)。如果是这种情况 - 要么避免使用向量,要么确保它们在循环之外声明。后者有帮助,因为当您重新分配/清除向量时,它们不会释放内存。

发布您的代码(或其中的一部分)以获得更多建议。

编辑:

看来你所有的瓶颈都与字符串管理有关。

std::getline(in, seqtemp); 读入 std::string 处理动态内存分配。 _names.push_back(name); 这更糟。首先,std::string 放入vector。意味着 - 字符串被复制,因此发生另一个动态分配/释放。此外,当 vector 最终在内部重新分配时 - 所有包含的字符串都会再次复制,并带来所有后果。

我建议既不使用标准格式化文件 I/O 函数 (Stdio/STL) 也不使用 std::string。为了获得更好的性能,您应该使用指向字符串的指针(而不是复制的字符串),如果您映射整个文件,这是可能的。另外,您必须实现文件解析(分成几行)。

就像这段代码:

class MemoryMappedFileParser

    const char* m_sz;
    size_t m_Len;

public:

    struct String 
        const char* m_sz;
        size_t m_Len;
    ;

    bool getline(String& out)
    
        out.m_sz = m_sz;

        const char* sz = (char*) memchr(m_sz, '\n', m_Len);
        if (sz)
        
            size_t len = sz - m_sz;

            m_sz = sz + 1;
            m_Len -= (len + 1);

            out.m_Len = len;

            // for Windows-format text files remove the '\r' as well
            if (len && '\r' == out.m_sz[len-1])
                out.m_Len--;
         else
        
            out.m_Len = m_Len;

            if (!m_Len)
                return false;

            m_Len = 0;
        

        return true;
    

;

【讨论】:

我不同意。我认为使用标准库可以很好地编写它(但是 OP 没有为上下文发布足够的代码)。通过执行上述操作,您只会使代码变得非常脆弱。 我不知道你所说的“脆弱”是什么意思。代码正确或不恕我直言。不过我也不坚持。如果你有一个“标准”库/函数可以做同样的事情 - 不客气 易碎意味着容易使用不正确,从而导致代码不正确。当然,如果按预期使用它会起作用。但是脆弱的代码很难维护和使用原始作者(因为只有他们知道如何正确使用它)。 好吧,不喜欢“脆弱”的代码 - 发表你的建议,用“标准”图书馆写成【参考方案2】:

如果_seqs_namesstd::vectors 并且您可以在处理整个3GB 数据之前猜测它们的最终大小,您可以使用reserve 以避免在推回期间重新分配大部分内存循环中的新元素。

您应该知道向量有效地在主内存中生成文件部分的另一个副本这一事实。因此,除非您有足够大的主内存来存储文本文件以及向量及其内容,否则您最终可能会遇到许多页面错误,这些页面错误也会对您的程序速度产生重大影响。

【讨论】:

我在循环之前为向量和字符串保留了空间,节省了 60 秒中的 2 秒。【参考方案3】:

您显然在使用&lt;stdio.h&gt;,因为使用了getline

也许fopen 使用fopen(path, "rm"); 对文件有帮助,因为m 告诉(它是一个GNU 扩展)使用mmap 进行读取。

也许用setbuffer 设置一个大缓冲区(即半兆字节)也有帮助。

可能,使用readahead 系统调用(可能在单独的线程中)会有所帮助。

但这一切都是猜测。你真的应该衡量事物。

【讨论】:

C++ 中也有一个getline 函数。【参考方案4】:

一般建议:

编写最简单、最直接、最干净的方法, 测量, 测量, 测量,

如果一切都失败了:

在页面对齐的块中读取原始字节 (read(2))。按顺序执行此操作,以便内核的预读对您有利。 重复使用相同的缓冲区以最大限度地减少缓存刷新。 避免复制数据、原地解析、传递指针(和大小)。

mmap(2)-ing [部分] 文件是另一种方法。这也避免了内核用户态复制。

【讨论】:

流缓冲区已经在执行此操作。没有必要手动进行。 是的。我发现手动操作并没有真正的优势。维护成本和更大规模的算法改进超过了任何本地性能提升。 当然。告诉我需要做什么的规范。仅基于一半信息(如上)编写内容不会有太大用处,因为我最终会在不了解更大背景的情况下针对当地情况进行优化。像这样过早的优化只是浪费时间。 :-) 看看上面的链接实际上都是关于操纵 DNA 序列的,我们应该能够使用这些信息来提高效率。但是如果没有完整的规范,我怎么知道。 您也希望得到报酬吗? :) 我想说什么。您在上面使用带有指针和大小的结构(如@valdo)的优化还为时过早。如果所有代码都在打印结果,那么很好(它可能会加快应用程序的速度)。但是,如果需要操纵字符串,您将需要一个副本。因此,您所做的只是将成本从加载函数移动到操作函数中(总体而言,应用程序的性能不会有任何提升)。此外,您还可以通过手动滚动自己的字符串处理技术来使代码更加脆弱。【参考方案5】:

根据您的磁盘速度,使用非常快速的去压缩算法可能会有所帮助,例如 fastlz(至少还有另外两个可能更有效,但在 GPL 下,因此许可证可能是个问题)。

此外,使用 C++ 数据结构和函数可以提高速度,也许可以实现更好的编译器时间优化。走 C 路并不总是最快的!在某些恶劣的条件下,使用 char* 您需要解析整个字符串以达到 \0 产生灾难性的性能。

对于解析数据,使用 boost::spirit::qi 也可能是最优化的方法http://alexott.blogspot.com/2010/01/boostspirit2-vs-atoi.html

【讨论】:

bootleneck不是HDD本身的IO(高性能raid系统)。尝试了使用压缩文件的方法,但比读取未压缩数据要慢(参见初始帖子) GZip 面向压缩性能,而不是压缩速度。 QuickLZ 或 FastLZ 以解压速度为导向。看看他们的基准quicklz.com。但当然,如果您以 300Mb/秒的速度阅读,那可能无济于事

以上是关于非常快的文本文件处理 (C++)的主要内容,如果未能解决你的问题,请参考以下文章

使用多处理和线程并行处理非常大的文本文件

在python中批处理非常大的文本文件

逐行处理非常大 (>20GB) 的文本文件

C++源文件从文本到可执行文件经历的过程?

常用软件

C++ WinAPI:处理长文件路径/名称