想象你是一家快炒餐厅的后厨负责人。几十个厨师同时在灶台前颠勺,可客人还是催单——你不知道问题出在哪个环节:是某个厨师动作太慢,还是传菜通道堵死了?是食材补货不及时,还是菜单设计本身就不合理?如果你直接冲到某个灶台前死盯,很可能盯了半天,真正的问题却在后厨和外场的交界处。GPU 性能调优正是如此:CUDA 程序“能跑”只是及格,“跑得快”才是目标,而 Nsight 工具家族就是你后厨里的监控系统。它可以像大堂经理一样看全局流水线,也可以像质检员一样放大到某一个厨师的每一个动作。本文会用一套“餐厅后厨”的比喻,带你学会用 Nsight Systems 和 Nsight Compute 定位瓶颈、解读指标、用 Roofline 模型规划优化方向,最终让 GPU 真正“火力全开”。

岗位关联

对 AI 从业者而言,掌握 Nsight 意味着你不再靠猜来优化算子,而是用数据说话——这是从“会写 CUDA”走向“会调优 CUDA”的必修课,也是大规模模型训练和推理性能优化的核心技能。

目录
  1. 可视化演示
  2. 工具家族分工
  3. 内核执行指标
  4. 瓶颈定位流程
  5. Roofline模型
  6. 优化策略
  7. 实战调优案例
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

通过系统级时间线分析、Roofline模型瓶颈定位、以及五步调优流程,展示Nsight Systems与Nsight Compute如何全栈分析GPU性能瓶颈。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统级时间线分析 Nsight Systems展示CPU/GPU协作时序和kernel执行间隙 时间线视图定位最耗时操作和GPU空闲时段 GPU利用率=执行时间/(执行+空闲) 系统级分析是调优的第一步

工具家族分工

想象你是一家快炒餐厅的后厨负责人。客人催单时,你首先得搞清楚:是某个厨师颠勺太慢,还是传菜通道堵了?是食材补货不及时,还是点单到出菜的整条流水线出了问题?如果你直接冲到某一个灶台前死盯,很可能盯了半天也找不到原因——因为真正的瓶颈可能在后厨和外场的交界处。GPU 程序的性能问题一模一样:慢,可能藏在内核之间巨大的启动间隙里,也可能藏在某个内核内部一条指令的等待中。所以你需要两类人配合:大堂经理看全局,质检员盯细节。

CUDA 程序“上菜慢”,NVIDIA 派出的两员大将正是对应这两个角色。Nsight Systems 负责全局时间线,像大堂经理一样记录 CPU 和 GPU 之间的 API 调用、内核启动、显存拷贝,让你一眼看到“哪一桌等了多久”;Nsight Compute 则深入单个内核,像质检员一样给出占用率、缓存命中率、线程束停滞原因等微观指标。工程师的正确姿势是:先让 Systems 回答“时间都烧在哪”,再让 Compute 回答“这个内核为什么慢”。

把这两者串联起来,就得到了程序总耗时的分解公式:

一句话总结:先让大堂经理圈出可疑环节,再请质检员放大细看。两个工具不是竞争关系,而是接力关系——Systems 负责“在哪里”,Compute 负责“为什么”。

Ttotal=Tcompute+Tmemory+Toverhead\displaystyle T_{\text{total}} = T_{\text{compute}} + T_{\text{memory}} + T_{\text{overhead}}
这个公式告诉你:程序总时间由计算、访存、额外开销三部分堆叠而成,每部分都有对应的工具和优化手段。
  1. 总耗时 $T_{\text{total}}$程序从启动到结束的墙钟时间,是性能优化的总目标。
    TtotalT_{\text{total}}
  2. 计算时间 $T_{\text{compute}}$GPU 内核真正执行算术指令的时间之和,由 Nsight Compute 测量。
    Tcompute=∑ktkT_{\text{compute}} = \sum_{k} t_{k}
  3. 访存时间 $T_{\text{memory}}$数据在显存与计算单元之间搬运、等待的时间,含主机与设备间拷贝。
    Tmemory=Tcopy+TstallT_{\text{memory}} = T_{\text{copy}} + T_{\text{stall}}
  4. 额外开销 $T_{\text{overhead}}$内核启动、API 调用等固定成本,内核越多开销越大。
    Toverhead=n×tlaunchT_{\text{overhead}} = n \times t_{\text{launch}}

