如何在C++中高效地从.txt文件加载数据?

3

我目前正在使用C++中的fstream加载数据,该数据有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加载数据的最有效方法是什么?


4
首先,由于向量的大小是已知的(字典也是如此),您应该预先分配空间或使用 std::array,这样可以减少由于 std::vector 机制而导致的大量释放/重新分配/复制。然后,您应该直接将内容写入目标容器,而不是使用临时向量(并进行许多额外的拷贝)。但我有一个问题,您的文件中是否也包含 "+-----+"?还是您为了可读性而添加的?因为您的代码似乎没有处理它们,这很令人困惑。 - Fareanor
1
毫无疑问,在读取权重之前调用weights.reserve(200)将是一个显著的改进。这将有额外的好处,即向量不会过大。其次,使用移动语义将其添加到映射中将有助于防止额外的复制。最后,在您的unordered_map上保留足够的桶将有助于避免重新哈希,这也是昂贵的。如果您不确定,请考虑使用std::map作为替代方案。 - paddy
1
请注意,即使从字符串解析double值也是一个不可忽视的成本。如果您知道您的数据始终符合特定格式(例如始终使用十进制表示法),则可以考虑编写自己简化的double值解析器,如果在进行这些其他优化后性能分析显示它具有重要意义。如果您可以使用float而不是double(假设您不需要高精度),这将几乎减半您的内存占用。 - paddy
2
另一个需要考虑的问题是,您正在完全单线程地执行此操作,并在处理文件的每一行之前等待阻塞I/O操作。您应该能够将此工作分配给所有CPU线程。应该有一个环境变量可以读取以确定CPU线程的数量(顺便说一下,并不总是与核心数相同),现代存储设备可以并行进行多次读取,因此我认为这值得研究。 - Chris Rollins
可能不是核心问题,但是:dict[key] = weights; - 这一行是一个复制赋值。直接将内容写入dict[key]或者至少进行移动或使用std::vector::swap - 直接填充更好。 - Aziuth
显示剩余8条评论
1个回答

2
您不应该在每次循环迭代中重新创建变量。一次性创建它们,然后在需要时重新分配它们。
如果您想使用 std::vector 而不是 std::array,那么应该使用 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操作等瓶颈问题...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接