比函数引用更有效的方法?

A more efficient way than function reference?

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

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

class 进入库,函数 u 由库的用户定义。所以我不能在 class.

中定义函数

我已阅读this:

(std::function) ... has the disadvantage of introducing some (very small) overhead when being called (so in a very performance-critical situation it might be a problem but in most it should not)

是否有更有效的方法将函数 u 传递给 class equation?这算作 "a very performance-critical situation" 吗?

编辑

似乎有点混乱。澄清一下,函数 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;
};

如果你不需要修改函数的参数,在函数中,最好按值传递(在内置类型的情况下,这很可能是在 CPU 注册或已经加载)。

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

这很可能会在 using_the_function 方法中内联 u。在你的情况下,编译器无法做到这一点,因为函数指针可以指向任何函数。

这种方法的可能的问题 if code bloat if you need to support a lot of different functions and/or class 很大.

既然可以用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

鉴于函数在编译时未知,您不会比函数更快 pointer/reference。

std::function 的优点是它允许您采用仿函数、成员函数指针或 lambda 表达式。但是有一些开销。

如一条评论所述,我会将 const double & 参数替换为 double。现在大多数平台上的大小都是一样的,它删除了一个取消引用。

这是一个使用 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;
}

但是,同样地,这种灵活性的提高确实会带来很小的运行时成本。它是否值得花费取决于应用程序。我可能会首先倾向于通用性和灵活的界面,然后再进行概要分析,看看这对于将在实践中使用的各种功能来说是否是一个真正的问题。

如果您可以将您的求解器变成一个仅包含头文件的库,那么当用户在他的代码中提供可内联的函数时,您可能可以获得更好的性能。例如:

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);
}

您对 Equation class 的实例化现在可以完全内联函数的执行。调用非常相似,给定 make_equation 函数(上面的实现假定 C++14,但 C++11 版本差别不大):

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);

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

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

现代CPU在分支预测方面非常好,在第一对夫妇调用后CPU会识别出这个"indirect"调用总是去同一个地方,并使用推测执行来保持管道满。

但是,仍然不会有跨函数边界的优化。没有内联,没有自动矢量化。

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

(这是通过减少跨界调用次数来处理互操作成本的一般原则的具体案例)

如果这不可能,则按值传递参数。正如其他人所说,这比浮点变量的 const 引用最有效。可能 lot 更有效,因为大多数调用约定将使用浮点寄存器(通常是 SSE 寄存器,在现代 Intel 体系结构上,在此之前他们使用 x87 堆栈),它们已准备好执行立即计算。为了通过引用传递溢出值 to/from RAM 是非常昂贵的,当函数被内联时,然后通过引用被优化掉,但这不会发生在这里。不过,这仍然不如传递整个数组。

有 10^8 次调用,并且无法在编译时向调用者提供函数定义,如果可能的话,我建议将设计更改为如下所示:

void u(int num, const double* x, const double* y, double* out_results);

允许 equation 和其他类似函数在一次调用中获得多个结果的想法。

现在这不会自动给你一些速度提升。如果你说,你可以很容易地将一种开销换成另一种开销,构建一个可变大小的工作队列来为 u 做,并且你的算法本质上是非常连续的。这在很大程度上取决于使用 u.

的算法的性质

但是,如果算法可以快速构造一个包含 N xy 值的数组以在硬件堆栈上计算,甚至一次说 8 个,那将有所帮助。

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