Sepals

Sepals

一个用于Minecraft服务器性能的极其激进和实验性的优化方案。

优化

Sepals

一个极度激进且实验性的 Minecraft 服务器性能优化模组。

我们建议将 Sepals 与 Lithium 和 Async 配合使用,以获得最佳性能。

前言

首先,Sepals 是一系列高度实验性的服务端优化集合,针对 Minecraft 的 AI、实体处理以及任务系统中的特定性能瓶颈。与 Lithium 提供的广泛“通用”优化不同,Sepals 专注于对少数几个极其昂贵的原版机制进行深度重写——尤其是村民大脑(villager brains)、青蛙行为(frog behavior)、实体挤压(entity cramming)、目标过滤(target filtering)和附近实体感知(nearby-entity sensing)。

那么它在实际使用中表现如何?

如果你运行大型刷怪塔、村民大厅、密集的生物围栏, 或任何在小范围内存在成百上千实体的场景, Sepals 通过避开原版最昂贵的 AI 操作,大幅降低每次游戏刻(tick)的耗时。

如果你运行的是农场较小的普通 SMP 服务器,你可能感觉不到太大差异。

它是实验性的,并非每项优化都保证与原版行为完全一致, 但在性能压力测试中,其提升效果可能非常显著。

兼容性

目前,Sepals 与几乎所有模组兼容。

以下为已与最新 Sepals 版本验证兼容的模组列表:

目标模组 所需目标版本
Sodium 全部
Iris 全部
FerriteCore 全部
Krypton 全部
Lithium >=0.18.0
C2ME >=0.3.4.0.0
Moonrise >=0.6.0-beta.1+45edfd7
Async >=0.1.7+alpha.7-1.21.8

如果你不确定该选择哪个版本,请使用优化模组的最新版本,因为 Sepals 会确保与它们的兼容性。

重要提示

Sepals 不支持任何 Minecraft 的旧版本和快照版本;所有错误修复和功能仅在最新版本中可用。

当与 Async 一起使用时, 请在启用 Sepals 和 Async 的情况下手动对比性能,并调整它们的配置,以找出哪些优化是值得的, 因为不同机器的结果会有差异。

Sepals 相关设置项:enableSepalsEntitiesCramming。

配置

配置名称 允许值 默认值
forceEnableSepalsPoi 布尔值 false
enableSepalsVillager 布尔值 true
enableSepalsFrogLookAt 布尔值 true
enableSepalsFrogAttackableSensor 布尔值 true
enableSepalsLivingTargetCache 布尔值 true
nearestLivingEntitiesSensorUseQuickSort 布尔值 true
enableSepalsBiasedLongJumpTask 布尔值 true
enableSepalsEntitiesCramming 布尔值 true
enableSepalsItemMerge 布尔值 true
enableSepalsQuickCanBePushByEntityPredicate 布尔值 true

性能

测试在由 feimia 提供的 Mars 服务器上进行,CPU:Intel i7-14700K;游戏内存:4G;操作系统:Ubuntu 24.04.1 LTS; Minecraft 版本:1.21。

实体挤压(Entities cramming)

使用箱体缓存来防止过多的 'getOtherEntities' 调用

-- 注意 --
此功能会忽略每个实体的记分板/队伍(scoreboard/team)判定条件,
可能导致意外行为。

此问题在原版中不会影响任何内容,除非使用
命令方块时。

-- 状态 --
默认启用

1390 个村民挤压在 7x7 的空间中:

环境 tickCramming 百分比(平均)
原版 53.6 ms 100 %
Lithium 54.4 ms 101 %
Sepals 10.2 ms 19 %
Sepals + Lithium 8.5 ms 15 %

加权随机(Weighted random)

使用二分查找替代原版的权重随机

-- 警告 --
此功能可能不值得启用,
因为 Spark 证明在构建范围表时它比原版更慢,
尽管二分查找本身接近 0 ms。

-- 状态 --
默认禁用

-- 警告 --
未经过测试。

偏向长跳跃任务(Biased long jump task)

使用 Sepals 的长跳跃任务实现替代原版

