gpt4 book ai didi

performance - GHC forkIO 双峰性能

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

我在做基准测试 forkIO使用以下代码:

import System.Time.Extra
import Control.Concurrent
import Control.Monad
import Data.IORef


n = 200000

main :: IO ()
main = do
bar <- newEmptyMVar
count <- newIORef (0 :: Int)
(d, _) <- duration $ do
replicateM_ n $ do
forkIO $ do
v <- atomicModifyIORef' count $ \old -> (old + 1, old + 1)
when (v == n) $ putMVar bar ()
takeMVar bar
putStrLn $ showDuration d

这会产生 20K 线程,计算有多少线程使用 IORef 运行,当它们都开始时,完成。在 Windows 上使用命令 ghc --make -O2 Main -threaded && main +RTS -N4 在 GHC 8.10.1 上运行时性能差异很大。有时需要 > 1 秒(例如 1.19 秒),有时需要 < 0.1 秒(例如 0.08 秒)。似乎它大约有 1/6 的时间在更快的桶中。为什么性能差异?是什么导致它跑得更快?

当我缩放 n高达 1M 效果消失,它始终在 5+s 范围内。

最佳答案

我也可以在 Ubuntu 上确认相同的行为。除非我设置 n=1M这种行为不会消失,我的运行时间范围为 2 到 7 秒。

我相信调度程序的不确定性是导致运行时出现如此显着差异的原因。当然,这不是一个确定的答案,因为这只是我的猜测。
atomicModifyIORef'使用 CAS(比较和交换)实现,因此取决于线程的执行方式 old + 1将或多或少地重新计算。换句话说,如果线程 B 更新 count ref 在线程 A 有机会更新 count 之前ref,但是在它开始更新之后,它必须从头开始更新操作,因此从 ref 中读取新的更新值并重新计算 old + 1再次。

如果你运行 main +RTS -N1 ,您会看到不仅运行程序所需的时间要少得多,而且执行之间的运行时间也非常一致。我怀疑这是因为任何时候只能运行一个线程,并且在 atomicModifyIORef' 之前没有抢占。已经完成了。

希望其他对 Haskell RTS 有深入了解的人可以对这种行为提供更多的见解,但这是我的看法。

编辑

@NeilMitchel 评论道:

I'm not convinced it's anything to do with the atomic modification at all



为了证明 IORef 确实存在错误,这里有一个使用 PVar 的实现。这依赖于 casIntArray# 下。它不仅快 10 倍,而且没有观察到差异:

import System.Time.Extra
import Control.Concurrent
import Control.Monad
import Data.Primitive.PVar -- from `pvar` package


n = 1000000

main :: IO ()
main = do
bar <- newEmptyMVar
count <- newPVar (0 :: Int)
(d, _) <- duration $ do
replicateM_ n $ do
forkIO $ do
v <- atomicModifyIntPVar count $ \old -> (old + 1, old + 1)
when (v == n) $ putMVar bar ()
takeMVar bar
putStrLn $ showDuration d

关于performance - GHC forkIO 双峰性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61971292/

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