Do iota、generate和手卷循环的执行都是一样的
Do iota, generate, and a hand rolled loop all perform the same?
这三种填充向量的方式在性能上有区别吗?
#include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
int main()
{
std::vector<int> v(10);
std::iota(v.begin(), v.end(), 0);
std::vector<int> v2(10);
int i = 0;
std::generate(v2.begin(), v2.end(), [&i](){return i++; });
std::vector<int> v3(10);
i = 0;
for (auto& j : v3)
{
j = i++;
}
return 0;
}
我知道它们都产生相同的结果,我感兴趣的只是知道对于较大的向量是否存在速度差异。对于不同的类型,答案会不同吗?
我们可以查看输出程序集(我使用的是gcc.godbolt.org, gcc -03,以及您的代码):
1)第一版,std::iota
:
main:
sub rsp, 8
mov edi, 40
call operator new(unsigned long)
mov DWORD PTR [rax], 0
mov DWORD PTR [rax+4], 1
mov rdi, rax
mov DWORD PTR [rax+8], 2
mov DWORD PTR [rax+12], 3
mov DWORD PTR [rax+16], 4
mov DWORD PTR [rax+20], 5
mov DWORD PTR [rax+24], 6
mov DWORD PTR [rax+28], 7
mov DWORD PTR [rax+32], 8
mov DWORD PTR [rax+36], 9
call operator delete(void*)
xor eax, eax
add rsp, 8
ret
2)包含std::generate
和Lambda的版本:
main:
sub rsp, 8
mov edi, 40
call operator new(unsigned long)
mov DWORD PTR [rax], 0
mov DWORD PTR [rax+4], 1
mov rdi, rax
mov DWORD PTR [rax+8], 2
mov DWORD PTR [rax+12], 3
mov DWORD PTR [rax+16], 4
mov DWORD PTR [rax+20], 5
mov DWORD PTR [rax+24], 6
mov DWORD PTR [rax+28], 7
mov DWORD PTR [rax+32], 8
mov DWORD PTR [rax+36], 9
call operator delete(void*)
xor eax, eax
add rsp, 8
ret
3)和最后一个版本,手写循环:
main:
sub rsp, 8
mov edi, 40
call operator new(unsigned long)
mov DWORD PTR [rax], 0
mov DWORD PTR [rax+4], 1
mov rdi, rax
mov DWORD PTR [rax+8], 2
mov DWORD PTR [rax+12], 3
mov DWORD PTR [rax+16], 4
mov DWORD PTR [rax+20], 5
mov DWORD PTR [rax+24], 6
mov DWORD PTR [rax+28], 7
mov DWORD PTR [rax+32], 8
mov DWORD PTR [rax+36], 9
call operator delete(void*)
xor eax, eax
add rsp, 8
ret
结论:
正如预期的那样,这三个程序集生成相同的程序集(全部展开),使用合适的编译器,启用了优化。
所以不,没有性能差异。
注意:
我做了一个测试,比较集合与足够大的向量,没有展开的循环(我不知道GCC启发式,但它开始为大小>~ 15)。
在这种情况下,程序集仍然是相同的对于所有3种情况,我不会复制这里的输出,因为它没有带来太多的答案,但问题是编译器真的非常擅长优化这类代码。
正确的方法当然是度量和/或比较生成的代码。由于std::vector<T>
为T
类型的对象使用连续内存,编译器可能会看到所有3个版本的循环并生成几乎相同的代码。此外,对于设置中的特定算法,智能实现可以做的事情也很少。事情会有所不同,例如,当使用std::deque<T>
时,算法可以单独处理段以提高性能(我不知道任何实际这样做的实现)。
如果性能是您最关心的问题,并且您正在使用大向量,您可能希望不最初创建一个大向量,因为这可能会触及所有内存,尽管它即将被覆盖。相反,你应该构造一个空向量,reserve()
足够的内存,然后使用合适的目标迭代器(例如,std::back_inserter(v)
)。不过,这些方法需要适当地改变。当在算法中构造对象时,实际上算法可以应用一些朴素循环使用的智能,例如push_back()
s或合适的追加迭代器可能不适用:因为算法可以看到它们将创建多少对象,它们可以根据循环外的容量进行检查(尽管它需要通过迭代器类型进行一些特殊访问)。即使在算法中没有优化,我也希望在向量上做一次传递比算法中的任何调整对性能有更大的好处。
您忘了提到另外一个标准算法——std::for_each
算法。
std::vector<int> v4(10);
int i = 0;
std::for_each(v4.begin(), v4.end(), [&i]( int &item ){ item = i++; } );
算法和基于范围的for语句之间没有本质的区别。事实上,它们是相互复制的。例如,基于范围的For语句使用相同的方法begin()和end()。
所以最好注意表达。在这种情况下,我更喜欢std::iota
。
也许读一下我关于算法std::iota的建议会很有趣,虽然遗传文本是用俄语写的,你将能够使用例如谷歌服务翻译来阅读它。
- 在执行其他功能的同时播放动画(LED矩阵和Arduino/ESP8266)
- C++,系统无法执行指定的程序
- 使用C++中的模板和运算符重载执行矩阵运算
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 执行函数时导致崩溃的变量
- 无论条件是否为true,if总是在c++中执行
- 当函数模板参数是具有默认参数的类模板时,函数模板参数的推导如何执行
- 在C++中对T*类型执行std::move的意外行为
- 使用QProcess执行命令,并将结果存储在QStringList中
- 如何在没有信号的情况下从C++执行QML插槽
- 如何确认我的constexpr表达式实际上已经在编译时执行
- C++17中的并行执行策略
- 如何将不同的可执行文件合并到一个窗口框架中进行编码?像浏览器一样
- 如何在运行时像可执行文件一样"启动"DLL?
- 为什么重新执行我的多线程代码后输出不一样
- Windows安装程序是否可以像普通应用程序一样执行逻辑
- STL 容器是否像shared_ptr一样执行智能释放
- Do iota、generate和手卷循环的执行都是一样的
- 为什么每次执行这个程序的CPU时间都不一样
- 托管代码可以像未托管代码一样快速地执行计算吗