内核执行指标

质检员上岗后,手里会拿一张“厨房健康检查表”,第一项就是:灶台满不满?在 GPU 里,每个 SM(流式多处理器)就像一组灶台,每个灶台最多能同时站 64 个“托盘”(线程束 Warp)。但注意,灶台站着人不代表在炒菜——如果食材没备好,厨师只能空等。Nsight Compute 报告的 占用率(Occupancy) 衡量的就是活跃线程束占硬件上限的比例,它直接决定你有没有足够的“人手”来隐藏等待时间。

一个关键的认知是:占用率不是越高越好,而是要看它能否支撑延迟隐藏。厨师等汤烧开的时候,可以顺手去切配菜;GPU 里一个线程束在等显存数据返回时,调度器会切换到另一个线程束去执行计算。只要活跃线程束够多,访存延迟就会被“藏”起来,SM 就不会闲着。

除了占用率,检查表上还有两把尺子:计算吞吐(每秒钟理论能完成多少浮点运算)和内存吞吐(每秒钟能从显存搬多少数据)。两者谁先触顶,谁就是瓶颈。Nsight Compute 会把这两条“吞吐计”同时亮出来,让你一眼判断当前是“等料”(访存受限)还是“锅不够”(计算受限)。

指标不是用来背的,而是用来回答“现在卡在哪”。占用率低,说明线程数不够或资源被共享内存占满;内存吞吐触顶,说明仓库通道堵了。检查表给了你两把尺子,但真正的高手会进一步追问:堵点到底在哪段路上?这就引出下一节的定位流程。

Occupancy=WactiveWmax⁡×100%\displaystyle \text{Occupancy} = \frac{W_{\text{active}}}{W_{\max}} \times 100\%
占用率就是“活跃线程束 ÷ 硬件允许的最大线程束数”,它回答的是“灶台上有没有足够的人在干活”。
  1. 活跃线程束 $W_{\text{active}}$当前时刻 SM 上可被调度器切换的线程束数量,Nsight Compute 直接读取。
    WactiveW_{\text{active}}
  2. 硬件上限 $W_{\max}$每个 SM 最多容纳的线程束数量,常见值为 64,由 GPU 架构决定。
    Wmax⁡=64W_{\max} = 64
  3. 比例与百分号两者相除再乘 100%,得到 0% 到 100% 的占用率,作为“人手够不够”的量化指标。
    WactiveWmax⁡×100%\frac{W_{\text{active}}}{W_{\max}} \times 100\%

瓶颈定位流程

后厨出了名的慢,不能靠直觉拍板。靠谱的做法是像中医看病:先望闻问切,再开方抓药,最后看药效。GPU 调优的完整闭环是“全局排查 → 深度分析 → 验证假设”三步走。第一步,用 Nsight Systems 拉出全程序时间线,把时间切分成内核执行、显存拷贝、API 启动三块,找出占比最大的那块“时间黑洞”;第二步,用 Nsight Compute 钻进黑洞内核,读占用率、吞吐和线程束停滞原因;第三步,基于假设修改代码,再跑一次测量对比。

为什么一定要先定位再动手?因为Amdahl 定律给优化收益画了一条硬性上限。如果一个内核只占总耗时的 10%,你把它提速 10 倍,整体也只能快约 9%;但如果瓶颈内核占了 60% 的时间,同样的优化投入就能带来 5 倍以上的整体收益。这也是为什么新手容易“优化了个寂寞”——他们把力气花在了占比很小的代码上,而 Nsight 的价值就是帮你把力气花在刀刃上。

先找最大的黑洞,再决定怎么填。调优不是玄学,而是循证医学——每一步改动都要用 Nsight 重新测量,用数据确认“药方”真的起效了。理解了“先定位”之后,下一个问题是:定位到什么程度才算到位?这就需要一张性能地图——Roofline 模型。

