想象你经营一家网红餐厅,总厨(CPU)一个人包揽所有菜。客人点了一百份相同的炒饭,他只能一份一份颠勺,锅气还没上来,等位的人已经走光了。有一天你雇了一支帮厨团队(GPU)——他们不负责创意摆盘,只负责按同一张工序卡机械地翻炒。只要工序卡写得清晰、备料摆位合理,一百份炒饭能同时出锅。这套“厨房管理体系”就是 WebGPU,而那张决定每个帮厨“做什么、按什么顺序做”的工序卡,就是计算着色器(Compute Shader)。在浏览器里跑 AI,从图片滤镜到大模型推理,本质上都是把海量重复计算交给这支帮厨团队。本文就用“厨房”当导游图,带你从零认识:工序卡怎么写(WGSL)、帮厨怎么站队(工作组)、备料怎么放(缓冲),以及怎么让后厨满负荷运转(性能优化)。

岗位关联

对 AI 从业者而言,掌握 WebGPU 计算着色器意味着能突破 WASM 的纯 CPU 算力瓶颈,在浏览器端直接驱动 GPU 完成模型推理与预处理,这是端侧 AI 工程化的重要加分项。

目录
  1. 可视化演示
  2. 计算着色器概念
  3. WGSL语言基础
  4. 线程与工作组
  5. 缓冲与内存
  6. 管线接入
  7. 性能优化策略
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

演示WebGPU计算着色器的核心机制:GPU计算管线流程、工作组网格并行调度,以及CPU与GPU计算性能对比分析。

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

WebGPU计算管线流程 输入缓冲区数据经管线传输至GPU显存 计算着色器在GPU上并行执行WGSL代码 计算结果写回缓冲区供CPU读取 Pipeline=Buffer→Shader→Dispatch→Readback WebGPU释放浏览器GPU计算能力

计算着色器概念

总厨发现最耗时间的不是调味创新,而是给一百份牛排做同一种熟度处理。他决定把这部分外包给帮厨团队:只要在订单上写明“七分熟、每面三分钟”,每个帮厨拿着同一张工序卡,各自负责一块牛排。这张工序卡就是计算着色器的雏形——一段写给 GPU 的批处理程序,专业说法叫“计算着色器(Compute Shader)”。

在浏览器生态里,这张工序卡由 WebGPU 的 WGSL 语言书写。与负责“画画”的顶点/片元着色器不同,计算着色器不关心屏幕上的三角形或颜色,它只关心“一批数据 → 一批结果”的数学运算。它像一个专门处理重复劳动的通用车间:输入一堆数字,输出一堆数字,过程高度并行。

为什么 AI 离不开它?因为神经网络里最耗时的矩阵乘法、卷积、归一化,全是“同一套动作重复上万次”的体力活。在浏览器 ML 这条路上,WASM 与 WebGPU 是并行的两个引擎:WASM 请的是 CPU 打工人,WebGPU 请的是 GPU 帮厨团队。GPU 里有数千个计算核心,计算着色器正好把它们全部发动起来。

所以计算着色器等于把同一份工序卡印了一千份发给帮厨团队,每人动作相同、互不打扰,后厨产能瞬间翻倍。但它不是万能的:如果每道菜工序不同、前后依赖强,帮厨们反而会互相等待。这也是为什么我们把矩阵运算交给 GPU,而把逻辑判断留在 JavaScript 里。

S=T串行T并行(p)≈p,(p=并行线程数)\displaystyle S = \frac{T_{串行}}{T_{并行}(p)} \approx p, \quad (p = \text{并行线程数})
加速比 S 衡量“并行省了多少时间”:理想情况下,线程数越多,省得越多。
  1. 串行时间 \(T_{串行}\)总厨一个人做完所有任务的时间,所有菜依次排队下锅。
    T串行=n×t0\displaystyle T_{串行} = n \times t_0
  2. 并行时间 \(T_{并行}(p)\)交给 p 个帮厨同时处理后的耗时,理想情况下是 \(T_{串行} / p\)。
    T并行(p)≈T串行p\displaystyle T_{并行}(p) \approx \frac{T_{串行}}{p}
  3. 加速比 \(S\)串行与并行耗时的比值,理想时正比于帮厨人数,即 p 倍提速。
    S=T串行T并行≈p\displaystyle S = \frac{T_{串行}}{T_{并行}} \approx p

WGSL语言基础

