生成临时密钥的好方法是什么?每个密钥对于程序是唯一的?理想情况下,形式的行为:
newKey :: IO Key
这样:
do a <- newKey
b <- newKey
return (a == b)
始终返回false。此外,应该可以在有效的关联容器中使用Key
(例如Map
)。
例如,这可用于维护支持随机插入和删除的事件处理程序集合:
Map Key EventHandler
我知道的选项:
malloc
:快速,Ptr ()
有一个Ord实例,但结果不是垃圾回收。
newStablePtr
()
:未收集垃圾,StablePtr
没有Ord
个实例。
newIORef
() >>=
makeStableName
:应该满足上面的不变量并且是垃圾收集,但更难以使用(要求我使用哈希表)。
mallocForeignPtrBytes
1
:满足这两个标准,但效率如何?
mallocForeignPtrBytes 1
似乎是我最好的选择。我想我可以通过直接使用GHC的newPinnedByteArray#
primop来提高效率。
还有更好的选择吗? mallocForeignPtrBytes
方法是否因某些非显而易见的原因而存在缺陷?
答案 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
与几个替代实现进行了基准测试:
{-# 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
基于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
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)