使用PyOpenCL将带有指针成员的struct传递给OpenCL内核

时间:2013-07-14 02:04:39

标签: python struct opencl memory-alignment pyopencl

假设我有一个内核来计算两个数组的元素和。我不是将a,b和c作为三个参数传递,而是按如下方式构造它们:

typedef struct
{
    __global uint *a;
    __global uint *b;
    __global uint *c;
} SumParameters;

__kernel void compute_sum(__global SumParameters *params)
{
    uint id = get_global_id(0);
    params->c[id] = params->a[id] + params->b[id];
    return;
}

如果您使用PyOpenCL [1]的RTFM,还有其他人已经解决了这个问题[2] [3] [4]。但是我找不到的OpenCL结构示例都没有指针作为成员。

具体来说,我担心主机/设备地址空间是否匹配,以及主机/设备指针大小是否匹配。有谁知道答案?

[1] http://documen.tician.de/pyopencl/howto.html#how-to-use-struct-types-with-pyopencl

[2] Struct Alignment with PyOpenCL

[3] http://enja.org/2011/03/30/adventures-in-opencl-part-3-constant-memory-structs/

[4] http://acooke.org/cute/Somesimple0.html

2 个答案:

答案 0 :(得分:2)

不,没有保证地址空间匹配。对于基本类型(float,int,...),你有对齐要求(标准的第6.1.5节),你必须使用OpenCL实现的cl_type名称(当用C语言编程时,pyopencl完成工作我的工作) '说)。

对于指针,由于这种不匹配,它甚至更简单。标准v 1.2的6.9节(版本1.1的第6.8节)的开头是:

  

在作为指针的程序中声明的内核函数的参数   必须使用__global,__ constant或__local限定符声明。

在p。点:

  

声明为struct或的内核函数的参数   union不允许将OpenCL对象作为元素传递   结构或联合。

还请注意d。点:

  

可变长度数组和具有灵活(或未确定)的结构   不支持数组。

所以,没有办法让你按照你的问题中的描述进行内核运行,这就是为什么你无法找到一些OpenCl结构的例子有指针作为成员。
我仍然可以提出一个利用内核在JIT中编译的事实的解决方法。它仍然需要您正确打包数据并注意对齐,最后在程序执行期间大小不会改变。老实说,我会选择一个内核,将3个缓冲区作为参数,但无论如何,它都是。

我们的想法是使用预处理器选项-D,如以下python中的示例所示:

内核:

typedef struct {
    uint a[SIZE];
    uint b[SIZE];
    uint c[SIZE];
} SumParameters;

kernel void foo(global SumParameters *params){
    int idx = get_global_id(0);
    params->c[idx] = params->a[idx] + params->b[idx];
}

主机代码:

import numpy as np
import pyopencl as cl

def bar():
   mf = cl.mem_flags
   ctx = cl.create_some_context()
   queue = cl.CommandQueue(self.ctx)
   prog_f = open('kernels.cl', 'r')
   #a = (1, 2, 3), b = (4, 5, 6)          
   ary = np.array([(1, 2, 3), (4, 5, 6), (0, 0, 0)], dtype='uint32, uint32, uint32')
   cl_ary = cl.Buffer(ctx, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=ary)
   #Here should compute the size, but hardcoded for the example
   size = 3
   #The important part follows using -D option
   prog = cl.Program(ctx, prog_f.read()).build(options="-D SIZE={0}".format(size))    
   prog.foo(queue, (size,), None, cl_ary)
   result = np.zeros_like(ary)
   cl.enqueue_copy(queue, result, cl_ary).wait()
   print result

结果:

[(1L, 2L, 3L) (4L, 5L, 6L) (5L, 7L, 9L)]

答案 1 :(得分:0)

我不知道我自己的问题的答案,但有三种解决方法,我可以想到我的头脑。我认为解决方法3是最佳选择。

解决方法1:这里我们只有3个参数,所以我们可以只生成a,b和c内核参数。但我已经读过你可以传递给内核的参数数量有限制,我个人喜欢重构任何需要超过3-4个参数的函数来使用结构(或者,在Python中,元组或关键字参数) 。所以这个解决方案使得代码更难阅读,并且不能扩展。

解决方法2:将所有内容转储到单个巨型阵列中。然后内核看起来像这样:

typedef struct
{
    uint ai;
    uint bi;
    uint ci;
} SumParameters;

__kernel void compute_sum(__global SumParameters *params, uint *data)
{
    uint id = get_global_id(0);
    data[params->ci + id] = data[params->ai + id] + data[params->bi + id];
    return;
}

换句话说,不使用指针,而是将偏移量用于单个数组。这看起来非常像实现我自己的内存模型的开始,感觉它正在重新发明存在于PyOpenCL或OpenCL或两者中的某个轮子。

解决方法3:制作setter内核。像这样:

__kernel void set_a(__global SumParameters *params, __global uint *a)
{
    params->a = a;
    return;
}

并且同上set_b,set_c。然后使用worksize 1执行这些内核以设置数据结构。你仍然需要知道为params分配的块有多大,但如果它太大,就不会发生任何不好的事情(除了一点浪费的内存),所以我只想假设指针是64位。

这种解决方法的性能可能很糟糕(我想一个内核调用有很大的开销),但幸运的是,对我的应用程序来说应该不会太重要(我的内核一次会运行几秒钟,它不是图形的东西必须以30-60 fps运行,所以我想,额外内核调用设置参数所花费的时间最终只占我工作量的一小部分,无论每个内核调用开销有多高。) / p>