在haskell中,异步代码的运行速度比同步版本慢

在haskell中,异步代码的运行速度比同步版本慢,haskell,concurrency,benchmarking,io-monad,criterion,Haskell,Concurrency,Benchmarking,Io Monad,Criterion,对以下各项进行基准测试: #!/usr/bin/env stack -- stack --resolver lts-16.2 script --package async --package criterion import Control.Concurrent.Async (async, replicateConcurrently_) import Control.Monad (replicateM_, void) import

对以下各项进行基准测试:

#!/usr/bin/env stack
-- stack --resolver lts-16.2 script --package async --package criterion

import           Control.Concurrent.Async (async, replicateConcurrently_)
import           Control.Monad            (replicateM_, void)
import           Criterion.Main

main :: IO ()
main = defaultMain [
    bgroup "tests" [ bench "sync" $ nfIO syncTest
                   , bench "async" $ nfIO asyncTest
                   ]
    ]

syncTest :: IO ()
syncTest = replicateM_ 100000 dummy

asyncTest :: IO ()
asyncTest = replicateConcurrently_ 100000 dummy

dummy :: IO Int
dummy = return $ fib 10000000000

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
给我这个:

% ./applicative-v-monad.hs
benchmarking tests/sync
time                 2.120 ms   (2.075 ms .. 2.160 ms)
                     0.997 R²   (0.994 R² .. 0.999 R²)
mean                 2.040 ms   (2.023 ms .. 2.073 ms)
std dev              77.37 μs   (54.96 μs .. 122.8 μs)
variance introduced by outliers: 23% (moderately inflated)

benchmarking tests/async
time                 475.3 ms   (310.7 ms .. 642.8 ms)
                     0.984 R²   (0.943 R² .. 1.000 R²)
mean                 527.2 ms   (497.9 ms .. 570.9 ms)
std dev              41.30 ms   (4.833 ms .. 52.83 ms)
variance introduced by outliers: 21% (moderately inflated)
很明显,asyncTest的运行时间比syncTest长


我本以为并发运行昂贵的操作比按顺序运行要快。我的推理有缺陷吗?

这个基准有一些问题

首先是懒惰 正如@David Fletcher所指出的,您并没有强迫计算fib。此问题的修复通常非常简单:

dummy::IO Int
dummy=返回$!fib 1000000000
这足以让我们等待永恒。下一步我们应该做的是将其降低到更易于管理的程度:

dummy::IO Int
dummy=返回$!fib 35
这通常就足够了,但是ghc太聪明了,它会发现这种计算非常纯粹,会将100000次迭代的循环优化为一次计算,并返回100000次相同的结果,所以实际上它只会计算一次fib。相反,让我们根据迭代次数确定
fib

xs::[Int]
xs=[1..35]
syncTest::IO()
syncTest=mapM_uuu虚拟xs
asyncTest::IO()
asyncTest=Map\uUx虚拟xs
虚拟::Int->IO Int
虚拟n=返回$!小谎
下一个问题是编译
堆栈脚本
将在没有线程化环境的情况下运行所需的代码。因此,您的代码将按顺序缓慢运行。我们通过手动编译和一些标志来修复它:

$stack exec--resolver lts-16.2--package-async--package-criteria--ghc-threaded-O2-rtsopts-with-rtsopts=-N bench-async.hs
$stack exec--解析器lts-16.2-./工作台异步
当然,对于一个完整的堆栈项目,所有这些标志都会进入一个cabal文件,而运行
stack bench
将完成其余的工作

最后不能不提线程太多。 在问题中,您有
asyncTest=replicateconcurrency\uu100000 dummy
。除非迭代次数非常少(事实并非如此),否则您不希望为此使用
async
,因为生成至少100000个线程不是免费的,最好使用偷工调度器来处理此类工作负载。我专门为此编写了一个库:

下面是一个如何使用它的示例:

将符合条件的控件.调度程序作为
main::IO()
main=defaultMain[
B组“测试”[工作台“同步”$whnfIO syncTest
,工作台“异步”$nfIO异步测试
,工作台“调度器”$nfIO schedulerTest
]
]
schedulerTest::IO()
SpultRealTest= S.TraceFrime2当前Par哑XS
现在,这将给我们提供更合理的数字:

benchmarking tests/sync
time                 246.7 ms   (210.6 ms .. 269.0 ms)
                     0.989 R²   (0.951 R² .. 1.000 R²)
mean                 266.4 ms   (256.4 ms .. 286.0 ms)
std dev              21.60 ms   (457.3 μs .. 26.92 ms)
variance introduced by outliers: 18% (moderately inflated)

benchmarking tests/async
time                 135.4 ms   (127.8 ms .. 147.9 ms)
                     0.992 R²   (0.980 R² .. 1.000 R²)
mean                 134.8 ms   (129.7 ms .. 138.0 ms)
std dev              6.578 ms   (3.605 ms .. 9.807 ms)
variance introduced by outliers: 11% (moderately inflated)

benchmarking tests/scheduler
time                 109.0 ms   (96.83 ms .. 120.3 ms)
                     0.989 R²   (0.956 R² .. 1.000 R²)
mean                 111.5 ms   (108.0 ms .. 120.2 ms)
std dev              7.574 ms   (2.496 ms .. 11.85 ms)
variance introduced by outliers: 12% (moderately inflated)

没有时间写出正确的答案,但如果对fib 1000000000进行评估,则需要大约2^1000000000个步骤,而不是几毫秒。你的两个测试都没有强迫它。我将对此进行投票,但去掉你说的最大的已知斐波那契只有465位的那部分,因为那是错误的。如果你愿意,你可以制作一个和硬盘一样大的。@Davidletcher我删除了这个评论,因为它确实是错的。我想我脑子里有些东西搞混了,不假思索地说了那句话。感谢您指出。您可以使用类似
35+(n`rem`2)
@dfeur-sure的方法降低对计数的依赖性。但是,你知道,这纯粹是演示,这使得看到实际并行化的效果变得不那么重要。