如上所述,二分查找的成本几乎为零,
Sepals 的实现会在生成目标的同时构建范围表,
并使用 Catheter 替代 Java Stream。

同时,重新调整了青蛙大脑中的跳跃条件,以获得更好的性能。

-- 注意 --
此功能无法在游戏运行时更改,
需要重启服务器以应用更改。

-- 状态 --
默认启用

800 只青蛙挤压在 7x7 的空间中:

环境 keepRunning 百分比(平均)
原版
(LongJumpTask)
43.1 ms 100 %
Lithium
(LongJumpTask)
7.5 ms 17 %
Sepals
(SepalsLongJumpTask)
0.2 ms 0.4 %
Sepals + Lithium
(SepalsLongJumpTask)
0.05 ms 0.1 %
环境 getTarget 百分比(平均) 百分比(在 keepRunning 内)
原版
(LongJumpTask)
43.1 ms 100 % 100 %
Lithium
(LongJumpTask)
3.6 ms 9 % 48 %
Sepals
(SepalsLongJumpTask)
N/A ms 0 % 0 %
Sepals + Lithium
(SepalsLongJumpTask)
N/A ms 0 % 0 %

NearestLivingEntitiesSensor 中的快速排序

使用来自 FastUtil 的快速排序替代 Java 的 TimSort

-- 状态 --
默认启用

800 只青蛙挤压在 7x7 的空间中:

环境 排序 (NearestLivingEntitiesSensor#sense) 百分比(平均)
原版 3.8 ms 100 %
Lithium 3.6 ms 94 %
Sepals 2.2 ms 57 %
Sepals + Lithium 2.2 ms 57 %

青蛙可攻击目标过滤器

重新排列可攻击条件,
将成本最低的条件放在最前面,
从而降低高成本计算的触发概率。

-- 反馈 --
Mojang 的可攻击判定是:

!entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN)
 && Sensor.testAttackableTargetPredicate(entity, target)
 && FrogEntity.isValidFrogFood(target)
 && !this.isTargetUnreachable(entity, target)
 && target.isInRange(entity, 10.0)

在这种情况下,'Sensor#testAttackableTargetPredicate' 调用了 'TargetPredicate#test',
当区域内实体过多时会导致大量射线检测(raycast)计算,
考虑到 Minecraft 的射线检测慢得令人痛苦,这使情况变得更糟。

在这种情况下,'TargetPredicate#test' (800 只青蛙) 每个游戏刻花费了 9.8ms,
其中 'BlockView.raycast' 贡献了 7.3ms。

因此,我将其修改为:

FrogEntity.isValidFrogFood(target) &&
 entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN) && 
 target.isInRange(entity, 10.0) && 
 Sensor.testAttackableTargetPredicate(entity, target) && 
 isTargetUnreachable(entity, target);
 
'isValidFrogFood' 是一个简单的条件,它检查实体的 'frog_food' 标签
并在实体是史莱姆且其尺寸不为 1 时提供一个额外的跳过检查。

'isInRange' 和 'hasMemoryModule' 也很好,因为它们只计算一些简单的操作。

-- 状态 --
默认启用

800 只青蛙挤压在 7x7 的空间中:

