中EN
概念页

多线程渲染与任务系统

Multithreaded Rendering & Task Systems

CPU 时钟频率约在 2003 年停在 3.4 GHz 附近后,性能来自多核。应用阶段的优化一是让代码与内存访问更缓存友好,二是利用多核:多处理器流水线(时间并行,提高吞吐但增加延迟)、并行处理(空间并行,延迟更低)以及两者结合的任务系统;图形 API 用命令缓冲让多个线程并行录制绘制命令,新一代 API(DirectX 12、Vulkan、Metal)把更多责任交给应用以降低驱动开销。

应用阶段与内存(18.4.1 节)

  • 用性能分析器找热点(多为每帧执行多次的内部循环);多试编译器优化选项和不同编译器,不要预设哪个选项更快(更激进的循环优化可能更慢)。
  • 内存墙:1980–2005 年 CPU 性能约每两年翻倍,DRAM 约每六年翻倍(冯·诺依曼瓶颈)。如今关键是内存访问模式,缓存访问模式可造成数量级的性能差异;在 GPU 上速度与功耗与数据移动距离成正比。缓存利用空间局部性和时间局部性。存储层次从寄存器(约 1 周期)、L1/L2/L3、DRAM、SSD、硬盘到云存储,每降一级延迟显著增加。
建议说明
顺序访问的数据顺序存储例如按访问顺序交错存放纹理坐标、法线、颜色、顶点
关键代码避免指针间接、跳转和函数调用分支预测与缓存预取对二叉树、链表、图失效,尽量用数组;McVoy 与 Staelin 的链表例子让 CPU 停顿超过百倍;把指针树扁平化为带跳跃指针的列表、用 van Emde Boas 布局、用高分支度树减少深度
数据结构对齐缓存行Intel、AMD 常见 64 字节缓存行;VTune、CodeAnalyst、Instruments、Valgrind 可帮助定位;对齐也影响 GPU 着色器性能
尝试不同数据组织结构数组 (AoS) Vertex {float x,y,z;}、数组结构 (SoA) {float x[1000],y[1000],z[1000];} 便于 SIMD,但数组变大时缓存未命中增加;混合 {float x[4],y[4],z[4];} 的 250 个元素可能最好
内存池启动时为同尺寸对象分配大池并自管分配释放,连续记录更缓存一致;但 C#、Java 等有垃圾回收的语言中池可能更慢
渲染循环内不分配 / 释放内存预先分配临时空间,栈和数组只增长,用标记表示已删除元素

两种并行方式(18.5 节)

实时渲染常用共享内存的对称多处理 (SMP),例如多核 PC。

flowchart LR
  subgraph "多处理器流水线"
    I1["输入"] --> S1["阶段1 CPU1"] --> S2["阶段2 CPU2"] --> S3["阶段3 CPU3"] --> O1["输出"]
  end
  subgraph "并行处理"
    I2["输入"] --> P1[CPU1]
    I2 --> P2[CPU2]
    I2 --> P3[CPU3]
    P1 & P2 & P3 --> O2["输出"]
  end

上图上半是时间并行,下半是空间并行;二者理想加速都是线性的 nn 倍。

多处理器流水线(18.5.1 节):把应用阶段分为粗粒度的 APP、CULL、DRAW 三段。APP 控制其他阶段,做碰撞检测、更新视点等;CULL 做场景图遍历与层次视锥剔除、LOD 选择、状态排序,并总是生成待渲染物体列表;DRAW 遍历列表发出所有图形调用。单核时三段都在一核;双核时按负载把 APP+CULL 与 DRAW 分开,或 APP 与 CULL+DRAW 分开;三核时各占一核。

  • 优点:吞吐(帧率)提高,只在切换帧时同步。缺点:延迟更大。延迟是从采集用户输入到最终图像的时间,不同于帧率:例如头显位置 10 ms 到达 CPU、渲染 15 ms,帧率 66.7 Hz 但延迟 25 ms,不做预测补偿会感觉迟钝。延迟随流水线阶段数增加。
  • 降低延迟:在 APP 末尾才更新视点等延迟敏感参数(约减少一帧);让 CULL 与 DRAW 通过 FIFO 重叠执行(满则 CULL 停顿,空则 DRAW 饥饿),代价是状态排序无法充分进行。

