6.2 多线程与岛分割


6.2 多线程与岛分割

本节摘要:用接触和关节当边、刚体当点,连通分量就是岛。岛内约束耦合,岛间无共享未知量,求解可并行。睡着的岛整岛跳过。线程按岛调度,接触数组按岛切片避免假共享。静态世界不进岛。唤醒沿边传播。先单线程分岛跳过睡眠,再开线程,否则你无法分辨是并行 bug 还是分岛 bug。

学习目标

阅读完本节,你应当能够:

  1. 用并查集或 BFS 从接触列表建岛
  2. 规定静态体、运动学体、关节如何入图
  3. 设计唤醒规则:接触、关节、射线击中
  4. 按岛并行求解时保证确定性或明确放弃

一、图怎么建

动态刚体是节点。每条接触、每条关节是无向边。静态体不是节点,但接触仍把动态体连到“地面”——地面不把不同动态体连在一起,除非你错误地把静态体当节点且所有静体是同一个。静体当同一个超级节点会把全图并成一座岛,分岛失败。正确:边只连两个动态体;与静体的接触不产生边,只挂在该动态体所属岛的约束列表里。

关节两端都是动态,必有边。运动学体:可当动态节点但不睡眠,或当移动的静体。电梯应唤醒站在上面的岛,所以电梯与乘客之间的接触要能唤醒,即使电梯自己不睡。

islands = connectedComponents(dynamicBodies, edgesFromContactsAndJoints) for each island: if allSleeping(island): skip solve(island.contacts, island.joints)

并查集在接触生成后扫一遍即可。物体几千,便宜。

规则 原因
静体不作为连通节点 否则全场景一座岛
关节必连边 否则链条一端动另一端睡
触发器接触不连边 否则传感器把无关物体焊在一岛
布料顶点可单独成岛系统 不要和刚体岛混用同一并查集

图 岛:堆与堆之间断开

图 岛:堆与堆之间断开

二、睡眠与唤醒

岛内所有动态体速度低于阈连续 N 帧,整岛睡:跳过积分和求解,宽相可简化为只对醒着的邻居。唤醒:新接触来自醒着的物体、关节电机启动、脚本施力、爆炸查询。唤醒必须沿关节边传播,否则铰链门一边醒一边睡,轴被撕开。

阈值要带滞回:睡的阈更低,以免边界振荡。爆炸用球体查询把区域内岛全部醒,不要指望接触下一帧才生成——睡着的宽相可能已经把它们摘出去了,要主动查。

💡 关键直觉:睡眠是最大的性能优化,比 SIMD 还大。开放世界里 90% 箱子在睡。错唤醒会把 CPU 打满,错不唤醒会穿模。

并行:每个岛一个任务。小岛合并成任务包,避免十万座单物体岛调度爆炸。单物体无接触岛积分很便宜,可以打包。大岛(倒塌楼房)单线程解,或岛内再着色分约束,那是进阶,第一版大岛单线程。

三、确定性、锁、假共享

线程完成顺序若影响 PGS 迭代顺序,结果会变。要确定性:岛内接触排序按 id 固定;岛之间本就不耦合,并行无妨。岛内再并行约束则必须固定颜色顺序。联机游戏:要么物理单线程,要么严格固定排序加定点数。7.2 会把这当成硬约束。

⚠️ 常见坑:多线程写同一刚体速度——发生在分岛错误,两边接触都指向同一刚体却分到两岛。单元测试:随机接触图,断言同一刚体只属于一岛。

宽相并行:按空间格子分,格子边界对要规则去重。比分岛更容易写错重复对。可以先串行宽相,并行只求解。宽相往往不是最热点。

双缓冲状态:求解写当前速度,渲染读上一帧快照。不要在求解未完成时插值。任务图:宽相 → 窄相(可并行对)→ 建岛 → 并行求解 → 写位置 → 睡眠判定。窄相对之间无写冲突如果接触输出槽预先分配。

测试:一千个分离的箱子应形成一千岛并很快全睡,CPU 接近空闲;推倒一个不应唤醒全图。用计数器显示“醒着的岛 / 总岛”。这是 7.1 要画的数字,6.2 就要打点。

关节跨很远两个堆(一根长杆),两堆变一岛。这是正确的。若不想,就不要用硬杆连它们。设计关卡时一根钢筋会让睡眠失效,是玩法问题也是性能问题。

线程数不超过物理核。超线程对这种不规则 gather 循环帮助有限。绑核可减轻迁移,通常不必第一步就做。

五、任务图与测试断言

任务图固定:更新 AABB → 宽相(可并行格子)→ 窄相写到预分配槽 → 建岛 → 并行求解岛 → 写位置 → 睡眠。窄相槽按下标写,不要 push_back。push 要锁,锁把并行吃掉。预分配 maxContacts,超了丢最浅的并打日志。

断言:每个动态体 islandId 唯一;接触两端若都是动态则 islandId 相同;关节两端相同。随机生成接触图跑一万次。失败就是分岛 bug,不要用“偶发抖动”蒙混。

调度:小于 8 个接触的岛打包到一个任务。大岛超过 512 接触单线程解,避免岛内再并行把确定性搞崩。线程数取物理核减一,留一核给渲染,或与引擎任务系统共享池,不要自己再创一套池抢核。

