标准::累积C++20版本
std::accumulate C++20 version
我试图理解这段代码,但我无法弄清楚为什么这个版本
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::move
的accumulate
,
std::string operator+(const std::string&, const std::string&);
将被使用。在每次迭代中,它将为生成的字符串分配堆上的存储,以便在下一次迭代时将其丢弃。
对于accumulate
std::move
,
std::string operator+(std::string&&, const std::string&);
将被使用。与前面的情况相反,第一个参数的缓冲区可以重用。如果初始字符串具有足够的容量,则在累积期间不会分配额外的内存。
简单演示
without std::move
n_allocs = 199
with std::move
n_allocs = 0
对于像int
这样的内置类型,移动只是一个副本 - 没有什么可移动的。对于优化的构建,您很可能会获得完全相同的汇编代码。如果您的基准测试显示任何速度改进/降级,则很可能您没有正确执行(没有优化、噪音、代码优化等(。
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 用C++20 fmt限制结果的总大小
- 导入库可以跨dll版本工作吗
- 如何在C++20中创建模板别名的推导指南
- 实施具有 C++20 概念的配对概念
- 在调用FreeLibrary后,释放动态链接到具有相同版本的CRT堆的DLL的内存
- 在clang++预处理器中确定gcc工具链版本
- 码头化的C++应用程序是否向后兼容早期的内核版本
- C++20概念:需要运算符重载
- 不同的Visual Studio版本中缺少.dll
- 用符号版本替换对函数的所有调用
- luaL_dofile在已知良好的字节码上失败,可以使用未编译的版本
- 为什么在C++20中对lambdas使用"std::bind_front"
- 填孔方法需要20分钟以上
- 正在解码MSVC 32位版本的程序集(作业).没有手术做什么
- 我需要分发哪些版本的可再分发文件
- 标准::累积C++20版本
- 错误:不支持 GLSL 3.30.支持的版本包括:1.10、1.20、1.30、1.00 ES、3.00 ES、3.10
- ld.so.1:protoc:fatal:libstdc++.so.6:在 Solaris 11 中找不到版本"GLIBCXX_3.4.20"
- 是 GCC 6.2 或更高版本中的 C++20 实验范围