【问题标题】:Writing fusible O(1) update for vector为向量编写可熔的 O(1) 更新
【发布时间】:2013-06-08 15:25:24
【问题描述】:

这是question 的延续。由于向量库似乎没有可融合的 O(1) 更新函数,我想知道是否可以编写不涉及 unsafeFreezeunsafeThaw 的可融合 O(1) 更新函数。我猜它会使用vector stream 表示-我不熟悉如何使用streamunstream 编写一个-因此,这个问题。原因是这将使我们能够在向量上编写一个缓存友好的更新函数,其中只有一个狭窄的向量区域被修改,因此,我们不想遍历整个向量来处理那个狭窄的区域(并且此操作在每个函数调用中可能发生数十亿次 - 因此,保持开销非常低的动机)。像map 这样的转换函数会处理整个向量 - 所以它们会太慢。

我有一个我想做的玩具示例,但是下面的 upd 函数使用了 unsafeThawunsafeFreeze - 它似乎没有在核心中进行优化,并且也违反了不再使用缓冲区:

module Main where
import Data.Vector.Unboxed as U
import Data.Vector.Unboxed.Mutable as MU
import Control.Monad.ST

upd :: Vector Int -> Int -> Int -> Vector Int
upd v i x = runST $ do
          v' <- U.unsafeThaw v
          MU.write v' i x
          U.unsafeFreeze v'

sum :: Vector Int -> Int
sum = U.sum . (\x -> upd x 0 73) . (\x -> upd x 1 61)

main = print $ Main.sum $ U.fromList [1..3]

我知道如何使用STVector 实现命令式算法。如果您想知道为什么使用这种替代方法,我想尝试这种使用纯向量的方法来检查特定算法的GHC 转换在使用可熔纯向量流编写时有何不同(当然还有单子操作) .

当使用STVector 编写算法时,它似乎不像我希望的那样迭代(我猜当存在大量可变性时,GHC 优化器更难发现循环)。所以,我正在研究这种替代方法,看看我可以在那里获得更好的循环。

【问题讨论】:

  • unsafeFreeze/Thaw 显然是为了更新向量,否则向量的纯度会被破坏。

标签: haskell vector stream fusion


【解决方案1】:

您编写的upd 函数看起来并不正确,更不用说可熔断了。 Fusion 是一种库级别的优化,需要您使用某些原语编写代码。在这种情况下,您想要的不仅仅是融合,而是recycling,这可以通过//update 等批量更新操作轻松实现。这些操作将融合在一起,甚至在大部分时间发生在原地。

如果您真的想编写自己的基于破坏性更新的代码,请勿使用unsafeThaw--使用modify

【讨论】:

【解决方案2】:

任何函数都是可熔更新函数;你似乎试图摆脱向量包试图让你使用的编程模型

module Main where
import Data.Vector.Unboxed as U

change :: Int -> Int -> Int
change 0 n = 73
change 1 n = 61
change m n = n

myfun2 = U.sum . U.imap change .  U.enumFromStepN 1 1 
main = print $ myfun2 30000000 

-- 这不会创建任何向量,更不用说“更新”它们了,如果你研究核心,你会看到。

【讨论】:

  • imap 处理每个向量元素,这使得 O(n) 的更新只影响一个向量元素。还是真的不是这样?就像ghc 发现只有 1..k(1..n 个)元素受到影响,因此,将其转换为仅处理这 k 个元素的循环?
  • 你的想法有问题,不是吗?事实上,imap 在最坏的情况下会研究一个实际的向量,并逐个元素地遍历它并编写一个新向量。如果它向左或向右融合——在上面它是双向的——那么这将不是一个正确的描述。
  • 因此,与 U.sum 融合后,在原始向量上只有一个折叠,依次添加其 (changed) 元素;它查看每个元素,但只查看一次,change 一次,U.sum 一次。
  • 如果它与U.enumFromStepN 之类的东西融合,最多将写入一个向量,那么普通的U.enumFromStepN 1 1 10000 将永远不存在,然后通过查看每个元素和changeing 来更改或复制它。所以问题不会出现,“我们实际处理了多少元素”。唯一存在的元素是 change -d 元素,因此“逐个检查”的明显成本是一种错觉。最后,和本例一样,左右融合,核对显示根本没有写向量。
  • 我明白你在说什么。是的,我熟悉这种处理向量的方式。但是,我正在编写的 diff/longest common subsequence 算法似乎并未转化为该模型。给定两个长度为 4k 的序列,在一种特定情况下,它将遍历向量更新函数 16M 次,每次迭代中的一个位置,取决于先前迭代的输出(即来自先前迭代的向量)。也许可以按照您建议的方式完成,而不会变慢。我会做更多的实验。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多