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

时间:2011-11-14 14:33:13

标签: c++ file text getline

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

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

我尝试了几种方法来尽可能地获取文件:

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

其他人是:

  • 将文件读取为gzip,以最小化IO,但速度比直接慢 阅读它。
  • 通过读取将文件复制到ram(filepointer,chararray,length) 并使用循环处理它以区分线条(也比增强速度慢)

有关如何让它运行得更快的任何建议吗?

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(行中)必须大于要读取的行数。

SOLUTION:

我找到了一个解决方案,它可以将文件中的读取时间从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++;
        }
    }
}

5 个答案:

答案 0 :(得分:7)

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

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

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

修改

似乎所有瓶颈都与字符串管理有关。

  • 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;
    }

};

答案 1 :(得分:5)

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

您应该意识到这些向量在主内存中有效地生成了文件的另一部分副本。因此,除非你有一个足够大的主存储器来存储文本文件加上向量及其内容,否则你可能会遇到一些页面错误,这些错误也会对程序的速度产生重大影响。

答案 2 :(得分:2)

您使用<stdio.h>后显然正在使用getline

使用fopen文件fopen(path, "rm");可能会有所帮助,因为m告诉(它是GNU扩展名)使用mmap进行阅读。

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

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

但所有这些都是猜测。你应该真正衡量一切。

答案 3 :(得分:2)

一般建议:

  • 编写最简单,最直接,最简洁的方法,
  • 测量
  • 测量
  • 测量

然后如果一切都失败了:

  • 以页面对齐的块读取原始字节(read(2))。按顺序执行,因此内核的预读对您有利。
  • 重复使用相同的缓冲区以最小化缓存刷新。
  • 避免复制数据,解析到位,传递指针(和大小)。

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

答案 4 :(得分:0)

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

此外,使用C ++数据结构和函数可以提高速度,因为可能可以实现更好的编译时优化。走C路并不总是紧张!在一些不好的情况下,使用char *你需要解析整个字符串以达到\ 0产生灾难性的表现。

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