生成独特的可比值

时间:2011-12-24 05:59:54

标签: haskell ghc

生成临时密钥的好方法是什么?每个密钥对于程序是唯一的?理想情况下,形式的行为:

newKey :: IO Key

这样:

do a <- newKey
   b <- newKey
   return (a == b)

始终返回false。此外,应该可以在有效的关联容器中使用Key(例如Map)。

例如,这可用于维护支持随机插入和删除的事件处理程序集合:

Map Key EventHandler

我知道的选项:

  • newIORef ():满足上面的不变量,但IORef没有Ord实例。

  • malloc:快速,Ptr ()有一个Ord实例,但结果不是垃圾回收。

  • newStablePtr ():未收集垃圾,StablePtr没有Ord个实例。

  • newIORef () >>= makeStableName:应该满足上面的不变量并且是垃圾收集,但更难以使用(要求我使用哈希表)。

  • mallocForeignPtrBytes 1:满足这两个标准,但效率如何?

mallocForeignPtrBytes 1似乎是我最好的选择。我想我可以通过直接使用GHC的newPinnedByteArray# primop来提高效率。

还有更好的选择吗? mallocForeignPtrBytes方法是否因某些非显而易见的原因而存在缺陷?

4 个答案:

答案 0 :(得分:11)

如果您不想在项目中添加任何其他依赖项,可以在基础包中使用Data.Unique

在内部,系统使用Integer的{​​{3}}(依赖于GHC的STM系统),这样每次调用newUnique时,TVar都会增加原子地,新的Integer存储在不透明的Unique数据类型中。由于TVar s不能同时被不同的线程修改,因此它们保证Unique按顺序生成,并且它们实际上必须是唯一的。

答案 1 :(得分:5)

hackage上有几个相关的包。 concurrent-supply package看起来非常精心设计。

答案 2 :(得分:3)

感谢,dflemstr,pointing out Data.Unique。我对Data.Unique与几个替代实现进行了基准测试:

Unique2.hs

基于mallocForeignPtrBytes

{-# LANGUAGE DeriveDataTypeable #-}
module Unique2 (Unique2, newUnique2) where

import Control.Applicative
import Data.Typeable (Typeable)
import Data.Word (Word8)
import Foreign.ForeignPtr

newtype Unique2 = Unique2 (ForeignPtr Word8)
    deriving (Eq, Ord, Typeable)

newUnique2 :: IO Unique2
newUnique2 = Unique2 <$> mallocForeignPtrBytes 1

Unique3.hs

基于newPinnedByteArray,由mallocForeignPtrBytes内部使用。它与Unique2基本相同,减去了一些包装器开销。

{-# LANGUAGE DeriveDataTypeable #-}
module Unique3 (Unique3, newUnique3) where

import Control.Applicative
import Data.Primitive.ByteArray
import Data.Primitive.Types
import Data.Typeable

newtype Unique3 = Unique3 Addr
    deriving (Eq, Ord, Typeable)

newUnique3 :: IO Unique3
newUnique3 = Unique3 . mutableByteArrayContents <$> newPinnedByteArray 1

独特-benchmark.hs

import Criterion.Main

import Control.Exception (evaluate)
import Control.Monad
import Data.Unique
import Unique2
import Unique3

import qualified Data.Set as S

main :: IO ()
main = defaultMain
    [ bench "Unique" $
        replicateM 10000 newUnique  >>= evaluate . S.size . S.fromList
    , bench "Unique2" $
        replicateM 10000 newUnique2 >>= evaluate . S.size . S.fromList
    , bench "Unique3" $
        replicateM 10000 newUnique3 >>= evaluate . S.size . S.fromList
    ]

结果:

使用ghc -Wall -O2 -threaded -fforce-recomp unique-benchmark.hs编译:

benchmarking Unique
mean: 15.75516 ms, lb 15.62392 ms, ub 15.87852 ms, ci 0.950
std dev: 651.5598 us, lb 568.6396 us, ub 761.7921 us, ci 0.950

benchmarking Unique2
mean: 20.41976 ms, lb 20.11922 ms, ub 20.67800 ms, ci 0.950
std dev: 1.427356 ms, lb 1.254366 ms, ub 1.607923 ms, ci 0.950

benchmarking Unique3
mean: 14.30127 ms, lb 14.17271 ms, ub 14.42338 ms, ci 0.950
std dev: 643.1826 us, lb 568.2373 us, ub 737.8637 us, ci 0.950

如果我将幅度从10000提升到100000

benchmarking Unique
collecting 100 samples, 1 iterations each, in estimated 21.26808 s
mean: 206.9683 ms, lb 206.5749 ms, ub 207.7638 ms, ci 0.950
std dev: 2.738490 ms, lb 1.602821 ms, ub 4.941017 ms, ci 0.950

benchmarking Unique2
collecting 100 samples, 1 iterations each, in estimated 33.76100 s
mean: 319.7642 ms, lb 316.2466 ms, ub 323.2613 ms, ci 0.950
std dev: 17.94974 ms, lb 16.93904 ms, ub 19.34948 ms, ci 0.950

benchmarking Unique3
collecting 100 samples, 1 iterations each, in estimated 21.22741 s
mean: 200.0456 ms, lb 199.2538 ms, ub 200.9107 ms, ci 0.950
std dev: 4.231600 ms, lb 3.840245 ms, ub 4.679455 ms, ci 0.950

结论:

Data.Unique(内置实现)和Unique3(基于newPinnedByteArray)在这些测试中处于领先地位。 newUnique3本身比newUnique快十倍,但密钥生成开销与使用成本相比相形见绌。

Unique2因包装器开销而丢失,但在Data.Unique和Unique3之间,我的结果不确定。我推荐Data.Unique只是因为它已经在base中可用。但是,如果您正在努力从某些应用程序中挤出最后一点性能,请尝试使用Unique3替换Data.Unique,并告诉我它是如何运行的。

答案 3 :(得分:1)

我见过的一种方式,如果你想要它在顶层是一个像这样的黑客:

import Data.IORef
import System.IO.Unsafe

newtype Key = Key Integer deriving (Ord, Eq, Show)

newKey :: IO Key
{-# NOINLINE newKey #-}
newKey = unsafePerformIO mkNewKey

mkNewKey :: IO (IO Key)
mkNewKey = do
  r <- newIORef 0
  return $ do
    modifyIORef r (+1)
    Key `fmap` (readIORef r)

main = do
  a <- newKey
  b <- newKey
  print (a,b)