我正在尝试实现here所述的边界框计算。长话短说,我有一个二叉树的边界框。叶节点全部填入,现在是时候计算内部节点了。除了节点(每个节点定义子/父索引)之外,每个内部节点都有一个计数器。
从每个叶节点开始,访问父节点并且其标志以原子方式递增。如果这是第一次访问节点,则线程退出(因为只保证一个子节点已初始化)。如果是第二次访问,则初始化两个子项,计算其边界框,并继续该节点的父项。
读取标志和阅读其子项数据之间的mem_fence
是否足以保证子项中的数据可见?
kernel void internalBounds(global struct Bound * const bounds,
global unsigned int * const flags,
const global struct Node * const nodes) {
const unsigned int n = get_global_size(0);
const size_t D = 3;
const size_t leaf_start = n - 1;
size_t node_idx = leaf_start + get_global_id(0);
do {
node_idx = nodes[node_idx].parent;
write_mem_fence(CLK_GLOBAL_MEM_FENCE);
// Mark node as visited, both children initialized on second visit
if (atomic_inc(&flags[node_idx]) < 1)
break;
read_mem_fence(CLK_GLOBAL_MEM_FENCE);
const global unsigned int * child_idxs = nodes[node_idx].internal.children;
for (size_t d = 0; d < D; d++) {
bounds[node_idx].min[d] = min(bounds[child_idxs[0]].min[d],
bounds[child_idxs[1]].min[d]);
bounds[node_idx].max[d] = max(bounds[child_idxs[0]].max[d],
bounds[child_idxs[1]].max[d]);
}
} while (node_idx != 0);
}
我仅限于OpenCL 1.2。
答案 0 :(得分:1)
不,它没有。 CLK_GLOBAL_MEM_FENCE仅在访问全局内存时提供工作组内的一致性。 OpenCL 1.x
中没有工作组间同步尝试使用单个大型工作组并迭代数据。和/或从一些适合单个工作组的小树开始。
答案 1 :(得分:1)
https://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/mem_fence.html
mem_fence(...)
仅对单个工作项同步mem-accessses。即使所有工作项都有这条线,它们也可能不会同时击中(并继续)它。
barrier(...)
会对工作组中的所有工作项进行同步,并让它们等待最慢的工作项(即访问作为参数指定的指定内存),但只连接到自己的工作组工作项。(例如,对于amd-intel只有64或256,对于nvidia可能只有1024)因为opencl设备驱动程序实现可能被设计为在加载波前的新分片之前完成所有波前,因为所有全局项都不适合芯片内存(例如64M)每个工作项使用1kB本地内存,需要64GB内存! - &gt;甚至软件仿真都需要数百或数千次通过,并将性能降低到单核cpu的水平)
无法实现全局同步(所有工作组同步)。
以防工作项工作组和处理元素具有混合含义, OpenCL: Work items, Processing elements, NDRange
您放置的原子功能已经在访问全局内存,因此添加组范围同步并不重要。
如果
,还要检查机器代码bounds[child_idxs[0]].min[d]
在访问bounds[child_idxs[0]]
之前,将整个min[d]
结构体转换为私有内存。如果是,您可以将min作为独立阵列分开访问其项目,以便为其提供100%以上的内存带宽。
测试英特尔高清400,超过100000个线程
__kernel void fenceTest( __global float *c,
__global int *ctr)
{
int id=get_global_id(0);
if(id<128000)
for(int i=0;i<20000;i++)
{
c[id]+=ctr[0];
mem_fence(CLK_GLOBAL_MEM_FENCE);
}
ctr[0]++;
}
2900ms(c数组有垃圾)
__kernel void fenceTest( __global float *c,
__global int *ctr)
{
int id=get_global_id(0);
if(id<128000)
for(int i=0;i<20000;i++)
{
c[id]+=ctr[0];
}
ctr[0]++;
}
500 ms(c数组有垃圾)。 500ms是围栏版本性能的约6倍(我的笔记本电脑有单通道4GB内存,只有5-10 GB / s,但其igpu本地内存有近38GB / s(每个周期64B,频率600 MHz))。本地围栏版本需要700毫秒,因此无栅栏版本甚至不会触及缓存或本地内存进行一些迭代。
没有循环,需要8到9毫秒,所以我没想在这些内核中优化循环。
编辑:
int id=get_global_id(0);
if(id==0)
{
atom_inc(&ctr[0]);
mem_fence(CLK_GLOBAL_MEM_FENCE);
}
mem_fence(CLK_GLOBAL_MEM_FENCE);
c[id]+=ctr[0];
完全符合
int id=get_global_id(0);
if(id==0)
{
ctr[0]++;
mem_fence(CLK_GLOBAL_MEM_FENCE);
}
mem_fence(CLK_GLOBAL_MEM_FENCE);
c[id]+=ctr[0];
对于这个英特尔igpu设备(只是偶然,但它证明了“所有”尾随线程可以看到更改的内存,但并不能证明它总是发生(例如第一个计算单元打嗝和第二次启动)和它不是单个线程访问它的原子。)