const_iterators更快吗?

时间:2009-04-16 09:20:54

标签: c++ stl iterator const-iterator

我们的编码指南更喜欢const_iterator,因为它们比普通iterator快一点。当您使用const_iterator时,编译器似乎会优化代码。

这真的是对的吗?如果是,内部真正发生的事情会使const_iterator更快?

编辑:我写了一个小测试来检查const_iterator vs iterator并发现了不同的结果:

对于迭代10,000个对象const_terator减少了几毫秒(约16毫秒)。但并不总是。有两次相同的迭代。

11 个答案:

答案 0 :(得分:75)

如果没有别的,const_iterator 更好地读取,因为它告诉任何人阅读代码“我只是在迭代这个容器,而不是弄乱所包含的对象”。

这是一场伟大的胜利,不用担心任何表现差异。

答案 1 :(得分:25)

我们使用的指南是:

总是喜欢const而非const

如果您倾向于使用const对象,那么您习惯只对所获得的对象使用常量操作,这与尽可能使用 const_iterator 一样多。

Constness具有病毒属性。一旦你开始使用它,它就会传播到你的所有代码。你的非变异方法变得不变,并且需要对属性仅使用常量操作,并且传递常量引用,这本身只会强制执行常量操作......

对我来说,使用常量迭代器而非非常量迭代器(如果有的话)的性能优势远不如代码本身的改进那么重要。操作意味着(设计)为非变异常量。

答案 2 :(得分:18)

它们适用于非平凡的容器/迭代器。直接养成你的习惯,当它重要时你不会失去表现。

此外,有几个理由喜欢const_iterator,无论如何:

  1. 使用const表示代码意图(即只读,不改变这些对象)。
  2. 使用const(_iterator)可防止意外修改数据。 (见上文)
  3. 有些库使用缺少const begin()来将数据标记为脏(即OpenSG),并将其同步发送到其他线程/网络上,因此它具有真正的性能影响。
  4. 此外,允许您访问非const成员函数可能会产生您不想要的副作用(与上面的方法大致相同),例如从共享数据中分离写时复制容器。 Qt for one,就是这样。
  5. 作为上述最后一点的一个例子,这里是Qt中qmap.h的摘录:

    inline iterator begin() { detach(); return iterator(e->forward[0]); }
    inline const_iterator begin() const { return const_iterator(e->forward[0]); }
    

    即使迭代器和const_iterator实际上是等价的(const除外), 如果有两个或更多对象使用它,detach()会创建数据的新副本。如果您要阅读使用const_iterator指明的数据,这完全没用。

    当然,另一方面有数据点:

    1. 对于STL容器和许多简单复制语义容器,性能无关紧要。代码等效的。但是,能够编写清晰的代码并避免错误会胜出。
    2. Const是病毒式的,所以如果您在遗留的代码库中工作,其中const很难(或根本没有)实现,那么您可能必须使用非const迭代器。
    3. 显然,一些预C ++ 0x STL有一个错误,其中const_iterators不能用于擦除容器中的元素。

答案 3 :(得分:14)

我看不出他们为什么会这样 - constness是一个编译时检查。但显而易见的答案是写一个测试。

编辑:这是我的测试 - 它在我的机器上提供相同的时间:

#include <vector>
#include <iostream>
#include <ctime>
using namespace std;;


int main() {
    vector <int> v;
    const int BIG = 10000000;
    for ( int i = 0; i < BIG; i++ ) {
        v.push_back( i );
    }
    cout << "begin\n";
    int n = 0;
    time_t now = time(0);
    for ( int a = 0; a < 10; a++ ) {
        for( vector <int>::iterator it = v.begin(); it != v.end(); ++it ) {
            n += *it;
        }
    }
    cout << time(0) - now << "\n";
    now = time(0);
    for ( int a = 0; a < 10; a++ ) {
        for( vector <int>::const_iterator cit = v.begin(); cit != v.end(); ++cit ) {
            n += *cit;
        }
    }
    cout << time(0) - now << "\n";;

    return n != 0;

}

答案 4 :(得分:7)

