迭代器的性能

Iterator Performance

本文关键字:性能 迭代器      更新时间:2023-10-16

与直接使用指针相比,实现和使用迭代器是否会带来任何性能损失?

(假设我们使用最高的编译器优化…)

代码来自http://www.cplusplus.com/reference/iterator/iterator/

// std::iterator example
#include <iostream>     // std::cout
#include <iterator>     // std::iterator, std::input_iterator_tag
class MyIterator : public std::iterator<std::input_iterator_tag, int>
{
  int* p;
public:
  MyIterator(int* x) :p(x) {}
  MyIterator(const MyIterator& mit) : p(mit.p) {}
  MyIterator& operator++() {++p;return *this;}
  MyIterator operator++(int) {MyIterator tmp(*this); operator++(); return tmp;}
  bool operator==(const MyIterator& rhs) {return p==rhs.p;}
  bool operator!=(const MyIterator& rhs) {return p!=rhs.p;}
  int& operator*() {return *p;}
};
int main () {
  int numbers[]={10,20,30,40,50};
  MyIterator from(numbers);
  MyIterator until(numbers+5);
  for (MyIterator it=from; it!=until; it++)
    std::cout << *it << ' ';
  std::cout << 'n';
  return 0;
}

实现和使用迭代器是否引入了任何类型的性能损失,与直接使用指针相比?

这个问题是有问题的,因为它假设所有迭代器都是指向内存中连续数组的迭代器。但迭代器是对指针的泛化。它也可能是一个指向链表、哈希映射、红黑树等的迭代器,所以在这种情况下,你无法真正比较基于连续数组的迭代器与更复杂类型(如树)的迭代器的性能。

现在,让我换一种方式问这个问题:

对连续数组实现和使用迭代器是否引入了任何类型的性能损失,与直接使用指针相比?

好吧,不是真的,编译器几乎剥离了大多数c++类包装器,并将汇编代码优化为使用C指针生成的相同汇编代码。

你不相信我吗?这里是生成的汇编代码从你的代码,编译与visual studio 2015更新4,x64:
int main() {
00007FF7A1D71000  mov         qword ptr [rsp+8],rbx  
00007FF7A1D71005  push        rdi  
00007FF7A1D71006  sub         rsp,40h  
00007FF7A1D7100A  mov         rax,qword ptr [__security_cookie (07FF7A1D75000h)]  
00007FF7A1D71011  xor         rax,rsp  
00007FF7A1D71014  mov         qword ptr [rsp+38h],rax  
00007FF7A1D71019  movdqa      xmm0,xmmword ptr [__xmm@000000280000001e000000140000000a (07FF7A1D732C0h)]  
00007FF7A1D71021  lea         rbx,[numbers]  
00007FF7A1D71026  movdqu      xmmword ptr [numbers],xmm0  
00007FF7A1D7102C  mov         dword ptr [rsp+30h],32h  
00007FF7A1D71034  mov         edi,5  
00007FF7A1D71039  nop         dword ptr [rax]  
00007FF7A1D71040  mov         edx,dword ptr [rbx]  
00007FF7A1D71042  mov         rcx,qword ptr [__imp_std::cout (07FF7A1D73080h)]  
00007FF7A1D71049  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7A1D73088h)]  
00007FF7A1D7104F  mov         rcx,rax  
00007FF7A1D71052  mov         dl,20h  
00007FF7A1D71054  call        std::operator<<<std::char_traits<char> > (07FF7A1D71110h)  
00007FF7A1D71059  lea         rbx,[rbx+4]  
00007FF7A1D7105D  sub         rdi,1  
00007FF7A1D71061  jne         main+40h (07FF7A1D71040h)  
00007FF7A1D71063  mov         rcx,qword ptr [__imp_std::cout (07FF7A1D73080h)]  
00007FF7A1D7106A  mov         dl,0Ah  
00007FF7A1D7106C  call        std::operator<<<std::char_traits<char> > (07FF7A1D71110h)  
00007FF7A1D71071  xor         eax,eax  
}
下面是使用c指针的代码:
int main() {
    int numbers[] = { 10,20,30,40,50 };
    for (MyIterator it = numbers; it != numbers + 5; it++)
        std::cout << *it << ' ';
    std::cout << 'n';
    return 0;
}
int main() {
00007FF6A72E1000  mov         qword ptr [rsp+8],rbx  
00007FF6A72E1005  push        rdi  
00007FF6A72E1006  sub         rsp,40h  
00007FF6A72E100A  mov         rax,qword ptr [__security_cookie (07FF6A72E5000h)]  
00007FF6A72E1011  xor         rax,rsp  
00007FF6A72E1014  mov         qword ptr [rsp+38h],rax  
00007FF6A72E1019  movdqa      xmm0,xmmword ptr [__xmm@000000280000001e000000140000000a (07FF6A72E32C0h)]  
00007FF6A72E1021  lea         rbx,[numbers]  
00007FF6A72E1026  movdqu      xmmword ptr [numbers],xmm0  
00007FF6A72E102C  mov         dword ptr [rsp+30h],32h  
00007FF6A72E1034  mov         edi,5  
00007FF6A72E1039  nop         dword ptr [rax]  
00007FF6A72E1040  mov         edx,dword ptr [rbx]  
00007FF6A72E1042  mov         rcx,qword ptr [__imp_std::cout (07FF6A72E3080h)]  
00007FF6A72E1049  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A72E3088h)]  
00007FF6A72E104F  mov         rcx,rax  
00007FF6A72E1052  mov         dl,20h  
00007FF6A72E1054  call        std::operator<<<std::char_traits<char> > (07FF6A72E1110h)  
00007FF6A72E1059  lea         rbx,[rbx+4]  
00007FF6A72E105D  sub         rdi,1  
00007FF6A72E1061  jne         main+40h (07FF6A72E1040h)  
00007FF6A72E1063  mov         rcx,qword ptr [__imp_std::cout (07FF6A72E3080h)]  
00007FF6A72E106A  mov         dl,0Ah  
00007FF6A72E106C  call        std::operator<<<std::char_traits<char> > (07FF6A72E1110h)  
00007FF6A72E1071  xor         eax,eax  
}

如果迭代器函数可以内联,则抽象的运行时成本为0。

这就是Stroustrup在c++基础中所说的零开销抽象:

一般来说,c++实现遵循零开销原则:你不用的东西,你就不用付钱。进一步说:你用的代码,你不能再写得更好了。

零开销抽象机制。通过"轻量级抽象",我指的是不会额外增加空间或时间开销的抽象对抽象的一个特定的例子进行仔细的手工编码。