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

时间:2015-04-24 19:40:55

标签: 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&);
}

在典型运行期间,此函数将被称为10 8 次。

该类进入库,函数u由库的用户定义。所以我不能在类中有函数定义。

我看过this

  

std::function)...有一些缺点(非常   被调用时的开销很小(所以在性能非常关键的情况下)   情况可能是一个问题,但在大多数情况下它不应该)

是否有更有效的方法将函数u传递给类equation?这会算是一个非常严重的性能问题"?

编辑

似乎有点混乱。为了说清楚,可执行文件中已知函数u 。编译时间,但不是在图书馆。在运行时获取该功能是我将在库的更高版本中考虑的功能,但现在不是。

5 个答案:

答案 0 :(得分:4)

鉴于函数在编译时是不可知的,你不会比函数指针/引用更快。

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

正如一条评论所提到的,我会将const double & args替换为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类的实例化可以完全内联函数的执行。考虑到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的调用结果。

答案 1 :(得分:4)

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

现代CPU在分支预测中非常好,在第一次调用之后,CPU将识别出这个&#34;间接&#34; call始终转到同一个地方,并使用推测执行来保持管道满。

但是,功能边界仍然没有优化。没有内联,没有自动矢量化。

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

(这是通过减少跨越边界的呼叫数来处理互操作成本的一般原则的特定情况)

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

答案 2 :(得分:3)

使用模板参数:

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

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

如果代码膨胀,如果您需要支持许多不同的功能和/或类,那么这种方法的可能的问题很大。

答案 3 :(得分:1)

有10 ^ 8个调用,并且没有能力在编译时向调用者提供函数定义,我建议如果可能的话改变设计:

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

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

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

但是,如果算法能够快速构建一个N xy值的数组,以便在硬件堆栈上进行计算,即使每次说8个,也可以提供帮助。

它还会使u适合于具有并行fors的多线程等,因为您通常希望u的工作足够大,以简化为{{{{}调度任务所涉及的线程开销。 1}}在每个线程中做。因此,设计u一次计算可变数量的结果可以真正帮助您设计一个更稳定的接口,避免因响应优化而被破坏。

答案 4 :(得分:-2)

由于您可以使用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