Cuda归约运算(一)——分析cudasample的reduction1

何谓归约

归约(redution)是一类并行算法,对传入的O(N)个输入数据,使用一个二元的复合结合律的操作符,生成O(1)的结果。这类操作包括取最小、最大、求和、平方求和、逻辑与、逻辑或、向量点积。归约也是其他高级运算中要用的基础算法。
除非操作符的求解代价极高,否则归约倾向于带宽受限型任务。下面就从SDK提供的reduction例子入手,详细理解该归约算法。

概述

因为该二元操作符复合结合律,O(N)个用于计算归约结果的操作可以以任意顺序执行。
∑ i 8 a i = a 0 ⨁ a 1 ⨁ a 2 ⨁ a 3 ⨁ a 4 ⨁ a 5 ⨁ a 6 ⨁ a 7 \sum_{i}^8a_i = {a_0}\bigoplus{a_1}\bigoplus{a_2}\bigoplus{a_3}\bigoplus{a_4}\bigoplus{a_5}\bigoplus{a_6}\bigoplus{a_7} i8ai=a0a1a2a3a4a5a6a7

下面展示了一些处理8元素数组的不同方式。为了方便对比。下面先给出它的串型实现。只需要具备一个可以执行的 ⨁ \bigoplus 操作符的执行单元,但是这种方法的性能就比较差了,因为完成它需要7步运算。

  • 串行实现
    ( a 0 ⨁ ( a 1 ⨁ ( a 2 ⨁ ( a 3 ⨁ ( a 4 ⨁ ( a 5 ⨁ ( a 6 ⨁ ( a 7 ) ) ) ) ) ) ) ({a_0}\bigoplus({a_1}\bigoplus({a_2}\bigoplus({a_3}\bigoplus({a_4}\bigoplus({a_5}\bigoplus({a_6}\bigoplus({a_7}))))))) (a0(a1(a2(a3(a4(a5(a6(a7)))))))

  • 对数步长的归约
    ( ( ( a 0 ⨁ a 1 ) ⨁ ( a 2 ⨁ a 3 ) ) ⨁ ( ( a 4 ⨁ a 5 ) ⨁ ( a 6 ⨁ a 7 ) ) ) ((({a_0}\bigoplus{a_1}) \bigoplus({a_2}\bigoplus{a_3}))\bigoplus(({a_4}\bigoplus{a_5})\bigoplus({a_6}\bigoplus{a_7}))) (((a0a1)(a2a3))((a4a5)(a6a7)))

  • 对数步长的归约
    ( ( ( a 0 ⨁ a 1 ) ⨁ ( a 1 ⨁ a 5 ) ) ⨁ ( ( a 2 ⨁ a 6 ) ⨁ ( a 3 ⨁ a 7 ) ) ) ((({a_0}\bigoplus{a_1}) \bigoplus({a_1}\bigoplus{a_5}))\bigoplus(({a_2}\bigoplus{a_6})\bigoplus({a_3}\bigoplus{a_7}))) (((a0a1)(a1a5))((a2a6)(a3a7)))

成对的方式是最直观的,并且只需要*O(lgN)*步来计算结果,但它在cuda中的性能较差。当读取全局内存时,让单个线程访问相邻的内存单元会导致非合并的内存事物。当读取共享内存时,所示的模式会引起存储片的冲突。
不论对全局内存还是共享内存,基于交替的策略效果更好。对于全局内存,使用blockDim.x * gridDim.x的倍数作为交替因子有良好的性能,因为所有的内存事物将被合并,最好的性能是按照所确定的交替因子来累计部分和,以避免存储片冲突,并保持线程块的相邻线程处于活跃状态。
一旦一个线程块处理完其交替子数组,将结果写入全局内存以备它随后启动的内核进一步处理。启动多个内核可能看起来开销很大,但内核启动时异步的,因此当GPU正在执行一个第一个内核时,CPU可以请求下一个内核启动,每个内核启动都有机会来指定不同的启动配置。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数组归约是指将一个数组中的所有元素经过某种操作后,得到一个最终结果的过程。例如,将一个数组中的所有元素相加,就是一种数组归约操作。在CUDA中,可以使用reduce函数来实现数组归约。 示例代码如下: ```cuda #include <stdio.h> #define N 1024 __global__ void reduce(int *g_idata, int *g_odata) { extern __shared__ int sdata[]; // 每个线程加载一个元素到共享内存 unsigned int tid = threadIdx.x; unsigned int i = blockIdx.x * blockDim.x + threadIdx.x; sdata[tid] = g_idata[i]; __syncthreads(); // 归约操作 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) { if (tid < s) { sdata[tid] += sdata[tid + s]; } __syncthreads(); } // 将归约结果存储到全局内存中 if (tid == 0) { g_odata[blockIdx.x] = sdata[0]; } } int main(void) { int *a, *d_a, *d_b; int size = N * sizeof(int); // 分配内存空间 a = (int *)malloc(size); cudaMalloc((void **)&d_a, size); cudaMalloc((void **)&d_b, size); // 初始化数组 for (int i = 0; i < N; i++) { a[i] = i; } // 将数组复制到设备上 cudaMemcpy(d_a, a, size, cudaMemcpyHostToDevice); // 归约操作 int block_size = 512; reduce<<<(N + block_size - 1) / block_size, block_size, block_size * sizeof(int)>>>(d_a, d_b); // 将结果从设备上复制回主机内存 int result; cudaMemcpy(&result, d_b, sizeof(int), cudaMemcpyDeviceToHost); printf("sum: %d\n", result); // 释放内存空间 free(a); cudaFree(d_a); cudaFree(d_b); return 0; } ``` 在上面的示例代码中,首先定义了一个大小为N的整型数组a,然后将该数组复制到设备上。接着定义了一个reduce函数,该函数使用共享内存实现了数组归约操作。最后,在主函数中调用reduce函数进行归约操作,并将结果从设备上复制回主机内存。最终,输出结果即为数组中所有元素的和。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值