工序卡要能被所有帮厨看懂,必须先统一写法。WGSL 就是 WebGPU 规定的“菜谱语言”:风格像 Rust,结构清晰、类型严格,写错了在编译阶段就会被厨师长拦下,不会等到上菜才发现问题。它的核心任务是告诉 GPU:“每个帮厨拿到这份卡,重复执行其中步骤。”

一份计算着色器代码的灵魂是“入口函数”,相当于工序卡上的“主流程”章。它用 @compute 属性标记,再用 @workgroup_size 声明每个工位站几个帮厨(工作组尺寸),例如 @workgroup_size(64) 表示每组 64 个线程。CPU 端则用 dispatch 决定这份工序卡被派发多少遍,两者相乘就是被召唤的总人数。

WGSL 还提供了一套基础语法积木:u32、f32 等标量类型,vec 向量、array 数组等容器类型,以及 global_invocation_id、local_invocation_id 等内置变量——它们相当于帮厨的“工牌编号”,让每个人知道自己在处理第几份食材。数据类型越严格,GPU 驱动的优化空间越大,这也是 WGSL 设计成强类型语言的原因。

把工序卡写得越规范,GPU 执行越不容易出错。WGSL 的严格类型就像餐厅的岗位责任制:每个帮厨只能做规定动作,虽然写起来约束多,却让编译器能提前发现错误,运行时也更少崩溃。

N线程=DxDyDz×SxSySz\displaystyle N_{线程} = D_x D_y D_z \times S_x S_y S_z
总线程数 = 派发次数(三个维度)× 工作组尺寸(三个维度),一维场景可简化为 \(N = D \times S\)。
  1. 工作组尺寸 \(S_d\)工序卡上写的“每工位几人”,三个维度分别记为 \(S_x, S_y, S_z\)。
    S=Sx×Sy×Sz\displaystyle S = S_x \times S_y \times S_z
  2. 派发次数 \(D_d\)总厨下令“这套工序卡重复几次”,对应 dispatch 的 x/y/z 三个参数。
    D=Dx×Dy×Dz\displaystyle D = D_x \times D_y \times D_z
  3. 总线程数两者相乘就是被同时召唤的帮厨总数。
    N=D×S\displaystyle N = D \times S

线程与工作组

帮厨团队不是乱哄哄挤在一个大厅,而是分成一个个“工位小组”(工作组,Workgroup)。每个小组有固定人数的帮厨(线程/调用),共享一口备料热锅(共享内存);小组之间各自独立,互不串门。这种“分组干活”的结构,是 GPU 高效调度的核心。

每个帮厨需要知道自己是“哪个组的第几个”。GPU 通过内置变量回答:workgroup_id 是组编号,local_invocation_id 是组内编号,两者合成 global_invocation_id——全球唯一工号。把“第 3 组第 5 个帮厨”换算成“全场第几号”,靠的就是下面这个公式。

工作组尺寸不是乱定的。WebGPU 规定 workgroup_size 每维上限通常为 256,总调用数受设备能力限制(常见 256/512/1024),同时共享内存容量也会卡住组的大小。工程上常用 64、128、256 等 2 的幂。组内共享内存就像小组的一口热锅:料放在锅里,大家伸手就能拿,比每次跑去大冷库(显存)快得多,这也是矩阵分块算法爱用它的原因。

global_id=workgroup_id×S+local_id\displaystyle \mathrm{global\_id} = \mathrm{workgroup\_id} \times S + \mathrm{local\_id}
全局编号 = 组编号 × 组内线程数 + 组内编号,先跳过前面所有组的线程,再加上组内偏移。
  1. 组编号 workgroup_id当前线程属于第几个工作组,从 0 开始计数。
    workgroup_id∈{0,1,…,D−1}\displaystyle \mathrm{workgroup\_id} \in \{ 0, 1, \dots, D-1 \}
  2. 组内编号 local_id线程在组内的偏移,范围是 0 到 S-1。
    local_id∈{0,1,…,S−1}\displaystyle \mathrm{local\_id} \in \{ 0, 1, \dots, S-1 \}
  3. 合成全局编号先跳过前面的 workgroup_id 个组(每组 S 人),再叠加组内偏移,得到唯一工号。
    global_id=workgroup_id×S+local_id\displaystyle \mathrm{global\_id} = \mathrm{workgroup\_id} \times S + \mathrm{local\_id}

缓冲与内存

