简体   繁体   English

现代编译器如何优化 c++ 中的 function object?

[英]How does modern compiler optimize function object in c++?

As I knew from the book Effective C++, it would have a better performance if I pass a Function Object by its value rather than function reference or function pointer in C++. As I knew from the book Effective C++, it would have a better performance if I pass a Function Object by its value rather than function reference or function pointer in C++. So how does the modern compiler do to optimize that kind of scenario?那么现代编译器如何优化这种场景呢?

Or let's say usually we do not recommend to pass an object of our self-customized class by value, but as function object is actually the same as a normal object but just implemented the " operator() " inside the class. Or let's say usually we do not recommend to pass an object of our self-customized class by value, but as function object is actually the same as a normal object but just implemented the " operator() " inside the class. So, there must be something different for the compiler to treat these two things when passing them by value, right?所以,编译器在通过值传递它们时必须有一些不同的东西来处理它们,对吧?

Below is a case giving a comparison between the function object and function pointer.下面是一个比较 function object 和 function 指针的案例。

#include <algorithm>
#include <vector>
#include <ctime>
#include <iostream>

bool cmp(int a, int b) { return a < b; }

int main() {
    std::vector<int> v(10000000);
    for (size_t i = 0; i < 10000000; ++i)
        v.push_back(rand());
    std::vector<int> v2(v);
    std::sort(v.begin(), v.end(), std::less<int>()); // This way would be faster than below;
    std::sort(v2.begin(), v2.end(), cmp);
}

In case of function pointer, compilers is likely to pass function pointer and performing indirect function call, instead of making direct function call or even inlining. In case of function pointer, compilers is likely to pass function pointer and performing indirect function call, instead of making direct function call or even inlining.

In contrast, operator() of a function object is likely to inline, or at least be called directly, since it is not passed, only data to it is passed (by value or by reference).相比之下, function object 的operator()可能是内联的,或者至少被直接调用,因为它没有被传递,只有数据传递给它(通过值或通过引用)。 In case of function object without data, you pass nothing (that would compile to a dummy integer, or even nothing).如果 function object 没有数据,则什么也不传递(这将编译为虚拟 integer,甚至什么都不传递)。

Especially it is true with std::function , there's almost no way from implementation side to avoid double indirect function call in case of function pointer.对于std::function尤其如此,在 function 指针的情况下,从实现方面几乎无法避免双重间接 function 调用。

A lambda is easiest way to make this optimization. lambda 是进行此优化的最简单方法。 Here is your example with one character difference:这是您的示例,其中有一个字符差异:

#include <algorithm>
#include <vector>
#include <ctime>
#include <iostream>

int main() {
    std::vector<int> v(10000000);
    for (size_t i = 0; i < 10000000; ++i)
        v.push_back(rand());
    std::vector<int> v2(v);
    std::sort(v.begin(), v.end(), [] (int a, int b) { return a < b; }); // This way would be faster than below;
    std::sort(v2.begin(), v2.end(), +[] (int a, int b) { return a < b; });
}

Modern compilers did not go much further than old compilers in this regard.在这方面,现代编译器并没有比旧编译器走得更远。 Although you can try your example on different modern compilers to check for sure (you can use https://godbolt.org/ and inspect disassembly)尽管您可以在不同的现代编译器上尝试您的示例来确定检查(您可以使用https://godbolt.org/并检查反汇编)

In case of gcc 7.5, std::sort uses internally __gnu_cxx::__ops::_Iter_comp_iter template which looks like that:对于 gcc 7.5, std::sort在内部使用__gnu_cxx::__ops::_Iter_comp_iter模板,如下所示:

template<typename _Compare>
struct _Iter_comp_iter
{
  _Compare _M_comp;
  explicit _GLIBCXX14_CONSTEXPR
  _Iter_comp_iter(_Compare __comp) : _M_comp(_GLIBCXX_MOVE(__comp)) { }

  template<typename _Iterator1, typename _Iterator2>
  _GLIBCXX14_CONSTEXPR bool
  operator()(_Iterator1 __it1, _Iterator2 __it2)
  { return bool(_M_comp(*__it1, *__it2)); }
}

In the first case _Compare is std::less<int> , in the second -- bool (*)(int, int) .在第一种情况下_Comparestd::less<int> ,在第二种情况下 -- bool (*)(int, int)

In the first case gcc inlines comparison, while in the second it generates something like callq *%r13 to call that pointer stored in _M_comp.在第一种情况下,gcc 内联比较,而在第二种情况下,它会生成类似于callq *%r13的内容来调用存储在 _M_comp 中的指针。

Update:更新:

After more digging around prompted by comments, it turns out that the problem is not in the type of _Compare -- gcc 7.5 can inline small pure functions with function pointers, too, even without inline modifier -- but rather in presence of recursion in the internal workings of std::sort .在评论提示进行更多挖掘之后,事实证明问题不在于 _Compare 的类型 - gcc 7.5 也可以使用 function 指针内联小型纯函数,即使没有inline修饰符 - 而是存在递归std::sort的内部工作原理。 That throws the compiler off and it generates indirect call.这会使编译器关闭并生成间接调用。 Good news is that gcc 8+ seems to be free of this drawback.好消息是 gcc 8+ 似乎没有这个缺点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM