假设我们构建一个对象来表示一些网络(社交,无线,等等)。所以我们有一些'node'对象来表示网络的KIND,不同的节点可能有不同的行为,等等。网络有一个MutableList节点。
但每个节点都有邻居,这些邻居也是节点。所以在某个地方,每个节点必须有一个该节点的所有邻居的列表 - 或者这样的列表必须在需要时动态生成。如果邻居列表存储在节点对象中,将它(a)存储为节点列表,或者(b)作为可用于引用网络外节点的数字列表是否更便宜?
为清晰起见,有些代码:
//approach (a)
class network {
val nodes = new MutableList[Node]
// other stuff //
}
class Node {
val neighbors = new MutableList[Node]
// other stuff //
}
//approach (b)
class Network {
val nodes = new MutableList[Node]
val indexed_list = //(some function to get an indexed list off nodes)
//other stuff//
}
class Node {
val neighbors = MutableList[Int]
//other stuff//
}
方法(a)似乎最简单。我的第一个问题是Scala 2.8中这是否代价高昂;第二个问题是它是否违反DRY的原则?
答案 0 :(得分:9)
简短回答:过早优化是其中的根源等。使用干净的参考方法。当您遇到性能问题时,无法替代分析和基准测试。
答案很长:Scala使用与Java完全相同的引用机制,因此这比Jala问题更像是一个JVM问题。正式地,JVM规范没有说明如何实现引用。在实践中,它们往往是字大小或更小的指针,指向对象或索引到指向对象的表(后者帮助垃圾收集器)。
无论哪种方式,refs数组的大小与32位vm上的int数组大小相同,或者64位vm上的double数组大小相同(除非使用压缩oops)。这种倍增可能对你很重要,也可能不重要。
如果使用基于ref的方法,则从节点到邻居的每次遍历都是参考间接。使用基于int的方法,从节点到邻居的每次遍历都是查找表,然后是参考间接。因此int方法在计算上更昂贵。这就是假设你将整数放入一个不包含整数的集合中。如果你确实填写了整数,那么它只是纯粹的疯狂,因为现在你有了与原始的一样多的引用并且你有一个表查找。
无论如何,如果你使用基于引用的方法,那么额外的引用可以为垃圾收集器做一些额外的工作。如果对节点的唯一引用位于一个数组中,那么gc将快速扫描那个节点。如果它们分散在图表中,那么gc将不得不更加努力地追踪它们。这可能会也可能不会影响您的需求。
从清洁的角度来看,基于ref的方法要好得多。所以一起去,然后剖析一下,看看你在哪里花时间。那个或基准都接近。
答案 1 :(得分:1)
问题是 - 什么样的成本?记忆方面,b)方法可能最终消耗更多内存,因为你有可变列表,列表中的盒装整数,以及另一个包含所有索引的全局结构。此外,它可能会更慢,因为你需要几个级别的间接来到达邻居节点。
一个重要的注意事项 - 一旦你开始将整数存储到可变列表中,它们就会经历拳击。因此,在这两种情况下,您都将拥有一个堆对象列表。为了避免这种情况,并且为了节省内存,在b)方法中,你必须保持动态增长的整数数组,这些整数是邻居的索引。
现在,即使您按照上面的建议修改方法b),并确保Network
类中的索引列表确实是一个有效的结构(直接查找表或哈希表),您仍然会支付间接费用来查找Node
。内存消耗仍然会更高。我看到的唯一好处是保留某种弱引用表,如果您担心可能会耗尽内存,并在需要时重新创建Node
对象,而在{{indexed_list
中找不到它1}}保留一组弱引用。
这当然只是一个假设,您必须对代码进行分析/基准测试才能看出差异。
我的建议是在ArrayBuffer
中使用类似Node
的内容,并使用它存储对节点的直接引用。
如果内存问题是一个问题,并且你想要与弱引用一起做b)方法,那么我会进一步建议在你自己的动态增长的整数数组中为邻居滚动,以避免用ArrayBuffer[Int]
进行装箱