帮厨再强,手里没料也做不出菜。CPU 要把数据交给 GPU,GPU 算完再把结果还回来,中间的“冰箱与货架”就是缓冲(Buffer)。WebGPU 中的存储缓冲(storage buffer)像一个可读可写的大冷库,AI 权重、张量数据都放这里;统一缓冲(uniform buffer)则像墙上贴的厨房规章,只读、小而快,每次调度所有线程共享同一份。

数据放进缓冲不是随手一扔,要遵守“货架对齐”规则。WGSL 的存储布局要求很多字段按 16 字节对齐——类似超市规定大件货物必须从格子的整数边界开始放,否则翻箱倒柜找料时会撞车。结构体成员的顺序和大小,直接决定这块缓冲最终占多大、每个字段从哪个偏移开始读。

CPU 与 GPU 之间的交流是异步的。GPU 算完后,JavaScript 用 mapAsync 把缓冲“映射”到内存地址,拿到结果再 unmap 归还——就像从冷库出货到前台:不是你一喊就到,而是按单出库、广播叫号。内存这块最容易出事故:结构体顺序写错、对齐不够、忘记 unmap,轻则数据错乱,重则管线报错。记住一句话:货架规则是 GPU 的礼貌,遵守它,整条流水线才顺畅。

offset=⌈size16⌉×16\displaystyle \mathrm{offset} = \lceil \textstyle\frac{\mathrm{size}}{16} \rceil \times 16
下一个字段的起始偏移 = 当前字段大小向上取整到 16 的倍数,保证货架从整数格边界开始摆。
  1. 字段占用 size一个数据成员占用的字节数,如 f32 占 4 字节、vec4 占 16 字节。
    size(vec4)=16 Bytes\displaystyle \mathrm{size}(\mathrm{vec4}) = 16\ \mathrm{Bytes}
  2. 除以 16看看这个字段需要占多少个“标准货格”。
    size16\displaystyle \frac{\mathrm{size}}{16}
  3. 向上取整装不下时也要多占一整格,避免两个字段挤进同一格。
    ⌈size16⌉\displaystyle \lceil \tfrac{\mathrm{size}}{16} \rceil
  4. 乘回 16得到下一个成员的起始偏移,保证始终落在整数边界上。
    offset=⌈size16⌉×16\displaystyle \mathrm{offset} = \lceil \tfrac{\mathrm{size}}{16} \rceil \times 16

管线接入

有了工序卡、工位、备料,还差“下单—执行—上菜”的完整流程。在 WebGPU 里,这条流程叫计算管线(Compute Pipeline):把 WGSL 源码编译成 GPU 认识的机器指令,配置好缓冲,最后一声令下 dispatch,GPU 便按工作组批量开工。

步骤大致分三步。第一,createShaderModule 编译着色器,再用 createComputePipeline 建管线;第二,用 createBindGroupLayout 声明缓冲“插槽”,再用 createBindGroup 把实际缓冲插进去——相当于告诉帮厨“料在第 0 层第 3 格”;第三,computePass.dispatchWorkgroups(Dx, Dy, Dz) 正式派活。注意 dispatch 的参数是“每维派发多少个工作组”,不是线程数。

任务总元素数是 N,每工作组线程数是 S 时,所需的组数要向上取整,否则末尾多出来的几个元素没人处理——就像 100 张桌子每桌坐 6 人,最后一桌只有 4 人也得开一桌。

算完之后,数据还在 GPU 的冷库里。要“上菜”给 JavaScript:要么 mapAsync 映射回读拿到结果;要么把计算缓冲直接作为下一步渲染的输入,省去一次搬运。这就像后厨做好半成品,直接转到摆盘台,不必先端回前台再端回来。

D=⌈NS⌉\displaystyle D = \lceil \textstyle\frac{N}{S} \rceil
派发的工作组数 = 元素总数 ÷ 每工作组线程数,向上取整保证每个元素都被线程覆盖。
  1. 元素总数 N要计算的数据条数,比如 1000 个浮点数。
    N=1000\displaystyle N = 1000
  2. 工作组线程数 S每个工位同时作业的帮厨人数。
    S=64\displaystyle S = 64
  3. 相除并向上取整1000 除以 64 得 15.625,不足 16 组装不下,所以取 16 组。
    D=⌈100064⌉=16\displaystyle D = \lceil \tfrac{1000}{64} \rceil = 16

性能优化策略

