使用 Scala Case 处理 GC 上的重负载 类

Handling heavy load on the GC with Scala Case Classes

我正在游戏中使用 Scala 开发模拟。我只想使用不可变对象来定义游戏逻辑,即使它效率低下。为什么?因为我能,所以我会。

现在我有点强调主要的游戏循环,用巨大的负载推动模拟。基本上我有这些嵌套案例 类 并且我使用 .copy() 来定义模拟中给定实体的以下状态。问题是所有这些操作都会生成大量未引用的对象,因此在每一步结束时,我都会触发 Full GC。这对游戏性能不利。

所以我开始了一些积极的优化:首先现在模拟步骤是 运行ning 在游戏后面并行,计算 "next state" 基本上涵盖了游戏中的一天。因此,在计算下一天时,玩家将看到当天的状态。这是可行的,因为基本上游戏中的每个主要实体(基本上是城市)都被假定为孤立地发展。如果某个代理(玩家或其他在城市之间穿梭的 AI 代理)将与城市取得联系,我将根据代理操作的操作重新计算 "next state"。反正这现在不相关了。

所以我让这些平行实体在幕后进化,当一天结束时(一天被定义为世界地图中玩家的 5 步)我使用 Await.result(nextWorldState, 5 seconds) 作为集合点更新模拟的当前状态。这不一定是游戏应该如何 运行 但我想在游戏的其余部分等待计算下一个状态时测试会合点,因为这可能是必要的。

我的问题是,在此之后,我一次取消引用了很多对象,这触发了 GC 收集。我尝试了一些解决方案,但是当我访问 Future 的结果并用它替换当前状态时总有一点,这总是会触发 GC。

假设我不想去常规 类(可能我会)并且假设我不想将状态分成多个部分并分别处理它们(我会尝试这个但它看起来无法维护),这个问题有什么巧妙的解决方案吗?

下面是处理此逻辑的实际函数中的一些不太伪的代码:

class WorldSimulationManager(var worldState: WorldState) {

  private var nextWorldState: Future[WorldState] =
    Future.successful(worldState)


  def doImmutableStep(gameMap:GameMap,movedCountToday:Int):Unit = {

    nextWorldState =
      nextWorldState.flatMap(_.doStep(gameMap))

    if (movedCountToday >= Constants.tileMovementsToDay) {
      worldState = Await.result(nextWorldState, 5 seconds)

    }
  }

}

为了减少 Full GC 带来的痛苦,我建议使用 G1 或 CMS 而不是并行收集器,并增加年轻 space 以减少提升为终身 space 的对象数量,但没有什么比首先创造更少的工作。

你创建的垃圾越多,你做的工作就越多,gc 清理对象的工作也就越多。使用更多 CPU 更快地创建对象不会减轻 GC 的痛苦。