【问题标题】:Fastest Way to Read a File Into Memory in c++?在 C++ 中将文件读入内存的最快方法?
【发布时间】:2016-11-18 19:12:15
【问题描述】:

我正在尝试以更快的方式读取文件。我目前的做法如下,但对于大文件来说非常慢。我想知道是否有更快的方法来做到这一点?我需要存储一个结构体的值,我在下面定义了它。

std::vector<matEntry> matEntries;
inputfileA.open(matrixAfilename.c_str());

// Read from file to continue setting up sparse matrix A
while (!inputfileA.eof()) {
    // Read row, column, and value into vector
    inputfileA >> (int) row; // row
    inputfileA >> (int) col; // col
    inputfileA >> val;       // value

    // Add row, column, and value entry to the matrix
    matEntries.push_back(matEntry());
    matEntries[index].row = row-1;
    matEntries[index].col = col-1;
    matEntries[index].val = val;

    // Increment index
    index++;
}

我的结构:

struct matEntry {
    int row;
    int col;
    float val;
};

文件格式如下(int、int、float):

1 2 7.9
4 5 9.008
6 3 7.89
10 4 10.21

更多信息:

  • 我知道文件在运行时的行数。
  • 我正面临瓶颈。分析器说 while() 循环是瓶颈。

【问题讨论】:

  • 您是否面临瓶颈?这通常被认为是“最快”的方式,但 mmap 可能是另一种选择,具体取决于您使用文件内容的方式。
  • 您的分析器说哪个部分很慢?文件读取? vector 重新分配? fstream 操作?
  • 特定平台:内存映射文件。
  • 您的分析器可能会说 while 循环占用了大部分执行时间,但 这是意料之中的某事 必须占用执行时间,否则您的程序将无法运行。这并不意味着您的 while 循环是一个瓶颈。瓶颈意味着某些事情导致的减速比预期或被认为是最佳的要多。
  • 当然while 循环被识别为慢速部分,因为所有工作都在循环内完成!你需要更多的粒度。

标签: c++ file fstream


【解决方案1】:

为了让事情更简单,我会为你的结构定义一个输入流操作符。

std::istream& operator>>(std::istream& is, matEntry& e)
{
    is >> e.row >> e.col >> e.val;
    e.row -= 1;
    e.col -= 1;

    return is;
}

关于速度,如果不达到very basic 级别的文件 IO,则没有太多改进。我认为你唯一能做的就是初始化你的向量,这样它就不会在循环内一直调整大小。使用定义的输入流运算符,它看起来也更干净:

std::vector<matEntry> matEntries;
matEntries.resize(numberOfLines);
inputfileA.open(matrixAfilename.c_str());

// Read from file to continue setting up sparse matrix A
while(index < numberOfLines && (is >> matEntries[index++]))
{  }

【讨论】:

  • 通过定义这样的自定义运算符,您可以使用std::istream_iteratorstd::back_inserterstd::copy() 完全删除手动循环,例如:std::vector&lt;matEntry&gt; matEntries; matEntries.reserve(numberOfLines); std::copy(std::istream_iterator&lt;matEntry&gt;(inputfileA), std::istream_iterator&lt;matEntry&gt;(), std::back_inserter(matEntries));
  • std::copy_n() 在 C++11 及更高版本中,例如:std::vector&lt;matEntry&gt; matEntries; matEntries.reserve(numberOfLines); std::copy_n(std::istream_iterator&lt;matEntry&gt;(inputfileA), numberOfLines, std::back_inserter(matEntries));
【解决方案2】:

根据我的经验,此类代码中最慢的部分是解析数值(尤其是浮点数)。因此,您的代码很可能受 CPU 限制,可以通过并行化加速,如下所示:

假设您的数据在 N 行,并且您将使用 k 个线程处理它,每个线程将必须处理大约 [N strong>/k] 行。

  1. mmap() 文件。
  2. 扫描整个文件以查找换行符并确定要分配给每个线程的范围。
  3. 使用implementation of an std::istream that wraps an in-memory buffer 让每个线程并行处理其范围。

请注意,这需要确保用于填充数据结构的代码是线程安全的。

【讨论】:

    【解决方案3】:

    按照 cmets 中的建议,您应该在尝试优化之前分析您的代码。如果您想尝试随机的东西直到性能足够好,您可以尝试先将其读入内存。这是一个简单的示例,其中包含一些基本的分析:

    #include <vector>
    #include <ctime>
    #include <fstream>
    #include <sstream>
    #include <iostream>
    
    // Assuming something like this...
    struct matEntry
    {
        int row, col;
        double val;
    };
    
    std::istream& operator << ( std::istream& is, matEntry& e )
    { 
        is >> matEntry.row >> matEntry.col >> matEntry.val;
        matEntry.row -= 1;
        matEntry.col -= 1;
        return is;
    }
    
    
    std::vector<matEntry> ReadMatrices( std::istream& stream )
    {
        auto matEntries = std::vector<matEntry>();
    
        auto e = matEntry();
        // For why this is better than your EOF test, see https://isocpp.org/wiki/faq/input-output#istream-and-while
        while( stream >> e ) {
            matEntries.push_back( e );
        }
        return matEntries;
    }
    
    int main()
    {
        const auto time0 = std::clock();
    
        // Read file a piece at a time
        std::ifstream inputFileA( "matFileA.txt" );
        const auto matA = ReadMatrices( inputFileA );
    
        const auto time1 = std::clock();
    
        // Read file into memory (from http://stackoverflow.com/a/2602258/201787)
        std::ifstream inputFileB( "matFileB.txt" );
        std::stringstream buffer;
        buffer << inputFileB.rdbuf();
        const auto matB = ReadMatrices( buffer );
    
        const auto time2 = std::clock();
        std::cout << "A: " << ((time1 - time0) * CLOCKS_PER_SEC) << "  B: " << ((time2 - time1) * CLOCKS_PER_SEC) << "\n";
        std::cout << matA.size() << " " << matB.size();
    }
    

    请注意连续两次读取磁盘上的同一个文件,因为磁盘缓存可能会隐藏性能差异。

    其他选项包括:

    • 在向量中预分配空间(可能为文件格式添加大小或根据文件大小或其他内容估算它)
    • 将文件格式更改为二进制或压缩数据,以最大限度地减少读取时间
    • 内存映射文件
    • 并行化(easy:在不同的线程中处理文件 A 和文件 B [参见 std::async()];medium:流水线化,因此读取和转换在不同的线程上完成线程;hard:在不同的线程中处理同一个文件)

    其他更高级别的考虑可能包括:

    • 看起来您有一个 4-D 数据数组(2D 矩阵的行/列)。在许多应用中,这是一个错误。花点时间重新考虑一下这种数据结构是否真的是您所需要的。
    • 有许多可用的高质量矩阵库(例如,Boost.QVMBlaze 等)。使用它们而不是重新发明轮子。

    【讨论】:

    • 我打算提出类似的建议,将整个文件加载到字符串流中(假设有足够的内存),然后解析可能会更快,因为它避免了旋转硬盘驱动器再次寻找文件。
    • I/O 通常有两个缓慢的部分:磁盘本身读取,以及从文本到二进制的转换。磁盘读取不会显示在分析器中,因为它发生在操作系统中,并且对于此答案中提供的两种方法,二进制文本将是相同的。
    猜你喜欢
    • 2012-06-05
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-19
    • 2021-06-21
    • 2012-02-29
    相关资源
    最近更新 更多