支起整个后厨只是开始,客人多了,翻台率才是利润。计算着色器的性能优化,本质是让 GPU 成千上万个核心始终有活干,而不是空转等数据。四个常用策略是:负载均衡、内存合并、占用率优化、计算与渲染重叠。

工作负载均衡,是让每个线程的任务量均匀,如果数据末尾不满一组,很多线程会在条件判断里“下岗摸鱼”;合并内存访问,是让相邻线程访问相邻内存地址,GPU 一次事务就能搬回整段数据,像帮厨按货架顺序挨个取料,而不是满仓库乱跑;占用率优化,是提高同时活跃的工作组数量,让硬件调度器有足够线程来隐藏访存延迟——帮厨足够多,等料的时候还有人接着炒。

计算与渲染重叠,是在录制命令时把计算 pass 穿插在渲染 pass 之间,让 GPU 在画一帧的同时顺手算完下一帧的物理或 AI 数据,像两班厨师轮轴转。优化要有先后观:先看负载是否均衡,再看内存访问是否连续,然后查占用率,最后才谈重叠——先保证每个帮厨手里有活,再优化取料路线,本末倒置往往白忙一场。

Occupancy=AwgMwg×100%\displaystyle \mathrm{Occupancy} = \frac{A_{wg}}{M_{wg}} \times 100\%
占用率 = 活跃工作组数 ÷ 硬件可容纳的最大工作组数,比值越高,GPU 核心越不容易饿肚子。
  1. 活跃工作组数 \(A_{wg}\)当前正在运行的工作组数量,代表实际在干活的人数。
    Awg=当前同时运行的工作组数\displaystyle A_{wg} = \text{当前同时运行的工作组数}
  2. 硬件最大容纳数 \(M_{wg}\)GPU 根据寄存器、共享内存等资源算出的上限。
    Mwg=硬件资源决定的容量上限\displaystyle M_{wg} = \text{硬件资源决定的容量上限}
  3. 占用率两者之比越高,核心越不空转,访存延迟越容易被并发任务隐藏。
    Occupancy=AwgMwg×100%\displaystyle \mathrm{Occupancy} = \frac{A_{wg}}{M_{wg}} \times 100\%

AI 应用场景

网页端实时 AI 图像滤镜

阶段:数据预处理

解决的问题:传统 Canvas 2D 逐像素遍历跑在 CPU 上,处理 4K 图明显卡顿;计算着色器把像素级运算分给上万线程并行执行,像帮厨们一人一块牛排同时开煎。

具体案例:视频会议网页对摄像头帧做美颜和背景分割,人像分割模型跑在 WebGPU 上,计算着色器负责输入归一化与后处理 softmax,整条链路端到端稳定在 30fps 以上。

浏览器内大语言模型推理

阶段:推理部署

解决的问题:WASM 纯 CPU 跑量化大模型速度感人,而 Transformer 的矩阵乘法和注意力计算并行度极高,正是计算着色器的用武之地。

具体案例:开源的 WebLLM 项目用 WebGPU 计算着色器实现矩阵乘与注意力计算,让 7B 量化模型在普通笔记本的浏览器里流式生成文字,推理速度比纯 WASM 快一个数量级。

Web 端信号处理加速

阶段:特征工程

解决的问题:FFT、滤波等信号处理有大量重复蝶形运算,JavaScript 单线程难以满足实时性;把数据拆到多个工作组并行计算后,延迟大幅下降。

具体案例:语音识别前端的频谱提取,用计算着色器把 4096 点 FFT 分到多个工作组并行,处理延迟从 40ms 降到了 5ms 以内。

Python 演示

