C++ 一种比函数引用更有效的方法?

C++ 一种比函数引用更有效的方法?,c++,performance,class,c++11,reference,C++,Performance,Class,C++11,Reference,我有一个使用函数引用的类: double u( const double& x, const double& y ) { return x * y; } class equation { equation( double (&in_u)(const double&, const double&) ); //... protected: double (&u)(const double&, const double&);

我有一个使用函数引用的类:

double u( const double& x, const double& y )
{
  return x * y;
}

class equation
{
  equation( double (&in_u)(const double&, const double&) );
//...
protected:
  double (&u)(const double&, const double&);
}
在一次典型的运行中,这个函数会被调用大约108次


类进入库,函数
u
由库的用户定义。因此,我不能在类中定义函数

我读过:

std::function
)。。。缺点是引入了一些(非常 调用时开销小(因此在性能非常关键的情况下) 在这种情况下,这可能是一个问题,但在大多数情况下不应该)

是否有更有效的方法将函数
u
传递给类
方程
?这算不算是“非常严重的性能问题”

编辑


似乎有点混乱。只是想说明一下,函数
u
在可执行文件的编译时是已知的,但在库中是未知的。在运行时获取函数是我在库的后期版本中考虑的一个特性,但不是现在。

使用模板参数:

struct u {
    double operator()(const double& x, const double& y) { return x * y; }
};

template <typename Function>
class equation {
    equation();
    //...
    double using_the_function(double x, double y) {
        //...
        auto res = f(x, y);
        //...
        return res;
    }

private:
    Function f;
};
这很可能会使用函数方法在
中内联
u。在您的情况下,编译器不能这样做,因为函数指针可以指向任何函数


如果需要支持许多不同的函数和/或类,那么这种方法可能存在的问题是代码膨胀。

由于可以使用c++11,因此可以使用
std::bind
。它将函数指针及其参数绑定到变量。参数可以是占位符,并在运行时动态更改

像这样:

double multiply( const double& x, const double& y )
{
    return x * y;
}

//Somewhere in your class
auto bound_fn = std::bind (multiply, 100, std::placeholders::_1);
bound_fn(5);  //calls fn(100,5), replacing _1 by the argument 5

如果在编译时不知道函数,那么获得的速度不会比函数指针/引用快

std::function
的优点是,它允许您获取(比如)一个函子、成员函数指针或lambda表达式。但是有一些开销

正如一条评论所提到的,我将用
double
替换
constdouble&
args。如今,大多数平台上的大小都是一样的,它消除了去引用

下面是一个使用
std::function
的示例:

#include <iostream>
#include <functional>
#include <math.h>

double multiply(double x, double y) { return x * y; }
double add(double x, double y) { return x + y; }

class equation
{
public:
    using ComputeFunction_t = std::function<double(double, double)>;

    template <typename FunctionPtr>
    equation(FunctionPtr pfn)
        : computeFunction_m(pfn)
    { }

    void compute(double d1, double d2)
    {
        printf("(%f, %f) => %f\n", d1, d2, computeFunction_m(d1, d2));
    }

protected:
    ComputeFunction_t computeFunction_m;
};

int main() {
    equation prod(multiply);
    prod.compute(10, 20); // print 200

    equation sum(add);
    sum.compute(10, 20);  // print 30

    equation hypotenuse([](double x, double y){ return sqrt(x*x + y*y); });
    hypotenuse.compute(3, 4); // print 5

    struct FooFunctor
    {
        FooFunctor(double d = 1.0) : scale_m(d) {}

        double operator()(double x, double y) { return scale_m * (x + y); }
      private:
        double scale_m;
    };

    equation fooadder(FooFunctor{});
    fooadder.compute(10, 20); // print 30

    equation fooadder10(FooFunctor{10.0});
    fooadder10.compute(10, 20);

    struct BarFunctor
    {
        BarFunctor(double d = 1.0) : scale_m(d) {}

        double scaledAdd(double x, double y) { return scale_m * (x + y); }
      private:
        double scale_m;
    };

    BarFunctor bar(100.0);
    std::function<double(double,double)> barf = std::bind(&BarFunctor::scaledAdd, &bar, std::placeholders::_1, std::placeholders::_2);
    equation barfadder(barf);
    barfadder.compute(10, 20); // print 3000

    return 0;
}
通过完全优化,您可能只会在编译的代码中找到对
printf
的调用以及计算结果

