【问题标题】:Recursion in CUDA returns illegal memory accessCUDA 中的递归返回非法内存访问
【发布时间】:2018-10-31 11:42:48
【问题描述】:

我正在编写一个数值积分程序,它实现了具有自适应步长的梯形规则。无需过多介绍细节,该算法使用递归来计算编码数学函数在具有指定相对容差的给定区间内的积分。 我已经简化了发布的代码,但保留了所有要点,因此某些部分可能看起来没有必要或过于复杂。这里是:

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cmath>
#include <iostream>
#include <iomanip>

class Integral {
public:
    double value;       // the value of the integral
    __device__ __host__ Integral() : value(0) {};
    __device__ __host__ Integral& operator+=(Integral &I);
};
__device__ Integral trapezoid(double a, double b, double tolerance, double fa, double fb);
__device__ __host__ double f(double x); // the integrand function

const int BLOCKS = 1;
const int THREADS = 1;

__global__ void controller(Integral *blockIntegrals, double a, double b, double tolerance) {
    extern __shared__ Integral threadIntegrals[]; // an array of thread-local integrals
    double fa = f(a), fb = f(b);
    threadIntegrals[threadIdx.x] += trapezoid(a, b, tolerance, fa, fb);
    blockIntegrals[blockIdx.x] += threadIntegrals[0];
}

int main() {
    // *************** Input parameters ***************
    double a = 1, b = 800;  // integration bounds
    double tolerance = 1e-7;
    // ************************************************

    cudaError cudaStatus;
    Integral blockIntegrals[BLOCKS]; // an array of total integrals computed by each block
    Integral *devBlockIntegrals;
    cudaStatus = cudaMalloc((void**)&devBlockIntegrals, BLOCKS * sizeof(Integral));
    if (cudaStatus != cudaSuccess)
        std::cout << "cudaMalloc failed!\n";

    double estimate = 0; // a rough 10-point estimate of the whole integral
    double h = (b - a) / 10;
    for (int i = 0; i < 10; i++)
        estimate += f(a + i*h);
    estimate *= h;
    tolerance *= estimate; // compute relative tolerance

    controller<<<BLOCKS, THREADS, THREADS*sizeof(Integral)>>>(devBlockIntegrals, a, b, tolerance);

    cudaStatus = cudaGetLastError();
    if (cudaStatus != cudaSuccess)
        std::cout << "addKernel launch failed: " << cudaGetErrorString(cudaStatus) << "\n";

    cudaStatus = cudaMemcpy(blockIntegrals, devBlockIntegrals, BLOCKS * sizeof(Integral), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess)
        std::cout << "cudaMemcpy failed: " << cudaGetErrorString(cudaStatus) << "\n";
    Integral result; // final result

    for (int i = 0; i < BLOCKS; i++) // final reduction that sums the results of all blocks
        result += blockIntegrals[i];

    std::cout << "Integral = " << std::setprecision(15) << result.value;
    cudaFree(devBlockIntegrals);
    getchar();
    return 0;
}

__device__  double f(double x) {
    return log(x);
}

__device__ Integral trapezoid(double a, double b, double tolerance, double fa, double fb) {
    double h = b - a;               // compute the new step
    double I1 = h*(fa + fb) / 2;    // compute the first integral
    double m = (a + b) / 2;         // find the middle point
    double fm = f(m);                       // function value at the middle point
    h = h / 2;                              // make step two times smaller
    double I2 = h*(0.5*fa + fm + 0.5*fb);   // compute the second integral
    Integral I;
    if (abs(I2 - I1) <= tolerance) {    // if tolerance is satisfied
        I.value = I2;
    }
    else {  // if tolerance is not satisfied
        if (tolerance > 1e-15) // check that we are not requiring too high precision
            tolerance /= 2; // request higher precision in every half
        I += trapezoid(a, m, tolerance, fa, fm);    // integrate the first half [a m]
        I += trapezoid(m, b, tolerance, fm, fb);    // integrate the second half [m b]
    }
    return I;
}

__device__ Integral& Integral::operator+=(Integral &I) {
    this->value += I.value;
    return *this;
}

为简单起见,我在这里只使用一个线程。 现在,如果我运行此代码,我会收到一条消息“cudaMemcpy 失败:遇到非法内存访问”。当我运行“cuda-memcheck”时出现此错误:

========= Invalid __local__ write of size 4
=========     at 0x00000b18 in C:/Users/User/Desktop/Integrator Stack/Integrator_GPU/kernel.cu:73:controller(Integral*, double, double, double)
=========     by thread (0,0,0) in block (0,0,0)
=========     Address 0x00fff8ac is out of bounds

它说问题出在第 73 行,它只是

double m = (a + b) / 2;

会不会是此时我的内存不足?

如果我通过将main 中的右边界从b = 800 更改为b = 700 来减小积分间隔,​​则程序运行良好并且给出正确的结果。 为什么我在创建新变量时会出现非法内存访问错误?

另外,我有一个相同这个程序的 CPU 版本,它运行完美,所以计算算法很可能是正确的。

【问题讨论】:

  • 您发布的代码不会(也不应该)编译。您显示的错误肯定不是由该代码产生的,所以我不确定您希望有人如何回答您的问题
  • 我也试过编译代码,还是不行。
  • 因为+= 的运算符重载签名是错误的。如果你认为我是从 Dropbox 链接匿名下载东西,那你就大错特错了.....
  • 我必须将const 添加到重载函数参数中才能编译。如前所述,您可能会用完堆栈空间。我的测试中的默认限制是每个线程 1024 字节。我不知道您正在执行多少递归或实际需要多少堆栈空间,但是当我将cudaSetLimit(cudaLimitStackSize, 32768ULL); 添加到代码的开头时,它运行时对我来说没有任何运行时错误。我不是说结果是正确的。您似乎正在使用未初始化的共享内存,并且您的代码中可能存在其他错误。
  • 我敢打赌,如果你仔细研究代码中的编译输出,你会看到这样的消息:ptxas warning : Stack size for entry function '_Z10controllerP8Integralddd' cannot be statically determined 这是一个线索,表明现在的负担可能在作为程序员,您要确保堆栈大小适合您的用例。

标签: c++ recursion cuda numerical-integration


【解决方案1】:

会不会是此时我的内存不足?

不完全是。我猜随着递归深度的增加,你的调用堆栈空间已经用完了。运行时为每个线程调用堆栈分配一个预设的默认值,通常约为 1kb(尽管它可能因硬件和 CUDA 版本而异)。如果该函数的递归深度超过约 16,我认为不会花费很长时间。

您可以使用cudaDeviceGetLimit 查询每个线程的确切堆栈大小,并使用cudaDeviceSetLimit 更改它,这可能使您的代码在大递归深度下正常工作。一般来说,CUDA 中的高度递归代码不是一个好主意,编译器和硬件使用约定循环会比深度递归代码做得更好。

【讨论】:

    猜你喜欢
    • 2015-06-28
    • 2018-12-27
    • 2020-12-27
    • 2015-07-23
    • 2021-03-25
    • 2015-05-02
    • 2022-01-18
    • 1970-01-01
    • 2021-08-12
    相关资源
    最近更新 更多