[英]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 ok
和write
)不應該編譯成相同的代碼(迭代列表、詢問條件、寫入數據)嗎? 如果不是,我可以做一些事情(重寫代碼、添加編譯標志、使用內聯編譯指示或其他東西)以使它們在計算上等效,還是應該在性能至關重要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.