编译器是否足够聪明,可以优化成员与静态方法参数相同的函子
Are compilers smart enough to optimize functors with members the same as static method arguments?
我对跨几个编译器(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样式的恐惧:
- 如果谓词是一个单词或更小,编译器是否足够聪明,可以通过寄存器传递它?在另一种风格中,单词将是一个参数,因此编译器不必推断任何内容
- 如果谓词为空,它是否足够聪明,可以避免实例化和传递它?在替代样式中,谓词从不实例化
所以我的直觉是替代风格应该更快,但也许我低估了现代优化器。
根据我对gcc(g++)的个人经验,现代编译器绝对能够优化函子。这可能不是真的一种情况是当函子位于不同的编译单元中时。
也就是说,这不应该阻止你,C++库和方向通过使用现代风格来奖励你,它是一种使用抽象的更易于管理的语言。
我运行了一个实验,比较了使用for、std::for_each(带函数)、std:::for_each(带函子)和std::for_each(带有lambda)的for循环。编译器能够看到将它们全部内联的过去,并且每个都有相同的执行时间和指令数量(尽管指令的结构略有不同)。
最后,Herb Sutter在他的一次演示中(我认为是在构建时)说,C++风格比C风格只增加了3%的开销,与更高的安全性相比,这算不了什么。
编译器可以执行许多优化,但需要记住的一点是,不同的编译器会有不同的优化,最适合一个编译器的优化可能不会影响另一个编译器。
在C++11中,有一些移动语义可以优化谓词对象的副本。由于这是标准中的,所有编译器都应该实现相同的优化,第一种风格的性能将与第二种风格相近。
支持STL样式的另一点是,作为一种常见的模式,您可能有更多的机会进行编译器优化,因为编译器供应商将针对这些使用模式。
此外,您应该使用探查器来评估性能提升,因为程序员通常不善于猜测代码中的瓶颈是什么以及在哪里。
- 使用在用于SFINAE的void_t中具有参数的方法
- 尝试了解在导入的静态方法上使用删除方法时的错误
- 使用模板参数重载C++方法:如何使其适用于模板的子类?
- 如何通过命名空间调用非静态方法
- 如何在没有实例的情况下获取非静态方法的类型?
- 调用从模板派生的类的静态方法,而不指定模板
- 如何在 c++ 中异步调用静态方法?
- C++ 将静态方法转换为简单方法
- 从另一个标头中的标头调用静态方法
- 如何将带有参数的方法传递给线程以执行?
- C++中静态方法的局部变量范围
- std::bind,无法让具有单个参数的方法工作
- 如何从 node-ffi 调用 c++ 中以结构向量作为参数的方法?
- 可变参数模板方法
- 获取非静态方法参数计数
- (C++)将"this"作为默认参数传递给静态方法
- 使用尽可能少的代码将非静态方法包装到 std::函数中"this"并绑定参数
- 编译器是否足够聪明,可以优化成员与静态方法参数相同的函子
- 将非静态方法指针作为参数传递给另一个方法
- 传递静态方法作为参数,不需要地址操作符