算法导论第十九章 并行算法:解锁计算新维度

第十九章 并行算法:解锁计算新维度

“并行计算不是未来,而是现在。” —— David Patterson

在单核性能增长放缓的时代,并行算法成为突破计算极限的关键。本章将带你探索多核处理器、分布式系统和GPU加速的奇妙世界,揭示如何通过协同计算解决传统串行算法难以企及的大规模问题。

19.1 并行计算革命

19.1.1 从单核到并行的演变

摩尔定律的变迁

  • 1986-2004:时钟频率每年增长40%
  • 2004至今:核心数量每18-24个月翻倍
单核时代
频率提升
功耗墙
多核时代
众核架构
异构计算

19.1.2 并行计算模型对比

模型代表技术通信方式适用场景
共享内存OpenMP内存共享单机多核
消息传递MPI网络通信超级计算机
SIMDGPU数据并行图形/科学计算
数据流TensorFlow数据依赖机器学习

19.2 并行算法设计模式

19.2.1 分治并行模式

问题
分解
子问题1
子问题2
并行解决
并行解决
合并
结果

19.2.2 流水线并行模式

// 图像处理流水线示例
#pragma omp parallel sections
{
    #pragma omp section
    stage1_process(image);  // 阶段1:去噪
    
    #pragma omp section
    stage2_process(image);  // 阶段2:边缘检测
    
    #pragma omp section
    stage3_process(image);  // 阶段3:特征提取
}

19.2.3 MapReduce模式

// 词频统计MapReduce伪代码
void map(string document) {
    for each word in document:
        emit(word, 1);
}

void reduce(string word, list<int> counts) {
    int sum = 0;
    for each count in counts:
        sum += count;
    emit(word, sum);
}

19.3 共享内存并行:OpenMP

19.3.1 OpenMP核心指令

#include <omp.h>
#include <stdio.h>

int main() {
    int n = 1000000;
    double sum = 0.0;
    
    // 并行区域
    #pragma omp parallel for reduction(+:sum)
    for (int i = 0; i < n; i++) {
        double term = (i % 2 == 0) ? 1.0 : -1.0;
        term /= 2 * i + 1;
        sum += term;
    }
    
    double pi = 4 * sum;
    printf("π ≈ %.15f\n", pi);
    return 0;
}

19.3.2 性能优化技巧

  1. 循环调度策略

    • schedule(static):均匀划分
    • schedule(dynamic):动态分配
    • schedule(guided):大块优先
  2. 数据局部性优化

// 缓存友好访问
#pragma omp parallel for collapse(2)
for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        // 连续内存访问
        A[i][j] = B[i][j] * C[j][i]; 
    }
}

19.4 分布式并行:MPI

19.4.1 MPI通信模式

点对点通信
集体通信
同步栅栏
进程0
进程1
进程2
所有进程
进程3
所有进程

19.4.2 矩阵乘法MPI实现

#include <mpi.h>
#include <stdio.h>

#define N 1024  // 矩阵大小

int main(int argc, char** argv) {
    int rank, size;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    // 计算每个进程负责的行块
    int rows_per_proc = N / size;
    double *A_local = malloc(rows_per_proc * N * sizeof(double));
    double *B = malloc(N * N * sizeof(double));
    double *C_local = malloc(rows_per_proc * N * sizeof(double));

    // 主进程初始化矩阵
    if (rank == 0) {
        // 初始化A和B矩阵...
    }

    // 分发数据
    MPI_Scatter(A, rows_per_proc * N, MPI_DOUBLE, 
               A_local, rows_per_proc * N, MPI_DOUBLE, 
               0, MPI_COMM_WORLD);
    MPI_Bcast(B, N * N, MPI_DOUBLE, 0, MPI_COMM_WORLD);

    // 本地矩阵乘法
    for (int i = 0; i < rows_per_proc; i++) {
        for (int j = 0; j < N; j++) {
            C_local[i * N + j] = 0;
            for (int k = 0; k < N; k++) {
                C_local[i * N + j] += A_local[i * N + k] * B[k * N + j];
            }
        }
    }

    // 收集结果
    MPI_Gather(C_local, rows_per_proc * N, MPI_DOUBLE, 
              C, rows_per_proc * N, MPI_DOUBLE, 
              0, MPI_COMM_WORLD);

    // 清理资源
    free(A_local);
    free(B);
    free(C_local);
    
    MPI_Finalize();
    return 0;
}

19.4.3 MPI通信优化

通信模式函数适用场景
点对点MPI_Send/Recv不规则数据交换
集合通信MPI_Bcast广播数据
MPI_Reduce全局归约
MPI_Alltoall全交换
非阻塞通信MPI_Isend/Irecv重叠计算和通信

19.5 GPU并行计算:CUDA

19.5.1 CUDA编程模型

数据传输
主机Host
设备Device
网格Grid
线程块Block
线程Thread

19.5.2 CUDA向量加法

