这个问题与Data.Vector
包有关。
考虑到一旦更新单元格,我永远不会使用某个单元格的旧值。更新操作是否总是创建一个新的向量,反映更新,还是作为就地更新完成?
注意:我知道Data.Vector.Mutable
答案 0 :(得分:13)
不,但可能会发生更好的事情。
使用"stream fusion"构建Data.Vector。这意味着如果您正在执行的操作序列构建然后拆除向量可以被融合,那么Vector本身将永远不会构建并且您的代码将变为优化循环。
Fusion通过将构建向量的代码转换为构建和拆除流的代码,然后将流放入编译器可以看到执行优化的形式来实现。
所以看起来像
的代码foo :: Int
foo = sum as
where as, bs, cs, ds, es :: Vector Int
as = map (*100) bs
bs = take 10 cs
cs = zipWith (+) (generate 1000 id) ds
ds = cons 1 $ cons 3 $ map (+2) es
es = replicate 24000 0
尽管看起来很容易建立和拆除,但是很多非常大的矢量可以一直融合到一个仅计算并增加10个数字的内环。
执行您提出的建议很棘手,因为它要求您知道在其他任何地方都不存在对术语的引用,这会导致将参考复制到环境中的任何尝试都会产生成本。而且,它与懒惰的相互作用相当差。你需要在你明显没有评估的thunk上添加一些仿射附加物。但是,要在多线程环境中执行此操作,可能会出现竞争,很难做到正确。
答案 1 :(得分:2)
那么,编译器究竟应该如何看到“旧的向量在任何地方都没有使用”?假设我们有一个改变矢量的函数:
changeIt :: Vector Int -> Int -> Vector Int
changeIt vec n = vec // [(0,n)]
从这个定义来看,编译器不能假设vec
表示对所讨论的向量的唯一引用。我们必须注释该函数,因此它只能以这种方式使用 - Haskell不支持(但就我所知,Clean就是这样)。
那么可以在Haskell中做什么?让我们说我们有另一个愚蠢的功能:
changeItTwice vec n = changeIt (changeIt vec n) (n+1)
现在GHC可以内联changeIt
,并且确实“看到”没有对中间结构的引用逃脱。但通常情况下,您会使用此信息来而不是生成该中间数据结构,而是直接生成最终结果!
这是一种非常常见的优化(例如,对于列表,有融合) - 我认为它完全符合您的角色:限制数据结构需要复制的次数。这种方法是否比就地更新更灵活有争议,但你绝对可以恢复很多性能,而不得不通过注释唯一性属性来打破抽象。
(但是,我认为Vector
目前实际上并没有执行这种特定的优化。可能还需要一些优化规则......)
答案 2 :(得分:1)
恕我直言,这肯定是不可能的,因为如果你随机改变一个物体(即使它不再使用),GHC垃圾收集器可能会受到严重破坏。那是因为对象可能会移动到老一代,并且变异可能会引入指向年轻一代的指针。如果现在年轻一代被垃圾收集,对象可能会移动,因此指针可能变得无效。
AFAIK,Haskell中的所有可变对象都位于一个特殊的堆上,由GC进行不同的处理,因此不会发生这样的问题。
答案 3 :(得分:1)
不一定。 Data.Vector
使用stream fusion,因此根据您的使用情况,可能根本不会创建向量,程序可能会编译为有效的常量空间循环。
这主要适用于转换整个矢量而不仅仅是更新单个单元格的操作。