Sspeedup=1(1−p)+pn\displaystyle S_{\text{speedup}} = \frac{1}{(1 - p) + \dfrac{p}{n}}
加速比上限由可并行比例 $p$ 决定:即使并行处理器 $n$ 趋于无穷,串行部分 $(1-p)$ 依然无法消除。
  1. 串行比例 $(1-p)$程序中无法并行化的那部分时间占比,比如数据拷贝、内核启动间隙。
    (1−p)(1 - p)
  2. 并行比例 $p$能被 $n$ 个并行处理单元分摊的那部分时间占比。
    pn\frac{p}{n}
  3. 加速比 $S_{\text{speedup}}$优化后总耗时的倒数,数值越大表示收益越高,且永远不会超过 $1/(1-p)$。
    1(1−p)+p/n\frac{1}{(1 - p) + p/n}

Roofline模型

质检员还随身带了一张“性能地图”,叫 Roofline 模型。想象一条高速公路:横轴是每辆车平均载客量(算术强度),纵轴是每秒送达的乘客数(性能)。路上有两种限速:车辆本身的最高时速(计算峰值 $P_{\text{peak}}$),以及每个收费站每秒放行的车辆数(带宽上限 $B_{\text{peak}}$)。当一辆车乘客很少时,速度受收费站限制——这叫访存受限;当乘客很多时,受车辆最高时速限制——这叫计算受限。两条限速线的交点就是脊点(Ridge Point)。

在 GPU 里,算术强度(Arithmetic Intensity)定义为一个内核每读写 1 字节数据能完成多少次浮点运算,记作 $I = F / Q$。把内核的 $I$ 算出来,再对照硬件平台的 $P_{\text{peak}}$ 和 $B_{\text{peak}}$,就能在 Roofline 图上找到内核当前的位置。Nsight Compute 甚至会自动帮你把这个点标注出来——这就是优化方向的“指南针”:点落在脊点左侧,说明每次搬运的数据利用率太低,应该优先减少访存;点落在右侧,才值得去优化计算指令。

有了这张地图,优化方向不再靠猜。脊点左边的人要想办法往右挪——用共享内存做数据复用、减少冗余搬运;脊点右边的人才值得去调整指令混合或提升 ILP。屋顶在哪里,梯子就架在哪里。方向定了,接下来就是具体的抓手:优化策略。

Pattain=min⁡(Ppeak, I×Bpeak)\displaystyle P_{\text{attain}} = \min\left(P_{\text{peak}},\ I \times B_{\text{peak}}\right)
可达性能是“计算峰值”和“带宽×算术强度”两者中的较小者——就像车速由最低的那条限速线决定。
  1. 运算总量 $F$内核完成的总浮点运算次数,单位 FLOPs,Nsight Compute 会统计。
    FF
  2. 访存总量 $Q$内核读写全局内存的字节总数,单位 Byte。
    QQ
  3. 算术强度 $I$每搬运 1 字节数据能做的运算量,单位 FLOPs/Byte,是 Roofline 的横轴。
    I=FQI = \frac{F}{Q}
  4. 可达性能 $P_{\text{attain}}$受计算与带宽双重约束,取较小值,是 Roofline 的纵轴。
    Pattain=min⁡(Ppeak, I×Bpeak)P_{\text{attain}} = \min(P_{\text{peak}},\ I \times B_{\text{peak}})

优化策略

知道了卡点,设计改进方案就有章法了。后厨改进无非三类:备料更快、工序更合理、颠勺更利索——对应 GPU 调优就是访存优化、核函数调整、指令优化。访存优化是最常见也最见效的一类:让相邻线程读取相邻地址(合并访问 Coalesced Access),相当于传菜员一次把一条走廊的菜都带上;用共享内存当操作台,把反复从仓库取的数据先端上灶台;用向量化加载(如 float4)一次端四盘菜,减少搬运趟数。

核函数调整讲究“分”与“合”的平衡:一个内核占用的寄存器太多导致占用率低,就拆分成多个小内核提高并行度;反过来,如果内核启动次数太多、间隙时间太长,就融合几个内核减少启动开销。指令优化则侧重于减少分支发散——让一个线程束里的 32 个线程尽量走同一条路,而不是分道扬镳后串行等待。这些手段效果如何,不能靠“感觉变快了”,要量化。