#include <stdio.h>
#include <cuda_runtime.h>

// CUDA核函数
__global__ void vectorAdd(int *a, int *b, int *c, int n) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if (tid < n) {
        c[tid] = a[tid] + b[tid];
    }
}

int main() {
    int n = 1 << 20; // 1M元素
    size_t size = n * sizeof(int);
    
    // 主机内存
    int *h_a = malloc(size);
    int *h_b = malloc(size);
    int *h_c = malloc(size);
    
    // 设备内存
    int *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, size);
    cudaMalloc(&d_b, size);
    cudaMalloc(&d_c, size);
    
    // 初始化数据
    for (int i = 0; i < n; i++) {
        h_a[i] = i;
        h_b[i] = i * 2;
    }
    
    // 数据复制到设备
    cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);
    
    // 启动核函数
    int threadsPerBlock = 256;
    int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
    vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, n);
    
    // 结果复制回主机
    cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);
    
    // 验证结果
    for (int i = 0; i < n; i++) {
        if (h_c[i] != h_a[i] + h_b[i]) {
            printf("Error at index %d\n", i);
            break;
        }
    }
    
    // 清理资源
    free(h_a); free(h_b); free(h_c);
    cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);
    
    return 0;
}

19.5.3 CUDA性能优化

  1. 内存层次优化

    • 全局内存 → 共享内存 → 寄存器
    • 合并内存访问
  2. 执行配置优化

dim3 blocks(256, 1, 1);
dim3 grid((width+255)/256, (height+63)/64, 1);
kernel<<<grid, blocks>>>(...);
  1. 异步执行
cudaMemcpyAsync(d_data, h_data, size, cudaMemcpyHostToDevice, stream);
kernel<<<grid, block, 0, stream>>>(...);
cudaMemcpyAsync(h_result, d_result, size, cudaMemcpyDeviceToHost, stream);

19.6 并行图算法

19.6.1 并行BFS实现

void parallel_bfs(Graph *g, int start) {
    int *visited = calloc(g->nvertices, sizeof(int));
    int *current_frontier = malloc(g->nvertices * sizeof(int));
    int *next_frontier = malloc(g->nvertices * sizeof(int));
    
    int current_size = 1;
    current_frontier[0] = start;
    visited[start] = 1;
    
    while (current_size > 0) {
        int next_size = 0;
        
        #pragma omp parallel for schedule(dynamic, 64)
        for (int i = 0; i < current_size; i++) {
            int node = current_frontier[i];
            
            for (int j = 0; j < g->degree[node]; j++) {
                int neighbor = g->edges[node][j];
                
                if (!__sync_fetch_and_or(&visited[neighbor], 1)) {
                    int pos;
                    #pragma omp atomic capture
                    pos = next_size++;
                    next_frontier[pos] = neighbor;
                }
            }
        }
        
        // 交换边界
        int *temp = current_frontier;
        current_frontier = next_frontier;
        next_frontier = temp;
        current_size = next_size;
    }
    
    free(visited);
    free(current_frontier);
    free(next_frontier);
}

19.6.2 并行PageRank算法

void parallel_pagerank(Graph *g, double damping, double epsilon) {
    int n = g->nvertices;
    double *rank = malloc(n * sizeof(double));
    double *new_rank = malloc(n * sizeof(double));
    
    // 初始化
    #pragma omp parallel for
    for (int i = 0; i < n; i++) {
        rank[i] = 1.0 / n;
    }
    
    double diff;
    int iter = 0;
    
    do {
        diff = 0;
        #pragma omp parallel for
        for (int i = 0; i < n; i++) {
            new_rank[i] = (1 - damping) / n;
        }
        
        // 传播排名
        #pragma omp parallel for reduction(+:diff)
        for (int i = 0; i < n; i++) {
            if (g->degree[i] > 0) {
                double contribution = damping * rank[i] / g->degree[i];
                
                for (int j = 0; j < g->degree[i]; j++) {
                    int neighbor = g->edges[i][j];
                    #pragma omp atomic
                    new_rank[neighbor] += contribution;
                }
            }
        }
        
        // 计算差异
        #pragma omp parallel for reduction(+:diff)
        for (int i = 0; i < n; i++) {
            diff += fabs(new_rank[i] - rank[i]);
            rank[i] = new_rank[i];
        }
        
        printf("迭代 %d, 差异: %f\n", ++iter, diff);
    } while (diff > epsilon);
    
    free(rank);
    free(new_rank);
}

19.7 并行排序算法

19.7.1 并行快速排序

void parallel_quicksort(int *arr, int left, int right) {
    if (right - left < 1000) {
        qsort(arr + left, right - left + 1, sizeof(int), compare);
        return;
    }
    
    int pivot = partition(arr, left, right);
    
    #pragma omp task shared(arr)
    parallel_quicksort(arr, left, pivot - 1);
    
    #pragma omp task shared(arr)
    parallel_quicksort(arr, pivot + 1, right);
    
    #pragma omp taskwait
}