并行处理(18.5.2 节):飞行模拟、第一人称射击、VR 等无法接受长延迟。静态分配把工作分成 nn 份,要求负载高度可预测;动态分配用工作池,空闲 CPU 取任务,必须保证一个任务只被一个 CPU 取走、维护队列开销不能太大;任务越大队列开销越小,但太大会负载不均。例:每帧工作 30 ms、三个 CPU 线性加速,流水线每 10 ms 出一帧(100 FPS)但延迟 30 ms;并行处理延迟和每帧时间都是 10 ms。但"视情况而定":若从 30 FPS、1 帧延迟变为 60 FPS、2 帧延迟,多出的一帧延迟并无可感知差别。

任务系统(18.5.3 节)

核心多时,趋势是任务 (job) 式多处理:任何核心生成的任务都放入工作池,空闲处理器领取,从而同时容纳流水线与并行。做法是先理清各系统间依赖(如 Frostbite 的 CPU 任务图),再把系统分解为任务。好的任务候选:输入输出明确;运行时独立、无状态、总会完成;不至于大到经常成为唯一在运行的任务。C++11 内置多线程支持,Intel TBB 简化任务生成、池化与同步。坑:处理器等待同步会停顿,加上任务管理开销,任务化版本甚至可能更慢。

  • 模拟、碰撞检测、遮挡测试、寻路等应自建任务并行。阴影贴图生成和深度预 pass 时 GPU 着色单元常闲置,可安排计算着色器任务;Lauritzen 认为由于硬件差异和语言限制,写出又快又可移植的计算着色器并不现实。

图形 API 的多线程支持(18.5.4 节)

  • DirectX 10 及更早只允许一个线程访问驱动。驱动中可并行的两类操作:资源创建(纯 CPU 时天然可并行,但创建删除也可能阻塞)和渲染相关调用。
  • 命令缓冲 (CB):状态切换和绘制调用的列表,源于 OpenGL 的显示列表,可创建、存储、重放、拼接。只有一个线程通过驱动把 CB 提交给 GPU,但所有线程都能并行创建或拼接 CB。DirectX 11 中该线程使用立即上下文,其他线程用延迟上下文生成 CB;只有立即上下文能做 GPU 查询和回读。
flowchart LR
  T2["线程2 延迟上下文: 录制 CB G"] --> M["线程1 立即上下文: 依次执行 CB A, M, T, G, N 后 Present"]
  T3["线程3 延迟上下文: 录制 CB M, N"] --> M
  T4["线程4 延迟上下文: 录制 CB T"] --> M
  T1["线程1 自己录制 CB A"] --> M

如上图,线程 1 在等待线程 3 的 CB N 时还能做其他工作。CB 创建时并未完全绑定:例如视图矩阵放在常量缓冲中,CB 只存引用,相机移动时改常量缓冲内容即可,无需重建 CB。并行度取决于按视图、物体还是材质为粒度创建和组合 CB。

  • 取舍:CB 本身有创建和内存开销;DirectX 11、OpenGL 中把 API 状态映射到 GPU 仍很贵,应用是瓶颈时 CB 有帮助,驱动是瓶颈时反而有害。新 API 提供贴合现代 GPU 的轻薄提交接口,命令缓冲管理、内存分配和同步由应用负责,CB 只在创建时验证一次,重放开销更低,从而降低驱动成为瓶颈的可能。

