Demystifying GPU Microarchitecture through Microbenchmarking

发表时间: 2010-01

文章标题:通过微基准测试揭秘GPU微架构
作者/机构:Henry Wong, Misel-Myrto Papadopoulou, Maryam Sadooghi-Alvandi, and Andreas Moshovos (Department of Electrical and Computer Engineering, University of Toronto)

速读

一句话结论 本文通过开发一套微基准测试程序,精确测量并逆向工程了 Nvidia GT200 GPU 中未公开的底层架构特性,为性能优化和精确建模提供了详实的硬件参数。

要解决什么问题 原有 GPU 程序的性能优化和建模卡在底层硬件信息的不透明上。业界主要依赖官方文档(如 CUDA 编程指南),但其信息往往含糊不清。这种缺失导致几个致命卡点:首先是控制流机制黑盒化,开发者不清楚分支分化(branch divergence,即同一线程束内的线程走向不同分支)和屏障同步的具体硬件行为,极易在单指令多线程(SIMT)模型下触发非直观的死锁;其次是内存层次模糊,官方缺乏对各级缓存、翻译后备缓冲区(TLB,用于虚拟到物理地址转换)的具体容量、相联度和延迟的描述,导致访存优化无从下手;最后是指令延迟不确定,无法评估算术流水线的真实吞吐瓶颈。这些卡点使得开发者只能盲目调优,无法建立高保真性能模型。

怎么做的 核心思路是通过构建一系列微基准测试(microbenchmarks),利用精确的计时器测量特定代码片段的执行延迟,再通过分析延迟的变化规律逆向推导出硬件的内部结构。为了绕开编译器优化对测试代码的干扰,作者先用 CUDA C 编写内核,再使用 decuda(第三方机器码反汇编工具)在本地指令级别验证生成的机器码序列,确保真正在 GPU 上执行的指令符合预期。在关键设计上,测试框架由几个核心部件构成。第一个是高精度的计时外壳:在被测代码前后使用 GPU 内置的 `clock()` 函数读取时钟寄存器。为防止全局内存慢速访问干扰计时,时间戳先暂存在寄存器中,等内核执行完毕再统一写回。同时,测试会忽略首次迭代以排除冷启动时的指令缓存未命中。第二个核心部件是基于跨步访问(stride access)的内存探测器。为测出缓存和 TLB 参数,程序以不同步长访问不同大小的数组,并绘制平均访问延迟图。其推导依赖于缓存的基本不变量:$$ \text{缓存大小} = \text{缓存组数} \times \text{行大小} \times \text{相联度} $$ 当数组刚好能装入缓存时,延迟保持在低位平台期;一旦数组超过缓存容量,延迟出现阶梯式增长。阶梯数量直接对应缓存组的数量,因为这些组是一个接一个溢出的;而触发每个延迟阶梯所需的数组大小增量,就等于缓存行的大小。当所有组都溢出后,延迟达到最高平台期。通过这种延迟图,算出组数和行大小就能推导相联度。第三个部件是针对控制流的探测器。通过向内核传入包含特定排列顺序的数组,强制线程按预定顺序触发条件分支,观察执行时间线,以此探测硬件用于管理分支分化和再收敛的分支同步栈机制。此外,通过在同一个流式多处理器(SM)或不同线程处理集群(TPC)上放置并发线程块,观察缓存争用导致的可用容量减半现象,精准定位各级缓存的共享范围。

效果如何 实验在 Nvidia GT200 (GTX280) GPU 上进行,该硬件包含 10 个 TPC,每个 TPC 包含 3 个 SM。本文的对比基线是 Nvidia 官方的 CUDA 编程指南,代表了开发者仅凭官方公开信息所能掌握的架构认知。量化结果修正并补充了大量官方未披露的细节:在算术流水线上,测得标量处理器延迟为 24 个周期,且需要 6 到 7 个线程束(warp)才能跑满流水线,而非官方建议的 6 个;在控制流上,证实了 `_syncthreads()` 屏障同步是在 warp 粒度而非线程粒度上执行的,若在分化的 warp 中误用会导致死锁;在内存架构上,首次完整测出了三级缓存体系,其中常量内存和指令内存各自拥有私有的 L1 缓存(常量 2 KB,指令 4 KB),但共享 8 KB 的 L2 缓存和 32 KB 的全局 L3 缓存;在地址翻译上,探明了全局内存存在 8 MB 全相联的 L1 TLB 和 32 MB 8路组相联的 L2 TLB。作者也承认了该方法的局限性:GPU 架构极其复杂,微基准测试无法对每一个硬件细节进行完全的逆向工程,且跨越互连网络的内存请求延迟会随 TPC 物理位置的不同而产生波动,需要通过多次平均来平滑误差。

