haskell – 基准过滤器和分区

我正在测试列表的分区功能的性能,并得到一些奇怪的结果,我想。

我们有那个分区p xs ==(filter p xs,filter(not。p)xs),但是我们选择了第一个实现,因为它只在列表中执行一次遍历。然而,我得到的结果说,使用两次遍历的实现可能会更好一些。

这是显示我看到的最小代码

import Criterion.Main
import System.Random
import Data.List (partition)

mypartition :: (a -> Bool) -> [a] -> ([a],[a])
mypartition p l = (filter p l, filter (not . p) l)



randList :: RandomGen g => g -> Integer -> [Integer]
randList gen 0 = []
randList gen n = x:xs
  where
    (x, gen') = random gen
    xs = randList gen' (n - 1)

main = do
  gen <- getStdGen
  let arg10000000 = randList gen 10000000
  defaultMain [
      bgroup "filters -- split list in half " [
        bench "partition100"         $ nf (partition (>= 50)) arg10000000
      , bench "mypartition100"       $ nf (mypartition (>= 50)) arg10000000
      ]
      ]

我运行测试都用-O和没有它,两次我得到双遍遍历更好。

我使用的是ghc-7.10.3与criteria-1.1.1.0

我的问题是:

这是预期吗?
我正在使用Criterion吗?我知道懒惰可能很棘手,(filter p xs,filter(not。p)xs)只会使用元组的两个元素进行两次遍历。
>这是否需要在Haskell中处理列表的方式做些什么?

非常感谢!

这个问题没有黑色或白色的答案。要解剖这个问题,请考虑以下代码:

import Control.DeepSeq
import Data.List (partition)
import System.Environment (getArgs)


mypartition :: (a -> Bool) -> [a] -> ([a],[a])
mypartition p l = (filter p l, filter (not . p) l)


main :: IO ()
main = do
  let cnt = 10000000
      xs = take cnt $ concat $ repeat [1 .. 100 :: Int]
  args <- getArgs
  putStrLn $ unwords $ "Args:" : args
  case args of
    [percent, fun]
      -> let p = (read percent >=)
         in case fun of
           "partition"      ->              print $ rnf $ partition   p xs
           "mypartition"    ->              print $ rnf $ mypartition p xs
           "partition-ds"   -> deepseq xs $ print $ rnf $ partition   p xs
           "mypartition-ds" -> deepseq xs $ print $ rnf $ mypartition p xs
           _ -> err
    _ -> err
  where
    err = putStrLn "Sorry, I do not understand."

我不使用Criterion来更好地控制评估的顺序。要获取时间,我使用RTS -s运行时选项。使用不同的命令行选项执行不同的测试用例。第一个命令行选项定义谓词拥有的数据的百分比。第二个命令行选项在不同的测试之间进行选择。

测试区分两种情况:

>数据生成懒惰(第二参数分区或mypartition)。
>数据已经在内存中完全评估(第二个参数partition-ds或mypartition-ds)。

分区的结果始终从左到右进行评估,即从包含谓词所含的所有元素的列表开始。

在1分区的情况下,优点是,在输入列表的所有元素均匀生成之前,第一个结果列表的元素被丢弃。情况1特别好,如果谓词匹配许多元素,即第一个命令行参数很大。

在情况2中,分区不能播放这个优点,因为所有元素都已经在内存中。

对于mypartition,无论如何,在对第一个结果列表进行评估后,所有元素都将保存在内存中,因为它们再次需要计算第二个结果列表。因此,两种情况之间没有什么区别。

看来,使用的内存越多,垃圾收集越难。因此,分区是非常适合的,如果谓词匹配许多元素,并且使用懒惰变体。

相反,如果谓词与许多元素不匹配,或者所有元素都已经在内存中,则mypartition会执行得更好,因为它的递归不会与分区对比来处理对。

Stackoverflow问题“Irrefutable pattern does not leak memory in recursion, but why?”可能会对递归分区中的对的处理提供更多见解。

http://stackoverflow.com/questions/38671397/benchmarking-filter-and-partition

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:haskell – 基准过滤器和分区