Performance 为什么haskell中的合并排序的两个版本之间存在1000倍的性能差异
编辑: 事实证明,慢版本实际上是一个插入排序O(n^2),而不是解释性能问题的合并排序O(n log n)。我想我会让未来的读者省去费力地通过代码来发现这个答案的痛苦Performance 为什么haskell中的合并排序的两个版本之间存在1000倍的性能差异,performance,haskell,Performance,Haskell,编辑: 事实证明,慢版本实际上是一个插入排序O(n^2),而不是解释性能问题的合并排序O(n log n)。我想我会让未来的读者省去费力地通过代码来发现这个答案的痛苦 原文从这里开始------------------------------------------- 我在haskell中编写了两个版本的合并排序,我无法理解为什么一个比另一个快1000倍。在这两种情况下,我们首先将列表中要排序的项设置为一个列表,然后创建一个列表列表。然后我们配对列表并合并它们,直到只剩下一个列表。问题似乎是,
原文从这里开始------------------------------------------- 我在haskell中编写了两个版本的合并排序,我无法理解为什么一个比另一个快1000倍。在这两种情况下,我们首先将列表中要排序的项设置为一个列表,然后创建一个列表列表。然后我们配对列表并合并它们,直到只剩下一个列表。问题似乎是,我在慢速版本中调用“doMerge(x1:x2:xs)=doMerge$merge x1 x2:doMerge xs”,但在快速版本中调用doMerge(mergerge xs)。我对1000倍的速度差感到惊讶
-- Better version: takes 0.34 seconds to sort a 100,000 integer list.
betMergeSort :: [Int] -> [Int]
betMergeSort list = doMerge $ map (\x -> [x]) list
where
doMerge :: [[Int]] -> [Int]
doMerge [] = []
doMerge [xs] = xs
doMerge xs = doMerge (mergePairs xs)
mergePairs :: [[Int]] -> [[Int]]
mergePairs (x1:x2:xs) = merge x1 x2 : mergePairs xs
mergePairs xs = xs
-- expects two sorted lists and returns one sorted list.
merge :: [Int] -> [Int] -> [Int]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = if x <= y
then x : merge xs (y:ys)
else y : merge (x:xs) ys
-- Slow version: takes 350 seconds to sort a 100,000 integer list.
slowMergeSort :: [Int] -> [Int]
slowMergeSort list = head $ doMerge $ map (\x -> [x]) list
where
doMerge :: [[Int]] -> [[Int]]
doMerge [] = []
doMerge (oneList:[]) = [oneList]
doMerge (x1:x2:xs) = doMerge $ merge x1 x2 : doMerge xs
-- expects two sorted list and returns one sorted list.
merge :: [Int] -> [Int] -> [Int]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys
libMergeSort分析
Wed Aug 21 12:23 2013 Time and Allocation Profiling Report (Final)
mergeSort +RTS -sstderr -p -RTS l
total time = 0.12 secs (124 ticks @ 1000 us, 1 processor)
total alloc = 139,965,768 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
randomInts.result Main 66.9 64.0
libMergeSort.merge Main 24.2 30.4
main Main 4.0 0.0
libMergeSort Main 2.4 3.2
libMergeSort.merge_pairs Main 1.6 2.3
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 74 0 0.0 0.0 100.0 100.0
main Main 149 0 4.0 0.0 100.0 100.0
main.sortedL Main 165 1 0.0 0.0 28.2 35.9
libMergeSort Main 167 1 2.4 3.2 28.2 35.9
libMergeSort.\ Main 171 40000 0.0 0.0 0.0 0.0
libMergeSort.libMergeSort' Main 168 17 0.0 0.0 25.8 32.7
libMergeSort.merge_pairs Main 169 40015 1.6 2.3 25.8 32.7
libMergeSort.merge Main 170 614711 24.2 30.4 24.2 30.4
main.sortVersion Main 161 1 0.0 0.0 0.0 0.0
randomInts Main 151 1 0.0 0.0 67.7 64.0
force Main 155 1 0.0 0.0 0.8 0.0
force.go Main 156 40001 0.8 0.0 0.8 0.0
randomInts.result Main 152 1 66.9 64.0 66.9 64.0
第二个是
O(n^2)
,不应使用,因为它是错误的算法(不应称为mergesort)
完全排序xs
,它只比原始列表短一个常量。合并一个很短的列表和一个很长的列表相当于插入,所以我们看到这实际上只是插入排序。mergesort的整个要点是分而治之,而不是分而治之。随着列表长度的增加,速度比将继续变差
第一种是正确的合并排序,因为它合并了长度约为偶数的列表。您可以修复慢速合并分析结果的格式吗?您是否使用类型为
[Integer]
的参数运行slowMergeSort
基准,而不是[Int]
?您应该使它们都采用[Int]或两种多态性,所以你实际上是在比较同一件事。部分(但可能不是全部)差异可能是因为多态性较慢。第二个版本反复合并非常长的列表。(a,b)表示合并两个长度为a和b的列表。第一个版本是:Nx(1,1),(N/2)x(2,2),(N/4)x(4,4)等等;第二个是Nx(1,1),然后是(2,2),(2,4),(2,6)。最后,合并长度为(2,N)的列表。如果你运气不好,你将不得不遍历第二个(很长)列表。这会降低性能。在分析的版本中,两个版本都采用[Int]。在运行分析之前,我开始发布文章,修复类型,使它们匹配,然后发布分析。我忘了在这里更新slowMergeSort代码。现在我有了。谢谢@Philip JF:我觉得这件事有点不好意思!
Wed Aug 21 12:23 2013 Time and Allocation Profiling Report (Final)
mergeSort +RTS -sstderr -p -RTS l
total time = 0.12 secs (124 ticks @ 1000 us, 1 processor)
total alloc = 139,965,768 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
randomInts.result Main 66.9 64.0
libMergeSort.merge Main 24.2 30.4
main Main 4.0 0.0
libMergeSort Main 2.4 3.2
libMergeSort.merge_pairs Main 1.6 2.3
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 74 0 0.0 0.0 100.0 100.0
main Main 149 0 4.0 0.0 100.0 100.0
main.sortedL Main 165 1 0.0 0.0 28.2 35.9
libMergeSort Main 167 1 2.4 3.2 28.2 35.9
libMergeSort.\ Main 171 40000 0.0 0.0 0.0 0.0
libMergeSort.libMergeSort' Main 168 17 0.0 0.0 25.8 32.7
libMergeSort.merge_pairs Main 169 40015 1.6 2.3 25.8 32.7
libMergeSort.merge Main 170 614711 24.2 30.4 24.2 30.4
main.sortVersion Main 161 1 0.0 0.0 0.0 0.0
randomInts Main 151 1 0.0 0.0 67.7 64.0
force Main 155 1 0.0 0.0 0.8 0.0
force.go Main 156 40001 0.8 0.0 0.8 0.0
randomInts.result Main 152 1 66.9 64.0 66.9 64.0
doMerge (x1:x2:xs) = doMerge $ merge x1 x2 : doMerge xs