python37 行
"""模拟 WebGPU 计算着色器的线程调度与向量加法""" import math WORKGROUP_SIZE = 4  def simulate_compute_shader(a, b, wg_size=WORKGROUP_SIZE):    """用 Python 模拟 GPU 线程: 每个线程负责一个元素相加"""    n = len(a)    output = [0] * n    wg_count = math.ceil(n / wg_size)    trace = []    for wg in range(wg_count):           # 派发工作组        for local_id in range(wg_size):  # 组内线程并行执行            global_id = wg * wg_size + local_id            if global_id < n:                output[global_id] = a[global_id] + b[global_id]                trace.append((wg, local_id, global_id))    return output, wg_count, trace  def main():    a = [1, 2, 3, 4, 5, 6, 7, 8, 9]    b = [10, 20, 30, 40, 50, 60, 70, 80, 90]    output, wg_count, trace = simulate_compute_shader(a, b)    print("数组长度:", len(a))    print("工作组大小:", WORKGROUP_SIZE)    print("派发工作组数:", wg_count)    print("执行轨迹(工作组, 组内线程, 全局编号):")    for item in trace:        print(" ", item)    print("计算结果:", output)  if __name__ == "__main__":    main()
目的:用 Python 复刻计算着色器的“线程组织 + 全局编号 + 逐元素计算”逻辑,直观看到工作组如何被派发、每个线程处理哪个元素。
  1. wg_count = math.ceil(n / wg_size)对应 dispatch 派发组数公式 \(D = \lceil N/S \rceil\),向上取整保证最后一个元素也有人处理。
  2. for wg in range(wg_count):模拟 CPU 逐个派发工作组,真实 GPU 中所有工作组由硬件调度。
  3. for local_id in range(wg_size):组内线程“并行”循环,真实 GPU 中这几条线程同时执行。
  4. global_id = wg * wg_size + local_id全局编号计算公式,把“第几组第几个”换算成全场的唯一工号。
  5. output[global_id] = a[global_id] + b[global_id]每个线程执行的核心任务:读两个数、相加、写回。
  6. trace.append((wg, local_id, global_id))记录执行轨迹,方便观察线程与数据的映射关系。

常见误区

误区我以为计算着色器必须跟随顶点着色器和片元着色器一起使用,没有渲染目标就运行不了。

正解计算着色器独立于渲染管线,与画图完全解耦。它不需要顶点缓冲、不需要渲染目标,只要建好计算管线、配好绑定组、调用 dispatch 就能运行。它和渲染着色器是 GPU 里两条平行的流水线,就像“炒菜车间”和“摆盘车间”各干各的,互不依赖。

为何常见:名字带“着色”(Shader)让人误以为只用于绘图,实际上 Shader 在 GPU 语境里泛指“可编程计算单元执行的程序”,计算着色器正是其中的通用工种。

误区每个工作组放 1024 个线程,一次处理更多数据,肯定比设 64 个线程快。

正解工作组尺寸受硬件硬上限约束(每维通常 ≤ 256,总数受 maxComputeInvocationsPerWorkgroup 限制)。超过限制直接编译失败;即使没超限,过大的工作组也会降低调度灵活性、挤占共享内存。工程上常用 64/128/256,并且要结合共享内存容量一起调优。

为何常见:把 CPU 里“线程越多越快”的直觉照搬到 GPU,忽略了 GPU 的调度单元是 warp/子组,以及共享内存和寄存器的容量瓶颈。

误区都是缓冲,把 AI 权重放进统一缓冲(uniform buffer)应该也一样能用。

正解统一缓冲有大小硬上限(约 64KB 量级)且只读,适合参数常量;存储缓冲(storage buffer)可读可写、容量可达 GB 级,适合权重和张量。两者的 usage 标志在创建时固定,互换要么报错,要么性能骤降。

为何常见:只看到“缓冲”两个字,没注意 WebGPU 对不同用途的缓冲设定了严格的大小、访问权和布局约束。

误区调用 dispatchWorkgroups 之后,马上读取缓冲就应该有数据。

正解GPU 执行是异步的,dispatch 只是把命令排进队列,命令还没执行完数据自然没就绪。必须用 mapAsync 并等待 Promise 回调,或用 fence 同步后才能安全回读;提前读可能拿到旧数据或触发校验错误。

为何常见:把 GPU 当成同步执行的普通函数,忘了它和 CPU 是两台隔着总线的设备,中间必然有一道“异步的墙”。

面试题

Q1. 计算着色器在 GPU 中的主要角色是什么?

单选题 · easy

  • A.负责三维模型的绘制
  • B.负责通用并行计算← 正确答案
  • C.负责网络请求的发送
  • D.负责页面样式的渲染

答案:B。计算着色器是 GPU 上“帮厨团队”执行通用并行计算的程序,与画图无关。A 是顶点着色器的职责,D 是浏览器渲染引擎的职责,C 是网络层的工作,都不属于计算着色器。

Q2. 在 WGSL 中,计算着色器入口函数使用哪个属性标记?

单选题 · easy

  • A.使用 vertex 标记入口
  • B.使用 fragment 标记入口
  • C.使用 compute 标记入口← 正确答案
  • D.使用 dispatch 标记入口

