问题 如何将C ++向量传递给OpenCL内核?


我是C,C ++和OpenCL的新手,现在正尽力学习它们。这是一个预先存在的C ++函数,我试图找出如何使用C或C ++绑定移植到OpenCL。

#include <vector>

using namespace std;

class Test {

private:

    double a;
    vector<double> b;
    vector<long> c;
    vector<vector<double> > d;

public:

    double foo(long x, double y) {
        // mathematical operations
        // using x, y, a, b, c, d
        // and also b.size()
        // to calculate return value
        return 0.0;
    }

};

从广义上讲,我的问题是如何将此函数访问的所有类成员传入绑定和内核。我理解如何传递标量值,但我不确定矢量值。是否有一种方法可以将指针传递给上述每个成员或内存映射它们,以便OpenCL的视图与主机内存同步?细分我的问题如下。

  1. 如果这些是可变大小的,我如何将成员b和c传递给绑定和内核?
  2. 如果它是二维的,我如何传递成员?
  3. 如何从内核中访问这些成员,以及在内核的参数中将它们声明为什么类型?将简单地使用数组索引表示法,即b [0]用于访问?
  4. 我如何在内核函数中调用等效于b.size()的操作,或者我不会将绑定中的大小作为额外参数传递给内核?如果它改变会发生什么?

我非常感谢C或C ++绑定以及答案中的内核代码示例源代码。

非常感谢。


11321
2017-09-14 13:57


起源

using namespace std;  - 永远不要在标题中这样做。 - Ed S.
@EdS。为什么会这样? - dominicbri7
@ dominicbri7:因为您要为包含标题的每个人污染全局命名空间。也许我不想要 std 导入我的全局命名空间。也许这是有充分理由的。你不会为我做出选择。 - Ed S.


答案:


  1. 您必须分配OpenCL缓冲区并将CPU数据复制到其中。 OpenCL缓冲区具有固定大小,因此如果数据大小发生更改或者使其“足够大”,则必须重新创建它,如果需要更少的内存,则只使用它的子部分。例如,要为其创建缓冲区 b 并同时将其所有数据复制到设备:

    cl_mem buffer_b = clCreateBuffer(
        context, // OpenCL context
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, // Only read access from kernel,
                                                 // copy data from host
        sizeof(cl_double) * b.size(), // Buffer size in bytes
        &b[0], // Pointer to data to copy
        &errorcode); // Return code
    

    也可以直接映射主机内存(CL_MEM_USE_HOST_PTR),但这对创建缓冲区后的对齐和对主机内存的访问施加了一些限制。基本上,当您没有映射它时,主机内存可能包含垃圾。

  2. 这取决于。第二维中矢量的大小是否一致?然后在将它们上传到OpenCL设备时将它们展平。否则会变得更复杂。

  3. 您将缓冲区参数声明为 __global 你的内核中的指针。例如, __global double *b 适用于在1中创建的缓冲区。您可以在内核中使用数组表示法来访问缓冲区中的各个元素。

  4. 您无法从内核中查询缓冲区大小,因此必须手动传递它。这也可以隐含地发生,例如,如果工作项的数量与大小相匹配 b

可以访问计算所有数据的内核可能如下所示:

__kernel void foo(long x, double y, double a, __global double* b, int b_size,
                  __global long* c, __global double* d,
                  __global double* result) {
  // Here be dragons
  *result = 0.0;
}

请注意,您还必须为结果分配内存。如果需要,可能需要传递额外的大小参数。您可以按如下方式调用内核:

// Create/fill buffers
// ...

// Set arguments
clSetKernelArg(kernel, 0, sizeof(cl_long), &x);
clSetKernelArg(kernel, 1, sizeof(cl_double), &y);
clSetKernelArg(kernel, 2, sizeof(cl_double), &a);
clSetKernelArg(kernel, 3, sizeof(cl_mem), &b_buffer);
cl_int b_size = b.size();
clSetKernelArg(kernel, 4, sizeof(cl_int), &b_size);
clSetKernelArg(kernel, 5, sizeof(cl_mem), &c_buffer);
clSetKernelArg(kernel, 6, sizeof(cl_mem), &d_buffer);
clSetKernelArg(kernel, 7, sizeof(cl_mem), &result_buffer);
// Enqueue kernel
clEnqueueNDRangeKernel(queue, kernel, /* ... depends on your domain */);

// Read back result
cl_double result;
clEnqueueReadBuffer(queue, result_buffer, CL_TRUE, 0, sizeof(cl_double), &result,
                    0, NULL, NULL);

13
2017-09-14 18:40



非常感谢reima。这有很大帮助。两个问题:(1)我的原始数据全部都是C ++类型,如您所知。但是上面代码中的所有内存分配都在cl_types中。我理解为什么。但是,在一个测试程序中,我将两个长向量传递给一个内核,它将[0]的值加到b [1],这只适用于程序中所有类型都是cl_types,包括原始向量声明,这看起来很奇怪原始数据必须是C ++类型。我在这里想念的是什么? (2)如何将'result'用作上面的C ++类型? - junkie
你是对的,我对那里的类型有点草率。我的示例代码只能用于 cl_long 与...类型相同 long。如果它们不相同,则可能必须先执行转换步骤,然后才能将数据上载到设备。 cl_long 和 cl_double 与其他C ++类型一样,它们只是typedef。您可以使用 result 直接,因为它可能已经是一个 double。 - reima
谢谢。我可以确认在我的测试程序中,使用指向vector <long>数据的指针不起作用。那么可能需要转换(创建第二组向量/数组并复制到它中?)。将结果设置为长变量会在VS中发出警告,说“可能会丢失数据”。 cl_platform.h中的cl_long设置为 typedef signed __int64 cl_long; 而long是4个字节。那么也许没有办法尽可能长时间没有数据丢失? - junkie