Haskell中高效的哈希映射容器?
我想使用Haskell计算存储在文件中的唯一块。 该块是长度为512的连续字节,目标文件的大小至少为1GB 这是我的第一次尝试Haskell中高效的哈希映射容器?,haskell,hashmap,hashtable,unordered-map,Haskell,Hashmap,Hashtable,Unordered Map,我想使用Haskell计算存储在文件中的唯一块。 该块是长度为512的连续字节,目标文件的大小至少为1GB 这是我的第一次尝试 import Control.Monad import qualified Data.ByteString.Lazy as LB import Data.Foldable import Data.HashMap import Data.Int import qualified Data.Li
import Control.Monad
import qualified Data.ByteString.Lazy as LB
import Data.Foldable
import Data.HashMap
import Data.Int
import qualified Data.List as DL
import System.Environment
type DummyDedupe = Map LB.ByteString Int64
toBlocks :: Int64 -> LB.ByteString -> [LB.ByteString]
toBlocks n bs | LB.null bs = []
| otherwise = let (block, rest) = LB.splitAt n bs
in block : toBlocks n rest
dedupeBlocks :: [LB.ByteString] -> DummyDedupe -> DummyDedupe
dedupeBlocks = flip $ DL.foldl' (\acc block -> insertWith (+) block 1 $! acc)
dedupeFile :: FilePath -> DummyDedupe -> IO DummyDedupe
dedupeFile fp dd = LB.readFile fp >>= return . (`dedupeBlocks` dd) . toBlocks 512
main :: IO ()
main = do
dd <- getArgs >>= (`dedupeFile` empty) . head
putStrLn . show . (*512) . size $ dd
putStrLn . show . (*512) . foldl' (+) 0 $ dd
我认为这主要是由于hashmap实现,并尝试了其他实现,如hashmap
、hashtables
和无序容器
。
但是没有任何明显的区别
请帮助我改进这个程序。我认为你无法打败python词典的性能。它们实际上是用c实现的,经过多年的优化,另一方面,hashmap是新的,没有太多优化。因此,在我看来,获得3倍的性能就足够了。您可以在某些地方优化haskell代码,但这并不重要。如果您仍然坚持要提高性能,我认为您应该在代码中使用高度优化的c库和ffi 下面是一些类似的讨论
这可能与您的用法完全无关,但我有点担心
插入(+)块1
。如果计数达到高位,则会在哈希映射的单元格中累积thunk。使用只强制脊椎的($!)
,这并不重要——这些值可能仍然是惰性的
Data.HashMap
没有像Data.Map
那样提供严格的版本insertWith'
。但您可以实现它:
insertWith' :: (Hashable k, Ord k) => (a -> a -> a) -> k -> a
-> HashMap k a -> HashMap k a
insertWith' f k v m = maybe id seq maybeval m'
where
(maybeval, m') = insertLookupWithKey (const f) k v m
此外,您可能希望输出(而不是输入)来自toBlocks
的strict bytestring列表,这将加快散列速度
这就是我所拥有的——不过我不是性能大师。我通过创建一个
data Blk=Blk{-#UNPACK}字64,可以挤出一点时间
保存512字节。如果您切换到strict ByteString,性能会有相当大的提高,但我不确定这其中有多少是由于缓存之类的影响,还有多少是由于我的老对手懒惰的ByteString块没有合理的对齐(这让我担心,因为它会导致分支、复制等)。最后,无序容器
做得最好(py为4.8秒,hs为6.5秒,但这是严格的testrings),而哈希表
由于没有插入
操作而令人沮丧。@luqui感谢您的回答,我从您那里学到了一些东西。实际上,无序容器中有Data.HashMap.Strict
,我试过了,但它不能改善情况,StrictByteString也不能toStrict
有点贵。toStrict
如果将defaultChunkSize
更改为512
的倍数,然后通过TestRing重新编译/安装,可能会有好处。如果不这样做,toStrict
函数将在几乎每个块边界复制数据。实际上,我最关心的是内存使用情况,我无法理解Haskell hashmaps的内存过度使用。例如,当输入文件仅包含600MB的唯一数据时,它会占用大约1GB或更多的内存。无论如何,谢谢你的回答和文章链接。我应该考虑使用FFI。@昏迷,这只是GHC。GHC垃圾收集策略使用复制收集器,速度非常快,但内存开销是原来的2倍。
insertWith' :: (Hashable k, Ord k) => (a -> a -> a) -> k -> a
-> HashMap k a -> HashMap k a
insertWith' f k v m = maybe id seq maybeval m'
where
(maybeval, m') = insertLookupWithKey (const f) k v m