【问题标题】:How can I load data from .txt efficiently in C++?如何在 C++ 中有效地从 .txt 加载数据?
【发布时间】:2020-07-21 06:24:28
【问题描述】:

我目前正在使用 fstream 通过 C++ 加载 7.1GB 的数据。 .txt 文件如下所示:

 item1  2.87  4.64  ... 
 item2  5.89  9.24  ... 
 ...     ...   ...  ... 

它有 300000 行和 201 列(1 列用于项目名称,200 用于其权重),每个单元格都有一个双精度类型编号。我现在的做法是这样的:

ifstream click_log(R"(1.txt)", ifstream::in);
string line;
unordered_map<string, vector<double>> dict;
while (getline(click_log, line)){
    istringstream record(line);
    string key;
    vector<double> weights;
    double weight;
    record >> key;
    while (record >> weight){
        weights.push_back(weight);
    }
    dict[key] = weights;
}

但是,我的电脑(AMD 3700X,8 核)大约需要 30 分钟才能完全加载文件。 它之所以慢是因为它的 O(m*n) 复杂性,还是仅仅是将字符串转换为双精度的事实很慢? 从 .txt 加载数据最有效的方法是什么?

【问题讨论】:

  • 大多数 C 编译器也有一个分析工具。也许试着找出大部分时间都花在了哪里。我怀疑字典可能不是在这种情况下使用的最佳数据结构。使用老式的线性链表(而不是预先分配大小的数组)怎么样?另外,如果你只是从文件中读取所有行(丢弃它们),那是什么时候?
  • 首先,您应该预先分配您的向量或使用std::array,因为大小是众所周知的(字典也是如此)。由于std::vector 机器,它将删除大量释放/重新分配/副本。然后你应该直接写入目标容器而不是使用临时向量(并做很多额外的副本)。但是我有一个问题,您的文件是否也包含“+-----+”?还是您添加它是为了便于阅读?这很令人困惑,因为您的代码似乎无法处理。
  • 毫无疑问,在读取权重之前调用weights.reserve(200) 将是一个显着的改进。这将具有矢量不会过大的额外好处。其次,使用移动语义将其添加到地图将有助于防止额外的副本。最后,在unordered_map 上保留足够的桶将有助于避免重新散列,这也很昂贵。如果您不确定,请考虑使用 std::map 作为替代方案。
  • 还要注意,即使从字符串中解析double 值也是一笔不小的开销。如果您知道您的数据始终符合特定格式(例如始终为十进制表示法),请考虑滚动您自己的简化 double 值解析器,如果在进行这些其他优化后分析显示它很重要。而且,如果您可以不用存储 float 而不是 double(假设您不需要高精度),那么这几乎可以将您的内存占用减半。
  • 这里的另一个考虑因素是,在处理文件的每一行之前,您完全是在执行此单线程操作并等待阻塞 I/O 操作。您应该能够在所有 CPU 线程之间分配这项工作。应该有一个可以读取的环境变量来确定 CPU 线程的数量(并不总是与内核数量相同),并且现代存储设备可以并行执行多个读取,因此值得研究 imo。

标签: c++ fstream


【解决方案1】:

您不应在每次循环迭代时重新创建变量。一劳永逸地创建它们,然后您可以在需要时重新分配它们。

如果您想使用std::vector 而不是std::array&lt;double, 200&gt;,那么您应该reserve(200) 所有向量,以避免由于std::vector 的机制而导致大量重新分配/复制/解除分配。

您可以为您的std::unordered_map 执行相同的操作。

最后,将你的数据直接写入目标容器,你不需要使用那么多的临时对象(它会消除所有这些不必要的副本所带来的巨大开销)。

我已根据这些准则重写了您的代码。我敢打赌这会提高你的表现:

int main()
{
    std::ifstream ifs("..\\tests\\data\\some_data.txt"); // Replace with your file
    if(!ifs)
        return -1;
    
    std::unordered_map<std::string, std::array<double, 200>> dict;
    dict.reserve(300000);
    
    std::string line;
    std::string key;
    double weight;
    std::size_t i;
    
    while(getline(ifs, line))
    {
        std::istringstream record(line);
        i = 0;
    
        record >> key;
    
        while(record >> weight)
        {
            dict[key].at(i++) = weight;
        }
    }

    ifs.close();

    // The whole file is loaded

    return 0;
}

当然,我并不认为这是最有效的方法。我相信我们可以带来更多我当时没有想到的改进。

无论如何,请记住,您仍然可能会遇到硬盘访问、IO 操作等方面的瓶颈......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-01
    • 1970-01-01
    相关资源
    最近更新 更多