评价访存改进最直接的指标是有效带宽:每秒实际搬运了多少字节。Nsight Compute 会报告内核的读写字节数和执行时间,把它们相除,你就得到了优化前后的“搬运效率”。如果带宽利用率从 40% 涨到 80%,说明仓库通道终于被用满了——这才是实打实的进步。

优化策略是工具箱,不是万能药。先按 Roofline 判断该用哪一类,再对症下药:访存受限就备料更快,计算受限就颠勺更利索。现在,我们把整套路数串起来,看一个完整的实战案例。

Beff=Dread+Dwritetkernel\displaystyle B_{\text{eff}} = \frac{D_{\text{read}} + D_{\text{write}}}{t_{\text{kernel}}}
有效带宽 = 内核读写总字节数 ÷ 内核耗时,它衡量“每秒真正搬了多少数据”,是访存优化前后的对照尺。
  1. 读数据量 $D_{\text{read}}$内核从全局内存读入的字节总数,Nsight Compute 的 Memory Workload 页可查。
    DreadD_{\text{read}}
  2. 写数据量 $D_{\text{write}}$内核写回全局内存的字节总数。
    DwriteD_{\text{write}}
  3. 内核耗时 $t_{\text{kernel}}$Nsight Compute 报告的 GPU 内核执行时间,单位秒。
    tkernelt_{\text{kernel}}
  4. 有效带宽 $B_{\text{eff}}$总字节数与耗时的比值,单位 B/s,数值接近硬件峰值即访存良好。
    Dread+Dwritetkernel\frac{D_{\text{read}} + D_{\text{write}}}{t_{\text{kernel}}}

实战调优案例

矩阵乘法是 GPU 界的“宫保鸡丁”——几乎每一个做性能调优的人都会拿它练手。朴素版实现很直观:输出矩阵的每一个元素都由一个线程计算,但这个线程需要从全局内存反复读取矩阵 A 的一行和矩阵 B 的一列。想象你每炒一道菜都要跑去仓库取一次食材,再跑回来下锅——大部分时间都浪费在路上了。用 Nsight Compute 一测,果然:内存吞吐几乎触顶,计算单元却在大量空闲,Roofline 图上那个点稳稳落在脊点左侧——访存受限。

针对这个诊断,经典药方是共享内存分块(Tiling):把矩阵切成 16×16 的小块,先由半个线程束合作把一块数据从全局内存搬进共享内存这个“操作台”,之后这一个块的数据可以被反复使用上百次。算术强度一下子提高了好几个数量级,点从脊点左侧往右移动,逐渐逼近计算上限。再配合向量化加载和寄存器缓存,内核的访存次数大幅下降,瓶颈从“仓库通道”转移到了“厨师颠勺”上。

最终效果用加速比来验收:

案例的意义不是背代码,而是学会看数据:Nsight 告诉你瓶颈在“仓库”,你就不会白费力气去做算术优化,而是去“端盘子”。从矩阵乘法到 Transformer 里的各类算子,这套“测量—定位—改进—验证”的闭环始终适用。

S=ToldTnew\displaystyle S = \frac{T_{\text{old}}}{T_{\text{new}}}
加速比 = 优化前耗时 ÷ 优化后耗时。大于 1 说明改进有效,N s i g h t 的两次测量让这一结论有据可查。
  1. 优化前耗时 $T_{\text{old}}$朴素版内核的 Nsight Compute 测量耗时,作为基线。
    ToldT_{\text{old}}
  2. 优化后耗时 $T_{\text{new}}$应用共享内存分块、向量化后的内核耗时。
    TnewT_{\text{new}}
  3. 加速比 $S$两者相除,衡量优化收益;同时配合 Roofline 图确认瓶颈转移。
    ToldTnew\frac{T_{\text{old}}}{T_{\text{new}}}

AI 应用场景

算子库开发中的性能验收

阶段:推理引擎优化 / 自定义算子开发

解决的问题:手写 CUDA 算子提交到模型仓库前,需要客观标准判断是否达标。传统做法靠“感觉快了”或只看端到端时间,无法回答“这个算子到底离硬件极限还有多远”。用 Nsight Compute 的 Roofline 视图,可以直接看到算子的位置:落在脊点左侧就优先优化访存,右侧才优化计算。