GEA 的补充:并发编程底层(卷 I 第 4 章)

  • RTR4 这里只讲到任务、同步开销和命令缓冲;GEA 第 4 章补齐了任务系统之下的底层:硬件并行分类与指令流水线(并发与并行基础)、线程与纤程(GEA 提到实现引擎任务系统时可用纤程做协作式调度,见 进程、线程与纤程)、互斥锁等同步原语及其内核调用开销(线程同步原语)、死锁与优先级反转(基于锁的并发问题)、原子操作与内存栅栏(无锁编程与内存序)以及 SIMD(SIMD 向量化)。
  • 「并行」一词两书用法不同:RTR4 18.5 节的「多处理器流水线 / 并行处理」指把渲染应用阶段分给多个 CPU(时间并行 vs 空间并行);GEA 4.1–4.2 的「流水线」指 CPU 内部的取指-译码-执行-访存-写回指令流水线,「任务并行 / 数据并行」指按异构操作还是按数据分工。阅读时注意区分层次。
  • 两书都提醒同步会吃掉并行收益:RTR4 说任务化版本可能因等待同步和管理开销反而更慢;GEA 说内核同步原语一次上下文切换可超过 1000 个时钟周期,主张少用锁、减少争用。

PBRT 的视角:离线光线追踪器的分块并行(PBRT 4e 1.3.4、1.5.10)

  • 分块与波次:ParallelFor2D() 按图像范围和处理器数自动选块大小。块要明显多于核数,因为各块耗时差异很大(书中图 1.17 的直方图),一核一块会让先做完的核闲着;块太多又要反复付"领取任务"的固定开销。前两波每像素各 1 个样本,之后每波翻倍并设上限(capped doubling),在预览和线程空闲之间折中。
  • 线程局部状态:ScratchBuffer、Sampler、RNG 都带可变状态,加锁代价过高,于是每线程一份(ThreadLocal、Sampler::Clone())。
  • 线程安全约定:渲染期绝大部分数据只读,可以无锁并发读取;Point3f、Transform 等底层类不保证线程安全;表 1.1 中各基础接口的实现要求线程安全,例外有两个:只在场景构建时由单线程调用的 Light::Preprocess(),以及 Sampler(有状态、不要求线程安全,所以每个渲染线程用 Clone() 各持一份);独立函数都线程安全(只要不同线程不传同一块数据的指针)。
  • 可重现调试:每个像素样本的执行是确定性的,崩溃时会打印类似 --debugstart 16,27,821 的参数,可直接从该像素样本重新开始,而不必再等几小时。 与本页游戏引擎做法的差别:游戏的任务系统要在每帧固定预算内调度异构任务,PBRT 面对的是几乎同质、彼此独立的像素样本,只需要解决负载不均衡这一个问题。

PBRT 的视角:线程池、ParallelFor 与统计(PBRT 4e B.6.4–B.7)

  • Amdahl 定律:串行比例为 ss、nn 核时最大加速比为 1/(s+(1−s)/n)1/(s+(1-s)/n),上限 1/s1/s;5% 的串行(解析场景、建加速结构)就把加速比封顶在 20 倍。作者在 GPU 路径上就遇到过解析和准备场景比渲染还慢的情况,因此并行化了场景解析与对象创建。
  • 线程池:InitPBRT() 创建线程池(通常每核一个),主线程也参与工作,所以只另启 n−1n-1 个线程。任务实现 ParallelJob 接口(HaveWork()、RunStep()),挂在一个由互斥锁保护的双向链表上;新任务插到表头,嵌套产生的任务先执行,相当于深度优先,避免任务数爆炸。RunStep() 在锁内领取一段工作、解锁后执行;没有工作时在条件变量上等待。
  • ParallelFor():按块 (chunk) 分发循环迭代,块大小与线程数成反比,在"抢锁次数"和"负载均衡"之间折中;所有工作都已领取后立刻把任务移出链表;调用线程自己也执行迭代,直到循环完成才返回,因此循环对象可以放在栈上。
  • RunAsync()/AsyncJob:用 std::bind 绑定参数,返回的 AsyncJob 用 std::optional 存结果;Wait() 期间会通过 DoParallelWork() 帮忙做线程池中的其他工作,而不是空等。任务系统与工作窃取可参考 Cilk(Blumofe 等 1996、1999)。
  • ThreadLocal<T>:固定大小的哈希表,按需为每个线程创建对象(如每线程的 Sampler、ScratchBuffer)。
  • 统计系统(B.7):STAT_COUNTER 等宏定义 thread_local 计数器,并注册一个 lambda;结束时用 ForEachThread() 让每个线程把自己的计数汇总。作者实测改用原子计数器会让渲染慢 10–15%。另有 STAT_PIXEL_COUNTER 可以输出每像素统计图(--pixelstats)。
  1. realtime_rendering_4ed.pdf 第 18 章 Pipeline Optimization
  2. Game_Engine_Architecture_4Ed_-_Jason_Gregory.pdf
  3. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 I 第 4 章
  4. PBRT 4e source excerpt: chapter 01 (Introduction)
  5. Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)第 1 章 1.3.4、1.5.10 节,pbr-book.org/4ed/Introduction
  6. PBRT 4e source excerpt: chapter B (Utilities)
  7. Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)附录 B B.2–B.7 节与延伸阅读,pbr-book.org/4ed/Utilities

