当我开发我的项目时,我发现std::function真的很慢。
所以我想知道为什么它真的很慢。
但我找不到明显的原因。
我认为Cpu不能利用指令重排序优化和cpu流水线,因为它不知道哪个函数被调用是性能不佳的原因。
它使记忆停滞和缓慢的表现..。
我说得对吗?
发布于 2021-05-20 07:13:22
封装在std::function中的代码总是比直接插入调用位置的代码慢。特别是如果你的代码很短,比如3-5 CPU指令.
如果您的函数的代码相当大,那么无论是使用std::function还是使用其他调用/包装代码的机制,都是没有区别的。
功能代码不是内联的。使用std::函数包装器的速度几乎与类中使用虚拟方法的速度相同。更重要的是,‘s::function机制看起来非常类似于虚拟调用机制,在这两种情况下,代码都不是内联的,并且使用汇编程序的call指令调用代码指针。
如果您确实需要速度,那么使用lambdas并将它们作为模板化的参数传递,如下所示。如果可能的话,Lambdas总是内联的(如果编译器决定它将提高速度)。
#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代码则没有。
模板参数总是比其他解决方案更快,在需要多态性的任何地方都应该使用模板,同时具有高性能。
发布于 2021-05-20 13:50:46
std::function会导致几种开销。
首先,编译器很难理解。如果您有一个原始函数指针,一些编译器能够比使用std函数更容易“撤消”间接。然而,在这些情况下,原始函数指针和std函数的使用往往是糟糕的。
其次,如何实现std函数通常涉及一个虚拟函数表,它导致最多2个间接方向,而不是一个函数指针。当虚拟函数表从CPU缓存中脱落时,这一命中率是最大的。
第三,C++编译器擅长内联,并通过std函数块进行间接转换。
现在,在我的经验中,当您执行缓冲区处理时,这种开销会变得最糟糕,例如每像素操作。
在这种情况下,您可以使用数百万或数十亿像素。每个像素所做的工作很小,对每个操作进行std函数调用的开销与实际工作相比是很大的。
解决这个(和相关的)问题的最简单的方法是保存一个缓冲区处理函数,而不是像这样的每一个元素函数。
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中的类型。这是一个玩具版本:
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()) {}
};复杂的部分是我接受vector或array的地方,因为它的.data()字段存在,所以返回一个兼容的指针。
您将转换如下代码:
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]);
}至
void foreachPixel( ScanlineOp op, Image img ) {
for (int i = 0; i < img.height(); ++i)
op(img.Scanline(i));
}现在,我演示的是一个具体的案例。一般的想法是,您可以将一些低级别的控制流注入到您的std::function中,并操作一个更高的级别,从而消除几乎所有的std::function开销。
https://stackoverflow.com/questions/67615330
复制相似问题