具体案例:某团队开发融合 LayerNorm 算子,初始版本带宽利用率仅 38%。Nsight Compute 定位到线程束每次只读 4 字节,改成 float4 向量化加载后,利用率升至 76%,端到端推理提速 1.9 倍,性能报告直接附在代码评审里。

大模型训练中的“GPU 饿肚子”诊断

阶段:训练框架性能诊断

解决的问题:GPT 类模型训练时 GPU 利用率忽高忽低,传统方式只能看到 GPU 空闲,却不知道是数据加载慢、通信等待还是内核启动间隙过大。Nsight Systems 的全时间线把 CPU 端数据加载、GPU 内核、通信三者对齐,一眼定位真凶。

具体案例:某训练任务 GPU 利用率仅 55%,Nsight Systems 时间线显示 GPU 每个 step 都在空等 DataLoader 产出 batch——瓶颈根本不在 GPU,而在 CPU 端图片解码。优化为异步预取后,利用率提升到 88%,训练吞吐提高 1.6 倍。

云上 GPU 选型的 Roofline 评估

阶段:AI 基础设施选型

解决的问题:云上租 A100 还是 H100,不能只看显存大小和价格。用 Roofline 模型比较两张卡的脊点位置:如果业务负载是低算术强度算子(如 GNN 聚合),带宽更高的卡更划算;若是高算术强度算子(如大矩阵乘法),计算峰值才是决定因素。

具体案例:某推荐平台对比 A100 与 H100 后发现,其核心算子算术强度约为 8 FLOPs/Byte,低于 H100 的脊点,带宽优势无法释放,最终选择性价比更高的 A100 集群,节省 30% 算力成本。

Python 演示

python45 行
import math class KernelReport:    """一份内核的测量报告,flops 为浮点运算量,bytes_moved 为访存字节数。"""    def __init__(self, name, flops, bytes_moved):        self.name = name        self.flops = flops        self.bytes_moved = bytes_moved     def arithmetic_intensity(self):        """算术强度 = 运算量 / 访存量,单位 FLOPs/Byte。"""        return self.flops / max(self.bytes_moved, 1.0)  def roofline_position(kernel, peak_flops, peak_bw):    """根据 Roofline 模型判断内核是访存受限还是计算受限。"""    ridge = peak_flops / peak_bw    intensity = kernel.arithmetic_intensity()    if intensity < ridge:        bound = "memory_bound"        max_flops = intensity * peak_bw    else:        bound = "compute_bound"        max_flops = peak_flops    efficiency = kernel.flops / max_flops    return bound, efficiency  def main():    peak_flops = 15.7e12      # 理论计算峰值 (FLOPs/s)    peak_bw = 2.0e12          # 理论显存带宽 (B/s)    kernels = [        KernelReport("vector_add", 0.3e9, 6.0e9),        KernelReport("matmul_naive", 2.0e9, 4.0e9),        KernelReport("matmul_tiled", 200.0e9, 2.5e9),    ]    print("kernel_name       bound           efficiency")    print("-" * 45)    for k in kernels:        kind, eff = roofline_position(k, peak_flops, peak_bw)        print(f"{k.name:16s} {kind:14s} {eff:6.2%}")  if __name__ == "__main__":    main()
目的:用迷你 Roofline 分析器模拟 Nsight Compute 的核心判断逻辑,输入内核的运算量和访存量,输出它是“访存受限”还是“计算受限”。
  1. def arithmetic_intensity(self)计算算术强度,即每搬运 1 字节能做多少次运算,这是 Roofline 横轴的关键值。
  2. ridge = peak_flops / peak_bw求出脊点位置,脊点左侧访存受限、右侧计算受限。
  3. if intensity < ridge:内核点落在脊点左侧,说明搬运效率低,应优先做访存优化。
  4. max_flops = intensity * peak_bw访存受限时理论最大性能由带宽决定,而非计算峰值。
  5. efficiency = kernel.flops / max_flops计算当前实现离理论上限还有多远,对应 Nsight 的“浮点利用率”。

常见误区

误区程序变慢了,我直接打开 Nsight Compute,盯着最长的那个内核看半天,改了一堆指令级细节,结果整体性能没怎么变。

