将元素追加到百万元素ArrayList
上会产生现在设置一个引用的成本,以及将来必须调整ArrayList
时复制一个引用的费用。
据我了解,将元素附加到百万元素PersistenVector
上必须创建一个新路径,该路径由4个大小为32的数组组成。这意味着必须触及超过120个引用。
Clojure如何设法将矢量开销保持在"差2.5倍"或者" 4倍恶化" (与#34;差60倍"相反),我最近看到的几个Clojure视频中声称了这一点?它与缓存或引用的位置或我不知道的东西有关吗?
或者以某种方式在某种程度上可以使用变异在内部构建一个向量,然后在将它暴露给外界之前将其变为不可变?
我也标记了问题scala,因为scala.collection.immutable.vector
基本上是相同的,对吗?
答案 0 :(得分:9)
Clojure的PersistentVector具有特殊的尾部缓冲区,可以在向量的末尾实现高效的操作。只有在填充了这个32元素数组之后,才会将其添加到树的其余部分。这使摊销成本保持在较低水平。 Here是关于实施的一篇文章。 source也值得一读。
关于,“是否有可能在内部构建带有变异的载体,然后在向外界揭示它之前将其变为不可变的?”,是的!这些在Clojure中称为transients,用于有效的批量更改。
答案 1 :(得分:7)
无法讲述Clojure,但我可以对Scala Vectors进行一些评论。
持久性Scala向量(scala.collection.immutable.Vector
s)在追加时比数组缓冲区多慢。实际上,它们比List
前置操作慢10倍。它们比追加到我们在并行集合中使用的Conc-tree慢2倍。
但是,Scala还有可变向量 - 它们隐藏在类VectorBuilder
中。附加到可变向量不会保留向量的先前版本,而是通过将指针保持在向量中最右边的叶子来使其变异。所以,是的 - 在内部保持向量可变,而不是返回不可变引用正是在Scala集合中完成的。
VectorBuilder
略快于ArrayBuffer
,因为它只需要分配一次数组,而ArrayBuffer
需要平均两次(因为增长)。我们用作并行数组合器的Conc.Buffer
s的速度是VectorBuilder
的两倍。
基准在这里。没有任何基准涉及任何拳击,它们与参考对象一起使用以避免任何偏见:
List
, Vector
and Conc
ArrayBuffer
, VectorBuilder
and Conc.Buffer
更多收藏基准here。
这些测试是使用ScalaMeter执行的。