gpt4 book ai didi

haskell - 基准过滤器和分区

转载 作者:行者123 更新时间:2023-12-02 08:06:01 26 4
gpt4 key购买 nike

我正在测试列表的 partition 函数的性能,我认为得到了一些奇怪的结果。

我们有这个partition 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.3criterion-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 运行时选项。不同的测试用例使用不同的命令行选项执行。第一个命令行选项定义谓词保存的数据百分比。第二个命令行选项在不同的测试之间进行选择。

测试区分两种情况:

  1. 数据是延迟生成的(第二个参数 partitionmypartition)。
  2. 数据已在内存中完全评估(第二个参数 partition-dsmypartition-ds)。

分区的结果始终从左到右计算,即从包含谓词所适用的所有元素的列表开始。

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

在情况 2 中,partition 无法发挥此优势,因为所有元素都已在内存中。

对于mypartition,在任何情况下,在计算第一个结果列表后,所有元素都会保存在内存中,因为再次需要它们来计算第二个结果列表。因此,这两种情况没有太大区别。

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

相反,如果谓词不匹配许多元素或所有元素都已在内存中,则 mypartition 性能更好,因为与 partition 相比,它的递归不处理对>.

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

关于haskell - 基准过滤器和分区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38671397/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com