hpc-lab-code/lab4/experiment_data/实验分析报告.md
2026-01-22 04:31:52 +08:00

11 KiB
Raw Permalink Blame History

CUDA矩阵乘法性能实验分析报告

实验环境

  • GPU: NVIDIA GeForce RTX 3090 (详见gpu_info.txt)
  • CUDA版本: 根据代码推断为CUDA 11.x或更高版本
  • CPU: 多核处理器(支持OpenMP)

实验一CPU、CUDA Kernel1、CUDA Kernel2性能对比

1.1 实验数据汇总表

表1-1不同实现方式的执行时间对比单位ms

矩阵规模 CPU(8线程) CPU(64线程) CPU(256线程) CUDA Kernel1 CUDA Kernel2
512×512 747.483 743.606 748.649 0.316 0.827
1024×1024 6033.205 6049.318 6051.757 2.374 6.484
2048×2048 51065.609 50995.406 51083.363 19.190 53.599
4096×4096 - - - 152.897 433.242

表1-2不同实现方式的性能对比GFLOPS

矩阵规模 CPU(8线程) CPU(64线程) CPU(256线程) CUDA Kernel1 CUDA Kernel2
512×512 0.36 0.36 0.36 849.49 324.65
1024×1024 0.36 0.35 0.35 904.75 331.22
2048×2048 0.34 0.34 0.34 895.23 320.52
4096×4096 - - - 898.90 317.23

表1-3GPU相对于CPU(8线程)的加速比

矩阵规模 CUDA Kernel1加速比 CUDA Kernel2加速比
512×512 2365.45倍 903.85倍
1024×1024 2541.37倍 930.48倍
2048×2048 2661.05倍 952.73倍

1.2 详细分析

1.2.1 CPU性能分析

关键发现:

  1. 小矩阵规模(256×256)的可扩展性

    • 8线程: 86.012ms, 0.39 GFLOPS
    • 64线程: 78.420ms, 0.43 GFLOPS (加速比1.14)
    • 256线程: 76.496ms, 0.44 GFLOPS (加速比1.28)
    • 结论: 小矩阵可以放入CPU缓存多线程扩展性较好
  2. 中大矩阵规模的性能瓶颈

    • 从512×512开始增加线程数几乎无性能提升
    • 所有线程配置的性能都在0.34-0.36 GFLOPS
    • 原因: 受限于内存带宽,而非计算能力
  3. 性能天花板

    • CPU最高性能仅0.44 GFLOPS
    • 远低于GPU的300-900 GFLOPS
    • 根本原因: CPU的并行度有限内存带宽远低于GPU

1.2.2 CUDA Kernel1性能分析

关键特点:

  1. 稳定的性能表现

    • 所有矩阵规模下性能稳定在850-905 GFLOPS
    • 不随矩阵规模变化而明显波动
    • 原因: 简单的线程映射,良好的内存合并访问
  2. 巨大的性能优势

    • 相比CPU(8线程)实现2000-2700倍加速比
    • 相比CPU(256线程)实现2000-2700倍加速比
    • 核心优势: GPU的大规模并行计算能力
  3. 设计优势

    • 每个线程计算一个结果元素,逻辑简单
    • 全局内存访问模式良好,支持合并访问
    • 无同步开销,执行效率高
  4. 设计劣势

    • 每个线程需要重复访问全局内存
    • 没有数据重用,内存带宽利用率低
    • 优化空间: 可以通过共享内存提升性能

1.2.3 CUDA Kernel2性能分析

意外发现:

  1. 性能反而下降

    • 性能稳定在317-331 GFLOPS
    • 相比Kernel1性能下降约2.7-2.8倍
    • 教训: 盲目优化可能适得其反
  2. 性能下降的根本原因

    a) TILE_WIDTH=4太小

    • 共享内存的开销大于收益
    • 每个tile只有16个元素数据重用率低
    • 频繁的tile加载增加了全局内存访问

    b) 同步开销

    • 每个tile需要两次__syncthreads()
    • 对于小矩阵,同步开销占比很高
    • 线程块内同步会阻塞所有线程

    c) 共享内存利用率低

    • 4×4的tile太小无法充分利用共享内存带宽
    • 现代GPU的共享内存设计用于更大的数据块
    • Bank conflicts可能进一步降低性能
  3. 设计问题

    • 过早优化:在没有充分理解硬件特性的情况下使用共享内存
    • Tile size选择不当4×4对于现代GPU来说太小
    • 忽略了同步开销小tile导致同步频率过高

