相关笔记

前置知识:

后续内容:

  • 暂无

关联Wiki:

概览

本节将经典的归并排序改造为多线程版本,通过 spawn/sync 机制并行化递归排序与合并操作。核心算法包括 P-MERGE-SORT(并行归并排序)和 P-MERGE(并行合并),其中 P-MERGE 利用二分搜索将合并操作分解为可并行执行的子问题。

核心要点:

  • P-MERGE-SORT 的 work 为 ,span 为 ,并行度为
  • P-MERGE 的 work 为 ,span 为
  • 通过 coarsening(粗化)优化,在子问题足够小时切换到串行算法,显著减少并行开销
  • 并行排序在比较模型下存在下界:使用 个处理器排序 个元素至少需要 时间

知识结构总览

flowchart TD
    A["多线程归并排序<br/>P-MERGE-SORT"] --> B["分治框架"]
    A --> C["并行合并<br/>P-MERGE"]
    A --> D["粗化优化<br/>Coarsening"]

    B --> B1["spawn 左半排序"]
    B --> B2["spawn 右半排序"]
    B --> B3["sync 等待完成"]
    B --> B4["P-MERGE 合并"]

    C --> C1["取较大子数组中间元素 x"]
    C --> C2["在较小子数组中二分搜索 x"]
    C --> C3["计算 x 在输出中的位置 q₃"]
    C --> C4["spawn 并行合并左半部分"]
    C --> C5["普通调用合并右半部分"]
    C --> C6["sync 等待完成"]

    D --> D1["子问题规模 < 阈值 k"]
    D --> D2["切换为串行归并排序"]
    D --> D3["减少 spawn/sync 开销"]

    E["复杂度分析"] --> E1["Work: Θ(n lg n)"]
    E --> E2["Span: Θ(lg² n)"]
    E --> E3["并行度: Θ(n / lg n)"]

核心思想

2.1 P-MERGE-SORT:多线程归并排序

算法执行流程

P-MERGE-SORT 将输入数组递归地分成两半,分别并行排序,最后并行合并两个有序子数组。

flowchart TD
    Start["P-MERGE-SORT(A, p, r)"] --> Check{"n = r - p + 1<br/>是否 ≤ 1?"}
    Check -->|是| Return["return"]
    Check -->|否| Mid["q = ⌊(p+r)/2⌋"]
    Mid --> SpawnL["spawn P-MERGE-SORT(A, p, q)"]
    Mid --> SpawnR["P-MERGE-SORT(A, q+1, r)"]
    SpawnL --> Sync["sync"]
    SpawnR --> Sync
    Sync --> Merge["P-MERGE(A, p, q, q+1, r, B, p)"]
    Merge --> Copy["将 B[p..r] 复制回 A[p..r]"]
    Copy --> End["return"]

P-MERGE-SORT

P-MERGE-SORT 是归并排序的多线程版本。它将数组 分成两半 ,使用 spawn 并行地对两半分别排序,然后调用 P-MERGE 将两个有序子数组合并到辅助数组 中,最后复制回

伪代码:

P-MERGE-SORT(A, p, r, B, s)
    n = r - p + 1
    if n == 1
        B[s] = A[p]
        return
    q = ⌊(p + r) / 2⌋
    q' = ⌊(s + r - p) / 2⌋
    spawn P-MERGE-SORT(A, p, q, B, s)
    P-MERGE-SORT(A, q + 1, r, B, q' + 1)
    sync
    P-MERGE(B, s, q', q' + 1, s + n - 1, A, p)

关键说明:

  • 第一个 spawn 使左半部分的排序与右半部分的排序并行执行
  • sync 确保两个排序都完成后才执行合并
  • P-MERGE 将结果从辅助数组 合并回 (注意这里合并方向与串行版本相反,交替使用 避免额外复制)

P-MERGE-SORT 的复杂度分析

假设 P-MERGE 的 work 为 ,span 为

Work 分析:

P-MERGE-SORT 的 work 满足递推关系:

【递推求解(work: ,由主定理情况2得 )】

这与串行归并排序的 work 相同,说明并行化没有增加总计算量。

Span 分析:

两个 spawn 的递归调用并行执行,因此 span 取两者中的较大值加上合并的 span:

【递推求解(span: ,展开 层得 )】

展开递推:

  • 第 0 层:
  • 第 1 层:
  • 层:

总 span =

并行度:


2.2 P-MERGE:并行合并(核心难点)

算法执行流程

P-MERGE 的核心思想是:从较大的有序子数组中取中间元素 ,在较小的子数组中二分搜索确定 的秩(rank),从而将合并问题分解为两个独立的子合并问题,并行求解。

flowchart TD
    Start["P-MERGE(T, p₁, r₁, p₂, r₂, A, p₃)"] --> Check{"n₁ = 0?"}
    Check -->|是| Return["return"]
    Check -->|否| Ensure{"n₁ < n₂?"}
    Ensure -->|是| Swap["交换使 T[p₁..r₁] 为较大子数组"]
    Ensure -->|否| Mid["q₁ = ⌊(r₁+p₁)/2⌋<br/>x = T[q₁]"]
    Swap --> Mid
    Mid --> Bisect["q₂ = BINARY-SEARCH(x, T, p₂, r₂)"]
    Bisect --> Pos["q₃ = p₃ + (q₁-p₁) + (q₂-p₂)"]
    Pos --> Place["A[q₃] = x"]
    Place --> SpawnL["spawn P-MERGE(T, p₁, q₁-1, p₂, q₂-1, A, p₃)"]
    Place --> CallR["P-MERGE(T, q₁+1, r₁, q₂, r₂, A, q₃+1)"]
    SpawnL --> Sync["sync"]
    CallR --> Sync
    Sync --> End["return"]

P-MERGE

P-MERGE 将两个有序子数组 合并到 。算法首先确保 是较大的子数组(或两者等大),然后取其中间元素 ,在 中二分搜索 的插入位置 ,将 放到输出数组的正确位置 ,最后 spawn 并行合并左半部分、普通调用合并右半部分。

伪代码:

P-MERGE(T, p₁, r₁, p₂, r₂, A, p₃)
    n₁ = r₁ - p₁ + 1
    n₂ = r₂ - p₂ + 1
    if n₁ == 0
        return
    if n₁ < n₂          // 确保第一个子数组较大
        exchange p₁ with p₂
        exchange r₁ with r₂
        exchange n₁ with n₂
    q₁ = ⌊(p₁ + r₁) / 2⌋
    q₂ = BINARY-SEARCH(T[q₁], T, p₂, r₂)
    q₃ = p₃ + (q₁ - p₁) + (q₂ - p₂)
    A[q₃] = T[q₁]
    spawn P-MERGE(T, p₁, q₁ - 1, p₂, q₂ - 1, A, p₃)
    P-MERGE(T, q₁ + 1, r₁, q₂, r₂, A, q₃ + 1)
    sync

其中 BINARY-SEARCH 的伪代码:

BINARY-SEARCH(x, T, p, r)
    low = p
    high = max(p, r + 1)    // 当 p > r 时,返回 p
    while low < high
        mid = ⌊(low + high) / 2⌋
        if x ≤ T[mid]
            high = mid
        else
            low = mid + 1
    return low

P-MERGE 执行示例

合并

第 1 层:

  • ,不需要交换
  • 中二分搜索 (5 应插入位置 6 之前,即索引 6)
  • spawn 合并
  • 普通调用合并

第 2 层(并行执行):

  • 左子问题:
  • 右子问题:,在 中搜索得
    • spawn 合并 (空)和
    • 普通调用合并

最终结果:

P-MERGE 的正确性

【正确性证明(关键不变式: 恰好是合并后数组的第 个元素)】

引理: P-MERGE 正确地将两个有序子数组合并为一个有序数组。

证明: 进行归纳。

  • 基础情况: 时,无需合并,直接返回。正确。
  • 归纳步骤: 假设对所有规模小于 的输入 P-MERGE 正确。考虑规模为 的输入。
    • 是较大子数组 的中间元素
    • 在较小子数组 中的插入位置,即 中所有元素 中所有元素
    • 因此,合并后数组中恰好有 个来自 的元素和 个来自 的元素小于
    • 在合并后数组中的位置为 ,正确
    • 左子问题合并 ,所有元素 ,规模
    • 右子问题合并 ,所有元素 ,规模
    • 由归纳假设,两个子问题都正确合并。加上 ,整体正确。

P-MERGE 的复杂度分析

Work 分析:

每次递归调用处理大约一半的元素( 被取出,剩余分成两部分),加上 的二分搜索开销:

【递推求解(work: ,由主定理情况1得 )】

验证:,取 即可满足。因此

Span 分析:

两个子问题并行执行,span 取较大值加上二分搜索的 span:

【递推求解(span: ,展开得 )】

展开递推:

  • 第 0 层:
  • 第 1 层:
  • 层:

总 span =


2.3 Coarsening(粗化优化)

Coarsening 的核心思想

当子数组规模足够小时,并行化的开销(spawn/sync 的线程管理代价)超过了并行执行带来的收益。此时应切换到串行算法,避免不必要的并行开销。

具体策略:

  • 设定一个阈值 (通常为几百到几千)
  • 时,P-MERGE-SORT 直接调用串行 MERGE-SORT
  • 时,P-MERGE 直接调用串行 MERGE

对复杂度的影响:

  • Work 不变,仍为
  • Span 在最底层 层后切换为串行,但渐近 span 不变
  • 实际运行时间显著降低,因为避免了大量细粒度的 spawn/sync 操作

Coarsening 的效果

假设 ,阈值

  • 不使用 coarsening:递归深度约 20 层,产生约 个 spawn 操作
  • 使用 coarsening:递归深度约 10 层(到 后串行),spawn 操作约
  • 线程管理开销减少约 1000 倍

补充理解与拓展

Cole 最优并行归并排序

来源:Richard Cole(1988),“Optimal Parallel Merge Sort”,SIAM Journal on Computing, 17(4), pp. 770-785 链接https://doi.org/10.1137/0217049

Cole 提出了一个在 CREW PRAM 模型上达到 时间、 总工作的最优并行归并排序算法。其核心创新是使用"overpick and underpick"技术,在每一步合并中通过巧妙的抽样策略,使得 轮合并即可完成排序。该算法的理论意义在于证明了并行归并排序可以达到最优 时间复杂度(匹配 AKS 排序网络的下界),但实际实现中常数因子较大,工程实用性不如本节介绍的 P-MERGE-SORT。

并行排序的下界

来源:Mikhail J. Atallah, S. R. Kosaraju(1984),“Tight Comparison Bounds on the Complexity of Parallel Sorting”,SIAM Journal on Computing, 13(3), pp. 588-600 链接https://doi.org/10.1137/0213037

比较模型下,使用 个处理器对 个元素排序,至少需要 时间。当 时,下界为 ;当 时,下界为 。本节的 P-MERGE-SORT 的 span 为 ,在 个处理器时接近最优。要达到 的 span,需要更复杂的算法(如 Cole 的算法)。

前缀和与并行排序的关系

来源:Guy E. Blelloch(1990),“Prefix Sums and Their Applications”,收录于 John H. Reif 编的 Synthesis of Parallel Algorithms, Morgan Kaufmann 链接https://www.cs.cmu.edu/~guyb/papers/Ble90.pdf

前缀和(Prefix Sums) 是并行计算中的基本原语,work 为 ,span 为 。许多并行排序算法(包括并行归并排序的某些变体)都依赖前缀和来高效计算元素位置。Blelloch 的工作系统性地展示了前缀和在并行算法设计中的核心地位,包括排序、扫描、压缩等应用。理解前缀和有助于深入掌握并行算法的设计范式。

Brent 定理与并行算法的实际性能

来源:Richard P. Brent(1974),“The Parallel Evaluation of General Arithmetic Expressions”,Journal of the ACM, 21(2), pp. 201-208 链接https://doi.org/10.1145/321812.321815

Brent 定理指出,任何 work 为 、span 为 的并行算法,在 个处理器上的运行时间 满足: 这意味着并行度 决定了算法能有效利用的最大处理器数。对于 P-MERGE-SORT,并行度为 ,因此使用超过 个处理器不会带来额外加速。Coarsening 的本质就是减少 中的常数因子,使实际 更接近下界

易混淆点与辨析

P-MERGE 中"较大子数组"的选择

P-MERGE 要求从较大的子数组中取中间元素 ,而非较小子数组。原因在于:如果从较小子数组中取中间元素,二分搜索在较大子数组中的开销为 为较大子数组长度),但递归分解后两个子问题的规模不平衡——一个子问题可能非常小而另一个仍然很大,导致 span 递推变为 ,远差于从较大子数组取中间元素时的

