首页 > 解决方案 > 高效的函子调度器

问题描述

我需要帮助理解函子调度程序的两个不同版本,请参见此处:

#include <cmath>
#include <complex>
double* psi;
double dx = 0.1;
int range;
struct A
{
    double operator()(int x) const
    {
        return dx* (double)x*x;
    }
};

template <typename T>
void dispatchA()
{
    constexpr T op{};

    for (int i=0; i<range; i++)
        psi[i]+=op.operator()(i);
}

template <typename T>
void dispatchB(T op)
{

    for (int i=0; i<range; i++)
        psi[i]+=op.operator()(i);
}

int main(int argc, char** argv)
{
    range= argc;
    psi = new double[range];
    dispatchA<A>();
    // dispatchB<A>(A{});
}

住在https://godbolt.org/z/93h5T46oq

调度程序会在一个大循环中被多次调用,所以我需要确保我做对了。在我看来,这两个版本都不必要地复杂,因为函子的类型在编译时是已知的。DispatchA,因为它不必要地创建了一个 (constexpr) 对象。DispatchB,因为它一遍又一遍地传递对象。

当然,这些可以通过 a) 在函子中创建一个静态函数来解决,但是静态函数是不好的做法,对吧?b) 在调度程序中创建函子的静态实例,但随后对象的生命周期会增长到程序的生命周期。

话虽这么说,我不知道足够的组装来有意义地比较这两种方法。有没有更优雅/更有效的方法?

标签: c++templatesdispatcher

解决方案


假设A是无状态的,就像在您的示例中一样,并且没有非静态数据成员,它们是相同的。编译器足够聪明,可以看到对象的构造是空操作并忽略它。让我们稍微清理一下您的代码以获得干净的程序集,我们可以很容易地推断出:

struct A {
  double operator()(int) const noexcept;
};

void useDouble(double);
int genInt();

void dispatchA() {
  constexpr A op{};
  auto const range = genInt();
  for (int i = 0; i < range; i++) useDouble(op(genInt()));
}

void dispatchB(A op) {
  auto const range = genInt();
  for (int i = 0; i < range; i++) useDouble(op(genInt()));
}

在这里,输入来自哪里以及输出去哪里被抽象出来。生成的程序集仅因op对象的创建方式而异。用 GCC 11.1 编译它,我得到相同的程序集生成。不会发生任何创建或初始化A


推荐阅读