Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

第十二章:归并

《Programming Massively Parallel Processors》第四版 - 学习笔记与练习

📚 学习内容

本章系统梳理归并(Merge)操作及其 CUDA 优化技术:

  • Co-rank 函数(二分搜索定位)
  • 顺序归并算法
  • 基础并行归并(全局内存)
  • 分块并行归并(共享内存优化)

相关博客笔记第十二章:归并


💻 代码实现

Exercise01 - 归并实现

实现多种归并 kernel,对应书中图12.9、12.11-12.13。

代码位置Exercise01/

实现列表

实现 书中对应 特点
merge_sequential - CPU参考实现
merge_basic_gpu 图12.9 基础并行,每线程独立 co-rank
merge_tiled_gpu 图12.11-12.13 分块优化,共享内存

核心代码

// Co-rank 函数核心思想
__host__ __device__ int co_rank(int k, float* A, int m, float* B, int n) {
    int i = min(k, m);
    int j = k - i;
    int i_low = max(0, k - n);
    int j_low = max(0, k - m);

    while (active) {
        if (i > 0 && j < n && A[i-1] > B[j]) {
            // i 太大,需要减少
            delta = cdiv(i - i_low, 2);
            i -= delta; j += delta;
        } else if (j > 0 && i < m && B[j-1] >= A[i]) {
            // i 太小,需要增加
            delta = cdiv(j - j_low, 2);
            i += delta; j -= delta;
        } else {
            break;  // 找到正确位置
        }
    }
    return i;
}

运行 Exercise01

cd Exercise01
make
make run

预期输出

================================================================
  第十二章:归并
  Merge Operations - Multiple Implementations
================================================================

配置:
  数组 A 长度: 10283
  数组 B 长度: 131131
  合并结果长度: 141414

=== 正确性验证 ===

1. CPU 顺序归并... ✅ 结果有序
2. 基础并行归并 (图12.9)... ✅ 结果正确!
3. 分块并行归并 (图12.11-12.13)... ✅ 结果正确!

📖 练习题解答

练习 1

题目: 假设需要合并两个列表 A=(1, 7, 8, 9, 10) 和 B=(7, 10, 10, 12)。C[8] 的协同排名(co-rank)值是多少?

解答:

首先得出合并结果:[1, 7, 7, 8, 9, 10, 10, 10, 12]

C[8] = 12,这是 B 中的最后一个元素。

执行 co_rank(8, A, 5, B, 4)

初始:
  i = min(8, 5) = 5
  j = k - i = 8 - 5 = 3
  i_low = max(0, 8-4) = 4
  j_low = max(0, 8-5) = 3

检查条件:
  i > 0 ✅ && j < n ✅ && A[4]=10 > B[3]=12 ❌  → 第一个 if 不满足
  j > 0 ✅ && i < m ❌ (5 < 5)                  → 第二个 if 不满足

结束循环,返回 i = 5

答案:i = 5, j = 3

这意味着 C[8:] 从 A[5:] 和 B[3:] 开始,即不取 A 的元素(已用完),只取 B[3] = 12。


练习 2

题目: 完成图12.6中线程2的协同排名函数计算。

Fig. 12.6

解答:

线程2从 k=6 开始,计算 co_rank(6, A, 5, B, 4)

初始:
  i = min(6, 5) = 5
  j = k - i = 6 - 5 = 1
  i_low = max(0, 6-4) = 2
  j_low = max(0, 6-5) = 1

检查条件:
  i > 0 ✅ && j < n ✅ && A[4]=10 > B[1]=10 ❌  → 不满足
  j > 0 ✅ && i < m ❌                          → 不满足

结束循环,返回 i = 5

答案:i = 5

这意味着 C[6:] 从 A 取0个元素(已用完),从 B[1:] 开始取3个元素。


练习 3

题目: 对于图12.12中加载 A 和 B 分块的 for 循环,添加 co_rank 函数调用,使得只加载当前 while 循环迭代中会被消耗的 A 和 B 元素。

解答:

while(counter < total_iteration){
    // 确定需要多少 A 元素才能合并出 tile_size 个结果
    int tileA = co_rank(tile_size, 
                        A + A_curr + A_consumed, A_length - A_consumed,
                        B + B_curr + B_consumed, B_length - B_consumed);
    int tileB = tile_size - tileA;

    // 只加载需要的 A 元素
    for(int i = 0; i < tileA; i += blockDim.x) {
        if (i + threadIdx.x < tileA) {
            A_S[i + threadIdx.x] = A[A_curr + A_consumed + i + threadIdx.x];
        }
    }

    // 只加载需要的 B 元素
    for(int i = 0; i < tileB; i += blockDim.x) {
        if(i + threadIdx.x < tileB) {
            B_S[i + threadIdx.x] = B[B_curr + B_consumed + i + threadIdx.x];
        }
    }
    __syncthreads();

优化效果:减少不必要的全局内存加载。


练习 4

题目: 考虑对两个大小为 1,030,400 和 608,000 的数组进行并行归并。假设每个线程归并8个元素,块大小为1024。

结果数组长度:1,030,400 + 608,000 = 1,638,400 元素

a. 基础归并 kernel(图12.9)中,有多少线程在全局内存上执行二分搜索?

每个线程归并8个元素,总共需要 1,638,400 / 8 = 204,800 个线程。

每个线程调用 co_rank 两次(起始和结束位置)。

答案:204,800 个线程执行全局内存二分搜索。

b. 分块归并 kernel(图12.11-12.13)中,有多少线程在全局内存上执行二分搜索?

每个块处理 8 × 1024 = 8,192 个元素。

块数量:1,638,400 / 8,192 = 200 个块。

每个块只有线程0执行全局内存 co_rank(两次),其他使用共享内存。

答案:200 个线程执行全局内存二分搜索。

c. 分块归并 kernel 中,有多少线程在共享内存上执行二分搜索?

每个块有 1024 个线程,每个线程执行共享内存 co_rank 三次(行41、43、50)。

200 个块 × 1024 线程 = 204,800 个线程执行共享内存二分搜索。


🔧 开发环境

  • CUDA Toolkit: 11.0 或更高版本
  • 编译器: GCC 7.5+ / Visual Studio 2019+ + NVCC
  • GPU: 支持 CUDA 的 NVIDIA 显卡(计算能力 3.5+)

💡 学习建议

  1. 理解 co-rank:归并的关键是找到正确的分割点
  2. 二分搜索:O(log n) 复杂度,理解边界条件
  3. 共享内存优化:Block 级别只需2次全局 co-rank
  4. 负载均衡:通过 co-rank 实现均匀的工作分配
  5. 性能调优实践:使用 Nsight Compute 分析内存访问模式,对于不同大小的输入数组,比较基础归并和分块归并的性能差异,理解何时共享内存优化最有效

🚀 下一步

完成本章学习后,继续学习:

  • 第十三章:排序
  • 第十四章:稀疏矩阵计算
  • 第十五章:图遍历

📚 参考资料

学习愉快! 🎓