【问题标题】:Performance of vector::size() : is it as fast as reading a variable?vector::size() 的性能:它和读取变量一样快吗?
【发布时间】:2010-05-02 12:02:16
【问题描述】:

我对一个大的整数向量进行了广泛的计算。在计算过程中向量大小不会改变。向量的大小经常被代码访问。通常更快的是:使用vector::size() 函数或使用辅助常量vectorSize 存储向量的大小? 我知道编译器通常能够在设置正确的编译器标志时内联size() 函数,但是,使函数内联是编译器可以做但不能强制的事情。

【问题讨论】:

  • 使用局部变量显然不会变慢。如果实际的速度差异对您很重要 - 计时。
  • 闻起来很像过早的优化。
  • @NomeN,这更像是避免过早悲观。 OP 已经知道他的向量很大,所以size() 会被调用很多次。
  • 没关系。即使是说它不会那么快的答案也承认它只会从两个指针中减去一个额外的减法。即使编译器没有优化它,那真的有什么意义吗?我对此表示怀疑。如果有疑问:测量。
  • @Autopulated:编译器右移我?

标签: c++ gcc stl vector


【解决方案1】:

有趣的问题。

那么,会发生什么?好吧,如果您使用 gdb 进行调试,您会看到类似 3 个成员变量(名称不准确):

  • _M_begin:指向动态数组第一个元素的指针
  • _M_end: 指针超过动态数组的最后一个元素
  • _M_capacity: 指针超过可以存储在动态数组中的最后一个元素

vector<T,Alloc>::size() 的实现因此通常简化为:

return _M_end - _M_begin;  // Note: _Mylast - _Myfirst in VC 2008

现在,在考虑可能的实际优化时,需要考虑两件事:

  • 这个函数会被内联吗?可能:我不是编译器编写者,但这是一个不错的选择,因为函数调用的开销会使这里的实际时间相形见绌,而且由于它是模板化的,我们在翻译单元中拥有所有可用的代码
  • 结果是否会被缓存(即有一个未命名的局部变量):很可能,但除非您反汇编生成的代码,否则您不会知道

换句话说:

  • 如果您自己存储size,它很有可能会以编译器所能获得的速度一样快。
  • 如果不这样做,则取决于编译器是否可以确定没有其他任何东西在修改vector;如果没有,它不能缓存变量,并且每次都需要执行内存读取(L1)。

这是一个微优化。通常,它不会引起注意,要么是因为性能无关紧要,要么是因为编译器无论如何都会执行它。在编译器不应用优化的关键循环中,这可能是一个显着的改进。

【讨论】:

  • "结果会被缓存吗?"这是一个即使在一个函数中也可以有 2 个答案的问题。编译器非常擅长确定它们是否以及在何处有足够的寄存器来保存中间结果。
  • @MSalters:我完全同意。特别是如果编译器认为没有足够的空间,那么引入虚假变量可能不会产生任何改进。
  • 在某些情况下,使用局部变量会明显更快。例如,我有一个Pintool,在30000 元素向量上循环,该向量在执行跟踪程序的每个基本块之前被调用。对于单次运行,8分钟后甚至一半的程序都没有完成。当我用变量替换size()时,执行时间减少到80秒。
  • @TheAhmad:确实。作为所有微优化,它很少值得,但它有时会产生巨大的影响——我已经改变了结论以更好地反映这一点。请注意,如果可能,您应该使用for (auto& e : vec) 来迭代向量,您将获得最优化的迭代形式。
【解决方案2】:

据我了解 1998 C++ 规范,vector<T>::size() 需要恒定时间,而不是线性时间。因此,这个问题可能归结为读取局部变量是否比调用几乎没有工作的函数更快。

因此,我声称 将向量的 size() 存储在局部变量中会稍微加快程序速度,因为您只会调用该函数(因此是小常数执行所需的时间)一次而不是多次。

【讨论】:

  • 我认为,如果您的编译器不够聪明,无法内联 vector::size,那么使用标准模板容器的任何代码的性能都会非常糟糕,以至于删除对 size 的一些调用将不会’没有帮助。所以这并不是真正的函数调用开销。如果是,那么在迭代器上对operator[]operator* 的所有调用会怎样?我们可以假设它们也很慢,并使用以&vec[0] 开头的指针遍历向量。如果您不信任 std::vector 的实现,最好只用 C 而不是 C++...
  • 也就是说,你可能是对的,这将是一个小的加速,只是不是因为提问者担心的内联问题。例如,也许size 每次都会命中内存(如果编译器无法向自己证明大小不会改变),而使用局部变量允许编译器将大小保存在寄存器中(如果你有足够的寄存器)。你永远不知道,基本上。
  • @Steve Jessop:指出得很好。真正节省时间(即使它非常小)当然是因为只调用函数一次而不是多次调用,而不是因为函数调用机制本身比读取变量更昂贵。 -- 但是,如果我自己可以轻松做到这一点,我不想依赖优化编译器将函数调用置于循环之外。
【解决方案3】:

vector::size() 的性能:是吗 和读取变量一样快?

可能不会。

有没有关系

可能不会。

除非您每次迭代所做的工作很小(例如一两个整数运算),否则开销将是微不足道的。

【讨论】:

    【解决方案4】:

    在我见过的每一个实现中,vector::size() 都会执行 end()begin() 的减法运算,即它不如读取变量快。

    当实现一个向量时,实现者必须选择哪一个最快,end()size(), 即存储初始化元素的数量或指向最后一个初始化元素之后的元素的指针/迭代器。 换句话说;使用迭代器进行迭代。

    如果您担心 size() 性能,请像这样编写基于索引的 for 循环;

    for (size_t i = 0, i_end = container.size(); i < i_end; ++i){
    // do something performance critical
    }
    

    【讨论】:

    • “在我见过的每个实现中,vector::size() 都会执行 end() 和 begin() 的减法,即它不如读取变量快。” -- 是的,但理论上编译器可能仍会将其置于循环之外,或者对您正在查看的 C++ 代码进行胡思乱想。
    • 没错,编译器经常会发现这类优化。但如果是热点,我还是会自己优化。
    【解决方案5】:

    我总是将vector.size() 保存在一个局部变量中(如果循环内的大小没有改变!)。
    在每次迭代中调用它与将其保存在局部变量中会更快。 至少,这是我所经历的。
    我不能给你任何真实的数字,因为我很久以前测试过这个。但是据我回忆,它产生了明显的不同(但可能仅在调试模式下),尤其是在嵌套循环时。

    致所有抱怨微优化的人:
    这是一行额外的代码,没有任何缺点。

    【讨论】:

      【解决方案6】:

      您可以为自己的循环体编写一个仿函数并通过std::for_each 调用它。它为您进行迭代,然后您的问题变得没有意义。但是,您要为每次循环迭代引入一个函数调用(可能会或可能不会内联),因此如果您没有获得预期的性能,最好对其进行分析。

      【讨论】:

      • 您假设 size() 仅在循环条件中使用。如果每次迭代都调用 foo(vec[i], vec.size()-i) 怎么办?您无法使用 for_each 拨打电话。
      【解决方案7】:

      在查看这种微优化之前,请务必先了解您的应用程序。请记住,即使它执行减法,编译器仍然可以轻松地以多种方式对其进行优化,从而消除任何性能损失。

      【讨论】:

        猜你喜欢
        • 2010-11-08
        • 1970-01-01
        • 1970-01-01
        • 2011-04-23
        • 2014-08-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-28
        相关资源
        最近更新 更多