第24章 Workgroups - 工作组测试

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 核心要点

  1. 逻辑函数: work_group_all/any用于工作组内条件判断
  2. 广播函数: work_group_broadcast在工作组内共享数据
  3. 归约函数: work_group_reduce_add/min/max计算工作组聚合值
  4. 扫描函数: 包含式和排除式扫描,支持add/min/max操作
  5. 同步机制: 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的子组功能。


相关推荐
DeeplyMind2 天前
第27章 Device_partition - 设备分区测试
opencl·opencl-cts
晚晶1 个月前
【Linux】opencv4.9.0静态库编译,开启opencl和EIGEN矩阵运算
linux·c++·opencv·矩阵·opencl
Hi202402178 个月前
PoCL环境搭建
opencl
Hi202402171 年前
RK3588上CPU和GPU算力以及opencv resize的性能对比测试
linux·opencv·arm·gpu·opencl·算力测试·mali-gpu
安全二次方security²1 年前
基于RISC-V的开源通用GPU指令集架构--乘影OpenGPGPU
risc-v·opencl·gpgpu·乘影·向量扩展指令集·gpgpu微架构·开源通用gpu
千里马-horse1 年前
在OpenCL 中输出CLinfo信息
开发语言·c++·算法·opencl·1024程序员节
千里马-horse1 年前
OpenCL内存模型
opencl·内存模型
qianbo_insist2 年前
opencl色域变换,处理传递显存数据
c++·opencl
遍地是牛2 年前
GraphicsMagick 的 OpenCL 开发记录(二十五)
c++·windows·graphicsmagick·opencl·imagemagick