这取决于您使用的容器和实现。

是的,const_iterator 可能会表现得更好。

对于某些容器,const迭代器和可变迭代器的实现可能不同。我能想到的第一个例子是SGI's STL rope container。可变迭代器具有指向父绳索的附加指针以支持更新。这意味着浪费额外的资源用于引用计数更新+指向父绳索的指针的内存。请参阅implementation notes here

但是,正如其他人所说,编译器本身不能使用const进行优化。 const只授予对引用对象的只读访问权限,而不是说它是不可变的。对于像std::vector这样的容器,其迭代器通常被实现为一个简单的指针,没有任何区别。

答案 5 :(得分:6)

  

我们的编码指南说更喜欢const_iterator

看看这个article by Scott Meyers here。他解释了为什么人们应该更喜欢迭代器而不是const_iterator。

答案 6 :(得分:3)

它们应该是相同的,因为constness是一个编译时检查。

为了向自己证明没有怪癖,我拿了anon的代码,修改它以使用clock_gettime,添加了一个外部循环来避免缓存偏差,并运行了很多次。结果令人惊讶地不一致 - 上升和下降20%(没有空闲框可用) - 但iteratorconst_iterator最短时间实际上相同

然后我得到了我的编译器(GCC 4.5.2 -O3)来生成汇编输出并在视觉上比较了两个循环:相同(除了一对夫妇的顺序)寄存器负载被颠倒了)

iterator循环

    call    clock_gettime
    movl    56(%esp), %esi
    movl    $10, %ecx
    movl    60(%esp), %edx
    .p2align 4,,7
    .p2align 3
.L35:
    cmpl    %esi, %edx
    je  .L33
    movl    %esi, %eax    .p2align 4,,7
    .p2align 3
.L34:
    addl    (%eax), %ebx
    addl    $4, %eax
    cmpl    %eax, %edx
    jne .L34
.L33:
    subl    $1, %ecx
    jne .L35
    leal    68(%esp), %edx
    movl    %edx, 4(%esp)
    leal    56(%esp), %esi
    movl    $1, (%esp)

const_iterator循环:

    movl    60(%esp), %edx
    movl    $10, %ecx
    movl    56(%esp), %esi
    .p2align 4,,7
    .p2align 3
.L38:
    cmpl    %esi, %edx
    je  .L36
    movl    %esi, %eax
    .p2align 4,,7
    .p2align 3
.L37:
    addl    (%eax), %ebx
    addl    $4, %eax
    cmpl    %eax, %edx
    jne .L37
.L36:
    subl    $1, %ecx
    jne .L38
    leal    68(%esp), %edx
    movl    %edx, 4(%esp)
    leal    56(%esp), %esi
    movl    $1, (%esp)

答案 7 :(得分:2)

当您对此进行基准测试时,请确保使用适当的优化级别 - 您将使用“-O0”与“-O”等完全不同的时序。

答案 8 :(得分:1)

container<T>::const_iterator::operator*返回const T&而不是T&,因此编译器可以对const对象进行常规优化。

答案 9 :(得分:1)

“Const-ness”,如访问限制(公共,受保护,私有),对程序员的好处多于对优化的帮助。
由于许多原因(例如const_cast,可变数据成员,指针/引用别名),编译器实际上无法针对涉及const的许多情况进行实际优化。这里最相关的原因是,仅仅因为const_iterator不允许修改它引用的数据,并不意味着不能通过其他方式更改数据。如果编译器无法确定数据是否为只读,那么它实际上不能比非const迭代器情况更优化。 有关更多信息和示例,请访问: http://www.gotw.ca/gotw/081.htm

答案 10 :(得分:0)

根据我的经验,编译器在使用const迭代器时不会进行任何可测量的优化。虽然声明“它可能”是正确的,但引用并未被定义为标准中的指针。

但是,您可以对同一个对象进行两次引用,因此一个可以是const,一个是非const。然后,我想this thread on restrict pointers中的答案适用:编译器无法知道对象是否被另一个线程更改,例如,或某些中断处理代码。