首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么std::功能太慢,CPU不能利用指令重排吗?

为什么std::功能太慢,CPU不能利用指令重排吗?
EN

Stack Overflow用户
提问于 2021-05-20 06:53:10
回答 2查看 726关注 0票数 2

当我开发我的项目时,我发现std::function真的很慢。

所以我想知道为什么它真的很慢。

但我找不到明显的原因。

我认为Cpu不能利用指令重排序优化和cpu流水线,因为它不知道哪个函数被调用是性能不佳的原因。

它使记忆停滞和缓慢的表现..。

我说得对吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-05-20 07:13:22

封装在std::function中的代码总是比直接插入调用位置的代码慢。特别是如果你的代码很短,比如3-5 CPU指令.

如果您的函数的代码相当大,那么无论是使用std::function还是使用其他调用/包装代码的机制,都是没有区别的。

功能代码不是内联的。使用std::函数包装器的速度几乎与类中使用虚拟方法的速度相同。更重要的是,‘s::function机制看起来非常类似于虚拟调用机制,在这两种情况下,代码都不是内联的,并且使用汇编程序的call指令调用代码指针。

如果您确实需要速度,那么使用lambdas并将它们作为模板化的参数传递,如下所示。如果可能的话,Lambdas总是内联的(如果编译器决定它将提高速度)。

在网上试试!

代码语言:javascript
复制
#include <functional>

template <typename F>
void __attribute__((noinline)) use_lambda(F const & f) {
    auto volatile a = f(13); // call f
    // ....
    auto volatile b = f(7); // call f again
}

void __attribute__((noinline)) use_func(
        std::function<int(int)> const & f) {
    auto volatile a = f(11); // call f
    // ....
    auto volatile b = f(17); // call f again
}

int main() {
    int x = 123;
    auto f = [&](int y){ return x + y; };
    use_lambda(f); // Pass lambda
    use_func(f); // Pass function
}

如果您查看上面示例的汇编程序代码(单击上面的Try-it-online链接),您可以看到lambda代码是内联的,而std::function代码则没有。

模板参数总是比其他解决方案更快,在需要多态性的任何地方都应该使用模板,同时具有高性能。

票数 5
EN

Stack Overflow用户

发布于 2021-05-20 13:50:46

std::function会导致几种开销。

首先,编译器很难理解。如果您有一个原始函数指针,一些编译器能够比使用std函数更容易“撤消”间接。然而,在这些情况下,原始函数指针和std函数的使用往往是糟糕的。

其次,如何实现std函数通常涉及一个虚拟函数表,它导致最多2个间接方向,而不是一个函数指针。当虚拟函数表从CPU缓存中脱落时,这一命中率是最大的。

第三,C++编译器擅长内联,并通过std函数块进行间接转换。

现在,在我的经验中,当您执行缓冲区处理时,这种开销会变得最糟糕,例如每像素操作。

在这种情况下,您可以使用数百万或数十亿像素。每个像素所做的工作很小,对每个操作进行std函数调用的开销与实际工作相比是很大的。

解决这个(和相关的)问题的最简单的方法是保存一个缓冲区处理函数,而不是像这样的每一个元素函数。

代码语言:javascript
复制
using Pixel = std::uint32_t;
using Scanline = std::span<Pixel>;
using ScanlineOp = std::function<void(Scanline)>;

template<class PixelOp>
ScanlineOp MakeScanlineOp( PixelOp op ) {
  return [op=std::move(op)](Scanline line) {
    for (Pixel& p : line)
      op(p);
  };
}

这里我接受每像素的操作,并将它与迭代代码一起保存到一个std::function中。

现在,当我处理4000像素乘4000像素的图像时,我没有承受1600万次std::function开销,而是碰到了4000次。这使管理费用减少了99.975%。

让一些4000倍的东西快几倍,你就不再关心它的价格了。

现在,std::span是一个不在c++11中的类型。这是一个玩具版本:

代码语言:javascript
复制
template<class It>
struct range {
    It b, e;
    using reference = typename std::iterator_traits<It>::reference;
    using value_type = typename std::iterator_traits<It>::value_type;

    range( It s, It f ):b(s), e(f) {}
    It begin() const { return b; }
    It end() const { return e; }
    bool empty() const { return begin()==end(); }
    reference front() const { return *begin(); }
};
template<class It>
struct random_range:range<It> {
    using range<It>::range;
    using reference = typename range<It>::reference;

    reference back() const { return *std::prev(this->end()); }
    std::size_t size() const { return this->end()-this->begin(); }
    reference operator[](std::size_t i) const{ return this->begin()[i]; }
};

template<class T>
struct array_view:random_range<T*> {
    array_view( T* start, T* finish ):random_range<T*>(start, finish) {}
    array_view( T* start, std::size_t length ):array_view(start, start+length) {}
    array_view():array_view(nullptr, nullptr) {}

    template<class C>
    using data_type = typename std::remove_pointer< decltype( std::declval<C>().data() )>::type;
    template<class U>
    static constexpr bool pointer_compatible() {
        return 
            std::is_same<
                typename std::decay<U>::type,
                typename std::decay<T>::type
            >::value
            && std::is_convertible<U*, T*>::value;
    }
    // accept any container whose 
    template<class C,
        typename std::enable_if< pointer_compatible<data_type<C>>(), bool >::type = true
    >
    array_view( C&& c ):array_view(c.data(), c.size()) {}
};

复杂的部分是我接受vectorarray的地方,因为它的.data()字段存在,所以返回一个兼容的指针。

您将转换如下代码:

代码语言:javascript
复制
void foreachPixel( PixelOp op, Image img ) {
  for (int i = 0; i < img.height(); ++i)
    for (int j = 0; j < img.width(); ++j)
      op(img[i][j]);
}

代码语言:javascript
复制
void foreachPixel( ScanlineOp op, Image img ) {
  for (int i = 0; i < img.height(); ++i)
    op(img.Scanline(i));
}

现在,我演示的是一个具体的案例。一般的想法是,您可以将一些低级别的控制流注入到您的std::function中,并操作一个更高的级别,从而消除几乎所有的std::function开销。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67615330

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档