答案:C。WGSL 中计算着色器入口函数用 @compute 标记,相当于给工序卡盖上“批量加工”章。@vertex 和 @fragment 属于渲染着色器,@dispatch 不是 WGSL 属性,派发动作发生在 JavaScript 侧。

Q3. 全局线程编号 global_invocation_id 的计算关系是什么?

单选题 · mid

  • A.组编号乘组内线程数加组内编号← 正确答案
  • B.组编号加组内线程数乘组内编号
  • C.组编号减组内编号再乘线程数
  • D.组编号与组内编号取最大公约数

答案:A。全局编号 = 组编号 × 组内线程数 + 组内编号,像“第 3 组第 5 个帮厨”换算成全场第 3×S+5 号。B 颠倒了运算顺序,会把不同组的帮厨算到同一编号,C/D 是干扰项。

Q4. 创建 GPU 计算管线的 WebGPU API 是哪个?

单选题 · mid

  • A.createComputePipeline← 正确答案
  • B.createRenderPipeline
  • C.createBindGroupLayout
  • D.createShaderModule

答案:A。createComputePipeline 把编译好的着色器模块组装成可派发的计算管线。B 创建的是渲染管线,C 只描述绑定组布局,D 只完成着色器编译,都没有真正创建计算管线。

Q5. dispatchWorkgroups(x, y, z) 中的参数含义是什么?

单选题 · mid

  • A.每个工作组内的线程数
  • B.三个维度派发的工作组数← 正确答案
  • C.缓冲区中的元素总个数
  • D.顶点缓冲的顶点个数

答案:B。dispatchWorkgroups 的参数是每维派发的工作组数量,不是线程数;线程数由 @workgroup_size 决定,二者相乘才是总线程数。A 描述的正是 workgroup_size,C/D 与派发参数无关。

Q6. 从 GPU 缓冲回读计算结果到 CPU 时使用的 API 是?

单选题 · easy

  • A.mapAsync 异步映射← 正确答案
  • B.dispatch 派发任务
  • C.createView 创建视图
  • D.draw 绘制图形

答案:A。mapAsync 把 GPU 冷库里的缓冲映射到 CPU 可读的内存地址,异步回调中就绪后可安全取料。dispatch 只是派活命令,createView 创建纹理视图,draw 属于渲染流程,回读结果都不靠它们。

Q7. 相比统一缓冲,存储缓冲(storage buffer)的突出特点是什么?

单选题 · hard

  • A.只能存放只读常量
  • B.支持大块数据读写← 正确答案
  • C.每帧必须重新上传
  • D.只能存放顶点数据

答案:B。存储缓冲可读可写、容量可达 GB 级,适合存放 AI 权重等大块数据;统一缓冲只读且有约 64KB 硬上限。A 描述的是统一缓冲的特点,C 是常见误解,D 把缓冲和顶点属性输入混淆了。

Q8. 以下哪些属于 WGSL 计算着色器的内置变量?

多选题 · mid

  • A.全局调用编号变量← 正确答案
  • B.局部调用编号变量← 正确答案
  • C.工作组编号变量← 正确答案
  • D.顶点位置编号变量

答案:ABC。global_invocation_id(全局调用编号)、local_invocation_id(局部调用编号)、workgroup_id(工作组编号)都是 WGSL 发给线程的“工牌编号”,分别对应全局号、组内号、组号。顶点位置来源于渲染管线的顶点输入,不是计算着色器内置变量。

Q9. 下列哪几项能有效提升计算着色器性能?

多选题 · hard

  • A.合并连续内存访问← 正确答案
  • B.提升线程占用率← 正确答案
  • C.计算与渲染重叠执行← 正确答案
  • D.把工作组分得越来越大

答案:ABC。合并内存访问让相邻线程读相邻地址,一次取回整段数据;高占用率让核心不容易空转;计算与渲染重叠让 GPU 闲时也有活干。D 违背工作组尺寸的硬件上限,只会导致编译失败或调度效率下降。

Q10. 工作组共享内存的典型用途有哪些?

多选题 · hard

  • A.组内线程快速交换中间数据← 正确答案
  • B.减少访问全局显存的延迟← 正确答案
  • C.用于矩阵分块计算的数据重用← 正确答案
  • D.完全替代存储缓冲的功能

答案:ABC。共享内存像组内一口热锅,速度比全局显存快得多,适合组内线程换料和复用数据,矩阵分块计算正是靠它反复读取同一块数据而提速。但它的容量极小,无法替代存储缓冲,D 错。