相关页面

图形学渲染瓶颈定位与性能度量Bottleneck Location & Performance Measurement渲染管线中最慢的阶段(瓶颈)决定整体吞吐量。优化就是"找到瓶颈 → 优化到瓶颈转移 → 重复",并让非瓶颈阶段做到与瓶颈同样多的工作以换取画质。定位靠逐阶段增减负载的实验和性能工具;度量要用毫秒帧时间而不是 FPS。两条箴言:了解你的架构 (KNOW YOUR ARCHITECTURE);测量、测量、再测量。图形学绘制调用与状态切换优化Draw Calls & State Change Optimization调用图形 API 的开销主要在 CPU 端的驱动里:状态切换昂贵,每次绘制调用有固定开销,大量小批次会让 GPU"饿死"。对策是:合理放置缓冲;按状态代价排序分组;共享纹理和着色器;合并网格和实例化以减少调用;并用一个整数排序键同时兼顾状态分组与前后排序。图形学渲染管线Rendering Pipeline渲染管线是实时图形的核心:给定虚拟相机、三维物体、光源等,把场景转换成一张二维图像。RTR4 把它粗分为四个功能阶段:应用、几何处理、光栅化、像素处理,各阶段并行执行,整体速度由最慢的阶段决定。图形学计算着色器Compute Shader计算着色器是 DirectX 11 引入的一种 GPU 计算方式:它不固定在图形管线的某个位置,但由图形 API 调用,和其他着色器共用统一着色器核心。它暴露了线程组和组内共享内存,最大的优势是能直接处理 GPU 上生成的数据,不用来回传给 CPU。图形学实时渲染Real-Time Rendering实时渲染是在计算机上快速生成图像、并让图像随用户输入即时变化的技术。RTR4 认为它由三个要素共同定义:交互性、三维空间感,以及图形加速硬件 (GPU)。图形学时间扭曲与异步重投影Timewarp & Asynchronous ReprojectionVR 中即使追踪完美,生成一帧仍需时间;掉帧会造成抖动 (judder)。厂商提供基于图像的补偿:只修正头部旋转的二维时间扭曲 (timewarp)、利用深度做三维重投影的位置时间扭曲、外推缺失帧的空间扭曲 (spacewarp),并常以异步方式运行作为掉帧保险。配合晚期朝向扭曲、late latching 和 CPU/GPU 流水线(running start / adaptive queue ahead),在不掉帧的前提下进一步缩短动作到光子的延迟。游戏开发 · UE5委托与 Tick 机制Delegates & TickUE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。游戏开发 · UE5模块、插件与构建系统Modules, Plugins & Build SystemUE 的代码由模块 (Module) 组成:每个模块是一个独立编译单元,用 [模块名].Build.cs 声明依赖。插件 (Plugin) 是可按项目启用/禁用的代码与内容集合,用 .uplugin 描述,可包含多个模块。虚幻构建工具 (UnrealBuildTool, UBT) 根据 .Target.cs 和 .Build.cs(而不是 IDE 的 .sln)编译,默认把模块编成 DLL 由一个可执行文件加载,也可整体链接成单体 (Monolithic) 可执行文件。游戏开发 · UE5MassEntity 数据导向框架MassEntity FrameworkMassEntity 是 UE5 面向玩法的数据导向 (Data-Oriented) 计算框架,属于实体-组件-系统 (Entity Component System, ECS) 一类设计:实体 (Entity) 只是一个 ID 加上一组片段 (Fragment) 数据,相同组成的实体归入同一原型 (Archetype) 并按内存块 (Chunk) 连续存放,无状态的处理器 (Processor) 通过实体查询 (EntityQuery) 批量处理片段。它适合成千上万个同质对象(人群、交通),MassGameplay 插件在其上提供表现、生成、LOD、复制、StateTree 等功能。游戏开发 · UE5垃圾回收与对象生命周期Garbage Collection & Object LifecycleUE 用标记-清除式的可达性分析管理 UObject 内存:从根集合 (Root Set) 出发沿反射可见的强引用遍历对象图,找不到的对象在下次垃圾回收 (Garbage Collection, GC) 时被删除。因此“谁持有 UPROPERTY 引用”决定了对象活多久;Actor 调用 Destroy() 只是请求销毁,真正释放仍取决于引用是否全部放开。游戏开发 · UE5UE5 性能分析UE5 ProfilingUE5 性能分析的基本流程是:先用 stat unit 判断瓶颈在游戏线程、渲染线程还是 GPU,再用对应的 stat 组、profilegpu/GPU 统计或 Unreal Insights 的 Timing/Memory 等视图向下钻取;渲染特性(RDG、VSM、TSR)各自提供作用域和可视化。测量时要避开会扭曲数据的设置(调试构建、异步计算、可视化模式)。游戏开发 · UE5UE5 渲染架构UE5 Rendering ArchitectureUE5 的渲染器是独立的 Renderer 模块,在渲染线程 (Rendering Thread) 上以「场景代理」镜像游戏线程的组件状态,每帧按固定顺序执行深度预通道、G-buffer 基础通道、光照、雾、半透明和后处理。现代渲染代码用渲染依赖图 (Render Dependency Graph, RDG) 声明通道与资源,由 RDG 自动排屏障、复用瞬态内存、并行录制;最终命令经 RHI 线程翻译成 D3D12/Vulkan 等图形 API 调用。默认走延迟着色 (Deferred Shading),也可切换前向着色 (Forward Shading)。综合帧时间预算与渲染性能Frame Budget & Rendering Performance本页把 wiki 中分散在 RTR4 第 18~23 章的性能知识串成一条线:一帧的时间预算由最慢的管线阶段决定,先用毫秒帧时间度量并定位瓶颈,再按瓶颈所在位置——CPU/驱动、几何、像素、带宽/内存——选用对应手段;最后从 GPU 硬件机制(占用率、HiZ/early-z、压缩、分块架构)理解这些手段为何有效。各结论的细节与出处见所链接的概念页。图形学并行图形架构与排序分类Parallel Graphics Architectures & Sort Taxonomy提升图形速度的最佳方式是并行:多个几何单元、光栅化单元和像素处理单元同时工作再合并结果。Amdahl 定律指出串行部分限制总加速比。由于 API 要求按提交顺序处理图元,并行单元之间必须在某处做"模型空间到屏幕空间"的排序,按排序位置分为 sort-first、sort-middle、sort-last fragment、sort-last image 四类,各有负载均衡与带宽取舍。本页还简述光线追踪硬件的共同特征。游戏开发 · UE5任务系统Tasks SystemUE::Tasks 是 UE5 的任务级并行库,是对 TaskGraph 的改进,与 TaskGraph 共用同一套调度器和工作线程。它支持启动任务、等待或取结果、声明前置依赖、嵌套任务、管道 (Pipe) 和任务事件。官方建议用依赖关系把工作连成图,而不是阻塞等待;5.5 起用「超额订阅」取代了容易死锁的忙等待。游戏开发数据局部性Data Locality数据局部性 (Data Locality) 是一种优化思路:CPU 读取内存时会把相邻的一整条缓存行一起载入,所以把数据按处理顺序连续存放,就能大幅减少缓存未命中。具体手段包括:组件按类型存进连续数组,不再到处跟随指针;把活跃对象排在前面;把冷热数据拆开。代价是要放弃一部分继承、接口与指针带来的抽象。综合游戏编程模式选型:解耦与性能Choosing Game Programming Patterns本页把《Game Programming Patterns》里的 19 个模式放到同一张取舍图上:大多数模式要么为了解耦(降低改代码时需要装进脑子里的知识量),要么为了性能(利用对数据和硬件的具体假设),而这两个目标经常互相拉扯。下面先讲全书的定位和 Nystrom 的架构观,再给出按问题选模式的路线和与 UE 的对照。游戏开发计算机硬件与内存层次Computer Hardware & Memory Hierarchy从冯·诺依曼结构讲起,依次是 CPU 的组成(ALU、VPU、寄存器、控制单元、时钟)、总线与字长、机器语言、汇编和寻址方式,再到内存体系:内存映射、虚拟内存和 TLB,以及为了缩小“内存鸿沟”而设计的寄存器文件、多级缓存(缓存行、标签、组相联、替换和写策略、一致性协议)和 NUMA(PS3 的 SPU 本地存储、PS2 的 scratchpad)。对游戏程序员最重要的结论是:数据要连续、紧凑、顺序访问;热循环的代码要小。游戏开发并发与并行基础Concurrency & Parallelism FundamentalsGEA 区分两个正交的概念:并发 (Concurrency) 是软件层面多个控制流读写共享数据,核心难题是数据竞争;并行 (Parallelism) 是硬件层面两个以上部件同时工作。硬件并行又分为提升单线程速度的隐式并行(流水线、超标量、VLIW,即指令级并行 ILP)和为同时运行多条指令流而设计的显式并行(超线程、多核、多处理器、集群/云)。游戏开发线程同步原语Thread Synchronization Primitives并发编程的中心问题是数据竞争 (Data Race):多个控制流交错地读改写同一共享对象。解决思路是让同一对象上的关键操作具备原子性(看起来瞬间完成,从而被串行化)。最简单可靠的工具是操作系统提供的线程同步原语:互斥锁、临界区、条件变量、信号量、Windows 事件。它们好用但昂贵(需要内核调用,上下文切换可超过 1000 个时钟周期)。游戏开发SIMD 向量化SIMD VectorizationSIMD(单指令多数据)用一条机器指令并行处理多个数据。x86 上从 MMX 发展到 SSE(128 位,4 个 float)、AVX(256 位,8 个)和 AVX-512(512 位,16 个)。GEA 用 SSE 内建函数演示:数组相加、点积、向量×矩阵、矩阵×矩阵,强调两点——避免寄存器内的水平相加(先转置数据),以及把 SIMD 寄存器看成 N 条并行的「通道 (lane)」而不只是一个 4D 向量;条件分支则用向量谓词化 (vector predication / select) 代替。游戏开发自定义内存分配器Custom Memory Allocators内存从两方面影响性能:动态分配(malloc / 全局 new)很慢,以及访问模式主导现代 CPU 的性能。GEA 的经验法则是「尽量少做堆分配,绝不在紧密循环里做堆分配」,并介绍游戏引擎常用的自定义分配器:栈分配器(含双端栈)、池分配器、对齐分配、单帧与双缓冲分配器;以及如何用栈/池避免碎片,或用碎片整理加指针重定位(智能指针、句柄)解决碎片。游戏开发作业系统Job System多核硬件上,游戏循环的工作要分解成可并行执行的子任务。GEA 依次比较「每子系统一个线程」「分散/汇集 (scatter/gather)」和通用的作业系统 (job system),并给出典型作业 API、基于线程池的简单实现及其局限、用协程或纤程让作业可以睡眠、作业计数器、作业同步原语与可视化剖析工具,最后介绍 Naughty Dog 基于纤程的作业系统。(卷 I 8.6 节)图形学图形 API 设备、管线状态与资源Graphics API Devices, Pipeline State & Resources所有图形 API(OpenGL、DirectX、Vulkan、Metal)都围绕同一组概念:代表 GPU 的设备、描述各阶段配置的管线状态、承载数据的资源及其视图。GEA 把 API 分成高层(OpenGL、D3D11 及更早)和底层(D3D12、Vulkan、Metal、PlayStation 的 Gnm),并介绍 D3D11 的特性级别、设备上下文与延迟上下文、D3D12 的命令列表、PSO 和根签名。本页按 GEA 的整理讲这些跨 API 的共同概念,不是任何一个 API 的教程。综合游戏中的内存管理Memory Management in Games本页把 wiki 中关于内存的内容串成一条线:内存从两方面影响游戏,一是访问模式(缓存未命中远比计算昂贵),二是分配本身(通用堆分配慢且产生碎片,而主机上没有自动换页的虚拟内存兜底)。对策按生命周期组织:常驻与关卡数据用栈、每帧临时数据用单帧/双缓冲分配器、同尺寸小对象用池或小内存分配器、大小不一的动态对象用堆加碎片整理与句柄;资源按引用计数和分块加载管理,流送系统把世界切成等大的块;UE 则在 UObject 层用垃圾回收管理对象生命周期。最后,没有准确的追踪工具,以上一切都无从验证。各结论的细节与出处见所链接的概念页。游戏开发基于锁的并发问题Problems with Lock-Based Concurrency即使所有共享数据操作都用锁保护好了,基于锁的并发仍有四类问题:死锁 (Deadlock)、活锁 (Livelock)、饥饿 (Starvation)、优先级反转 (Priority Inversion)。哲学家就餐问题集中体现了它们。GEA 由此总结出经验法则:全局排序、事务式算法、尽量减少争用;并反对把所有接口都做成「100% 线程安全」,主张正视并发、少用锁。游戏开发无锁编程与内存序Lock-Free Programming & Memory Ordering「无锁 (lock-free)」的真正含义不是不用互斥锁,而是从不让线程阻塞(作者说叫 block-free 更贴切)。数据竞争有三个来源:关键操作被打断、编译器与 CPU 的指令重排、缓存一致性协议优化造成的内存序问题。对应的工具是原子指令(TAS、XCHG、CAS、LL/SC)和内存屏障/栅栏(acquire、release、full fence),C++11 用 std::atomic 与 std::memoryorder 把它们封装成可移植接口。在此基础上可以写出自旋锁、可重入锁、读写锁,以及基于「本地准备 + 一次原子提交」事务的无锁数据结构。游戏开发进程、线程与纤程Processes, Threads & Fibers并发编程依赖操作系统提供的服务:内核态与用户态、中断、系统调用、抢占式多任务。进程只是为线程提供运行环境(虚拟地址空间与资源),线程才是操作系统的基本执行单位(调用栈 + 寄存器)。线程由内核抢占调度;纤程 (Fiber) 由程序协作调度,适合实现引擎任务系统;用户级线程与协程完全在用户空间切换上下文,更轻量。游戏开发对象池Object Pool对象池 (Object Pool) 预先一次性分配一组可复用对象,需要时从池里取一个未使用的对象并初始化,用完标记为未使用,而不是逐个 new/delete。它能避免频繁分配的开销和堆碎片,常用于粒子、子弹、正在播放的声音等。代价是池大小要调校、同时活跃的数量有上限、复用的对象不会被自动清零,在有 GC 的环境里还可能让本该回收的对象一直活着。图形学空间数据结构Spatial Data Structures空间数据结构把几何体按 n 维空间组织起来,通常是层次化的树,使"几何体是否重叠"一类查询从 O(n) 降到大约 O(\log n)。常见类型有包围体层次 (BVH)、BSP 树(轴对齐的 k-d 树与多边形对齐两种)、四叉树/八叉树(含松散八叉树),以及更偏向组织模型关系的场景图。它们是剔除、光线求交、碰撞检测等加速算法的基础。游戏开发 · UE5资产管理与异步加载Asset Management & Async LoadingUE 资产默认按引用自动加载:硬引用 (Hard Reference) 会让被引用资产随引用者一起加载,不加控制就可能在启动时加载全部内容。软引用 (Soft Reference,TSoftObjectPtr/FSoftObjectPath) 只存路径,配合流式管理器 (Streamable Manager) 异步按需加载;资产管理器 (Asset Manager) 进一步把内容划分为主资产 (Primary Asset) 和次资产 (Secondary Asset),用资产包 (Asset Bundle) 管理加载、卸载、烘焙分块与内存审计。

被这些页面引用1 篇

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原