这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《游戏引擎实现原理》7.1 动态数组与开放寻址哈希
从零实现 helio 的两个基础容器:增长因子可配的动态数组,以及用 FNV-1a 加线性探测、Robin Hood 抢占与墓碑删除的开放寻址哈希表。给出 1.25×/1.5×/2× 三档因子的扩容与搬运实测,以及探测步数分布和墓碑退化数据,全部本机跑出。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
从零实现 helio 的两个基础容器:增长因子可配的动态数组,以及用 FNV-1a 加线性探测、Robin Hood 抢占与墓碑删除的开放寻址哈希表。给出 1.25×/1.5×/2× 三档因子的扩容与搬运实测,以及探测步数分布和墓碑退化数据,全部本机跑出。
把实体的创建、销毁、句柄复用收进一个实体注册表,复用第 6 章的世代句柄:删除实体后旧句柄立即失效,新实体沿用同一槽位却拿到新世代。给出可编译实现、断言用例、复用与不复用的实测对比,以及世代回绕撞上 NULL_HANDLE 这个真实边界与应对办法。
把「每种组件一个稀疏集」和「按组件集合分组的 archetype」两种组件存储都从零实现出来,用同一组操作对比:建 20 万个实体的耗时与堆净增量、以及查询并原地更新的耗时,把本机实测的真实数字写进正文,并给出各自的取舍边界与可操作的选择判据。
把「让物体动起来」用继承、组合、数据三种方式各写一遍,对照它们各自的代价;再在 6.2 的世代句柄之上从零搭出一套最小 ECS,用可编译的 MiniEcs 落地实体、组件与查询三件套,并用 vitest 断言「销毁实体后旧句柄立即失效」这一关键行为。
把 for (const e of entities) e.update(dt) 这样一段面向对象的更新循环,一步步改造成按组件列分组的批处理。本节给出改造前后两份可编译的 helio 源码与一份基准,在本机实测出耗时约 1.5 倍、堆增量 16.6 MB 对 0 MB 的差距,并说明哪些场景根本不该改。
同样 1M 个元素,AoS(对象数组)与 SoA(平行 TypedArray)在本机实测出 22.94 ms 对 14.47 ms、约 1.59 倍的差距,但堆增量是 62.6 MB 对 0 MB——TypedArray 的底层 ArrayBuffer 属外部内存,不进 JS 堆。本节讲清三种布局的真实差异,并给出「什么时候不该用数据导向」的判据。
缓存层级决定了一次访存要等多久:L1 一个周期,主存上百个周期。本节用 1M 元素的连续遍历与指针追逐、以及 4096 方阵的行主序与列主序求和做对照,在本机实测出 2.2 倍与 6.2 倍的差距,并说清在 V8 里数据导向的收益主要在分配与 GC 压力,而不在访存速度。
数值计算的另一类问题是边界而非精度:比较不能用等号、除法会炸出 Infinity、NaN 会顺着运算扩散。本节实现一组数值稳健工具——相对 epsilon 比较、saturate、clamp、safeDiv、sanitize、safeNormalize,并用 vitest 把每个边界行为钉成断言。
定点数把小数点钉死在固定位置,用纯整数运算换来跨平台逐位一致。本节实现 Q16.16:一个 int32 的低 16 位是小数、高 16 位是整数,乘法拆高低位避免溢出。实测同一段弹簧积分跑 10000 步:单精度与双精度相差 2.9e-7,而定点版本跑两遍结果逐位相同。
同一份浮点代码在不同平台上会算出不同结果,根源是加法不满足结合律、FMA 改变了舍入时机、单精度与双精度位宽不同。本节在 Node 里实测:(0.1+0.2)+0.3 与 0.1+(0.2+0.3) 相差 1.1e-16,一段积分跑 10 万步后单精度比双精度差了 0.2,有无 FMA 甚至能把 4.4e-16 变成 0。
体积云与大气散射是实时渲染中最具「物理感」的效果之一。本文从体积渲染方程出发,讲解 Raymarching 积分、Beer-Lambert 吸收定律、Henyey-Greenstein 与 Mie 相位函数、Precomputed Atmospheric Scattering 的 LUT 方案、Worley/Perlin 噪声构造云形,以及降采样、时间重投影、蓝噪声抖动等性能优化手段,给出可落地的工程实现路径。
凭直觉优化性能几乎总是错的。本节把「先测量、再优化」落成可执行的流程:用 --cpu-prof 与 --prof 采集 CPU 剖析、用自顶向下与自底向上两种视角读火焰图、用堆快照定位内存泄漏,并解释采样开销、JIT 预热、异步栈与 source map 四个让剖析失真的陷阱。读完你能独立定位热点函数并验证优化是否真的有效。
类型擦除不是一句「类型都没了」就能带过:本节逐条对照 .ts 与编译产物,看清哪些语法被彻底删除、哪些留下了运行时代码。重点讲 enum、namespace、参数属性、装饰器与类字段的真实编译结果,以及为什么 private 与 #private 在运行时完全不同。读完你能预判任意一段 TS 会编译成什么,并知道何时必须补运行时校验。
本节把 TypeScript 的类型声明放到一边,转而看真正执行代码的那台机器:V8 如何用 Ignition 采集类型反馈、用隐藏类与内联缓存加速属性访问,再由 TurboFan 做推测式优化并在假设破裂时去优化。读完你能读懂 --trace-deopt 日志,知道哪些写法会让函数永远留在解释器里,并写出对 JIT 友好的代码。
本节讨论契约改动之后的事:先给出破坏性变更的判定清单,把「加字段安全、改语义危险」这类直觉变成可检查的规则;再对比 URL、Header、内容协商三种版本化策略的代价;然后用 expand-contract 三步法演示字段改名如何做到不打断旧客户端;最后讲弃用头、下线指标与消费者驱动契约测试的落地方式。
本节讲清「schema 先行」这条契约路线的完整闭环:如何从 TypeScript 服务端实现自动导出 OpenAPI 文档,如何用 openapi-typescript 与 orval 把文档变成带类型的客户端 SDK,以及 GraphQL 如何借助 codegen 生成 TypedDocumentNode,最后给出三条路线的选型判据与 CI 门禁。
本节从前后端分离项目里最常见的契约漂移讲起,说明为什么手写接口类型守不住边界。随后完整走一遍 tRPC 的工程落地:初始化 router、用 context 传递请求级依赖、用 Zod 校验输入、用 middleware 做鉴权与日志,再把类型一路带到 React 客户端。最后给出 tRPC 的适用边界与常见坑,帮你在单体全栈与开放 API 之间做出有依据的选型。
拆开 TransactionSynchronizationManager 的线程绑定登记机制与 TransactionSynchronization 的九个回调,讲清 @TransactionalEventListener 四个 phase 各自挂在哪个回调上、为什么「提交后再发消息」必须靠它,并给出 REQUIRES_NEW 与监听器组合时的三个坑。
把 mvn package 产出的 fat jar 变成可缓存、可增量构建的镜像:讲清 Dockerfile 分层顺序为何要分开依赖层与应用层、4.1 用 jarmode=tools 提取分层(layertools 已移除)、build-image 与手写 Dockerfile 的取舍、Temurin JRE 21 基础镜像与非 root 运行。
事务注解写了却不生效,是入门阶段最费解的一类问题。本节把自调用绕过代理、方法非 public、异常被 try-catch 吞掉、rollbackFor 未配置、跨线程传播丢失、数据库引擎不支持等场景逐个拆开,给出原因与解法,并用一张速查表和日志排查法收尾。