唤醒洪水:爆炸查询半径过大,全图醒来,帧时间炸。用层掩码限制可唤醒类型,碎片层可以不睡也可以睡得更狠提前删。开放世界爆炸是睡眠系统的压力测试,必须在大场景测,不要只在五只箱子的盒子里测睡眠。

实践问答

单物体岛要不要每个都当任务?

不要。十万任务调度会爆炸。打包小岛。大岛单线程。调度策略比线程数先定。

爆炸唤醒洪水怎么限制?

查询半径和层掩码。碎片可提前删。必须在大场景测,五只箱子测不出洪水。

宽相也并行吗?

可以按格,边界去重要小心。第一版串行宽相、并行求解。宽相往往不是最热点。先对的并行比全面并行重要。

同一刚体两岛的测试怎么写?

随机图加断言。失败就是分岛 bug。不要等“偶发抖动”才查。这是单元测试,不是玩法测试。

确定性与岛并行如何共存?

岛间无耦合,并行无妨。岛内顺序按 id 固定。岛内再并行约束必须着色且顺序固定。联机写进协议。

参数扫描笔记:岛并行

扫描 1:窄相 push_back 加锁

并行被吃。预分配槽按下标写。

扫描 2:超限接触不打日志

静默丢接触难查。丢最浅的并记录。

扫描 3:大岛再并行约束无着色

确定性崩。第一版大岛单线程。

扫描 4:超线程拉满

不规则 gather 帮助有限。核减一留给渲染。

扫描 5:电梯当静体不唤醒乘客

站上去还睡。运动学要能唤醒。

扫描 6:触发器连边

传感器焊无关物体成一岛。

扫描 7:关节忘了加边

链条一端动一端睡轴撕开。

扫描 8:五只箱子测爆炸睡眠

测不出唤醒洪水。必须大场景。

工程备忘录

备忘 1

睡眠和唤醒属于「02-多线程岛分割」正确性,不只是性能。该醒不醒会穿模,不该醒全醒会卡顿。爆炸查询、关节边、运动学平台都是唤醒图的边。漏一条边,现场像鬼。 本条对应「02-多线程岛分割」第 1 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 2

「02-多线程岛分割」的默认值要进协议版本。改默认迭代次数、默认 dt、默认皮肤半径,旧回放会分叉。版本头拒绝错版本,比静默错乱重放更负责任。 本条对应「02-多线程岛分割」第 2 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 3

先写 2D 再写 3D 适用于「02-多线程岛分割」里所有几何麻烦事。二维金字塔、二维绳、二维铰链能看见点的增减。三维裁剪 bug 极难看,会让你怀疑人生。二维过了再加一根轴。 本条对应「02-多线程岛分割」第 3 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 4

「02-多线程岛分割」不要在热路径调用脚本。求解器迭代十次回调十次,缓存和确定性一起死。标记事件,步末再发。这是布局问题也是架构问题。 本条对应「02-多线程岛分割」第 4 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 5

对账数字比观感先行。静止支撑冲量是否接近质量乘重力乘 dt,自由落体一秒是否接近四点九米,两球对心是否交换速度。「02-多线程岛分割」相关实现先过对账,再谈好看。 本条对应「02-多线程岛分割」第 5 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 6

「02-多线程岛分割」出抖动时按清单:时间步是否固定、流形点是否闪、法向是否跳、恢复系数是否在静止时仍生效、bias 是否过大、迭代是否其实在清零热启动。不要先把摩擦系数调到十。 本条对应「02-多线程岛分割」第 6 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 7

空间结构必须在世界系。把「02-多线程岛分割」绑到摄像机或屏幕格子,逻辑和确定性都会在转视角时崩。渲染剔除不能代替宽相,看不见的物体仍能被撞到。 本条对应「02-多线程岛分割」第 7 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 8

「02-多线程岛分割」的预分配策略:按关卡上限留容量,超出打日志并降级,不要在 step 中途默默扩容。扩容是卡顿尖峰,也是回放时分配顺序不同导致的潜在分叉源。 本条对应「02-多线程岛分割」第 8 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 9

把「02-多线程岛分割」相关的失败做成最小复现:关掉渲染特效、关掉音频、只留固定 dt 的 step 和一份输入日志。能在十秒内重放出来的 bug 才叫被抓住。不能重放就先补录制,再谈修。很多人在这一步省时间,后面用几天陪着偶发抖动。 本条对应「02-多线程岛分割」第 9 号备忘,和相邻条目不要合并成一句空话,分开验收。

备忘 10

在「02-多线程岛分割」路径上加计数器要进 step 末尾快照,不要在热循环里格式化字符串。计数器本身若分配内存,你会优化一个被测量污染的世界。发布版可用宏剥掉绘制,但计数器的定义要保留,方便线上开一个极轻的统计开关。 本条对应「02-多线程岛分割」第 10 号备忘,和相邻条目不要合并成一句空话,分开验收。

温故知新

  • 岛是动态体在接触关节图上的连通分量;静体不当超级节点。
  • 睡眠整岛跳过;唤醒沿关节走,爆炸要主动查询。
  • 先单线程分岛正确,再并行
  • 同一刚体只能一岛;用断言抓住分岛 bug。
  • 小岛打包、大岛单线程 是务实调度。
  • 确定性要求岛内顺序固定
  • 醒岛计数是性能仪表盘

下一节回到复杂度:宽相和邻居结构才是 N 变大时决定死活的那一层。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U