[英]Can be std::function inlined or should I use different approach?
我正在使用std::function<>
作为许多函数的参数的复杂框架。 通过分析,发现以下性能问题之一。
有人可以解释一下为什么Loop3a这么慢吗? 我希望将使用内联并且时间将相同。 组装也一样。 有什么方法可以提高性能或以其他方式吗? C ++ 17是否以这种方式进行任何更改?
#include <iostream>
#include <functional>
#include <chrono>
#include <cmath>
static const unsigned N = 300;
struct Loop3a
{
void impl()
{
sum = 0.0;
for (unsigned i = 1; i <= N; ++i) {
for (unsigned j = 1; j <= N; ++j) {
for (unsigned k = 1; k <= N; ++k) {
sum += fn(i, j, k);
}
}
}
}
std::function<double(double, double, double)> fn = [](double a, double b, double c) {
const auto subFn = [](double x, double y) { return x / (y+1); };
return sin(a) + log(subFn(b, c));
};
double sum;
};
struct Loop3b
{
void impl()
{
sum = 0.0;
for (unsigned i = 1; i <= N; ++i) {
for (unsigned j = 1; j <= N; ++j) {
for (unsigned k = 1; k <= N; ++k) {
sum += sin((double)i) + log((double)j / (k+1));
}
}
}
}
double sum;
};
int main()
{
using Clock = std::chrono::high_resolution_clock;
using TimePoint = std::chrono::time_point<Clock>;
TimePoint start, stop;
Loop3a a;
Loop3b b;
start = Clock::now();
a.impl();
stop = Clock::now();
std::cout << "A: " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
std::cout << "ms\n";
start = Clock::now();
b.impl();
stop = Clock::now();
std::cout << "B: " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
std::cout << "ms\n";
return a.sum == b.sum;
}
使用带有“ -O2 -std = c ++ 14”的g ++ 5.4的示例输出:
A: 1794ms
B: 906ms
在探查器中,我可以看到许多内部信息:
double&& std::forward<double>(std::remove_reference<double>::type&)
std::_Function_handler<double (double, double, double), Loop3a::fn::{lambda(double, double, double)#1}>::_M_invoke(std::_Any_data const&, double, double, double)
Loop3a::fn::{lambda(double, double, double)#1}* const& std::_Any_data::_M_access<Loop3a::fn::{lambda(double, double, double)#1}*>() const
std::function
不是零运行时成本的抽象。 它是一种类型擦除的包装器,在调用operator()
时具有类似virtual
调用的成本,并且还可能潜在地进行堆分配(这可能意味着每个调用都存在缓存丢失) 。
编译器很可能无法内联它 。
如果要以不引起额外开销且允许编译器内联的方式存储函数对象 ,则应使用模板参数。 这并非总是可能的,但可能适合您的用例。
我写了一篇与此主题相关的文章:
“将功能传递给功能”
它包含一些基准测试,这些基准测试显示与模板参数和其他解决方案相比, std::function
生成了多少程序集。
std::function
大约具有虚拟调用开销。 这很小,但是如果您的操作更小,则可能会很大。
在您的情况下,您将大量遍历std::function
,并使用一组可预测的值对其进行调用,并且可能在其中执行几乎所有操作。
我们可以解决这个问题。
template<class F>
std::function<double(double, double, double, unsigned)>
repeated_sum( F&& f ) {
return
[f=std::forward<F>(f)]
(double a, double b, double c, unsigned count)
{
double sum = 0.0;
for (unsigned i = 0; i < count; ++i)
sum += f(a,b,c+i);
return sum;
};
}
然后
std::function<double(double, double, double, unsigned)> fn =
repeated_sum
(
[](double a, double b, double c) {
const auto subFn = [](double x, double y) { return x / (y+1); };
return sin(a) + log(subFn(b, c));
}
);
现在repeating_function
需要一个double, double, double
函数,并返回一个double, double, double, unsigned
。 此新函数反复调用上一个,每次都将最后一个坐标增加1。
然后,我们如下替换impl
:
void impl()
{
sum = 0.0;
for (unsigned i = 1; i <= N; ++i) {
for (unsigned j = 1; j <= N; ++j) {
fn(i,j,0,N);
}
}
}
在这里,我们通过重复调用我们的重复函数来替换“最低级别的循环”。
这将使虚拟呼叫开销减少300倍,基本上使虚拟呼叫消失。 基本上,50%的时间/ 300 = 0.15%的时间(实际上是0.3%,因为我们将时间减少2倍,这会使贡献增加一倍,但是谁在计算十分之一的百分比?)
现在,在实际情况下,您可能不会使用300个相邻值来调用它。 但是通常有一些模式。
上面我们所做的是移动了一些控制fn
在fn
内部的调用的逻辑。 如果您能做到这一点,则可以消除虚拟呼叫开销。
std::function
开销通常是可忽略的,除非您想以每秒数十亿次的数量来调用它,这就是我所说的“每像素”操作。 用“每条扫描线”(每行相邻像素)替换此类操作,开销就不再是问题。
这可能需要公开一些有关如何在标头中使用功能对象的逻辑。 根据我的经验,仔细选择公开的逻辑可以使其相对通用。
最后,请注意,可以内联std::function
,编译器会对此做得更好。 但这是困难而脆弱的。 在这一点上依靠它是不明智的。
还有另一种方法。
template<class F>
struct looper_t {
F fn;
double operator()( unsigned a, unsigned b, unsigned c ) const {
double sum = 0;
for (unsigned i = 0; i < a; ++i)
for (unsigned j = 0; j < b; ++j)
for (unsigned k = 0; k < c; ++k)
sum += fn(i,j,k);
return sum;
}
};
template<class F>
looper_t<F> looper( F f ) {
return {std::move(f)};
}
现在我们编写循环程序:
struct Loop3c {
std::function<double(unsigned, unsigned, unsigned)> fn = looper(
[](double a, double b, double c) {
const auto subFn = [](double x, double y) { return x / (y+1); };
return sin(a) + log(subFn(b, c));
}
);
double sum = 0;
void impl() {
sum=fn(N,N,N);
}
};
这样就消除了3维循环的整个操作,而不仅仅是尾随的维。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.