1.2.4 综合对比分析

性能排名(从高到低):

  1. CUDA Kernel1: ~900 GFLOPS
  2. CUDA Kernel2: ~325 GFLOPS
  3. CPU (任何线程数): ~0.36 GFLOPS

关键结论:

  1. GPU的绝对优势: 即使是最简单的GPU实现也比CPU快2000-2700倍
  2. 优化需谨慎: 设计不当的"优化"反而会降低性能
  3. 简单往往更好: Kernel1的简单设计优于Kernel2的复杂设计
  4. 硬件理解很重要: 必须根据GPU架构特性选择优化策略

实验二BLOCK_SIZE对CUDA程序性能的影响

2.1 实验数据汇总表

表2-1不同BLOCK_SIZE下的执行时间单位ms

矩阵规模 4×4 8×8 16×16 32×32
256×256 0.116 0.040 0.029 0.026
512×512 0.831 0.265 0.189 0.178
1024×1024 6.539 2.022 1.397 1.364
2048×2048 54.023 16.080 11.454 11.019

表2-2不同BLOCK_SIZE下的性能GFLOPS

矩阵规模 4×4 8×8 16×16 32×32
256×256 289.26 838.19 1170.29 1292.94
512×512 323.04 1014.10 1423.49 1506.57
1024×1024 328.40 1061.88 1536.94 1574.44
2048×2048 318.01 1068.38 1499.84 1559.16

表2-3相对于4×4的加速比

矩阵规模 8×8加速比 16×16加速比 32×32加速比
256×256 2.90倍 4.00倍 4.46倍
512×512 3.14倍 4.40倍 4.67倍
1024×1024 3.23倍 4.68倍 4.79倍
2048×2048 3.36倍 4.72倍 4.90倍

2.2 详细分析

2.2.1 BLOCK_SIZE对性能的影响规律

性能提升趋势:

  1. 4×4 → 8×8: 性能提升约3倍289→838 GFLOPS
  2. 8×8 → 16×16: 性能提升约1.5倍838→1423 GFLOPS
  3. 16×16 → 32×32: 性能提升约1.05倍1423→1574 GFLOPS

关键发现:

  • 性能提升幅度递减,呈现边际效应递减规律
  • 32×32接近性能饱和点
  • 不同矩阵规模下规律一致

2.2.2 性能提升的深层原因分析

1. 共享内存利用率提升

数据重用率分析:

  • 4×4 tile: 每个元素被重用4次
  • 16×16 tile: 每个元素被重用16次
  • 32×32 tile: 每个元素被重用32次

全局内存访问减少:

全局内存访问次数 ∝ 矩阵大小 / TILE_SIZE
  • TILE_SIZE越大全局内存访问次数越少
  • 减少全局内存访问是性能提升的关键

2. 线程级并行提升

线程块大小对比:

  • 4×4: 每个block只有16个线程
  • 16×16: 每个block有256个线程
  • 32×32: 每个block有1024个线程

延迟隐藏效果:

  • 更多的线程可以更好地隐藏内存延迟
  • GPU的warp scheduler有更多调度选择
  • 提高了SM的利用率

3. 计算与内存访问平衡

计算强度分析:

  • 小tile: 内存访问时间 > 计算时间(内存受限)
  • 大tile: 计算时间 ≈ 内存访问时间(平衡)
  • 最优tile: 计算与内存访问充分重叠

指令级并行:

  • 大tile提供了更多的独立计算
  • 编译器和硬件可以更好地优化指令调度
  • 提高了流水线效率

2.2.3 性能饱和现象分析

从16×16到32×32性能提升有限的原因

