最佳HashSet初始化(Scala | Java)

时间:2013-02-05 19:01:39

标签: scala optimization hashset

我正在写A.I.解决“Maze of Life”难题。尝试将状态存储到HashSet会减慢一切。没有一组探索状态,运行它会更快。我相当自信我的节点(状态存储)实现了equals和hashCode,因为测试显示HashSet不会添加重复状态。我可能需要重新设计hashCode函数,但我相信正在放慢速度的是HashSet重新调整和调整大小。

我已经尝试将初始容量设置为一个非常大的数字,但它仍然非常慢:

 val initCapacity = java.lang.Math.pow(initialGrid.width*initialGrid.height,3).intValue()
 val frontier = new QuickQueue[Node](initCapacity)

这是快速队列代码:

class QuickQueue[T](capacity: Int) {

val hashSet = new HashSet[T](capacity)
val queue = new Queue[T]
    //methods below

有关详细信息,请参阅哈希函数。我将网格值以字节存储在两个数组中,并使用元组访问它:

override def hashCode(): Int = {
  var sum = Math.pow(grid.goalCoords._1, grid.goalCoords._2).toInt
  for (y <- 0 until grid.height) {
     for (x <- 0 until grid.width) {
        sum += Math.pow(grid((x, y)).doubleValue(), x.toDouble).toInt
     }
     sum += Math.pow(sum, y).toInt
  }
  return sum
}

有关如何设置不会降低速度的HashSet的任何建议?也许还有另外一个关于如何记住探索状态的建议?

P.S。使用java.util.HashSet,即使初始容量设置,它需要80秒vs&lt;没有7秒没有设置

2 个答案:

答案 0 :(得分:6)

好的,首先,请替换

override def hashCode(): Int =

override lazy val hashCode: Int = 

因此,每次需要访问哈希码时,都不会计算(grid.height*grid.width)浮点功率。这应该可以大大加快速度。

然后,除非你以某种方式依赖具有紧密哈希码的close单元格,否则不要重新发明轮子。使用scala.util.hashing.MurmurHash3.seqHash或某些来计算您的哈希值。这应该会使你的哈希速度增加20倍左右。 (仍然保持懒惰的val。)

然后您只需要从所需的集合操作中获得开销。现在,除非你有很多0x0网格,否则你正在耗尽绝大部分时间等待math.pow给你一个结果(冒着一切变成Double.PositiveInfinity0.0的风险,这取决于关于值有多大,这将产生哈希冲突,这将进一步减慢速度。)

答案 1 :(得分:2)

请注意,以下假设您的所有对象都是不可变的。使用散列时这是一个明智的假设。

此外,您应该在应用优化之前对代码进行概要分析(例如,使用JDK附带的免费jvisualvm)。

快速hashCode

的记事

计算哈希码通常是一个瓶颈。通过为每个对象仅计算一次哈希代码并存储结果,您可以将哈希代码计算的成本降低到最小(一旦创建对象),代价是增加空间消耗(可能是中等)。要实现此目的,请将def hashCode转换为lazy valval

快速equals

实习

一旦消除了hashCode的费用,计算equals就成了问题。 equals对于收集领域和深层结构来说特别昂贵。

您可以通过实习最小化equals的费用。这意味着您通过工厂方法获取类的新对象,该方法检查所请求的新对象是否已存在,如果是,则返回对现有对象的引用。如果断言这种类型的每个对象都是以这种方式构造的,那么您就知道每个不同对象只有一个实例,equals等同于对象标识,这是一个便宜的引用比较(eq在斯卡拉)。