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

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

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

这里是第一个例子:

module Main
    where 

import Control.Monad
import qualified Data.Primitive as P
import Control.Concurrent
import Data.IORef
import Criterion.Main
import Control.Monad.Primitive(PrimState)

main = do 
  let n = 100000
  allTheArrays <- newIORef []
  defaultMain $
    [ bench "array creation" $ do
         newArr <- P.newArray 64 () :: IO (P.MutableArray (PrimState IO) ())
         atomicModifyIORef'  allTheArrays (\l-> (newArr:l,()))
    ]

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

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

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

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

问题

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

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

奖金示例

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

import Control.Monad
import System.Environment

import qualified Data.Primitive as P
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Data.IORef
import Criterion.Main
import Control.Exception(evaluate)
import Control.Monad.Primitive(PrimState)

import qualified Data.Array.IO as IO
import qualified Data.Vector.Mutable as V

import System.cpuTime
import System.Mem(performGC)
import System.Environment
main :: IO ()
main = do
  [n] <- fmap (map read) getArgs
  arrs <- replicateM (n) $ (P.newArray 64 () :: IO (P.MutableArray (PrimState IO) ()))
  arrsRef <- newIORef arrs

  t0 <- getcpuTimeDouble

  cnt <- newIORef (0::Int)
  replicateM_ 1000000 $
    (atomicModifyIORef' cnt (\n-> (n+1,())) >>= evaluate)

  t1 <- getcpuTimeDouble

  -- make sure these stick around
  readioRef cnt >>= print
  readioRef arrsRef >>= (flip P.readArray 0 . head) >>= print

  putStrLn "The time:"
  print (t1 - t0)

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

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

name:                small-concurrency-benchmarks
version:             0.1.0.0       
build-type:          Simple
cabal-version:       >=1.10

executable small-concurrency-benchmarks
  main-is:             Main.hs 
  build-depends:       base >=4.6,criterion,primitive

  default-language:    Haskell2010
  ghc-options: -O2  -rtsopts

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

main4 :: IO ()
main4= do
  [n] <- fmap (map read) getArgs
  let ns = [(1::Integer).. n]
  arrsRef <- newIORef ns
  print $ length ns

  t0 <- getcpuTimeDouble
  mapM (evaluate . sum) (tails [1.. 10000])
  t1 <- getcpuTimeDouble

  readioRef arrsRef >>= (print . sum)

  print (t1 - t0)

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

Baseline  20M   200M
Lists:   0.7       1.0    4.4
Arrays:  0.7       2.6   20.4

结论(WIP)

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

Array size   Array create time  Time for "payload":
   8            3.164           14.264
   16           1.532           9.008
   32           1.208           6.668
   64           0.644           3.78
   128          0.528           2.052
   256          0.444           3.08
   512          0.336           4.648
   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相关文章