A1 主要贡献

本文旨在通过开发一套微基准测试程序,来测量和揭示Nvidia GT200 (GTX280) GPU中对CUDA可见的架构特性。由于制造商提供的文档信息有限且有时含糊不清,开发者、架构和编译器研究人员需要对现代GPU设计有更深入的理解。本文的研究重点是影响GPU性能的两个主要部分:算术处理核心和为这些核心提供指令与数据的内存层次结构。

核心问题与研究目标:
- 核心问题: 业界对GPU架构的了解主要依赖于制造商的文档(如NVIDIA的CUDA编程指南),但这些文档中的信息往往不够详尽或模糊,缺乏对底层硬件组织的深入解释。这给性能优化、死锁避免以及精确的性能建模带来了挑战。
- 研究目标: 通过微基准测试方法,精确测量Nvidia GT200 GPU的处理核心和内存层次结构的各种未公开特性,从而为性能优化、分析和建模提供详细、准确的硬件信息。

主要贡献:
* 验证官方性能特性: 验证了CUDA编程指南中列出的部分性能特性。
* 揭示控制流细节: 深入探究了分支分化(branch divergence)和屏障同步(barrier synchronization)的详细功能。发现了会导致死锁的非直观分支代码序列,并阐明了通过理解内部架构可以避免这些问题。
* 测量内存与缓存层次结构: 测量了内存缓存层次结构的组织和性能,包括翻译后备缓冲区(TLB)层次结构、常量内存(constant memory)、纹理内存(texture memory)和指令内存(instruction memory)的缓存。
* 分享测量技术: 详细介绍了所使用的测量技术,这些技术对于分析和建模其他GPU及类GPU系统,以及提高GPU性能建模与仿真的保真度具有参考价值【索引2, A. Bakhoda 等人, Analyzing CUDA Workloads Using a Detailed GPU Simulator, ISPASS 2009】。

GPU架构概览:
论文中展示了GT200的层级化硬件资源结构,线程块(Blocks of threads)在流式多处理器(Streaming Multiprocessors, SM)中执行。多个SM组成一个线程处理集群(Thread Processing Clusters, TPC),整个GPU则由多个TPC和内存系统构成。

图1:每个流式多处理器(SM)包含8个标量处理器(SP)
图1:每个流式多处理器(SM)包含8个标量处理器(SP)

图2:每个线程处理集群(TPC)包含3个SM
图2:每个线程处理集群(TPC)包含3个SM

图3:包含TPC和内存库的GPU整体结构
图3:包含TPC和内存库的GPU整体结构

A3 背景知识与测量方法

GPU架构与编程模型

测量方法

A2 方法细节

本节详细介绍我们的测试和结果。我们首先测量clock()函数的延迟,然后研究SM的各种算术流水线、分支分化和屏障同步。我们还探讨了SM内部及周围的内存缓存层次结构,以及内存转换和TLB。


表 II:算术流水线延迟与吞吐量

时钟开销和特性

算术流水线

控制流

屏障同步

寄存器文件

共享内存

全局内存

纹理内存

内存翻译

常量内存

指令供给

A4 实验环境

A4 实验结果

本文通过一系列微基准测试,揭示了Nvidia GT200 GPU的多项微架构细节,主要实验结果总结如下:

A5 结论

本文介绍了对Nvidia GT200 GPU的分析以及我们的测量技术。通过我们开发的微基准测试套件,揭示了处理核心和内存层次结构的架构细节。GPU是一个复杂的设备,我们不可能对每个细节都进行逆向工程,但我们相信我们已经研究了一部分有趣的特性。下表 V 总结了我们的架构发现。

我们的结果验证了CUDA编程指南【索引1, Nvidia, CUDA Programming Guide Version 2.0】中提出的一些硬件特性,但也揭示了一些未记录的硬件结构的存在,例如控制流机制以及缓存和TLB层次结构。此外,在某些情况下,我们的发现与文档记录的特性有所偏离(例如,纹理和常量缓存)。

我们还介绍了用于架构分析的技术。我们相信这些技术对于分析其他类GPU架构和验证类GPU性能模型将非常有用。最终目标是更好地了解硬件,以便我们能够充分发挥其潜力。

GT200架构总结表:




表 V: GT200架构总结

A6 附录

本文档不包含附录。

方法细节中的引用汇总