【问题标题】:Why transposing this std::vector<std::vector<std::string> > is so slow?为什么转置这个 std::vector<std::vector<std::string>> 这么慢?
【发布时间】:2020-02-01 13:19:58
【问题描述】:

我有一个大约 400MB 的 1000 行文件,它表示一些表示为字符串的数字数据。 我想转置数据,以便每行只有 1000 个字符串(这样我就可以打开它并用 pandas 快速绘制它)。

我将整个文件导入到我想转置的字符串向量中(最终我想写回文件)。

我使用两个嵌套循环来遍历二维结构,并将其写入一些 std::ofstream。它很长。 然后我试着把注意力集中在转置上,我写了以下代码:

//Read 400MB file, 90K strings per line and 1K lines, and store it into
std::vector<std::vector<std::string>> mData;

// ... 
// IO the file and populate mData with raw data 
// ...

//All rows have same number of string
size_t nbRows = mData.size();
size_t nbCols = mData[0].size();

std::vector<std::vector<std::string> > transposedData(nbCols);
for(size_t i = 0 ; i < nbCols  ; ++i)
{
    transposedData[i].resize(nbRows);
    for(size_t j = 0 ; j < nbRows ; ++j)
    {
        transposedData[i][j] = doc.mData[j][i];
    }
}

我认为几秒钟就足够了,但需要几分钟。 此外,我正在尝试使用不同的输入尺寸(对于相同的 400MB 文件大小,每行只有 3 行和更多的字符串),而且速度要快得多。

编辑 1

根据人们的建议,我使用 callgrind 进行了分析。 我在此过程中收到此消息: ... brk 线程 #1 中的段溢出:不能增长到 ...

我分析了结果,在这里总结一下:
25 % 用于基本字符串的 operator=
21 % 用于构建 basic_string(只有 3% 的时间用于新建)
14 % 用于外部向量的 operator()[]
11 % 用于内部向量的 operator()[]

感谢您的建议。

【问题讨论】:

  • 您是否分析了代码?我很确定瓶颈是输入和输出。
  • 字符串是长还是短?如果它们很长并且您不打算重用 mData,则可以移动字符串而不是复制它们。
  • 在 Linux 上,您可以使用 callgrind 来分析代码
  • 嗯,你的实际代码中有IO,对吧?您是否已经确认它不是大部分时间使用的 IO?
  • 注意:看看这个问题:1D or 2D array, what's faster? 关于向量构造的向量以及为什么应该使用一维方法(预分配)。

标签: c++ string multidimensional-array transpose


【解决方案1】:

首先,在对一段代码为什么慢的原因提出任何声明之前,您应该真正衡量它在您的机器上的性能,然后根据手头的数据推断为什么

也就是说,我在这种情况下非常有信心说问题可能在于您正在分配 90k 字符串向量,每个向量的大小为 1k。如您所知,内存分配成本很高,它可能解释了您的性能损失。

以下是仅使用预先分配的1D 数组来实现代码的方法。

size_t nbRows = mData.size();
size_t nbCols = mData[0].size();

auto get_idx = [](const int i, const int nr, const int j)
{
    return i*nr+j;
};

std::vector<std::string> transposedData(nbCols*nbRows);  
for(size_t i = 0 ; i < nbCols  ; ++i)
{
    for(size_t j = 0 ; j < nbRows ; ++j)
    {
        const int idx = get_idx(j, nbCols,i);
        transposedData[idx] = std::move(mData[j][i]);
    }
}

for(size_t i = 0 ; i < nbCols  ; ++i)
{
    for(size_t j = 0 ; j < nbRows ; ++j)
    {
        const int idx = get_idx(j, nbCols,i);
        cout<<transposedData[idx]<<" ";
    }
    cout<<endl;
}    

我想再次强调一下:分析您的代码。试用valgrind --tool= callgrindgprof 等软件,让您可以分析和可视化应用的性能数据。

【讨论】:

  • 谢谢,实际上内存似乎太多了...“在抛出 std::bad_alloc 的实例后调用终止”。
【解决方案2】:

惩罚可能来自您在 for 循环中过度使用 resize 的事实。

根据reference

复杂性

当前大小和计数之间的差值呈线性关系。如果容量小于计数,则可能由于重新分配而增加复杂性

内存分配成本很高,因此您可能希望避免过度分配。

正如其他人所指出的,预先分配将是一种有趣的方法,可以避免每次都重新创建(调整大小)你的向量。

【讨论】:

    【解决方案3】:

    我的机器上没有足够的可用内存来执行此任务(见下文)。 将我的数据分成三部分,我在几秒钟内完成了任务。 这是检查内存的代码的输出:

    free ram 2.5GB  
    IO populating mData with raw data  
    free ram 0.2GB  
    Empty string capacity : 15 bytes  
    Intending to allocate 1.4 GB  
    terminate called after throwing an instance of 'std::bad_alloc'  
      what() : std::bad_alloc  
    Aborted
    

    【讨论】:

    • 请使用您问题上的编辑链接添加更多信息。 Post Answer 按钮应仅用于问题的完整答案。 - From Review
    • 我对这个解决方案很满意,所以我编辑了我的答案,使它看起来像一个完整的答案。
    【解决方案4】:

    该程序在多个级别上有冗余。

    显而易见的是,您无需转置向量即可转置文件。

    vector<vector<string> originalData;
    // read the file to originalData
    
    for(size_t i = 0 ; i < nbCols  ; ++i)
    {
        for(size_t j = 0 ; j < nbRows ; ++j)
        {
            cout << originalData[j][i] << " ";
        }
        cout<<endl;
    }
    

    假设您出于某种原因确实需要生成转置向量,编写转置循环的一种方法是

    vector<vector<string>> transposedData (nbCols);
    for (size_t j = 0; j < nbCols; ++j)
    {
        transposedData[j].reserve(nrows);
        for (size_t i = 0; i < nbRows; ++i) 
        {
            transposedData[j].emplace_back(originalData[i][j]);
            // if keeping original veector is not needed ...
            // transposedData[j].emplace_back(std::move(originalData[i][j]));
        }
    }
    

    在我的(相当强大的)机器上,转置一个 1000x90000 的 3 字符字符串矩阵大约需要 6-7 秒。这并不是特别令人印象深刻,如果您不需要每天 24 小时转置数百万个元素的矩阵,它就可以满足您的需要,而不会产生太多开销。

    【讨论】:

      猜你喜欢
      • 2014-10-24
      • 1970-01-01
      • 1970-01-01
      • 2021-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-03-27
      相关资源
      最近更新 更多