正解这就像客人催单,你不先看是不是传菜通道堵了,却冲进后厨让厨师颠勺更快。Nsight Systems 看全局,Nsight Compute 看内核,两者是接力关系。正确的顺序永远是先用 Systems 找到占比最大的“时间黑洞”,再决定要不要用 Compute 深入。否则很可能优化了一个只占总耗时 5% 的内核,白费力气。

为何常见:新手容易被 Compute 丰富的指标吸引,误以为“越微观越专业”,忽略了先做整体时间分布排查这一步。

误区Nsight Compute 显示占用率只有 50%,我拼命调大线程块、减少共享内存,想把占用率顶到 100%,结果性能反而下降了。

正解占用率只是“灶台上站着多少人”,不是“炒出多少菜”。过高的占用率意味着每个线程分到的寄存器变少、共享内存被挤占,反而导致每个线程变慢。目标不是 100%,而是让活跃线程束足够多,能隐藏访存延迟就够了。正确做法是结合 stall 原因分析:如果线程束都在等数据,才需要提高占用率;如果都在执行指令,占用率已经够了。

为何常见:因为“N s i g h t 显示的百分比”天然让人觉得越高越健康,和 CPU 使用率的直觉混在了一起。

误区Nsight 显示 GPU 利用率 95%,那程序应该没问题了,不用再优化。

正解GPU 利用率高只能说明 SM 上有线程在“挂着”,不代表它们在干正事。大量线程可能都在等显存数据返回——这是“虚假繁忙”,就像厨师都站在灶台边但锅里没水。Nsight Compute 的 stall 原因(如 long scoreboard、wait)能区分真忙和假忙。健康的信号不是利用率高,而是计算吞吐贴近 Roofline 上限且 stall 原因合理。

为何常见:人天生会混淆“硬件没闲着”和“硬件在高效工作”,利用率这个指标太容易误导人了。

误区我把核函数优化得飞快,端到端时间却没缩短,于是觉得 Nsight 根本没用。

正解如果 Nsight Systems 显示大部分时间花在 H2D/D2H 拷贝和内核启动间隙上,那内核再快也是杯水车薪。Amdahl 定律早就说了:优化收益取决于被优化部分的占比。这时该做的不是抠内核指令,而是用 CUDA Graphs 合并启动、用零拷贝或异步传输消除拷贝,或者把多次小拷贝合并成一次大拷贝。

为何常见:新手天然把“调 GPU 性能”等同于“优化核函数”,忽略了主机与设备之间的数据搬运同样是时间开销大户。

面试题

Q1. 拿到一个缓慢的 CUDA 程序,想先看整个程序的时间都花在哪里,首选工具是?

单选题 · easy

  • A.Nsight Compute 内核分析
  • B.Nsight Systems 全链路分析← 正确答案
  • C.CUDA-GDB 断点调试工具
  • D.CUDA-MEMCHECK 内存检查

答案:B。Nsight Systems 负责全局时间线,像大堂经理一样展示内核执行、显存拷贝和 API 启动的分布;Nsight Compute 是深入单个内核的微观分析工具。调优应先看全局再钻内核,所以先选 Systems。

Q2. 关于线程束占用率,最准确的理解是?

单选题 · easy

  • A.GPU 温度升高所占的百分比
  • B.活跃线程束占硬件上限比例← 正确答案
  • C.活跃线程占显存容量的比例
  • D.内核代码中循环语句的占比

答案:B。占用率 = 活跃线程束 / 每个 SM 最大线程束数(常见 64),衡量的是“灶台上有多少人在干活”,与温度、显存容量、代码循环无关。它决定调度器能否有效隐藏访存延迟。

Q3. Roofline 模型中的“脊点”(Ridge Point)是指?

单选题 · mid

  • A.程序算法达到理论最优性能的点
  • B.计算峰值与带宽峰值交汇的点← 正确答案
  • C.显存容量达到上限的临界点
  • D.所有线程束同步执行的时刻

答案:B。脊点是计算峰值 $P_{peak}$ 与带宽上限 $B_{peak}$ 的交汇处,横坐标为 $P_{peak}/B_{peak}$。脊点左侧访存受限,右侧计算受限,它决定了优化的主攻方向,而非“算法最优”或“显存容量”。

