24.1 概述
工作组(Workgroups)是OpenCL并行执行模型的核心概念。工作组测试验证OpenCL 2.0引入的工作组集合函数(collective functions),这些函数允许工作组内的所有工作项协同完成操作,如归约、扫描和广播。
24.1.1 测试目录结构
test_conformance/workgroups/
├── main.cpp # 测试主入口
├── procs.h # 测试函数声明
├── testBase.h # 测试基类
├── test_wg_all.cpp # work_group_all测试
├── test_wg_any.cpp # work_group_any测试
├── test_wg_broadcast.cpp # work_group_broadcast测试
├── test_wg_scan_reduce.cpp # 扫描和归约测试
├── test_wg_suggested_local_work_size.cpp # 建议工作组大小测试
└── CMakeLists.txt # 构建配置
24.1.2 工作组集合函数
OpenCL 2.0引入的工作组集合函数包括:
1. 逻辑函数
work_group_all()- 所有工作项条件为真work_group_any()- 任一工作项条件为真
2. 广播函数
work_group_broadcast()- 将一个工作项的值广播到整个工作组
3. 归约函数
work_group_reduce_add()- 求和归约work_group_reduce_min()- 最小值归约work_group_reduce_max()- 最大值归约
4. 扫描函数
work_group_scan_inclusive_add()- 包含式加法扫描work_group_scan_exclusive_add()- 排除式加法扫描work_group_scan_inclusive_min/max()- 最小/最大值扫描work_group_scan_exclusive_min/max()- 排除式最小/最大值扫描
24.1.3 测试覆盖范围
| 测试类别 | 函数 | 维度 |
|---|---|---|
| 逻辑测试 | all, any | 1D |
| 广播测试 | broadcast | 1D, 2D, 3D |
| 归约测试 | reduce_add/min/max | 1D |
| 扫描测试 | scan_inclusive/exclusive | 1D |
| API测试 | suggested_local_work_size | 1D, 2D, 3D |
24.2 工作组逻辑函数
24.2.1 work_group_all() 函数
测试工作组内所有工作项的条件是否都为真:
c
int work_group_all(int predicate);
// 返回值:
// - 如果工作组内所有工作项的predicate都非零,返回1
// - 否则返回0
使用示例:
c
__kernel void test_all(__global int *input, __global int *output) {
int tid = get_global_id(0);
int lid = get_local_id(0);
// 测试: 所有工作项的输入是否都大于0
int predicate = (input[tid] > 0);
// 工作组内所有工作项协同判断
int result = work_group_all(predicate);
output[tid] = result;
// 同一工作组内的所有工作项得到相同的result值
}
测试场景:
c
// 场景1: 所有条件都为真
int input[] = {1, 2, 3, 4, 5, 6, 7, 8}; // 工作组大小=8
// 所有输入>0, work_group_all返回1
// output: {1, 1, 1, 1, 1, 1, 1, 1}
// 场景2: 存在条件为假
int input[] = {1, 2, -3, 4, 5, 6, 7, 8}; // 有负数
// 存在输入≤0, work_group_all返回0
// output: {0, 0, 0, 0, 0, 0, 0, 0}
// 场景3: 多个工作组
int input[] = {1,2,3,4, -1,-2,-3,-4}; // 两个工作组,各4个元素
// 第一个工作组: 全部>0, 返回1
// 第二个工作组: 全部≤0, 返回0
// output: {1, 1, 1, 1, 0, 0, 0, 0}
24.2.2 work_group_any() 函数
测试工作组内是否存在至少一个工作项的条件为真:
c
int work_group_any(int predicate);
// 返回值:
// - 如果工作组内至少一个工作项的predicate非零,返回1
// - 否则返回0
使用示例:
c
__kernel void test_any(__global int *input, __global int *output) {
int tid = get_global_id(0);
// 测试: 是否存在负数
int predicate = (input[tid] < 0);
// 工作组内只要有一个为真就返回1
int result = work_group_any(predicate);
output[tid] = result;
}
测试场景:
c
// 场景1: 没有条件为真
int input[] = {1, 2, 3, 4, 5, 6, 7, 8};
// 所有输入≥0, work_group_any返回0
// output: {0, 0, 0, 0, 0, 0, 0, 0}
// 场景2: 存在条件为真
int input[] = {1, 2, -3, 4, 5, 6, 7, 8};
// 存在负数, work_group_any返回1
// output: {1, 1, 1, 1, 1, 1, 1, 1}
// 场景3: 所有条件都为真
int input[] = {-1, -2, -3, -4, -5, -6, -7, -8};
// 全部为负数, work_group_any返回1
// output: {1, 1, 1, 1, 1, 1, 1, 1}
24.2.3 组合使用
c
__kernel void test_logic_combined(__global int *data,
__global int *all_positive,
__global int *any_negative) {
int tid = get_global_id(0);
int value = data[tid];
// 检查是否所有值都是正数
int is_positive = (value > 0);
all_positive[tid] = work_group_all(is_positive);
// 检查是否存在负数
int is_negative = (value < 0);
any_negative[tid] = work_group_any(is_negative);
// 工作组内所有工作项获得相同的结果
}
24.3 工作组广播函数
24.3.1 1D广播
将一个工作项的值广播到工作组内所有工作项:
c
gentype work_group_broadcast(gentype value, size_t local_id);
// value: 要广播的值
// local_id: 源工作项的本地ID
// 返回: local_id对应工作项的value值
使用示例:
c
__kernel void test_broadcast_1D(__global float *input,
__global float *output) {
int tid = get_global_id(0);
int lid = get_local_id(0);
// 每个工作项读取自己的输入
float my_value = input[tid];
// 广播第0个工作项的值到整个工作组
float broadcast_value = work_group_broadcast(my_value, 0);
output[tid] = broadcast_value;
// 工作组内所有工作项都得到input[group_start]的值
}
测试示例:
c
// 工作组大小: 4
// 输入: [10, 20, 30, 40, 50, 60, 70, 80]
// --------------- ---------------
// 工作组0 工作组1
// 广播local_id=0的值:
// 工作组0: 所有工作项得到10
// 工作组1: 所有工作项得到50
// 输出: [10, 10, 10, 10, 50, 50, 50, 50]
// 广播local_id=2的值:
// 工作组0: 所有工作项得到30
// 工作组1: 所有工作项得到70
// 输出: [30, 30, 30, 30, 70, 70, 70, 70]
24.3.2 2D广播
c
gentype work_group_broadcast(gentype value, size_t local_id_x, size_t local_id_y);
// 广播2D坐标(local_id_x, local_id_y)处工作项的值
使用示例:
c
__kernel void test_broadcast_2D(__global float *input,
__global float *output) {
size_t tid_x = get_global_id(0);
size_t tid_y = get_global_id(1);
size_t lid_x = get_local_id(0);
size_t lid_y = get_local_id(1);
size_t index = tid_y * get_global_size(0) + tid_x;
float my_value = input[index];
// 广播本地坐标(0, 0)的值
float broadcast_value = work_group_broadcast(my_value, 0, 0);
output[index] = broadcast_value;
}
24.3.3 3D广播
c
gentype work_group_broadcast(gentype value,
size_t local_id_x,
size_t local_id_y,
size_t local_id_z);
// 广播3D坐标处工作项的值
使用示例:
c
__kernel void test_broadcast_3D(__global float *input,
__global float *output) {
size_t tid_x = get_global_id(0);
size_t tid_y = get_global_id(1);
size_t tid_z = get_global_id(2);
size_t index = (tid_z * get_global_size(1) * get_global_size(0))
+ (tid_y * get_global_size(0))
+ tid_x;
float my_value = input[index];
// 广播本地坐标(1, 1, 1)的值
float broadcast_value = work_group_broadcast(my_value, 1, 1, 1);
output[index] = broadcast_value;
}
24.4 工作组归约函数
24.4.1 加法归约
对工作组内所有工作项的值求和:
c
gentype work_group_reduce_add(gentype value);
// 返回: 工作组内所有工作项value的总和
// 支持类型: int, uint, long, ulong, float, double
使用示例:
c
__kernel void test_reduce_add(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int my_value = input[tid];
// 工作组内求和
int sum = work_group_reduce_add(my_value);
output[tid] = sum;
// 工作组内所有工作项得到相同的sum值
}
测试示例:
c
// 工作组大小: 4
// 输入: [1, 2, 3, 4, 5, 6, 7, 8]
// ---------- ----------
// 工作组0 工作组1
// 工作组0: 1+2+3+4 = 10
// 工作组1: 5+6+7+8 = 26
// 输出: [10, 10, 10, 10, 26, 26, 26, 26]
24.4.2 最小值归约
c
gentype work_group_reduce_min(gentype value);
// 返回: 工作组内所有工作项value的最小值
使用示例:
c
__kernel void test_reduce_min(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int my_value = input[tid];
// 工作组内求最小值
int min_value = work_group_reduce_min(my_value);
output[tid] = min_value;
}
测试示例:
c
// 输入: [8, 3, 6, 1, 9, 2, 7, 4]
// ---------- ----------
// 工作组0 工作组1
// 工作组0: min(8,3,6,1) = 1
// 工作组1: min(9,2,7,4) = 2
// 输出: [1, 1, 1, 1, 2, 2, 2, 2]
24.4.3 最大值归约
c
gentype work_group_reduce_max(gentype value);
// 返回: 工作组内所有工作项value的最大值
使用示例:
c
__kernel void test_reduce_max(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int my_value = input[tid];
// 工作组内求最大值
int max_value = work_group_reduce_max(my_value);
output[tid] = max_value;
}
测试示例:
c
// 输入: [8, 3, 6, 1, 9, 2, 7, 4]
// ---------- ----------
// 工作组0 工作组1
// 工作组0: max(8,3,6,1) = 8
// 工作组1: max(9,2,7,4) = 9
// 输出: [8, 8, 8, 8, 9, 9, 9, 9]
24.4.4 应用场景
c
// 计算每个工作组的统计信息
__kernel void compute_stats(__global float *data,
__global float *sum,
__global float *min,
__global float *max) {
int tid = get_global_id(0);
int gid = get_group_id(0);
float value = data[tid];
// 归约操作
float group_sum = work_group_reduce_add(value);
float group_min = work_group_reduce_min(value);
float group_max = work_group_reduce_max(value);
// 第一个工作项写入结果
if (get_local_id(0) == 0) {
sum[gid] = group_sum;
min[gid] = group_min;
max[gid] = group_max;
}
}
24.5 工作组扫描函数
24.5.1 包含式扫描 (Inclusive Scan)
包含式扫描将当前元素包含在结果中:
c
gentype work_group_scan_inclusive_add(gentype value);
// 对于工作项i,返回: value[0] + value[1] + ... + value[i]
使用示例:
c
__kernel void test_scan_inclusive_add(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int my_value = input[tid];
// 包含式加法扫描
int scan_result = work_group_scan_inclusive_add(my_value);
output[tid] = scan_result;
}
测试示例:
c
// 输入: [1, 2, 3, 4, 5, 6, 7, 8]
// ---------- ----------
// 工作组0 工作组1
// 工作组0 (包含式加法扫描):
// 工作项0: 1
// 工作项1: 1+2 = 3
// 工作项2: 1+2+3 = 6
// 工作项3: 1+2+3+4 = 10
// 工作组1:
// 工作项0: 5
// 工作项1: 5+6 = 11
// 工作项2: 5+6+7 = 18
// 工作项3: 5+6+7+8 = 26
// 输出: [1, 3, 6, 10, 5, 11, 18, 26]
24.5.2 排除式扫描 (Exclusive Scan)
排除式扫描不包含当前元素:
c
gentype work_group_scan_exclusive_add(gentype value);
// 对于工作项i,返回: value[0] + value[1] + ... + value[i-1]
// 工作项0返回单位元(0 for add)
使用示例:
c
__kernel void test_scan_exclusive_add(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int my_value = input[tid];
// 排除式加法扫描
int scan_result = work_group_scan_exclusive_add(my_value);
output[tid] = scan_result;
}
测试示例:
c
// 输入: [1, 2, 3, 4, 5, 6, 7, 8]
// ---------- ----------
// 工作组0 工作组1
// 工作组0 (排除式加法扫描):
// 工作项0: 0 (单位元)
// 工作项1: 1
// 工作项2: 1+2 = 3
// 工作项3: 1+2+3 = 6
// 工作组1:
// 工作项0: 0
// 工作项1: 5
// 工作项2: 5+6 = 11
// 工作项3: 5+6+7 = 18
// 输出: [0, 1, 3, 6, 0, 5, 11, 18]
24.5.3 最小值/最大值扫描
c
// 包含式最小值扫描
gentype work_group_scan_inclusive_min(gentype value);
// 排除式最小值扫描
gentype work_group_scan_exclusive_min(gentype value);
// 包含式最大值扫描
gentype work_group_scan_inclusive_max(gentype value);
// 排除式最大值扫描
gentype work_group_scan_exclusive_max(gentype value);
测试示例:
c
// 包含式最小值扫描
// 输入: [8, 3, 6, 1]
// 输出: [8, 3, 3, 1] // min(8), min(8,3), min(8,3,6), min(8,3,6,1)
// 排除式最小值扫描
// 输入: [8, 3, 6, 1]
// 输出: [INT_MAX, 8, 3, 3] // identity, min(8), min(8,3), min(8,3,6)
// 包含式最大值扫描
// 输入: [3, 8, 1, 6]
// 输出: [3, 8, 8, 8] // max(3), max(3,8), max(3,8,1), max(3,8,1,6)
24.5.4 前缀和应用
c
// 使用扫描计算数组前缀和
__kernel void parallel_prefix_sum(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int lid = get_local_id(0);
int gid = get_group_id(0);
int value = input[tid];
// 工作组内包含式扫描
int local_sum = work_group_scan_inclusive_add(value);
output[tid] = local_sum;
}
24.6 barrier与work_group_barrier
24.6.1 传统barrier
OpenCL 1.x的barrier函数:
c
void barrier(cl_mem_fence_flags flags);
// flags:
// CLK_LOCAL_MEM_FENCE - 同步local内存
// CLK_GLOBAL_MEM_FENCE - 同步global内存
使用示例:
c
__kernel void use_barrier(__global int *data, __local int *scratch) {
int tid = get_global_id(0);
int lid = get_local_id(0);
// 从global加载到local
scratch[lid] = data[tid];
// 等待所有工作项完成加载
barrier(CLK_LOCAL_MEM_FENCE);
// 现在可以安全访问scratch的任何位置
int sum = 0;
for (int i = 0; i < get_local_size(0); i++) {
sum += scratch[i];
}
data[tid] = sum;
}
24.6.2 work_group_barrier (OpenCL 2.0)
OpenCL 2.0引入的新barrier:
c
void work_group_barrier(cl_mem_fence_flags flags);
void work_group_barrier(cl_mem_fence_flags flags, memory_scope scope);
// scope:
// memory_scope_work_item
// memory_scope_work_group
// memory_scope_device
// memory_scope_all_svm_devices
使用示例:
c
__kernel void use_work_group_barrier(__global int *data,
__local int *scratch) {
int tid = get_global_id(0);
int lid = get_local_id(0);
scratch[lid] = data[tid];
// OpenCL 2.0 barrier
work_group_barrier(CLK_LOCAL_MEM_FENCE, memory_scope_work_group);
// 处理数据
int result = scratch[(lid + 1) % get_local_size(0)];
data[tid] = result;
}
24.7 建议的工作组大小
24.7.1 查询建议的工作组大小
OpenCL提供API查询内核的建议工作组大小:
c
cl_int clGetKernelWorkGroupInfo(
cl_kernel kernel,
cl_device_id device,
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret);
24.7.2 1D建议大小测试
c
// 测试1D工作组大小建议
void test_1D_work_group_size(cl_kernel kernel, cl_device_id device) {
size_t preferred_multiple;
clGetKernelWorkGroupInfo(kernel, device,
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
sizeof(size_t), &preferred_multiple, NULL);
printf("Preferred work-group size multiple: %zu\n", preferred_multiple);
// 使用建议的倍数
size_t global_size = 1024;
size_t local_size = preferred_multiple;
clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
&global_size, &local_size,
0, NULL, NULL);
}
24.7.3 2D建议大小测试
c
// 测试2D工作组大小
void test_2D_work_group_size(cl_kernel kernel, cl_device_id device) {
size_t max_work_group_size;
clGetKernelWorkGroupInfo(kernel, device,
CL_KERNEL_WORK_GROUP_SIZE,
sizeof(size_t), &max_work_group_size, NULL);
// 选择合适的2D工作组大小
size_t local_size[2] = {16, 16}; // 256个工作项
if (local_size[0] * local_size[1] > max_work_group_size) {
// 调整大小
local_size[0] = 8;
local_size[1] = 8;
}
size_t global_size[2] = {1024, 1024};
clEnqueueNDRangeKernel(queue, kernel, 2, NULL,
global_size, local_size,
0, NULL, NULL);
}
24.7.4 3D建议大小测试
c
// 测试3D工作组大小
void test_3D_work_group_size(cl_kernel kernel, cl_device_id device) {
size_t max_work_item_sizes[3];
clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_ITEM_SIZES,
sizeof(max_work_item_sizes), max_work_item_sizes, NULL);
// 选择3D工作组大小
size_t local_size[3] = {4, 4, 4}; // 64个工作项
// 确保不超过设备限制
for (int i = 0; i < 3; i++) {
if (local_size[i] > max_work_item_sizes[i]) {
local_size[i] = max_work_item_sizes[i];
}
}
size_t global_size[3] = {64, 64, 64};
clEnqueueNDRangeKernel(queue, kernel, 3, NULL,
global_size, local_size,
0, NULL, NULL);
}
24.8 实际应用场景
24.8.1 并行归约求和
c
// 使用work_group_reduce_add计算数组总和
__kernel void parallel_sum(__global float *input,
__global float *partial_sums,
int n) {
int tid = get_global_id(0);
int gid = get_group_id(0);
float value = (tid < n) ? input[tid] : 0.0f;
// 工作组内归约
float group_sum = work_group_reduce_add(value);
// 第一个工作项写入部分和
if (get_local_id(0) == 0) {
partial_sums[gid] = group_sum;
}
}
24.8.2 直方图计算
c
// 使用原子操作和工作组同步计算直方图
__kernel void histogram(__global uchar *image,
__global uint *hist,
__local uint *local_hist,
int width, int height) {
int tid = get_global_id(0);
int lid = get_local_id(0);
int lsize = get_local_size(0);
// 初始化local直方图
for (int i = lid; i < 256; i += lsize) {
local_hist[i] = 0;
}
work_group_barrier(CLK_LOCAL_MEM_FENCE);
// 统计像素值
if (tid < width * height) {
uchar pixel = image[tid];
atomic_inc(&local_hist[pixel]);
}
work_group_barrier(CLK_LOCAL_MEM_FENCE);
// 合并到全局直方图
for (int i = lid; i < 256; i += lsize) {
atomic_add(&hist[i], local_hist[i]);
}
}
24.8.3 前缀和应用
c
// 使用扫描实现流压缩(Stream Compaction)
__kernel void stream_compact(__global int *input,
__global int *output,
__global int *predicate,
__global int *count,
int n) {
int tid = get_global_id(0);
int lid = get_local_id(0);
int pred = (tid < n && predicate[tid]) ? 1 : 0;
// 排除式扫描计算输出位置
int output_pos = work_group_scan_exclusive_add(pred);
// 写入满足条件的元素
if (tid < n && pred) {
output[output_pos] = input[tid];
}
// 最后一个工作项记录总数
if (lid == get_local_size(0) - 1) {
int total = output_pos + pred;
atomic_add(count, total);
}
}
24.8.4 矩阵转置
c
// 使用local内存和barrier进行矩阵转置
__kernel void transpose(__global float *input,
__global float *output,
__local float *tile,
int width, int height) {
int gx = get_global_id(0);
int gy = get_global_id(1);
int lx = get_local_id(0);
int ly = get_local_id(1);
// 加载tile到local内存
if (gx < width && gy < height) {
tile[ly * get_local_size(0) + lx] = input[gy * width + gx];
}
work_group_barrier(CLK_LOCAL_MEM_FENCE);
// 转置写回(交换坐标)
int tx = get_group_id(1) * get_local_size(1) + lx;
int ty = get_group_id(0) * get_local_size(0) + ly;
if (tx < height && ty < width) {
output[ty * height + tx] = tile[lx * get_local_size(1) + ly];
}
}
24.9 性能优化考虑
24.9.1 选择合适的工作组大小
c
// ❌ 不好的做法: 固定工作组大小
size_t local_size = 256; // 可能不适合所有设备
// ✅ 好的做法: 查询设备能力
size_t max_work_group_size;
clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE,
sizeof(size_t), &max_work_group_size, NULL);
size_t preferred_multiple;
clGetKernelWorkGroupInfo(kernel, device,
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
sizeof(size_t), &preferred_multiple, NULL);
// 选择合适的大小
size_t local_size = preferred_multiple;
while (local_size * 2 <= max_work_group_size) {
local_size *= 2;
}
24.9.2 减少barrier使用
c
// ❌ 过多的barrier
__kernel void bad_example(__local int *data) {
int lid = get_local_id(0);
data[lid] = lid;
barrier(CLK_LOCAL_MEM_FENCE); // barrier 1
data[lid] += 1;
barrier(CLK_LOCAL_MEM_FENCE); // barrier 2 (可能不必要)
data[lid] *= 2;
barrier(CLK_LOCAL_MEM_FENCE); // barrier 3 (可能不必要)
}
// ✅ 只在必要时使用barrier
__kernel void good_example(__local int *data) {
int lid = get_local_id(0);
data[lid] = lid;
barrier(CLK_LOCAL_MEM_FENCE);
// 后续操作不需要barrier(没有跨工作项依赖)
data[lid] = data[lid] + 1;
data[lid] = data[lid] * 2;
}
24.9.3 利用工作组集合函数
c
// ❌ 手动实现归约(效率低)
__kernel void manual_reduce(__global int *input,
__global int *output,
__local int *scratch) {
int tid = get_global_id(0);
int lid = get_local_id(0);
int lsize = get_local_size(0);
scratch[lid] = input[tid];
barrier(CLK_LOCAL_MEM_FENCE);
// 手动归约(多次barrier)
for (int offset = lsize / 2; offset > 0; offset /= 2) {
if (lid < offset) {
scratch[lid] += scratch[lid + offset];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if (lid == 0) {
output[get_group_id(0)] = scratch[0];
}
}
// ✅ 使用工作组集合函数(更快)
__kernel void wg_reduce(__global int *input,
__global int *output) {
int tid = get_global_id(0);
int value = input[tid];
// 单个函数调用完成归约
int sum = work_group_reduce_add(value);
if (get_local_id(0) == 0) {
output[get_group_id(0)] = sum;
}
}
24.10 小结
工作组测试验证OpenCL 2.0的工作组集合函数,这些函数简化了并行算法的实现并提高了性能。
24.10.1 核心要点
- 逻辑函数: work_group_all/any用于工作组内条件判断
- 广播函数: work_group_broadcast在工作组内共享数据
- 归约函数: work_group_reduce_add/min/max计算工作组聚合值
- 扫描函数: 包含式和排除式扫描,支持add/min/max操作
- 同步机制: work_group_barrier替代传统barrier
24.10.2 测试覆盖总结
| 函数类别 | 函数数量 | 支持维度 |
|---|---|---|
| 逻辑 | 2 | 1D |
| 广播 | 1 (3个重载) | 1D, 2D, 3D |
| 归约 | 3 | 1D |
| 扫描 | 6 | 1D |
24.10.3 实际应用
- ✅ 并行归约(求和、最值)
- ✅ 前缀和算法
- ✅ 流压缩
- ✅ 直方图统计
- ✅ 矩阵运算
24.10.4 性能优势
- ⚡ 硬件加速的集合操作
- ⚡ 减少显式同步
- ⚡ 优化的实现
- ⚡ 更简洁的代码
24.10.5 注意事项
- ⚠️ OpenCL 2.0+才支持
- ⚠️ 工作组内所有工作项必须参与
- ⚠️ 选择合适的工作组大小
- ⚠️ OpenCL 3.0中是可选特性
24.10.6 下一步
完成工作组测试后,下一章将介绍第25章 - Subgroups(子组测试),测试OpenCL的子组功能。