P-MERGE-SORT 的合并方向

注意伪代码中 P-MERGE-SORT 的合并方向:排序结果先写入辅助数组 ,然后 P-MERGE 将 中的两个有序段合并回 。这与串行归并排序(通常从辅助数组合并回原数组)的方向交替使用,目的是避免额外的数组复制操作。在递归的每一层, 的角色交替互换。

Span 分析中"并行取最大"的常见错误

在分析 P-MERGE 的 span 时,容易犯的错误是将递推写为 (与 work 分析混淆)。正确的做法是:由于 spawn 使两个子问题并行执行,span 只取其中一个分支的深度,即 。只有当两个操作必须串行执行时,span 才相加。

习题精选

题号题目描述难度考察重点
27.3-1解释如何对 P-MERGE 进行粗化★☆☆Coarsening 实践
27.3-2使用中位数查找的变体并行合并★★★P-MERGE 变体设计
27.3-3并行化 PARTITION 过程★★☆并行分区算法
27.3-4并行化 RECURSIVE-FFT★★☆并行 FFT
27.3-5并行化 RANDOMIZED-SELECT★★★并行选择算法

视频学习指南

资源讲者/来源时长覆盖内容推荐度
MIT 6.006 Lecture 12: Searching and SortingErik Demaine~80min归并排序基础★★★
MIT 6.046 Lecture 9: Multithreaded AlgorithmsErik Demaine~80min动态多线程、work/span★★★★
Parallel Merge Sort 可视化网络资源~15minP-MERGE 执行过程动画★★★

教材原文

算法导论(第4版)第26.3节

“We now examine how to multithread merge sort, a divide-and-conquer algorithm that we saw in Chapter 2. To multithread merge sort, we need to parallelize both the divide step and the combine step. We can parallelize the divide step trivially: just divide the array in half. The combine step, however, requires merging two sorted subarrays, which is more challenging to parallelize.”

“The key idea behind P-MERGE is to find the median element of the larger subarray and use binary search to find its position in the smaller subarray. This approach allows us to split the merging problem into two independent subproblems that can be solved in parallel.”

参见Wiki

第26章-并行算法 多线程归并排序