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

时间:2012-02-23 19:10:56

标签: c++ templates optimization portability compiler-optimization

我有兴趣在多个编译器(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. 如果Predicate是一个单词或更小的单词,编译器是否足够聪明以通过寄存器传递它?在替代样式中,单词将是一个参数,因此编译器不必推断任何内容。
  2. 如果谓词是空的,它是否足够聪明以避免实例化并传递它?在替代风格中,Predicate永远不会被实例化。
  3. 所以我的直觉是替代风格应该更快,但也许我低估了现代优化器。

2 个答案:

答案 0 :(得分:2)

根据我对gcc(g ++)的个人经验,现代编译器绝对能够优化仿函数。这种情况可能不正确的一种情况是,仿函数在不同的编译单元中。

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

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

最后,Herb Sutter在他的一篇演讲中(在我认为的构建中)说,C风格的C风格只增加了3%的开销,这与其更高的安全性相比毫无意义。

答案 1 :(得分:1)

编译器可以执行许多优化,但要记住的一件事是,不同的编译器将有不同的优化,哪一个最好的一个可能不会对另一个有影响。

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

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

此外,您应该使用分析器来评估性能提升,因为程序员通常不会猜测代码中的瓶颈和位置。