1. 共享内存容量限制

  • 每个SM的共享内存有限如64KB
  • 32×32的tile已经占用较多共享内存
  • 进一步增大tile会减少并发block数量

2. 寄存器压力

  • 更大的tile需要更多寄存器存储累加器
  • 寄存器使用过多可能导致spilling
  • Spilling会将数据溢出到本地内存严重降低性能

3. 线程块调度效率

  • 过大的block会减少SM上驻留的block数量
  • 降低了线程级并行度
  • 可能导致SM资源利用率下降

4. 内存带宽饱和

  • 当计算强度达到一定水平后
  • 性能瓶颈转移到共享内存带宽
  • 进一步增大tile无法提升性能

2.2.4 最优BLOCK_SIZE选择策略

针对当前GPU架构RTX 3090

  • 最优选择: 32×32
  • 性能: 1506-1574 GFLOPS
  • 相比4×4提升: 4.5-4.9倍

通用选择原则:

  1. 考虑GPU架构

    • 不同架构有不同的最优值
    • 需要查阅GPU架构文档
    • 可以通过实验确定
  2. 考虑问题规模

    • 小矩阵可能不适合大tile
    • 需要平衡tile大小和矩阵规模
    • 边界处理会增加复杂度
  3. 资源平衡

    • 共享内存使用
    • 寄存器使用
    • 线程块调度
  4. 性能调优方法

    • 使用CUDA性能分析工具nvprof, Nsight
    • 监控共享内存使用率
    • 监控寄存器使用情况
    • 测试多个tile size选择最优

2.2.5 与Kernel1的对比

性能对比:

  • Kernel1 (无共享内存): ~900 GFLOPS
  • Kernel2 (32×32共享内存): ~1574 GFLOPS
  • 性能提升: 1.75倍

关键结论:

  1. 正确的共享内存优化非常有效

    • 从900提升到1574 GFLOPS
    • 提升幅度达75%
  2. Tile size是关键

    • 4×4: 性能差323 GFLOPS
    • 32×32: 性能优1574 GFLOPS
    • 相差近5倍
  3. 优化需要系统性思考

    • 不能盲目使用共享内存
    • 必须选择合适的tile size
    • 需要考虑硬件特性

总体结论与建议

3.1 主要发现

  1. GPU相比CPU有压倒性优势

    • 性能提升2000-2700倍
    • 对于计算密集型任务GPU是必然选择
  2. 优化策略的重要性

    • 简单实现(Kernel1)已经很好
    • 正确优化(Kernel2+32×32)可以再提升75%
    • 错误优化(Kernel2+4×4)反而降低性能
  3. Tile size的关键作用

    • 4×4: 性能灾难
    • 32×32: 性能最优
    • 选择合适的tile size比使用共享内存本身更重要

3.2 实践建议

对于CUDA矩阵乘法优化

  1. 从简单实现开始

    • 先实现Kernel1这样的基础版本
    • 确保正确性和基本性能
    • 作为性能对比的基准
  2. 谨慎使用共享内存

    • 理解共享内存的优势和代价
    • 选择合适的tile size至少16×16推荐32×32
    • 避免过小的tile如4×4
  3. 系统化性能调优

    • 使用性能分析工具
    • 测试多个tile size
    • 监控资源使用情况
  4. 考虑更高级的优化

    • 寄存器分块
    • 循环展开
    • 使用Tensor Cores现代GPU
    • 使用cuBLAS库

3.3 实验的价值

本实验很好地展示了:

  1. 不同实现策略的巨大性能差异
  2. 优化不当可能带来的负面影响
  3. 系统化性能分析的重要性
  4. 硬件特性对优化策略的影响

这些经验对于其他CUDA程序优化同样适用。


附录:图表说明

实验生成的图表:

  1. experiment1_analysis.png: CPU、Kernel1、Kernel2性能对比
  2. experiment2_analysis.png: 不同BLOCK_SIZE对性能的影响

原始数据文件:

  1. matrixmul_comparison.txt: CPU、Kernel1、Kernel2的原始数据
  2. blocksize_analysis.txt: 不同BLOCK_SIZE的原始数据
  3. gpu_info.txt: GPU硬件信息