JEIOptimizer

JEIOptimizer

15倍更快的JEI物品过滤构建——大幅减少大型模组服务器上“加入世界中...”的等待时间。

JEIOptimizer

并行构建 JEI 的物品筛选索引 —— 在大型模组包中可将加入服务器的时间缩短最多 17 秒。


背景

我和朋友在一个私有的 AllTheMods 服务器上玩。每次断线、每次重启、每次“我去拿点零食”,都意味着我要盯着 “正在加入世界...” 看 40 秒,而其他人早已开始挖矿。

受不了了。我花了整个周末打开 JEI 的源码,而不是玩游戏。这个模组就是由此诞生的。


功能

当你连接到一个有模组的服务器时,JEI 会从头重建它的整个物品搜索索引——因为服务器可能会提供自定义配方。在一个有 250 个模组、约 50,000 个物品的整合包中,这一过程会在 主线程上占用 12 秒以上。你的客户端就这么卡死了。

JEIOptimizer 将 JEI 的 IngredientFilter 构建过程并行化到工作线程上。每个搜索前缀(物品名称、模组 ID、标签、提示文本……)都有自己的线程,运行在独立的数据结构上——没有争用、没有竞态、没有风险。


实际测量数据

在 250+ 模组的整合包(AE2、Mekanism、Create、Cobblemon、Apotheosis、AllTheCompressed……)上测试。Ryzen 5600X · 10 个工作线程 · NVMe · 分配 8 GB 内存 · 远程服务器(Hetzner 数据中心)。

阶段 原版 JEI + JEIOptimizer 缩减幅度
构建物品筛选器 12.00 秒 0.79 秒 −94%(15 倍快)
JEI 启动总计 25.84 秒 14.17 秒 −45%
总连接时间 约 40 秒 约 23 秒 −42%(−17 秒)

配合下面的额外优化(参见 免费再省 8 秒),你可以将服务器连接时间从 40 秒 稳定降到 约 23 秒。


工作原理(技术细节)

JEI 的 IngredientFilter 会构建一个搜索索引,其中每个“前缀”(名称、模组 ID、标签、提示文本……)都有自己的 GeneralizedSuffixTree。独立的结构 → 可以安全地并行构建。

原版 JEI:

遍历每个物品(50,000):
    遍历每个前缀(7-10):
        计算标记 → 插入后缀树

一个线程 = 约 12 秒。

JEIOptimizer:

对前缀进行 parallelStream:
    遍历每个物品:
        计算标记 → 插入自己的后缀树

6-10 核心同时处理 7-10 个前缀 = 约 0.8 秒。

筛选模式(配置中)

  • OFF — 原版 JEI 行为
  • BATCH — 小幅度缓存友好改进,单线程
  • PARALLEL_PREFIX — 按前缀并行构建(安全)
  • PARALLEL_FULL — 在前缀内部也并行化标记计算(默认,最快)

免费再省 8 秒(手动 JEI 调整)

在 config/jei/jei-client.ini 中,改为:

tooltipSearchMode = DISABLED

JEI 每次连接时都会索引所有物品的提示文本。有些模组在提示文本渲染时会做大量 I/O 操作——吃掉了原本 12 秒中的约 8 秒,却毫无用处。玩家几乎总是通过名称搜索,而不是提示文本。

如果玩家想通过 #前缀 进行提示搜索,请改用 REQUIRE_PREFIX。


配置

config/jeioptimizer-client.toml(自动生成):

[filter]
    mode = "PARALLEL_FULL"   # 默认。安全,最快。
    async = false            # 实验性:后台构建
    log_timing = true        # 记录耗时供验证

worker_count = 0             # 0 = 自动(核心数 - 2)

[plugins]
    parallel_phases = []                # 实验性——见下文
    parallel_creative_tabs = false      # 实验性——见下文

开箱即用的安全默认值。 放入 jar 文件即可生效。无需修改配置文件。


兼容性

  • Minecraft: 1.21.1
  • 模组加载器: NeoForge 21.x
  • JEI: 19.x(1.21.1 版本)
  • 运行端: 仅客户端——无需在服务器端安装

这不会加速什么

坦诚说明范围:

  • ❌ 游戏启动(模组构造、模型烘焙、资源包重载)
  • ❌ 服务器数据同步(配方、标签、注册表)——受限于网络和模组序列化
  • ❌ 每个模组运行时数据重新生成——Create 每次连接会生成 2,124 个配方,Cobblemon 会重新同步所有化石/标记/浆果,Apotheosis 会重新加载所有词缀。这些是模组设计上的选择。

JEIOptimizer 将 JEI 从瓶颈中移除。它不再是短板——其他模组仍会做它们的事情。


实验性功能(默认关闭)

已在真实模组包上测试,但发现会破坏东西。适用于精心挑选的模组集合,可选择启用:

  • plugins.parallel_phases — 并行化插件配方注册。许多 JEI 插件(Theurgy、Ars Nouveau、Compact Machines)不是线程安全的,会崩溃。
  • plugins.parallel_creative_tabs — 并行化创造模式标签页构建。许多模组以非线程安全方式缓存标签页状态;在我们的测试中,有 22% 的 JEI 物品丢失。
  • filter.async — 在后台构建筛选器;玩家可更早进入世界。如果他们在前 2 秒内打开 JEI,会看到一个空白网格,直到准备就绪。

除非你知道自己在做什么,否则请保持关闭。


常见问题

问:这会破坏 JEI 搜索吗? 默认模式(PARALLEL_FULL)改变了索引的 构建方式,而非 构建内容。相同的物品、相同的配方、相同的搜索结果。

问:我用的整合包比较小。效果会差一些吗? 是的。在约 10,000 个物品以下,收益甚微。专为 30,000+ 物品的整合包设计。

问:为什么不为 JEI 提交 PR? 以后也许会。Mezz(JEI 的作者)历来偏好保守的改动——核心代码中的并行性可能不会被接受。这个模组的存在是为了让你不必等待上游。

问:需要在服务器端安装吗? 不需要。纯客户端 Mixin。将其放入服务器的 mods/ 文件夹不会有任何效果。

问:它与 EMI / REI 兼容吗? 不,这个模组专门针对 JEI。EMI 有自己的架构(而且已经很快了)。


致谢

在自定义 250+ 模组整合包以及 AllTheMods10、AllTheMons 上进行了测试。

感谢 Mezz 保持 JEI 源码的开放和可读性,使得外部优化成为可能。