环境 时间 百分比(平均)
原版 (FrogAttackablesSensor#matches) 10 ms 100 %
Lithium (FrogAttackablesSensor#matches) 5.7 ms 57 %
Sepals (SepalsFrogBrain#attackable) 0.1 ms 1 %
Sepals + Lithium (SepalsFrogBrain#attackable) 0.1 ms 1 %

青蛙注视目标过滤器

使用 'SepalsLivingTargetCache' 提高目标搜索性能
你必须启用该选项,否则此功能与原版相同。

-- 注意 --
射线检测在 TargetPredicate 测试中,
在 LivingTargetCache 的 'findFirst' 中当输入判定成功时执行。

但是如果后续条件失败,那么进一步计算是无用的,
因为即使 findFirst 已经找到(射线检测成功),
我们也不会在后续环境中使用这个结果。

-- 状态 --
默认启用

800 只青蛙挤压在 7x7 的空间中:

环境 findFirst 百分比
原版
(LookAtMobWithIntervalTask$$Lambda#findFirst)
2.7 ms 100 %
Lithium
(LookAtMobWithIntervalTask$$Lambda#findFirst)
2.5 ms 92 %
Sepals
(SepalsLookAtMobWithIntervalTask$$Lambda#findFirstPlayer)
0.1 ms 3 %
Sepals + Lithium
(SepalsLookAtMobWithIntervalTask$$Lambda#findFirstPlayer)
0.1 ms 3 %

村民杂项优化

以下是 Sepals 所做的:

1. 使用 Catheter 替代 Java 的 Stream,因为与 Stream 相比,它具有更好的性能和可扩展性。
2. 缓存任务(tasks)、活动(activities)、运行中的任务(running tasks)和记忆(memory),以改善任务启动和更新的时间。
3. 使用 Sepals 复合任务替代原版复合任务。
4. 在可能的情况下,寻找机会跳过更多的射线检测和无用的判定。
5. 使用 'SepalsLivingTargetCache' 替代原版缓存。在传感器 tick 期间多花费一些成本,以减少寻找交互目标或注视生物任务期间的成本。
6. 重新排列判定条件并添加低成本判定,目的是将高成本判定延后或最好不执行,以提前跳过剩余的高成本判定。
7. 从 Lithium 复制并修改了 'SerializingRegionBasedStorage' 的优化。
8. 针对特定任务,不使用泛型以减少无用操作。
9. 使用二分查找列表替代哈希集查找。

-- 注意 --
建议与 Lithium 和 C2ME 一起使用以获得最佳性能。

-- 警告 --
尚未进行长期稳定性测试,仅一个月的运行表明目前没有问题。
此功能尚未被证明与原版行为完全一致,
但也没有显示出统计学上的显著差异。

-- 状态 --
默认启用

800 个村民在正午挤压在 7x7 的空间中:

环境 Brain#tick (总) 百分比 Brain#startTasks 百分比(startTasks) Brain#tickSensors 百分比(tickSensors) Brain#updateTasks 百分比(updateTasks) Brain#tickMemories 百分比(tickMemories)
原版 18 ms 100 % 9.3 ms 100 % 5.2 ms 100 % 3 ms 100 % 0.5 ms 100 %
Lithium 12.4 ms 68 % 4.8 ms 51 % 5.9 ms 113 % 1.2 ms 40 % 0.5 ms 100 %
Sepals 9.7 ms 53 % 3.6 ms 38 % 3.7 ms 71 % 2 ms 66 % 0.4 ms 80 %
Sepals + Lithium 10 ms 55 % 3.4 ms 36 % 3.7 ms 71 % 2.5 ms 83 % 0.4 ms 80 %

800 个村民在夜晚挤压在 7x7 的空间中:

环境 Brain#tick (总) 百分比 Brain#startTasks 百分比(startTasks) Brain#tickSensors 百分比(tickSensors) Brain#updateTasks 百分比(updateTasks) Brain#tickMemories 百分比(tickMemories)
原版 16.7 ms 100 % 8.2 ms 100 % 6 ms 100 % 2 ms 100 % 0.5 ms 100 %
Lithium 10.2 ms 61 % 3.2 ms 24 % 6 ms 113 % 0.5 ms 25 % 0.5 ms 100 %
Sepals 9 ms 53 % 3.3 ms 16 % 4.7 ms 78 % 0.7 ms 35 % 0.3 ms 60 %
Sepals + Lithium 8.7 ms 52 % 2.9 ms 11 % 4.6 ms 76 % 0.7 ms 35 % 0.5 ms 100 %

判定条件优化

1172 只青蛙挤压在 3x3 的空间中:

环境 时间 百分比(平均)
原版 (java.util.function.Predicate.lambda$and$0()) 49.01 ms 100 %
Sepals (com.github.cao.awa.sepals.entity.predicate.SepalsEntityPredicates$$Lambda/0x000002d8f116e000.test()) 22.6 ms 46 %