代码变得更慢,因为分配了更多的盒子数组

前端之家收集整理的这篇文章主要介绍了代码变得更慢,因为分配了更多的盒子数组前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
编辑:事实证明,通常情况下(不只是数组/ ref操作)减慢更多的数组已创建,所以我想这可能只是测量增加的GC时间,可能不像我想象的奇怪。但我真的想知道(并学习如何发现)这里发生了什么,如果有一些方法来减轻这种效果代码中创建了很多小的数组。原始问题如下。

在调查一些图书馆中一些奇怪的基准测试结果,我偶然发现了一些我不明白的行为,虽然这可能是很明显的。看来,许多操作(创建新的MutableArray,读取或修改IORef)所需的时间与内存中的数组数成比例增加

这里是第一个例子:

  1. module Main
  2. where
  3.  
  4. import Control.Monad
  5. import qualified Data.Primitive as P
  6. import Control.Concurrent
  7. import Data.IORef
  8. import Criterion.Main
  9. import Control.Monad.Primitive(PrimState)
  10.  
  11. main = do
  12. let n = 100000
  13. allTheArrays <- newIORef []
  14. defaultMain $
  15. [ bench "array creation" $ do
  16. newArr <- P.newArray 64 () :: IO (P.MutableArray (PrimState IO) ())
  17. atomicModifyIORef' allTheArrays (\l-> (newArr:l,()))
  18. ]

我们正在创建一个新的数组并将其添加到堆栈。作为标准,更多的样本和堆栈增长,数组创建需要更多的时间,这似乎是线性和规律地增长:

更奇怪的是,IORef的读写操作受到影响,我们可以看到atomicModifyIORef’更快的估计是更多的数组是GC’d。

  1. main = do
  2. let n = 1000000
  3. arrs <- replicateM (n) $ (P.newArray 64 () :: IO (P.MutableArray (PrimState IO) ()))
  4. -- print $ length arrs -- THIS WORKS TO MAKE THINGS FASTER
  5. arrsRef <- newIORef arrs
  6. defaultMain $
  7. [ bench "atomic-mods of IORef" $
  8. -- nfIO $ -- OR THIS ALSO WORKS
  9. replicateM 1000 $
  10. atomicModifyIORef' arrsRef (\(a:as)-> (as,()))
  11. ]

评论的两行之一摆脱了这种行为,但我不知道为什么(也许在我们强制的列表的脊柱之后,元素实际上可以通过收集)。

问题

>这里发生了什么?
>它是预期的行为吗?
>有没有办法,我可以避免这种放缓?

编辑:我认为这与GC有关,需要更长的时间,但我想更准确地了解发生了什么,特别是在第一个基准。

奖金示例

最后,这里有一个简单的测试程序,可以用来预分配一些数组和时间一堆atomicModifyIORefs。这似乎表现出慢IORef行为。

  1. import Control.Monad
  2. import System.Environment
  3.  
  4. import qualified Data.Primitive as P
  5. import Control.Concurrent
  6. import Control.Concurrent.Chan
  7. import Control.Concurrent.MVar
  8. import Data.IORef
  9. import Criterion.Main
  10. import Control.Exception(evaluate)
  11. import Control.Monad.Primitive(PrimState)
  12.  
  13. import qualified Data.Array.IO as IO
  14. import qualified Data.Vector.Mutable as V
  15.  
  16. import System.cpuTime
  17. import System.Mem(performGC)
  18. import System.Environment
  19. main :: IO ()
  20. main = do
  21. [n] <- fmap (map read) getArgs
  22. arrs <- replicateM (n) $ (P.newArray 64 () :: IO (P.MutableArray (PrimState IO) ()))
  23. arrsRef <- newIORef arrs
  24.  
  25. t0 <- getcpuTimeDouble
  26.  
  27. cnt <- newIORef (0::Int)
  28. replicateM_ 1000000 $
  29. (atomicModifyIORef' cnt (\n-> (n+1,())) >>= evaluate)
  30.  
  31. t1 <- getcpuTimeDouble
  32.  
  33. -- make sure these stick around
  34. readioRef cnt >>= print
  35. readioRef arrsRef >>= (flip P.readArray 0 . head) >>= print
  36.  
  37. putStrLn "The time:"
  38. print (t1 - t0)

带有-hy的堆配置文件主要显示为MUT_ARR_PTRS_CLEAN,我不完全理解。

如果你想重现,这里是我一直在使用的cabal文件

  1. name: small-concurrency-benchmarks
  2. version: 0.1.0.0
  3. build-type: Simple
  4. cabal-version: >=1.10
  5.  
  6. executable small-concurrency-benchmarks
  7. main-is: Main.hs
  8. build-depends: base >=4.6,criterion,primitive
  9.  
  10. default-language: Haskell2010
  11. ghc-options: -O2 -rtsopts

编辑:这里是另一个测试程序,可以用于比较减缓与相同大小的阵列vs [整数]的堆。它需要一些试验和错误调整n和观察分析,以获得类似的运行。

  1. main4 :: IO ()
  2. main4= do
  3. [n] <- fmap (map read) getArgs
  4. let ns = [(1::Integer).. n]
  5. arrsRef <- newIORef ns
  6. print $ length ns
  7.  
  8. t0 <- getcpuTimeDouble
  9. mapM (evaluate . sum) (tails [1.. 10000])
  10. t1 <- getcpuTimeDouble
  11.  
  12. readioRef arrsRef >>= (print . sum)
  13.  
  14. print (t1 - t0)

有趣的是,当我测试这个,我发现相同的堆大小的数组的数组影响性能的程度大于[Integer]。例如。

  1. Baseline 20M 200M
  2. Lists: 0.7 1.0 4.4
  3. Arrays: 0.7 2.6 20.4

结论(WIP)

>这很可能是由于GC行为
>但是可变的unBoxed数组似乎导致更多的服务器减速(见上文)。设置RTS -A200M使数组垃圾版本的性能符合列表版本,支持这与GC有关。
>减速与分配的阵列数量成比例,而不是阵列中总单元数。这里是一组运行,显示,对于类似的测试,main4,分配的数量分配的时间分配的时间和一个完全不相关的“有效载荷”的影响。这是为16777216总细胞(分为许多阵列):

  1. Array size Array create time Time for "payload":
  2. 8 3.164 14.264
  3. 16 1.532 9.008
  4. 32 1.208 6.668
  5. 64 0.644 3.78
  6. 128 0.528 2.052
  7. 256 0.444 3.08
  8. 512 0.336 4.648
  9. 1024 0.356 0.652

并且在16777216 * 4单元上运行相同的测试,显示与上面基本相同的有效负载时间,仅向下移动两个位置。
>从我理解GHC的工作原理,看看(3),我认为这种开销可能只是指向所有这些数组贴在周围的remembered set(参见:here),以及任何开销导致GC。

解决方法

您正在为每个可变数组的每个次要GC支付线性开销,该数组保持活跃并且被提升为旧一代。这是因为GHC无条件地将所有可变数组放在可变列表上,并且遍历整个列表。有关详细信息,请参阅 https://ghc.haskell.org/trac/ghc/ticket/7662,以及我的邮件列表回复您的问题: http://www.haskell.org/pipermail/glasgow-haskell-users/2014-May/024976.html

猜你在找的CSS相关文章