我有一个opencl内核,可以在字符串中找到最大的ASCII字符。 问题是我无法将多个读写同步到全局和本地内存。 我试图通过将它与local_maximum进行比较来更新共享内存中的local_maximum字符,并在工作组(最后一个线程)的末尾更新global_maximum字符。我想,线程正在写一个在另一个上面。
例如:输入字符串:"加勒比海盗"。
输出字符串:' r' (但它应该是')。
请查看代码并提供解决方案,了解如何使所有内容保持同步。我相信拥有良好知识的人可以理解代码。欢迎优化提示。
代码如下:
__kernel void find_highest_ascii( __global const char* data, __global char* result, unsigned int size, __local char* localMaxC )
{
//creating variables and initialising..
unsigned int i, localSize, globalSize, j;
char privateMaxC,temp,temp1;
i = get_global_id(0);
localSize = get_local_size(0);
globalSize = get_global_size(0);
privateMaxC = '\0';
if(i<size){
if(i == 0)
read_mem_fence( CLK_LOCAL_MEM_FENCE );
*localMaxC = '\0';
mem_fence( CLK_LOCAL_MEM_FENCE);
////////////////////////////////////////////////////
/////UPDATING PRIVATE MAX CHARACTER/////////////////
////////////////////////////////////////////////////
for( j = i; j<size; j+=globalSize )
{
if( data[j] > privateMaxC )
{
privateMaxC = data[j];
}
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////
////UPDATING SHARED MAX CHARACTER//////////////////
///////////////////////////////////////////////////
temp = *localMaxC;
read_mem_fence( CLK_LOCAL_MEM_FENCE );
if(privateMaxC>temp)
{
*localMaxC = privateMaxC;
write_mem_fence( CLK_LOCAL_MEM_FENCE );
temp = privateMaxC;
}
//////////////////////////////////////////////////
//UPDATING GLOBAL MAX CHARACTER.
temp1 = *result;
if(( (i+1)%localSize == 0 || i==size-1) && (temp > temp1 ))
{
read_mem_fence( CLK_GLOBAL_MEM_FENCE );
*result = temp;
write_mem_fence( CLK_GLOBAL_MEM_FENCE );
}
}
}
答案 0 :(得分:1)
你是正确的,线程将覆盖彼此的值,因为你的代码充满了race conditions。在OpenCL中,无法在不同工作组中的工作项之间进行同步。您可以使用内置的原子函数,而不是尝试使用显式栅栏实现这种同步,而是使代码更多更简单。特别是,内置atomic_max
可以完美地解决您的问题。
因此,代替您当前必须更新本地和全局内存最大值的代码,只需执行以下操作:
kernel void ascii_max(global int *input, global int *output, int size,
local int *localMax)
{
int i = get_global_id(0);
int l = get_local_id(0);
// Private reduction
int privateMax = '\0';
for (int idx = i; idx < size; idx+=get_global_size(0))
{
privateMax = max(privateMax, input[idx]);
}
// Local reduction
atomic_max(localMax, privateMax);
barrier(CLK_LOCAL_MEM_FENCE);
// Global reduction
if (l == 0)
{
atomic_max(output, *localMax);
}
}
这将要求您更新本地内存暂存空间和最终结果以使用32位整数值,但总的来说,这是解决此问题的一种非常简洁的方法(更不用说它实际上有效)。
非原子解决方案
如果您真的不想使用原子,那么您可以使用本地内存和工作组障碍实现沼泽标准缩减。这是一个例子:
kernel void ascii_max(global int *input, global int *output, int size,
local int *localMax)
{
int i = get_global_id(0);
int l = get_local_id(0);
// Private reduction
int privateMax = '\0';
for (int idx = i; idx < size; idx+=get_global_size(0))
{
privateMax = max(privateMax, input[idx]);
}
// Local reduction
localMax[l] = privateMax;
for (int offset = get_local_size(0)/2; offset > 1; offset>>=1)
{
barrier(CLK_LOCAL_MEM_FENCE);
if (l < offset)
{
localMax[l] = max(localMax[l], localMax[l+offset]);
}
}
// Store work-group result in global memory
if (l == 0)
{
output[get_group_id(0)] = max(localMax[0], localMax[1]);
}
}
这使用本地内存作为临时空间一次比较元素对。每个工作组将生成一个结果,该结果存储在全局内存中。如果您的数据集很小,您可以使用单个工作组运行它(即使全局和本地大小相同),这将正常工作。如果它更大,你可以通过运行两次内核来运行两级减少,例如:
size_t N = ...; // something big
size_t local = 128;
size_t global = local*local; // Must result in at most 'local' number of work-groups
// First pass - run many work-groups using temporary buffer as output
clSetKernelArg(kernel, 1, sizeof(cl_mem), d_temp);
clEnqueueNDRangeKernel(..., &global, &local, ...);
// Second pass - run one work-group with temporary buffer as input
global = local;
clSetKernelArg(kernel, 0, sizeof(cl_mem), d_temp);
clSetKernelArg(kernel, 1, sizeof(cl_mem), d_output);
clEnqueueNDRangeKernel(..., &global, &local, ...);
我会留给你运行它们,并决定哪种方法最适合你自己的数据集。