函数指针(或引用,在实现级别几乎相同)可以正常工作

现代CPU非常擅长分支预测,在第一对调用之后,CPU将认识到这个“间接”调用总是去同一个地方,并使用推测执行来保持管道满

然而,在函数边界上仍然没有优化。没有内联,没有自动矢量化

如果此函数被调用108次,则很可能有大量函数处于一个参数变化的非常紧密的循环中。在这种情况下,我建议更改函数原型以接受参数值数组并输出结果数组。然后在函数中有一个循环,编译器可以在其中执行优化,例如展开和自动向量化

(这是通过减少跨境呼叫数量来处理互操作成本的一般原则的一个具体案例)


如果不可能,则按值传递参数。正如其他人所说,对于浮点变量,这比常量引用更有效。可能效率更高,因为大多数调用约定将使用浮点寄存器(在使用x87堆栈之前,在现代英特尔体系结构上通常是SSE寄存器),以便立即执行计算。为了通过引用传递而向RAM中溢出值是非常昂贵的,当函数内联后,通过引用传递会得到优化,但在这里不会发生这种情况。不过,这仍然不如传递整个数组好。

对于10^8个调用,并且无法在编译时向调用方提供函数定义,我建议在可能的情况下对如下内容进行设计更改:

void u(int num, const double* x, const double* y, double* out_results);
允许
等式
和其他类似函数在一次调用中获得多个结果的想法

现在,这不会自动给你一些速度提升。您可以轻松地将一种开销交换为另一种开销,例如,为
u
构建一个可变大小的工作队列,并且您的算法本质上是串行的。这在很大程度上取决于使用
u
的算法的性质

但是,如果这些算法可以,比如说,快速构造一个由N
x
y
值组成的数组,以便在硬件堆栈上进行计算,甚至可以说一次计算8个值,这会有所帮助


它还将使
u
适用于具有并行for的多线程处理等,因为您通常希望
u
的工作足够大,以减少调度每个线程中的
u
任务所涉及的线程开销。因此,设计
u
一次计算可变数量的结果确实可以帮助您设计一个更稳定的界面,避免在响应优化时被破坏。

通过值传递两个double可能比取消引用它们更快。编译时是否知道
u
?@gha.st,是的,现在就知道了。尽管目标是使库可用,并且最终应用程序使用指向它的动态链接。@Furihr:头文件也是库的一部分。为了使用类比,你似乎专注于用C++例程编写一个接口,比如C程序>代码> QSCORE <代码>,而写一个界面,更像C++例程 STD::排序可以更好地执行。@ Peter除了性能的提高之外,当然,它在概念上是相似的。OP声明函数只在运行时已知。@sfjac OP在哪里
template <typename ComputeFunction>
class Equation
{
  public:

    Equation(ComputeFunction fn)
      : computeFunction_m(fn)
    { }

    void compute(double d1, double d2)
    {
        printf("(%f, %f) => %f\n", d1, d2, computeFunction_m(d1, d2));
    }

  protected:
    ComputeFunction computeFunction_m;
};

template <typename ComputeFunction>
auto make_equation(ComputeFunction &&fn)
{
    return Equation<ComputeFunction>(fn);
}
auto fooadder2 = make_equation(FooFunctor{});
fooadder2.compute(10, 20);

auto hypot2 = make_equation([](double x, double y){ return sqrt(x*x + y*y); });
hypot2.compute(3, 4);
void u(int num, const double* x, const double* y, double* out_results);