编译器是否足够聪明,可以优化成员与静态方法参数相同的函子

Are compilers smart enough to optimize functors with members the same as static method arguments?

本文关键字:参数 静态方法 成员 优化 是否 编译器      更新时间:2023-10-16

我对跨几个编译器(GCC、MSVC、Clang)编写高性能代码很感兴趣。我看到了两种将函数作为编译时参数传递的模式,我很好奇编译器是否通常足够聪明,能够识别出这两种模式是等效的,或者我问得太多了。这是STL样式,传递一个函子对象:

  template<class InputIterator, class Predicate>
  InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )
  {
    for ( ; first!=last ; first++ ) if ( pred(*first) ) break;
    return first;
  }

这是另一种风格:

  template<class InputIterator, class Predicate, class PredData>
  InputIterator find_if ( InputIterator first, InputIterator last, PredData data )
  {
    for ( ; first!=last ; first++ ) if ( Predicate::eval(*first, data) ) break;
    return first;
  }

在STL样式中,Predicate类通常包含其所需的任何数据作为成员,并调用operator()来计算谓词。在替代样式中,您从来没有Predicate对象,而是它包含一个静态方法,该方法接受要检查的项,并且数据作为参数传递,而不是存储为Predicate上的成员。

我有一些使用STL样式的恐惧:

  1. 如果谓词是一个单词或更小,编译器是否足够聪明,可以通过寄存器传递它?在另一种风格中,单词将是一个参数,因此编译器不必推断任何内容
  2. 如果谓词为空,它是否足够聪明,可以避免实例化和传递它?在替代样式中,谓词从不实例化

所以我的直觉是替代风格应该更快,但也许我低估了现代优化器。

根据我对gcc(g++)的个人经验,现代编译器绝对能够优化函子。这可能不是真的一种情况是当函子位于不同的编译单元中时。

也就是说,这不应该阻止你,C++库和方向通过使用现代风格来奖励你,它是一种使用抽象的更易于管理的语言。

我运行了一个实验,比较了使用for、std::for_each(带函数)、std:::for_each(带函子)和std::for_each(带有lambda)的for循环。编译器能够看到将它们全部内联的过去,并且每个都有相同的执行时间和指令数量(尽管指令的结构略有不同)。

最后,Herb Sutter在他的一次演示中(我认为是在构建时)说,C++风格比C风格只增加了3%的开销,与更高的安全性相比,这算不了什么。

编译器可以执行许多优化,但需要记住的一点是,不同的编译器会有不同的优化,最适合一个编译器的优化可能不会影响另一个编译器。

在C++11中,有一些移动语义可以优化谓词对象的副本。由于这是标准中的,所有编译器都应该实现相同的优化,第一种风格的性能将与第二种风格相近。

支持STL样式的另一点是,作为一种常见的模式,您可能有更多的机会进行编译器优化,因为编译器供应商将针对这些使用模式。

此外,您应该使用探查器来评估性能提升,因为程序员通常不善于猜测代码中的瓶颈是什么以及在哪里。