标准::累积C++20版本

std::accumulate C++20 version

本文关键字:版本 C++20 累积 标准      更新时间:2023-10-16

我试图理解这段代码,但我无法弄清楚为什么这个版本

for (; first != last; ++first) 
init = std::move(init) + *first;

比这更快

for (; first != last; ++first)
init += *first;

我确实从std::accumulate中获取了它们。 第一个版本的汇编代码比第二个版本长。即使第一个版本创建了 init 的右值引用,它也总是通过添加 *first 然后将其分配给 init 来创建一个临时值,这与第二种情况下的过程相同,它创建一个临时值,然后将其分配给 init。那么,为什么使用 std::move 比使用 += 运算符的"附加值"更好?

编辑

我正在查看C++20版本的累积代码,他们说在C++20累积之前是这个

template<class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init)
{
for (; first != last; ++first) {
init = init + *first;
}
return init;
}

在 C++20 之后,它变成了

template<class InputIt, class T>
constexpr // since C++20
T accumulate(InputIt first, InputIt last, T init)
{
for (; first != last; ++first) {
init = std::move(init) + *first; // std::move since C++20
}
return init;
}

我只是想知道,通过使用std::move是否有任何真正的改进。

编辑2

好的,这是我的示例代码:

#include <utility>
#include <chrono>
#include <iostream>
using ck = std::chrono::high_resolution_clock;
std::string
test_no_move(std::string str) {
std::string b = "t";
int count = 0;
while (++count < 100000)
str = std::move(str) + b;   // Without std::move
return str;
}
std::string
test_with_move(std::string str) {
std::string b = "t";
int count = 0;
while (++count < 100000)        // With std::move
str = str + b;
return str;
}
int main()
{
std::string result;
auto start = ck::now();
result = test_no_move("test");
auto finish = ck::now();
std::cout << "Test without std::move " << std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count() << std::endl;
start = ck::now();
result = test_with_move("test");
finish = ck::now();
std::cout << "Test with std::move " << std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count() << std::endl;
return 0;
}

如果你运行它,你会注意到std::move版本确实比另一个版本快,但是如果你使用内置类型尝试它,你会得到std::move版本比另一个慢。

所以我的问题是,既然这种情况可能与 std::累积相同,为什么他们说带有 std::move 的 C++20 累积版本比没有它的版本更快? 为什么使用 std::move 之类的字符串我得到了这样的改进,但没有使用 int 之类的东西? 如果在这两种情况下,程序都会创建一个临时字符串 str + b(或 std::move(str( + b(,然后移动到 str,为什么所有这些?我的意思是,这是相同的操作。为什么秒更快?

感谢您的耐心等待。希望这次我说清楚了。

对于具有非平凡移动语义的类型,它可能更快。考虑累积足够长的字符串std::vector<std::string>

std::vector<std::string> strings(100, std::string(100, ' '));
std::string init;
init.reserve(10000);
auto r = accumulate(strings.begin(), strings.end(), std::move(init));

对于没有std::moveaccumulate

std::string operator+(const std::string&, const std::string&);

将被使用。在每次迭代中,它将为生成的字符串分配堆上的存储,以便在下一次迭代时将其丢弃。

对于accumulatestd::move

std::string operator+(std::string&&, const std::string&);

将被使用。与前面的情况相反,第一个参数的缓冲区可以重用。如果初始字符串具有足够的容量,则在累积期间不会分配额外的内存。

简单演示

without std::move
n_allocs = 199
with std::move
n_allocs = 0

对于像int这样的内置类型,移动只是一个副本 - 没有什么可移动的。对于优化的构建,您很可能会获得完全相同的汇编代码。如果您的基准测试显示任何速度改进/降级,则很可能您没有正确执行(没有优化、噪音、代码优化等(。