gpt4 book ai didi

performance - 来自 monad 变压器基准测试的奇怪结果。一个错误?

转载 作者:行者123 更新时间:2023-12-03 23:26:55 25 4
gpt4 key购买 nike

我做了一些 Criterion 基准测试来估计通过在 monad 堆栈上运行我的代码会损失多少性能。结果相当奇怪,我可能在我的基准测试中偶然发现了一些懒惰的陷阱。

基准测试告诉我运行 WriterT String IO比运行普通 IO 慢 20 倍(!) , 即使不使用 tell .奇怪的是,如果我堆叠 WriterTReaderTContT它只是慢了 5 倍。这可能是我的基准测试中的一个错误。我在这里做错了什么?

基准

{-#LANGUAGE BangPatterns#-}
module Main where
import Criterion.Main
import Control.Monad
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Cont

process :: Monad m => Int -> m Int
process = foldl (>=>) return (replicate 100000 (\(!x) -> return (x+1)))

test n = process n >> return ()

main = defaultMain [
bench "Plain" t0
,bench "Writer" t1
,bench "Reader" t2
,bench "Cont" t3
,bench "RWC" t4
]

t0 = test 1 :: IO ()
t1 = (runWriterT (test 1:: WriterT String IO ()) >> return ()) :: IO ()
t2 = (runReaderT (test 1:: ReaderT String IO ()) "" >> return ()) :: IO ()
t3 = (runContT (test 1:: ContT () IO ()) (return) >> return ()) :: IO ()
t4 = ((runWriterT . flip runReaderT "" . flip runContT return $
(test 1 :: ContT () (ReaderT String (WriterT String IO)) ())) >> return ()) :: IO ()

结果

标杆平原
平均值:1.938814 ms,lb 1.846508 ms,ub 2.052165 ms,ci 0.950
标准开发:519.7248 us,lb 428.4684 us,ub 709.3670 us,ci 0.950

基准测试作家
平均值:39.50431 ms,lb 38.25233 ms,ub 40.74437 ms,ci 0.950
标准开发:6.378220 毫秒,磅 5.738682 毫秒,ub 7.155760 毫秒,ci 0.950

基准阅读器
平均值:12.52823 ms,lb 12.03947 ms,ub 13.09994 ms,ci 0.950
标准开发:2.706265 毫秒,磅 2.324519 毫秒,ub 3.462641 毫秒,ci 0.950

基准测试续
平均值:8.100272 ms,lb 7.634488 ms,ub 8.633348 ms,ci 0.950
标准开发:2.562829 毫秒,磅 2.281561 毫秒,ub 2.878463 毫秒,ci 0.950

基准 RWC
平均值:9.871992 ms,lb 9.436721 ms,ub 10.37302 ms,ci 0.950
标准开发:2.387364 ms,lb 2.136819 ms,ub 2.721750 ms,ci 0.950

最佳答案

正如您所注意到的,懒惰的 writer monad 非常慢。使用 Daniel Fischer 建议的严格版本有很大帮助,但为什么在大堆栈中使用它会变得如此之快?

为了回答这个问题,我们来看看这些转换器的实现。首先,懒惰的作家单子(monad)变压器。

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

instance (Monoid w, Monad m) => Monad (WriterT w m) where
return a = WriterT $ return (a, mempty)
m >>= k = WriterT $ do
~(a, w) <- runWriterT m
~(b, w') <- runWriterT (k a)
return (b, w `mappend` w')

如您所见,这做了很多事情。它运行底层 monad 的操作,进行一些模式匹配并收集写入的值。几乎是你所期望的。严格版本是类似的,只是没有无可辩驳的(惰性)模式。
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

instance (Monad m) => Monad (ReaderT r m) where
return = lift . return
m >>= k = ReaderT $ \ r -> do
a <- runReaderT m r
runReaderT (k a) r

阅读器变压器有点精简。它分发阅读器环境并调用底层 monad 来执行操作。这里没有惊喜。

现在,让我们看看 ContT .
newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }

instance Monad (ContT r m) where
return a = ContT ($ a)
m >>= k = ContT $ \c -> runContT m (\a -> runContT (k a) c)

注意到有什么不同吗? 它实际上并没有使用来自底层 monad 的任何函数! 事实上,它甚至不需要 m成为一个单子(monad)。这意味着根本没有进行缓慢的模式匹配或追加。只有当您真正尝试从底层 monad 中解除任何操作时, ContT使用它的绑定(bind)运算符。
instance MonadTrans (ContT r) where
lift m = ContT (m >>=)

因此,由于您实际上并没有做任何特定于作家的事情, ContT避免使用 WriterT 中的慢速绑定(bind)运算符.这就是为什么拥有 ContT在你的堆栈之上使它变得如此之快,以及为什么 ContT () IO () 的运行时间与更深的堆栈非常相似。

关于performance - 来自 monad 变压器基准测试的奇怪结果。一个错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8092787/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com