矢量移动构造函数比复制构造函数慢
Vector move constructor slower than copy constructor
我正在开发我的第一个 C++ 项目,它是一个 CSV 解析器 (full source code here)。已经到了工作的地步,现在我想做基本的重构/提高性能。
目前解析器的工作方式是将每一行作为 std::vector<std::string>
返回,我想与其每次都分配一个新的向量和一个新的字符串,不如分配一个新的向量和一个新的字符串,我只需要一个内部向量和内部向量带有保留内存的字符串,我会一次又一次地清除它。
这行得通,我开始查看其他可能进行内存分配的地方,我看到了这个复制内部向量然后清除它的函数:
auto add_row() -> std::vector<std::string> {
auto row(m_bufvec);
m_bufvec.clear();
return row;
}
我想如果我改为更改此行
auto row(m_bufvec);
到
auto row(std::move(m_bufvec));
这会导致某种速度提升,因为根据 http://en.cppreference.com/w/cpp/container/vector/vector it would take constant time instead of linear. To my surprise, it made the parser significantly slower (according to my really rough benchmark of running time ./main.o
over this file)。
我对优化、基准测试以及调优 C++ 代码所带来的一切完全陌生。也许这种优化即使有效也没有用,但无论如何,我很好奇为什么 std::move
会导致速度变慢。我错过了什么吗?
当你复制bufvec时,它的容量是不变的,但是当你移动它时,它的容量被清除了。因此,稍后当您填充bufvec时,会进行对数次分配以再次扩展其容量,而此类分配很容易成为您的性能瓶颈。
move 版本使 that 运行更快。但这会使其他代码变慢。微优化并不能可靠地使程序更快。
OP 编辑:
搬家后m_bufvec.reserve(row.size())
评论中Cheers and hth. - Alf
提出的解决方案解决了问题,证实上述推理正确。而且它更有效率,(尽管只是一点点)因为
you avoid copying the items [in bufvec]. If the items are simple integer values, that doesn't matter so much. If the items are e.g. strings, with dynamic allocation, then it really does matter.
的确,第一个版本预计会更快。原因是:
auto row(m_bufvec);
调用复制构造函数,它立即为 row
分配必要的内存。 bufvec
也保留其分配的内存。因此,分配 per-element 被最小化,这很重要,因为它们涉及大量的 重定位 .
在第二个版本中,auto row(std::move(m_bufvec));
bufvec
的内存变成了 row
的所有,这个操作比复制构造函数更快。但是由于 bufvec
已经失去了它分配的内存,当你以后一个元素一个元素地填充它时,它会做很多 re-allocations 和(昂贵的)重定位。 re-allocations的数量通常与向量的最终大小成对数。
编辑
以上解释了主要问题中的 "unexpected" 结果。最后,原来这个操作的"ideal"是移动然后立即保留:
auto row(std::move(m_bufvec);
m_bufvec.reserve(row.size());
return row;
这实现了三个目标:
无element-by-element分配
bufvec
没有无用的初始化
没有无用的元素从 m_bufvec
复制到 row
。
我正在开发我的第一个 C++ 项目,它是一个 CSV 解析器 (full source code here)。已经到了工作的地步,现在我想做基本的重构/提高性能。
目前解析器的工作方式是将每一行作为 std::vector<std::string>
返回,我想与其每次都分配一个新的向量和一个新的字符串,不如分配一个新的向量和一个新的字符串,我只需要一个内部向量和内部向量带有保留内存的字符串,我会一次又一次地清除它。
这行得通,我开始查看其他可能进行内存分配的地方,我看到了这个复制内部向量然后清除它的函数:
auto add_row() -> std::vector<std::string> {
auto row(m_bufvec);
m_bufvec.clear();
return row;
}
我想如果我改为更改此行
auto row(m_bufvec);
到
auto row(std::move(m_bufvec));
这会导致某种速度提升,因为根据 http://en.cppreference.com/w/cpp/container/vector/vector it would take constant time instead of linear. To my surprise, it made the parser significantly slower (according to my really rough benchmark of running time ./main.o
over this file)。
我对优化、基准测试以及调优 C++ 代码所带来的一切完全陌生。也许这种优化即使有效也没有用,但无论如何,我很好奇为什么 std::move
会导致速度变慢。我错过了什么吗?
当你复制bufvec时,它的容量是不变的,但是当你移动它时,它的容量被清除了。因此,稍后当您填充bufvec时,会进行对数次分配以再次扩展其容量,而此类分配很容易成为您的性能瓶颈。
move 版本使 that 运行更快。但这会使其他代码变慢。微优化并不能可靠地使程序更快。
OP 编辑:
搬家后m_bufvec.reserve(row.size())
评论中Cheers and hth. - Alf
提出的解决方案解决了问题,证实上述推理正确。而且它更有效率,(尽管只是一点点)因为
you avoid copying the items [in bufvec]. If the items are simple integer values, that doesn't matter so much. If the items are e.g. strings, with dynamic allocation, then it really does matter.
的确,第一个版本预计会更快。原因是:
auto row(m_bufvec);
调用复制构造函数,它立即为 row
分配必要的内存。 bufvec
也保留其分配的内存。因此,分配 per-element 被最小化,这很重要,因为它们涉及大量的 重定位 .
在第二个版本中,auto row(std::move(m_bufvec));
bufvec
的内存变成了 row
的所有,这个操作比复制构造函数更快。但是由于 bufvec
已经失去了它分配的内存,当你以后一个元素一个元素地填充它时,它会做很多 re-allocations 和(昂贵的)重定位。 re-allocations的数量通常与向量的最终大小成对数。
编辑
以上解释了主要问题中的 "unexpected" 结果。最后,原来这个操作的"ideal"是移动然后立即保留:
auto row(std::move(m_bufvec);
m_bufvec.reserve(row.size());
return row;
这实现了三个目标:
无element-by-element分配
bufvec
没有无用的初始化
没有无用的元素从
m_bufvec
复制到row
。