如何为多个内核使用模板化循环结构

时间:2019-02-17 16:21:44

标签: c++

是否存在一种优雅而有效的方法来为多个循环内核操作重用相同的循环结构,从而允许循环内核内联?

我有几个函数可以从大型3D网格上的某些选定点计算一些属性。它们通常遵循相同的模式:

  1. 确定要使用的网格
  2. 确定沿着每个方向搜索的最小/最大网格索引
  3. 在相关的网格索引上循环,即在i,j,k上的三个嵌套循环
  4. 计算网格点的坐标并检查其是否在所需区域内
  5. 如果网格点在区域中,请根据网格坐标和网格索引(i,j,k)计算一些属性

目前,我至少有4个遵循该模式的函数,基本上所有这些函数都只是复制粘贴了,而第(5)点已更改。

但是,我正在寻找一种更少重复代码的更优雅的解决方案。尽管可以通过将OOP解决方案与实现循环的基类方法一起使用并调用虚拟子类方法来做到这一点,或者编写一个包含指向执行步骤(5)的函数的指针的函数。但是,我担心从这个紧密循环中调用函数的开销,因此希望内联该操作。我也很好奇这个问题是否有解决方案。

这是我所拥有的简化示例(实际上,函数具有不同的返回类型并且更加复杂):

double calculateA(size_t N, double x0, double dx, double max_dist) {
    double a = 0.0;
    for (size_t i = 0; i < N; ++i) {
        const double x = i * dx;
        if (std::abs(x - x0) < max_dist) {
            a += doInlinedOperationA(x);
        }
    }

    return a;
}

double calculateB(size_t N, double x0, double dx, double max_dist) {
    double b = 0.0;
    for (size_t i = 0; i < N; ++i) {
        const double x = i * dx;
        if (std::abs(x - x0) < max_dist) {
            b += doInlinedOperationB(x);
        }
    }

    return b;
}

理想情况下,我只想在一个地方定义循环结构,这样就只需要在一个地方更改它,就可以减少错误的风险和需要编写的测试量。

编辑:顺便说一句,我只能使用C ++ 11,没有使用14/17功能或库的解决方案。

3 个答案:

答案 0 :(得分:3)

只需创建一个以函子作为模板参数的模板化函数,大多数编译器应内联作为参数传递的简单函数和lamda。例如:

firebase.firestore().runTransaction(...)

Clang和GCC内联包括#include <cmath> #include <iostream> double a( double d ) { return d * 2; } template <typename Kernel> double calculate(size_t N, double x0, double dx, double max_dist, const Kernel& kernel) { double b = 0.0; for (size_t i = 0; i < N; ++i) { const double x = i * dx; if (std::abs(x - x0) < max_dist) { b += kernel(x); } } return b; } int main() { std::cout << calculate(1, 2, 3, 4, a) << "\n"; std::cout << calculate(1, 2, 3, 4, [](double d){ return d*d; }) << "\n"; } 函数的所有内容:https://godbolt.org/z/pKRNBm

答案 1 :(得分:0)

您想要的是一个带有一个将调用内核的函数的模板:

>>> afiles_names = [x[0] for x in afiles]
>>> [x for x in bfiles if x not in afiles_names] # so you won't have to compute that each time
['file3', 'file4']

然后是template<typename Kernel> struct Loops { static void call(params) { for(int i = 0; i < loop1; ++i) { Kernel::kernel(params); } } };

Kernel

当我在3D地震应用程序(C ++ 03)上执行此操作时,struct Kernel { static void kernel(params); }; 可在不同的Kernel中使用,Loops也将在计算类型上模板化({{ 1}}或Loops),不同类型的循环将包含不同的缓存机制,循环遍历和并行性实现,并且float本身也被模板化以适应边界条件之类的特征。

选择实现是通过注册表机制完成的,该机制正在加载包含double个实例的插件。

答案 2 :(得分:0)

听起来基本上就像一个功能模板。编译器通常会将模板专业化视为单独的函数,可能会提供与复制和稍加修改的代码版本完全相同的性能。

您的示例可以写为:

template <typename Func>
double calculateOnGrid(Func&& func, size_t N, double x0, double dx, double max_dist) {
    double retval = 0.0;
    for (size_t i = 0; i < N; ++i) {
        const double x = i * dx;
        if (std::abs(x - x0) < max_dist) {
            retval += func(x);
        }
    }

    return retval;
}

然后您可以像使用它

// If "doInlinedOperationA" is a function name:
double a = calculateOnGrid(doInlinedOperationA, N, x0, dx, max_dist);

// Or using a lambda as the function:
double c = calculateOnGrid([](double x) { return std::exp(x); },
                           N, x0, dx, max_dist);