簡體   English   中英

為什么 `filterM + mapM_` 比 `mapM_ + when` 慢得多,列表很大?

[英]Why is `filterM + mapM_` so much slower than `mapM_ + when`, with large lists?

我不太了解 Haskell 優化如何在內部工作,但我一直在使用過濾器,非常希望它們被優化為類似於 C++ 中的簡單 if 的東西。 例如

mapM_ print $ filter (\n -> n `mod` 2 == 0) [0..10]

將編譯成相當於

for (int i = 0; i < 10; i++)
    if (i%2 == 0)
        printf("%d\n", i);

對於長列表(10 000 000 個元素),基本filter似乎是正確的,但如果我使用單子filterM會有很大的不同。 我為此速度測試編寫了一段代碼,很明顯,使用filterM比使用更命令式的方法持續更長的時間(250 倍) when

import Data.Array.IO
import Control.Monad
import System.CPUTime

main :: IO ()
main = do
  start <- getCPUTime
  arr <- newArray (0, 100) 0 :: IO (IOUArray Int Int)
  let
    okSimple i =
      i < 100

    ok i = do
      return $ i < 100
    -- -- of course we don't need IO for a simple i < 100
    -- -- but my goal is to ask for the contents of the array, e.g.
    -- ok i = do
    --   current <- readArray arr (i `mod` 101)
    --   return$ i `mod` 37 > current `mod` 37
    
    write :: Int -> IO ()
    write i =
      writeArray arr (i `mod` 101) i

    writeIfOkSimple :: Int -> IO ()
    writeIfOkSimple i =
      when (okSimple i) $ write i

    writeIfOk :: Int -> IO ()
    writeIfOk i =
      ok i >>= (\isOk -> when isOk $ write i)

  -------------------------------------------------------------------
  ---- these four methods have approximately same execution time ----
  ---- (but the last one is executed on 250 times shorter list)  ----
  -------------------------------------------------------------------
  -- mapM_ write$ filter okSimple [0..10000000*250] -- t = 20.694
  -- mapM_ writeIfOkSimple [0..10000000*250]        -- t = 20.698
  -- mapM_ writeIfOk [0..10000000*250]              -- t = 20.669
  filterM ok [0..10000000] >>= mapM_ write          -- t = 17.200

  -- evaluate array
  elems <- getElems arr
  print $ sum elems

  end <- getCPUTime
  print $ fromIntegral (end - start) / (10^12)

我的問題是:這兩種方法(使用writeIfOk / 使用filterM okwrite )不應該編譯成相同的代碼(迭代列表、詢問條件、寫入數據)嗎? 如果不是,我可以做一些事情(重寫代碼、添加編譯標志、使用內聯編譯指示或其他東西)以使它們在計算上等效,還是應該在性能至關重要when始終使用?

將這個問題歸結為本質,您詢問兩者之間的區別

f (filter g xs)

f =<< filterM (pure . g) xs

這基本上歸結為懶惰。 filter g xs根據需要逐步生成其結果,僅將xs足夠遠以找到結果的下一個元素。 filterM的定義如下:

filterM _p [] = pure []
filterM p (x : xs)
  = liftA2 (\q r -> if q then x : r else r)
           (p x)
           (filterM p xs)

由於IO是一個“嚴格”的應用程序,因此在遍歷整個列表之前不會產生任何東西,在 memory 中累積px結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM