为什么转置这个 std::vector<std::vector<std::string> > 这么慢?
Why transposing this std::vector<std::vector<std::string> > is so slow?
我有一个大约 400MB 的 1000 行文件,表示一些表示为字符串的数字数据。 我想转置数据,以便每行只有 1000 个字符串,(这样我就可以打开它并用 pandas 快速绘制它(。
我将整个文件导入到我要转置的字符串向量中(最终我想写回文件(。
我使用两个嵌套循环来遍历 2d 结构,并将其写入一些 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执行了分析。 我在此过程中收到此消息: ...线程 #1 中的 brk 段溢出:无法增长到 ...
我分析了结果并总结在这里:
25% 花在运算符 = 的 basic_string 21% 花在构造basic_string
上(只有 3% 的时间在新的(14% 花在运算符(([] 上 在外部向量上 11% 花在运算符((
[] 上 在内部向
量上
感谢您的建议。
首先,在对一段代码运行缓慢的原因提出任何声明之前,您应该真正测量它在机器上的性能,然后根据手头的数据推断出原因。
也就是说,在这种情况下,我很有信心说问题可能在于您正在分配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= callgrind
或gprof
等软件,这些软件可让您分析和可视化有关应用的性能数据。
该程序在多个层面上都有冗余。
显而易见的是,您无需转置矢量即可转置文件。
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 小时转置数百万个元素的矩阵,它可以满足您的需求而不会产生太多开销。
惩罚可能来自您在 for 循环中过度使用调整大小的事实。
根据参考资料:
复杂性
当前大小和计数之间的差异呈线性。如果容量小于计数,则可能由于重新分配而增加复杂性
内存分配成本很高,因此您可能希望避免过度分配。
正如其他人所指出的,前期分配将是一种有趣的方法,可以避免每次都重新创建(调整(矢量大小。
我的计算机上没有足够的可用内存来执行此任务(见下文(。 将我的数据分成三部分,我在几秒钟内解决了任务。 这是检查内存的代码的输出:
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
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- std::vector<;uint8_t>;当C++11/14启用时,手动复制而不是调用memcpy
- 是std :: set&lt; std :: future&gt;不可能存在
- 在修改列表后,std :: list&lt; t&gt; :: end()的值是否会更改
- 使用 std::vector<boost::shared_ptr<Base_Class>> 或 boost::p tr_vector 的性能注意事项是什么<Base>
- std::map<std::set, double> AND std:<long>map< std::p air<long, long>, double>
- 如何获取std::vector<DMatch>