精简对象

精简对象

去重Minecraft中最热门的查找对象,使比较变为指针比较而非字符串比较

性能

Lean Object 通过对 Minecraft 中最常用的查找对象进行去重,使其开销更低。当一个值只存在唯一一份实例时,比较它就变成了指针比较,而不是字符串比较——而这种比较会发生在每一次注册表查找、标签检查和成分匹配中。

下载与你的 Minecraft 版本和模组加载器相匹配的文件。

为什么去重能提升速度

哈希码只负责选定桶。该桶内的每一次哈希冲突随后都通过 equals() 来解析,而这正是代价高昂之处——对于标签键来说,就是比较一个注册表键和一个 location,也就是字符串链。模组整合包会令情况更糟:成千上万个共享 minecraft: 或 mymod: 之类前缀的标识符,其哈希码会聚集在一起,于是比较堆积的那些桶恰好就是最繁忙的桶。

每个逻辑值只保留一个实例,equals 就可以简化为 o == this,而 hashCode 可以简化为 System.identityHashCode。所有这些比较都变成了指针比较。更少的内存是这一改变带来的副作用,而不是目标。

功能

所有功能默认开启,可在 config/leanobject.toml 中单独关闭。

功能 作用
ResourceLocation 每个 id 共享一个实例。重复的实例及其字符串消失,location 比较不再是字符串比较,而这正是注册表和模型查找耗时的地方。
TagKey 自定义驻留(interning)取代原版驻留器,equals/hashCode 变为基于身份的。标签成员测试是大多数整合包中最热门的路径。
ResourceKey 注册表键通过单次缓存探测构建,而不是填充原版的驻留映射,因此键的创建更廉价且分配更少。
Ingredient 单条目成分复用同一个共享实例,因此配方不再重建相同的 Ingredient 对象及其延迟构建的物品数组。
NBT 容器 在分配点用更精简的等价物替换标签类内部的映射和列表,因此原版容器从不会被创建。
模型对象 为烘焙后的方块和物品模型提供更精简的容器。客户端侧。
AE2 存储键 对 AE2 的物品和流体存储键去重,因此大型 ME 网络在每次查找时不再分配并哈希一个键。需要 AE2。

针对其他模组的功能仅在该模组存在时才应用,否则跳过——AE2、Farmer's Delight 和 FerriteCore 都按此方式处理,均无需手动配置。由于特定 Minecraft 版本可用的模组不同,各个文件支持的功能集合可能有所差异;config/leanobject.toml 始终列出该文件所支持的功能。

在玩科技整合包?开启 AE2 键去重

AE2 本身对存储键没有驻留机制:每个工厂都会构建一个新的键对象并对其内容做哈希,因此后期的 ME 网络几乎全是键——成排的接口、存储总线、输入/输出总线和合成 CPU 都持有海量的键,全部按需分配并按值比较。

这正是该功能所针对的工作负载,而且它加速的是键的创建,而不仅仅是键的查找:

  • 不带组件的键变成一次共享实例查找——完全没有分配,也没有哈希。
  • 带组件的键的开销是一次以这些组件为键的缓存探测,这就是全部成本。对于给定值,只有第一次探测会进行分配;重复时返回已有的键。
  • 由于键只存在一份,比较变成了引用检查,而键缓存的哈希就是身份哈希——这正是大型网络耗时的地方。
  • 数据包读取器和序列化编解码器都走同一个缓存,因此通过网络到达或从磁盘加载的键也会被共享。

在模组整合包中,这种情况持续发生。AE2 很少只操作自己的存储:存储总线、输入/输出总线和接口将 ME 网络连接到其他模组的库存——箱子、桶、机器、抽屉、管道——而跨过这一边界的每个物品都会在 ItemStack 和 AE 键之间转换。这些传输持续批量进行,按物品、按总线、按 tick。每个逻辑键共享一个实例,可将这种转换变成一次缓存探测,而不是一次分配加一次哈希。

取代 Fast Tag

Lean Object 取代了 Fast Tag,并应代替它使用。TagKey 和 ResourceKey 功能即源自它——替换的是同样的方法,理由也相同,且两者中驻留键都按引用比较。变化的是缓存后端:Fast Tag 使用 Guava 的 MapMaker 并反射进入其内部,而 Lean Object 使用自己的缓存,采用普通的身份映射且不使用反射。

不要同时安装两者。 它们替换相同的方法,后应用的获胜。

配置

config/leanobject.toml 会在每次启动时重新生成:

enabled = true          # master switch
loggingInGC = true      # force a GC and log heap usage when joining a world

[features.tagKey]
    safeMode = false    # false = replace the methods, true = wrap the original ones
    enabled = true

safeMode 适用于那些提供非破坏性变体的功能。如果另一个模组与替换路径冲突,请将其设为 true:它会改为包装原方法,兼容性更好但稍慢,并放弃身份比较。

兼容性说明

  • FerriteCore 会自动处理。 在 FerriteCore 缓存了本模组所替换的同一个模型 location 调用之处,它自身的处理会被关闭——两个替换无法同时注入同一条指令。这需要一个允许其他模组禁用某项选项的 FerriteCore 构建版本。
  • 注入冲突。 大多数功能直接替换方法。如果另一个模组修改了同一目标,两者会相互冲突,后应用的获胜。模型功能尤其已知在大型整合包上会冲突;它可以单独关闭。
  • 引用相等。 驻留键按引用比较,因此在驻留入口点之外创建的值不会与其驻留孪生值比较相等。在功能提供非破坏性变体的情况下,切换到该变体可保留值比较。
  • AE2 支持是版本特定的。 它挂钩 AE2 键类的私有成员,因此 AE2 的分支或其他构建版本可能改变这些内部结构——这时该功能会明确地应用失败,而不是出现异常行为。
  • 可选集成会被自动检测。 未安装对应模组的功能会自动跳过。
  • Location 驻留既消耗 CPU 也节省内存。 Location 被极其频繁地构造,而驻留会为每次构造增加一次缓存探测。当同一个 id 被反复创建时它才划算;在那些持续产生不同 id、什么都无法共享的热路径上,它可能消耗 CPU。如果你追求的是 CPU 而非内存,请先尝试关闭这一个功能。

从源码构建

./gradlew build

依据 GNU LGPL v3.0 或更高版本授权。