我有以下代码来实现队列。一个使用链表,一个使用两个堆栈(具有昂贵的出列操作),而这又使用链表实现。
我很困惑为什么堆栈实现上的enqueue操作更快,即使它们都使用相同的底层数据结构(链表)。我的代码如下(不显示重现行为所必需的操作)。
case class Node[T](data: T, var next: Node[T])
class StackList[T] {
private var top: Node[T] = _
private var total: Int = 0
def push(item: T): Unit = {
val o = top
val n = Node(item, o)
total = total + 1
top = n
}
}
class QueueStackDq[T] extends Queue[T] {
val in = new StackList[T]
val out = new StackList[T]
private var total: Int = 0
/**
* Add an element to the queue's tail
*/
def enqueue(item: T): Unit = {
total = total + 1
in.push(item)
}
}
class QueueList[T] extends Queue[T] {
private var tail: Node[T] = _
private var head: Node[T] = _
private var total: Int = 0
/**
* Add an element to the queue's tail
*/
def enqueue(item: T): Unit = {
val n = Node(item, null) // Create new node
if (total == 0) {
head = n
tail = n
}
total += 1
tail.next = n
tail = n
}
}
使用1,000,000条记录测试入队操作,显示链接列表实现在0.01秒内运行,而堆栈实现在0.00026秒内运行!这是一个重要的时间差异!
答案 0 :(得分:3)
我为此示例问题和ScalaMeter创建了基于placed it on GitHub的基准。
在我的机器上,我为原始程序(标记为V1.0)获得如下基准(以毫秒为单位):
QueueStackDq: 5.757413
QueueList: 5.699053
删除冗余并修复我的评论中提到的错误后,基准测试成为(标记为V2.0):
QueueStackDq: 5.317035
QueueList: 5.670139
无论哪种方式,两种实现之间没有太大差异(这些结果可能没有统计意义,因为基准测试的样本量并不大)。
所以为了回答你的问题,事实证明堆栈实现毕竟不是几个数量级。你最初的直觉认为这两种方法应该是相似的,因为它们使用相似的数据结构,这是正确的。
BTW,使用null
和可变变量&集合在 Scala 中不受欢迎。一个不使用它们的功能版本将更容易推理。
答案 1 :(得分:1)
两个enqueue
方法的不同之处在于,一个方法通过StackList.push
方法具有间接方式而另一方方法没有(就像您已经内联了该方法一样)。
假设您正确地进行了基准测试(并且您没有在此处显示该代码),那么您似乎正在测量进行该方法调用和内联该方法之间的区别。
如果您经常调用该方法,我希望JVM能够为您内联该方法,但很难解释为什么它没有看到您的基准代码。我建议ScalaMeter或JMH用于微基准测试。
我还要提醒您,这种微观基准很少有用或信息丰富。你真的有一个生产系统,这两个代码之间的差异会很大吗?几乎在所有情况下,这种微小的性能差异都会被网络性能或更大规模的架构选择等其他因素所淹没。