// 调用方式
#pragma omp parallel
{
    #pragma omp single
    parallel_quicksort(arr, 0, n-1);
}

19.7.2 并行归并排序

void parallel_mergesort(int *arr, int l, int r) {
    if (l < r) {
        int m = l + (r - l) / 2;
        
        #pragma omp task if(r-l > 100000)
        parallel_mergesort(arr, l, m);
        
        #pragma omp task if(r-l > 100000)
        parallel_mergesort(arr, m+1, r);
        
        #pragma omp taskwait
        merge(arr, l, m, r);
    }
}

19.7.3 排序算法性能对比

算法时间复杂度并行效率适用数据规模
快速排序O(n log n)中小规模
归并排序O(n log n)大规模
基数排序O(nk)中等整数数据
样本排序O(n log n)超大规模

19.8 并行性能分析

19.8.1 加速比定律

  1. Amdahl定律
    S=1(1−P)+PNS = \frac{1}{(1 - P) + \frac{P}{N}}S=(1P)+NP1

  2. Gustafson定律
    S=N+(1−N)(1−P)S = N + (1 - N)(1 - P)S=N+(1N)(1P)

问题规模
串行比例P
Amdahl定律
并行比例1-P
Gustafson定律

19.8.2 性能分析工具

  1. 时间线分析
$ nsys profile ./parallel_program
  1. 性能计数器
#include <papi.h>

int events[2] = {PAPI_TOT_CYC, PAPI_TOT_INS};
long long values[2];

PAPI_start_counters(events, 2);

// 并行区域...

PAPI_stop_counters(values, 2);
double cpi = (double)values[0] / values[1];

19.9 实际应用案例

19.9.1 气候模拟(MPI)

void climate_simulation() {
    // 初始化MPI
    MPI_Init(...);
    
    // 划分全球网格
    int local_rows = global_rows / size;
    Grid *local_grid = create_grid(local_rows, COLS);
    
    while (time < MAX_TIME) {
        // 边界交换
        exchange_boundaries(local_grid);
        
        // 本地物理计算
        compute_physics(local_grid);
        
        // 全局同步
        MPI_Barrier(MPI_COMM_WORLD);
        
        time += TIME_STEP;
    }
    
    MPI_Finalize();
}

19.9.2 深度学习训练(GPU)

void train_neural_network() {
    // 初始化模型和数据
    Model *model = create_model();
    DataBatch *batch = next_batch();
    
    while (!converged) {
        // 前向传播
        forward_propagate<<<blocks, threads>>>(model, batch);
        
        // 损失计算
        compute_loss<<<blocks, threads>>>(model, batch);
        
        // 反向传播
        backward_propagate<<<blocks, threads>>>(model, batch);
        
        // 参数更新
        update_parameters<<<blocks, threads>>>(model);
        
        batch = next_batch();
    }
}

19.9.3 实时渲染(多核+GPU)

void render_frame() {
    // CPU任务:场景管理
    #pragma omp parallel sections
    {
        #pragma omp section
        update_physics();
        
        #pragma omp section
        process_ai();
        
        #pragma omp section
        handle_input();
    }
    
    // GPU任务:渲染
    render_scene<<<grid, block>>>(scene);
    
    // 显示结果
    display_frame();
}

19.10 并行计算挑战与未来

19.10.1 当前挑战

  1. 负载均衡:动态任务分配
  2. 通信开销:减少数据传输
  3. 并发错误:数据竞争、死锁
  4. 编程复杂性:学习曲线陡峭

19.10.2 未来趋势

  1. 异构计算:CPU+GPU+FPGA协同
  2. 量子并行:量子算法加速
  3. 神经形态计算:模拟大脑并行处理
  4. 自动并行化:编译器智能优化
并行计算未来
更多核心
更智能运行时
新型硬件架构
自动并行化

19.11 总结与下一章预告

并行算法将计算从线性推向多维空间:

  1. 模型多样:共享内存、消息传递、SIMD架构
  2. 框架成熟:OpenMP、MPI、CUDA各司其职
  3. 算法丰富:并行排序、图算法、科学计算
  4. 应用广泛:从科学模拟到深度学习

下一章预告:近似算法

第二十章我们将探索近似算法的智慧:

  • NP难问题的实用解决方案
  • 近似比与性能保证分析
  • 经典问题:旅行商问题、顶点覆盖、背包问题
  • 随机化近似方法
  • 近似方案:PTAS与FPTAS

当精确解难以获得时,近似算法提供了在可接受时间内获得高质量解的优雅途径,平衡计算效率与结果质量。

并行计算不仅是技术演进的结果,更是解决人类面临复杂问题的关键。从天气预报到基因测序,从深度学习到宇宙模拟,并行算法正在扩展人类认知的边界,开启计算科学的新纪元。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W说编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值