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

时间:2017-01-19 20:18:58

标签: java scala garbage-collection case-class

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

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

所以我开始了一些激进的优化:首先,现在模拟步骤在游戏后面并行运行,计算一个基本上涵盖游戏中一天的“下一个状态”。因此,玩家将在当前一天显示状态,同时计算下一个状态。这是有效的,因为基本上游戏中的每个主要实体(基本上都是城市)被假定为孤立地进化。如果某个代理人(在城市之间旅行的玩家或其他AI代理人)将与城市联系,我将根据代理人的操作重新计算“下一个状态”。现在无论如何这都不相关。

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

我的问题是,在此之后,我同时取消引用了很多对象,这会触发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)

    }
  }

}

1 个答案:

答案 0 :(得分:2)

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

您创建的垃圾越多,您执行的工作就越多,gc清理对象所需的工作就越多。使用更多CPU更快地创建对象不会减少GC的痛苦。