代码之家  ›  专栏  ›  技术社区  ›  aJ.

常量迭代器更快吗?

  •  35
  • aJ.  · 技术社区  · 15 年前

    我们的编码指南更喜欢 const_iterator 因为他们比正常人快一点 iterator . 似乎编译器在使用时优化了代码 常数迭代器 .

    这是真的吗?如果是的话,内部真正发生的事情 常数迭代器 更快?.

    编辑:我写了小测试来检查 常数迭代器 VS 迭代器 发现了不同的结果:

    用于迭代10000个对象 const_terator 减少了几毫秒(大约16毫秒)。但是 并不总是 . 在一些迭代中,两个都是相等的。

    11 回复  |  直到 8 年前
        1
  •  75
  •   unwind    15 年前

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

    这是一个伟大的胜利,不介意任何性能差异。

        2
  •  25
  •   cmh    11 年前

    我们使用的准则是:

    总是喜欢常量而不是非常量

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

    有一个恒心 病毒的 财产。一旦你开始使用它,它就会传播到你的所有代码中。您的非可变方法变为常量,这要求对属性只使用常量操作,并传递常量引用,而本身只强制执行常量操作…

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

        3
  •  18
  •   Macke    8 年前

    它们适用于非平凡的容器/迭代器。把你的习惯弄清楚,当它真的很重要时,你不会失去表现。

    此外,有几个原因可以选择const_迭代器,不管是什么:

    1. const的使用表示代码意图(即只读取,不改变这些对象)。
    2. 使用const(_迭代器)可以防止意外修改数据。(见上文)
    3. 有些图书馆使用缺少常量的方法。 begin() 将数据标记为脏数据(即opensg),并将其同步发送到其他线程/通过网络发送,因此它具有真正的性能影响。
    4. 此外,允许您访问非常量成员函数可能会产生您不希望的副作用(与上面的方法大致相同),例如从共享数据中分离写时复制容器。qt代表一个,就是这样。

    作为上面最后一点的例子,这里是qt中qmap.h的摘录:

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

    即使迭代器和const_迭代器实际上是等效的(除了 const ) detach() 如果有两个或多个对象正在使用数据,则创建该数据的新副本。如果您只想读取数据,这是完全无用的,您可以使用 const_iterator .

    当然,还有其他方向的数据点:

    1. 对于STL容器和许多简单的复制语义容器来说,性能并不重要。代码 当量。然而,能够写清楚的代码和避免错误的能力最终胜出。
    2. const是病毒性的,所以如果您在一个旧代码库中工作,而const实现得很差(或者根本没有实现),那么您可能需要使用非const迭代器。
    3. 显然,一些C++0xSTL有一个bug,其中不能使用CONTYSATIORATER来擦除容器中的()元素。
        4
  •  14
  •   anon    15 年前

    我不明白他们为什么会这样做-常量是编译时检查。但显而易见的答案是写一个测试。

    编辑: 这是我的测试-它在我的机器上给出相同的计时:

    #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;
    
    }
    
        5
  •  7
  •   Yakov Galka    13 年前

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

    对, const_iterator 可以 表现更好。

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

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

        6
  •  6
  •   Shree    15 年前

    我们的编码指导方针说偏好常量迭代器

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

        7
  •  3
  •   Tony Delroy    13 年前

    它们应该是相同的,因为常量是编译时检查。

    为了证明自己没有怪癖,我取了anon的代码,修改后使用 clock_gettime ,添加了一个外部循环以避免缓存偏差,并多次运行它。结果出人意料地不一致——上下波动了20%(没有空闲的盒子),但是 最短时间 对于两者 iterator const_iterator 实际上 完全相同的 .

    然后我让编译器(gcc 4.5.2-o3)生成 装配输出 并对这两个回路进行了目视比较: 完全相同的 (除了一对寄存器加载顺序相反)

    迭代器

        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)
    

    常数迭代器 循环:

        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)
    
        8
  •  2
  •   user497611    13 年前

    当您对其中任何一个进行基准测试时,请确保使用适当的优化级别——您将使用“-O0”和“-O”等获得完全不同的计时。

        9
  •  1
  •   tstenner    15 年前

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

        10
  •  1
  •   mjmt    15 年前

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

        11
  •  0
  •   Community TheSoundDefense    7 年前

    根据我的经验,编译器在使用常量迭代器时不会进行任何可测量的优化。尽管语句“it could”为真,并且引用没有定义为标准中的指针。

    但是,可以有两个对同一对象的引用,因此一个可以是常量,一个不是常量。那么,我猜答案在 this thread on restrict pointers 应用:编译器无法知道对象是被另一个线程(例如)更改,还是被某些中断处理代码更改。