我正在写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秒没有设置
答案 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.PositiveInfinity
或0.0
的风险,这取决于关于值有多大,这将产生哈希冲突,这将进一步减慢速度。)
答案 1 :(得分:2)
请注意,以下假设您的所有对象都是不可变的。使用散列时这是一个明智的假设。
此外,您应该在应用优化之前对代码进行概要分析(例如,使用JDK附带的免费jvisualvm)。
hashCode
计算哈希码通常是一个瓶颈。通过为每个对象仅计算一次哈希代码并存储结果,您可以将哈希代码计算的成本降低到最小(一旦创建对象),代价是增加空间消耗(可能是中等)。要实现此目的,请将def hashCode
转换为lazy val
或val
。
equals
一旦消除了hashCode
的费用,计算equals
就成了问题。 equals
对于收集领域和深层结构来说特别昂贵。
您可以通过实习最小化equals
的费用。这意味着您通过工厂方法获取类的新对象,该方法检查所请求的新对象是否已存在,如果是,则返回对现有对象的引用。如果断言这种类型的每个对象都是以这种方式构造的,那么您就知道每个不同对象只有一个实例,equals
等同于对象标识,这是一个便宜的引用比较(eq
在斯卡拉)。