对HashTable性能问题感到好奇

Ale*_*tto 50 haskell hashtable ghc

我读到Haskell中的哈希表存在性能问题(2006 年的Haskell-Cafe和2009年的Flying Frog Consultancy的博客),因为我喜欢Haskell,所以我很担心.

那是一年前,现在的状况是什么(2010年6月)?GHC中是否修复了"哈希表问题"?

Don*_*art 135

问题是垃圾收集器需要遍历可变指针数组("盒装数组"),寻找可能准备解除分配的数据的指针.盒装可变数组是实现哈希表的主要机制,因此特定结构显示了GC遍历问题.这在许多语言中都很常见.症状是垃圾收集过多(GC中花费的时间高达95%).

修复是在GC中为可变指针数组实现"卡标记",这些指针发生在2009年末.现在,在Haskell中使用可变指针数组时,不应该看到过多的GC.在简单的基准测试中,大散列的哈希表插入提高了10倍.

请注意,GC行走问题不会影响纯粹的功能结构,也不会影响Haskell中的未装箱数组(如大多数数据并行数组或类似矢量的数组.也不会影响存储在C堆上的哈希表(如judy).它不会影响日常Haskeller不使用命令式哈希表.

如果你在Haskell中使用哈希表,你现在不应该发现任何问题.例如,这是一个简单的散列表程序,它将1000万个int插入到散列中.我将进行基准测试,因为原始引文不提供任何代码或基准.

import Control.Monad
import qualified Data.HashTable as H
import System.Environment

main = do
  [size] <- fmap (fmap read) getArgs
  m <- H.new (==) H.hashInt
  forM_ [1..size] $ \n -> H.insert m n n
  v <- H.lookup m 100
  print v
Run Code Online (Sandbox Code Playgroud)

使用GHC 6.10.2,在修复之前,插入10M整数:

$ time ./A 10000000 +RTS -s
...
47s.
Run Code Online (Sandbox Code Playgroud)

使用GHC 6.13,修复后:

./A 10000000 +RTS -s 
...
8s
Run Code Online (Sandbox Code Playgroud)

增加默认堆区域:

./A +RTS -s -A2G
...
2.3s
Run Code Online (Sandbox Code Playgroud)

避免哈希表和使用IntMap:

import Control.Monad
import Data.List
import qualified Data.IntMap as I
import System.Environment

main = do
  [size] <- fmap (fmap read) getArgs
  let k = foldl' (\m n -> I.insert n n m) I.empty [1..size]
  print $ I.lookup 100 k
Run Code Online (Sandbox Code Playgroud)

我们得到:

$ time ./A 10000000 +RTS -s        
./A 10000000 +RTS -s
6s
Run Code Online (Sandbox Code Playgroud)

或者,使用judy数组(通过外部函数接口调用C代码的Haskell包装器):

import Control.Monad
import Data.List
import System.Environment
import qualified Data.Judy as J

main = do
  [size] <- fmap (fmap read) getArgs
  j <- J.new :: IO (J.JudyL Int)
  forM_ [1..size] $ \n -> J.insert (fromIntegral n) n j
  print =<< J.lookup 100 j
Run Code Online (Sandbox Code Playgroud)

跑这个,

$ time ./A 10000000 +RTS -s
...
2.1s
Run Code Online (Sandbox Code Playgroud)

因此,正如您所看到的,哈希表的GC问题是固定的,并且始终存在非常适合的其他库和数据结构.总之,这不是问题.

注意:从2013年开始,您应该只使用hashtables包,它本身支持一系列可变哈希表.

  • 出于好奇,我在自己的系统上运行了这些微基准测试,并发布了一个使用Boost哈希表的C++版本.与运行较大堆的Haskell哈希表版本相比,它的挂壁时间并不快:C++ 1.613s,Haskell 1.862s.我很惊讶! (11认同)
  • 哇,完美答案.Stack Overflow社区非常棒!我还不能投票(没有足够的声誉),否则我肯定会投票!非常感谢你的回答,它非常强大! (6认同)
  • 与往常一样,GHC的独特,前沿分析和优化系统的可爱演示(http://www.reddit.com/r/haskell/comments/cbnwp/when_translating_c_to_haskell_use_the_same/c0rh4sv). (5认同)
  • @Jules:在运行32位Windows Vista的2×2.0GHz E5405 Xeon上,我使用最新的Haskell平台版本(GHC 6.12.1)获得Visual F#2010的0.8s和Haskell的哈希表的19.2s.出于兴趣,我获得了32个用于Haskell的纯功能`Map`和用于F#中的36个.所以Haskell仍然比F#慢24倍. (4认同)
  • 干得好,像往常一样,完全覆盖(定义问题,并通过示例显示,为什么它不再是问题).+1 (3认同)
  • @Don:F#的数据并没有过时,Don.其他语言仍然比可以在Haskell中编写的任何语言快得多.没有任何一个人为Haskell引用的结果已接近去年在旧机器上引用的F#的0.7s. (3认同)
  • 也许你们应该在同一台机器上运行这个Haskell基准测试和等效的F#来查找. (2认同)
  • @Jules:这些程序来自我最初的博客文章,其中F#在Release模式下编译,而Haskell由GHC 6.12.1(来自最新的Haskell平台)编译,使用`ghc -O2 --make hash.hs -o hash` .引用的时间是三次运行中的最小值. (2认同)

Nor*_*sey 26

像这样的问题只能通过实验来解决.但如果你没有时间或金钱做实验,你必须问别人他们的想法.当您这样做时,您可能需要考虑来源并考虑所提供的信息是否已经过任何方式的审查或审查.

Jon Harrop提出了一些有关Haskell的有趣主张.我建议您在Google Groups和其他地方搜索Harrop在Haskell,Lisp和其他函数语言方面的专业知识.您还可以阅读Chris Okasaki和Andy Gill在Haskell的Patricia树上的作品,看看他们的专业知识是如何被看到的.您还可以找到第三方已检查过哪些索赔(如果有).然后,您可以自己决定如何认真地采取不同的人对不同功能语言的表现的主张.

哦,不要喂巨魔.


PS你做自己的实验是非常合理的,但也许没有必要,因为可信赖的唐·斯图尔特在他的好答案中提出了一些不错的微基准.这是Don回答的附录:


附录:在AMD Phenom 9850黑色版上使用Don Stewart的代码,主频为2.5GHz,4GB RAM,32位模式,带ghc -O,

  • 使用默认堆,IntMap比哈希表快40%.
  • 使用2G堆,哈希表比40%快IntMap.
  • 如果我去用默认堆千万元,将IntMap快四倍比哈希表(CPU时间)或者快两倍的挂钟时间.

我对这个结果感到有点惊讶,但确信功能数据结构表现相当不错.并且我相信在我们将要使用的实际条件下对您的代码进行基准测试确实是值得的.

  • @Justice:*我*不想在Haskell中使用哈希表.我对默认的Patricia树和其他功能结构非常满意.整个线程开始是因为Jon Harrop似乎正在对Haskell进行某种私人战争(可能为什么这个问题现在被锁定).哈罗普的一个策略就是说哈斯克尔的哈希表是可怕的,纯粹的功能结构是可怕的等等.我们中的许多人不相信他的信息的质量,并希望人们不要把他带到面值.这就是这个问题的意义所在. (11认同)
  • +1表示更多的研究.在像Haskell这样基于研究的社区中(例如GHC),这是可行的方法. (2认同)
  • 其他语言(例如C++或.NET)中的哈希表比Haskell中的任何内容都快*运行*包括`IntMap`:http://www.mail-archive.com/haskell-cafe@haskell.org/ msg57390.html (2认同)
  • 为什么在Haskell中使用哈希表?目的是使Haskell成为一种具有良好语法,灵活性和性能的纯函数式语言.如果你正在寻找一个高性能的可变数据结构(即非纯函数),那么Haskell可以为你工作,但也许有更好的解决方案来解决你的问题,因为这个问题不是Haskell专门解决的问题. (2认同)

Jon*_*rop 6

In short, even with the fix in the latest GHC, Haskell is still incapable of providing a dictionary (mutable or immutable) that is competitively efficient.

Haskell's hash tables were 32× slower than alternatives like C++ and .NET with GHC 6.10. That was partly due to a performance bug in the GHC garbage collector that was fixed for GHC 6.12.2. But Simon Marlow's results there show only a 5× performance improvement which still leaves Haskell's hash tables many times slower than most alternatives.

Purely functional alternatives are also much slower than a decent hash table. For example, Haskell's IntMap is 10× slower than .NET's hash table.

Using F# 2010 and the latest Haskell Platform 2010.2.0.0 (released yesterday!) with GHC 6.12.3 on this 2.0GHz E5405 Xeon running 32-bit Windows Vista to insert 20M int->int bindings into an empty hash table we find that Haskell is still 29× slower than F# in real time and over 200× slower in terms of CPU time because the Haskell burns all cores:

GHC 6.12.3 Data.HashTable: 42.8s (new!)
.NET hash table:            1.47s

Provided you run only short-lived microbenchmarks you can disable the GHC garbage collector as Don Stewart suggests above. By asking for a nursery generation so large that this particular program will never fill it, he brought the time for the Haskell hash table down to only 1.5s here. However, this completely undermines the whole point of having a nursery generation and will massively degrade the performance of other code because freshly allocated values will now always be cold in the cache (which is why the nursery generation is typically the size of the L2 cache, orders of magnitude smaller than this).

  • 我编辑了这个(修订版5),以明确你正在谈论的GHC的哪个版本,因为有些含糊不清.你为什么回滚? (5认同)
  • 亚历山德罗:我赞扬诺曼关于将乔恩的主张视为面子的建议.请查看Bradford发布的C++基准测试示例. (4认同)
  • 该基准测试用于1000万个密钥,该链接表示"合理的性能和相当低的内存使用率,高达约1000万到2000万个条目." 关于boost哈希表. (3认同)
  • @Jon:在你去年的帖子中,你从未包含C++代码或基准测试结果,并且发布评论结果的人只用1M键而不是10M测试. (3认同)
  • @Alessandro:我说"只"因为它甚至没有让你获得C++或.NET速度的一半. (2认同)
  • @Ganesh:你如何解释布拉德福德最新的C++代码比我去年发布的F#代码慢2倍的事实? (2认同)
  • 我不需要,我只是评论你对C++的主张.毫无疑问,如果你足够努力,你将能够产生不同的测量,但它仍然是非GCed语言的一个有趣的数据点. (2认同)
  • @Ganesh:毫无疑问,你将能够用非GCed语言集合更慢的哈希表.只有最快的结果才有意思. (2认同)
  • 是的但是Nick Welch已经将这个Boost哈希表的性能描述为在这个int键的上下文中"惊人",因为他发现它比替代品慢了5倍(甚至包括内置的`unordered_map`):http:/ /incise.org/hash-table-benchmarks.html (2认同)
  • @Jon:看一下Nick Welch在10M参赛作品上的图表.在10M条目中,对于运行时实验,Boost unordered_map和std unordered_map彼此非常接近.Ganesh也指出了这一点.我的结果与尼克韦尔奇所呈现的一致. (2认同)