Q4. 一个内核的算术强度低于 Roofline 脊点,最值得优先优化的是?

单选题 · mid

  • A.减少多余的算术指令数量
  • B.提高缓存命中与访存合并率← 正确答案
  • C.给 GPU 增加更多的新核心
  • D.提高核心时钟频率的上限值

答案:B。低于脊点表示访存受限,瓶颈是“每搬运 1 字节做的运算太少”。此时应该提高数据复用、对齐访问以减少搬运量,让点往右移动;减少算术指令是脊点右侧计算受限时该做的事,硬件核心和频率也不是软件能改的。

Q5. Nsight Compute 的内核分析能提供以下哪些信息?

多选题 · mid

  • A.全局内存访问的缓存命中率← 正确答案
  • B.线程束发散与指令混合比例← 正确答案
  • C.整条程序的端到端耗时时间线
  • D.显存带宽与计算吞吐利用率← 正确答案

答案:ABD。Nsight Compute 是“质检员”,专注单个内核的缓存命中、指令混合、Warp 发散、带宽与计算利用率;端到端耗时时间线是 Nsight Systems 的职责,选 C 就会答错。

Q6. 某内核报告显示“显存吞吐很高,但 SM 利用率极低”,最可能的原因是?

单选题 · hard

  • A.计算单元正处在满载忙碌状态
  • B.大量线程在等待数据从显存返回← 正确答案
  • C.全局显存容量空间严重不足
  • D.内核启动的固定开销过于巨大

答案:B。显存吞吐高而 SM 利用率低,说明搬运通道被塞满,但计算单元在“等料下锅”——线程束全部卡在访存等待上,调度器没有可切换的任务。这正是访存受限的典型画像,与计算满载、显存容量、启动开销都无关。

Q7. 以下哪些访问模式会造成访存非合并(uncoalesced access)?

多选题 · hard

  • A.相邻线程访问相邻的内存地址
  • B.线程按大步长跳跃式访问地址← 正确答案
  • C.各线程访问随机散列的内存地址← 正确答案
  • D.所有线程读取同一个标量常数

答案:BC。合并访问要求一个线程束的 32 个线程尽量落在同一段连续内存上,A 是教科书式合并访问,D 是广播(一次取数发给所有线程,也是高效的)。B 的步长跳跃和 C 的随机散列会让硬件拆成多次事务,吞吐骤降。

Q8. 使用 Nsight 系列工具进行性能调优,正确的流程是?

单选题 · easy

  • A.先用 Systems 看全局,再用 Compute 看内核← 正确答案
  • B.先用 Compute 看内核,再用 Systems 看全局
  • C.只用 Compute 就能覆盖所有分析场景
  • D.优化时必须关闭所有 Nsight 工具

答案:A。调优像看病,先全局后局部。Nsight Systems 先圈出时间黑洞(哪个阶段最慢),Nsight Compute 再解释黑洞内部原因(为什么慢),最后改代码并用工具验证。顺序颠倒或只用其一,都会漏掉关键线索。

Q9. 以下哪些手段可以有效改善内核的访存性能?

多选题 · mid

  • A.使用共享内存做数据分块复用← 正确答案
  • B.使用向量化类型如 float4 加载← 正确答案
  • C.让相邻线程访问相邻的内存← 正确答案
  • D.在循环内部增加更多除法指令

答案:ABC。共享内存复用减少全局访问次数,向量化一次取 16 字节提升单次事务效率,合并访问让线程束少走冤枉路——三者都是访存优化。增加除法只会加重计算负担,与访存无关。

Q10. Amdahl 定律给性能优化最重要的启示是?

单选题 · hard

  • A.优化收益上限取决于可并行部分占比← 正确答案
  • B.增加线程数量总能线性提高加速比
  • C.编译器优化等级决定最终性能上限
  • D.显存总容量决定加速比的最高值

答案:A。Amdahl 定律指出加速比上限是 $1/(1-p)$,$p$ 为可并行比例。串行部分哪怕只占 10%,加速比也不可能超过 10 倍,所以要先定位占比最大的瓶颈再优化。B 忽略了串行部分和通信开销,C、D 与定律无关。