iota,generate和手动循环都执行相同的操作吗?

时间:2014-08-24 19:47:22

标签: c++ performance vector

这三种填充矢量的方法之间是否存在性能差异?

#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;
}

我知道它们都会产生相同的结果,我只想知道较大的矢量是否存在速度差异。 不同类型的答案会有所不同吗?

3 个答案:

答案 0 :(得分:7)

我们可以查看输出程序集(我使用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种情况,程序集仍然相同,我不会在这里复制输出,因为它没有带来太多答案,但问题是编译器是非常非常擅长优化此类代码。

答案 1 :(得分:5)

找出的正确方法是测量和/或比较生成的代码,当然。由于std::vector<T>T类型的对象使用连续内存,因此编译器可能会查看所有3个版本的循环并生成几乎相同的代码。此外,您的设置中的特定算法几乎没有智能实现。事情会有所不同,例如,当使用std::deque<T>时,算法可以单独处理段以提高性能(我不知道实际上是这样做的任何实现)。

如果性能是您最关心的并且您正在使用大型向量,您可能希望最初创建一个大型向量,因为它可能会触及所有内存,尽管它将被覆盖。相反,你构造并清空向量,reserve()足够的内存,然后使用合适的目标迭代器(例如,std::back_inserter(v))。不过,这些方法需要适当改变。当在算法中构造对象时,算法实际上可以应用一些智能,其中使用的天真循环,例如push_back() s或合适的附加迭代器可能不适用:因为algoirthms他们可以看到他们要创建多少个对象,他们可以对循环中的容量进行检查(尽管它需要通过迭代器类型进行一些特殊访问)。即使算法中没有优化,我也希望对矢量进行单次传递比算法中的任何调整都有更大的性能优势。

答案 2 :(得分:-2)

你忘了再提一个标准算法 - 算法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

也许阅读my proposal on algorithm std::iota会很有趣虽然遗传文本是用俄语写的,但你可以使用google service translate来阅读它。