ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

为什么Pufferfish的GC更快?实体Tick路径中减少分配的优化清单

为什么Pufferfish的GC更快?实体Tick路径中减少分配的优化清单 为什么Pufferfish的GC更快实体Tick路径中减少分配的优化清单【免费下载链接】PufferfishA high-performance fork of Paper designed for large servers.项目地址: https://gitcode.com/gh_mirrors/puf/PufferfishPufferfish 是一款专为大型服务器打造的高性能 Paper 分支它的核心卖点之一就是「更快的 GC」——通过系统性减少实体 Tick 路径中的对象分配让垃圾回收的频率和耗时都显著下降从而消除卡顿尖刺、提升整点 TPS。本文带你逐条梳理 Pufferfish 在实体 Tick 路径上做了哪些「少分配、少干活」的优化。为什么 GC 是大型服务器的头号瓶颈先建立一个直觉Java 程序每new一个对象就会给垃圾回收器GC增加一次未来回收成本。而 Minecraft 服务端每 50 毫秒就要把所有实体 Tick 一遍。在大型服务器里成百上千个实体村民、猪灵、掉落物、箭矢……每一 Tick 都会创建临时坐标对象BlockPos创建迭代器、lambda 等中间对象克隆各种参数容器这些「用完即弃」的小对象就是典型的短生命周期分配压力。它们单独看很便宜但每秒累积成千上万次GC 就会被频繁唤醒表现为偶发的 TPS 掉线GC 尖刺玩家感知到的「卡一下」CPU 空耗在回收上真正用于游戏逻辑的算力变少Pufferfish 的思路不是换 JVM 参数而是从代码层面把分配源头掐掉。Pufferfish 实体Tick路径减分配清单以下优化均来自仓库中的补丁文件集中在patches/server/目录按「减少分配」和「减少无用工作」两类整理#优化项对应补丁原理一句话1缓存实体坐标对象patches/server/0026-Reduce-entity-allocations.patch每个实体复用一块可变的BlockPos不再反复 new2缓存 lambda 引用同上把匿名 lambda 提为字段避免 JIT 无法消除时反复分配3移除迭代器patches/server/0016-Remove-iterators-from-inventory-contains.patch用下标 for 循环替代Iterator少分配一个迭代器对象4内联 Tick 守卫patches/server/0027-Remove-lambda-from-ticking-guard.patch把方法引用this::tickNonPassenger替换为直接调用省掉守卫包装的开销5跳过战利品参数克隆patches/server/0024-Skip-cloning-loot-parameters.patch用只读视图代替深拷贝消除高频克隆产生的大量无用对象6液体查找剪枝patches/server/0028-Reduce-entity-fluid-lookups-if-no-fluids.patch区块段没有流体时整段跳过不再逐格查询7非激活目标选择器节流patches/server/0029-Throttle-goal-selector-during-inactive-ticking.patch远处实体的 AI 目标选择器降频运行8实体 TTLpatches/server/0030-Entity-TTL.patch给雪球等实体设最大寿命自动回收堆积实体下面挑几条讲透。1️⃣ 缓存可复用对象坐标与 lambda这是 0026-Reduce-entity-allocations.patch 的核心。原版代码中实体每 Tick 经常临时构造坐标对象Pufferfish 给Entity加了一个每个实体只创建一次、终身复用的可变坐标字段public final BlockPos.MutableBlockPos cachedBlockPos new BlockPos.MutableBlockPos();同一补丁还处理了一个更隐蔽的问题AttributeMap.getInstance里传给computeIfAbsent的 lambda。注释写得很直白——「Java 出于某种原因无论如何都会分配它」。解决方法是把 lambda 提升为实例字段缓存起来this.createInstance attributex - this.supplier.createInstance(this::onAttributeModified, attributex);这类「小改动、高频率」正是 Pufferfish 的优化哲学单次省不了多少但每 Tick × 每实体地省总量可观。2️⃣ 迭代器与方法引用的悄悄开销0016-Remove-iterators-from-inventory-contains.patch 把物品栏contains检查中的Iterator换成了下标循环——Inventory.contains在物品交互、合成、掉落判定中被高频调用原来每次都要 new 一个迭代器现在归零。0027-Remove-lambda-from-ticking-guard.patch 则针对每个实体每 Tick 都会走的守卫逻辑原版用Consumer方法引用包一层guardEntityTickPufferfish 直接内联 try-catch 调用去掉了方法引用与对象传递这条链路上的额外开销。3️⃣ 别克隆你不需要克隆的东西0024-Skip-cloning-loot-parameters.patch 的提交说明说得很清楚「CPU 提升略小但分配提升明显」——因为玩家一移动就会新建战利品上下文随之而来的参数 Map 克隆会源源不断制造垃圾。改法很优雅用Collections.unmodifiableMap包一层只读视图零拷贝又保持安全性。4️⃣ 少干活也是减分配剩下的优化思路是跳过计算自然就没有中间对象。液体查找剪枝0028 补丁给区块段增加fluidStateCount计数实体流体碰撞检测发现某段没有流体时直接整段跳过而不是逐块查询。目标选择器节流0029 补丁非激活状态下的怪物 AI 目标选择器按 20 Tick 节流一次。配置项inactive-goal-selector-throttle默认开启注释写明「性能可提升百分之几但游戏性上有微小影响」——典型的性能/玩法权衡开关。实体 TTL0030 补丁为每种实体类型设置最大存活 Tick 数在pufferfish.yml的entity_timeouts配置段下超时自动移除。对掉落物、投射物堆积严重的大型服务器尤其有效。另外配套的 0013-Dynamic-Activation-of-Brain.patch动态激活大脑 DAB按玩家距离动态调整远处实体的 AI 频率从源头上减少了非激活 Tick 的总量。如何验证 GC 优化的效果Pufferfish 内置了 0015-Flare-Profiler.patch 实现的零开销性能分析器可以直接观察实体 Tick 耗时、内存总量Memory Total等内置指标随时间的变化曲线判断减分配优化是否在你的服务器场景下生效。项目 READMEREADME.md也把这一特性列为核心卖点之一「通过移除无用分配降低 GC 时间频率同时改善 CPU 性能」。小结把优化写进 Tick 热点路径Pufferfish 的 GC 优化不是靠调参而是一份针对实体 Tick 热点路径的减分配清单能复用就复用缓存坐标、缓存 lambda能删就删迭代器、方法引用包装能免拷贝就免拷贝只读视图代替克隆能跳过就跳过流体剪枝、AI 节流、TTL这些补丁大多只有几十行却作用在每 Tick 被执行成千上万次的路径上——这正是「大型服务器性能」与「普通服务器性能」的分水岭。如果你对具体实现感兴趣patches/server/目录下从 0013 到 0032 的补丁文件就是最权威的一手资料。【免费下载链接】PufferfishA high-performance fork of Paper designed for large servers.项目地址: https://gitcode.com/gh_mirrors/puf/Pufferfish创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表