【发布时间】:2020-12-30 07:53:50
【问题描述】:
我已经编写了length 函数的六个版本。一些性能差异是有道理的,但其中一些似乎与我读过的文章完全不同(例如this one 和this one)。
-- len1 and lenFold1 should have equivalent performance, right?
len1 :: [a] -> Integer
len1 [] = 0
len1 (x:xs) = len1 xs + 1
lenFold1 :: [a] -> Integer
lenFold1 = foldr (\_ a -> a + 1) 0
-- len2 and lenFold2 should have equivalent performance, right?
len2 :: [a] -> Integer
len2 xs = go xs 0 where
go [] acc = acc
go (x:xs) acc = go xs (1 + acc)
lenFold2 :: [a] -> Integer
lenFold2 = foldl (\a _ -> a + 1) 0
-- len3 and lenFold3 should have equivalent performance, right?
-- And len3 should outperform len1 and len2, right?
len3 :: [a] -> Integer
len3 xs = go xs 0 where
go [] acc = acc
go (x:xs) acc = go xs $! (1 + acc)
lenFold3 :: [a] -> Integer
lenFold3 = foldl' (\a _ -> a + 1) 0
在我机器上的实际表现令人费解。
*Main Lib> :set +m +s
*Main Lib> xs = [1..10000000]
(0.01 secs, 351,256 bytes)
*Main Lib> len1 xs
10000000
(5.47 secs, 2,345,244,016 bytes)
*Main Lib> lenFold1 xs
10000000
(2.74 secs, 1,696,750,840 bytes)
*Main Lib> len2 xs
10000000
(6.02 secs, 2,980,997,432 bytes)
*Main Lib> lenFold2 xs
10000000
(3.97 secs, 1,776,750,816 bytes)
*Main Lib> len3 xs
10000000
(5.24 secs, 3,520,354,616 bytes)
*Main Lib> lenFold3 xs
10000000
(1.24 secs, 1,040,354,528 bytes)
*Main Lib> length xs
10000000
(0.21 secs, 720,354,480 bytes)
我的问题:
- 为什么每个函数的
fold版本始终优于使用显式递归的版本? - 尽管有this article 的警告,这些实现都没有在我的机器上达到堆栈溢出。为什么不呢?
- 为什么
len3的性能不如len1或len2? - 为什么 Prelude 的
length的性能比这些实现中的任何一个都好得多?
编辑:
感谢 Carl 的建议,我的第一个和第二个问题已通过 GHCI 默认解释代码这一事实得到解决。使用-fobject-code 再次运行它说明了显式递归和折叠之间的不同性能。新测量:
Prelude Lib Main> xs = [1..10000000]
(0.00 secs, 354,136 bytes)
Prelude Lib Main> len1 xs
10000000
(1.62 secs, 1,612,661,544 bytes)
Prelude Lib Main> lenFold1 xs
10000000
(1.62 secs, 1,692,661,552 bytes)
Prelude Lib Main> len2 xs
10000000
(2.46 secs, 1,855,662,888 bytes)
Prelude Lib Main> lenFold2 xs
10000000
(2.53 secs, 1,772,661,528 bytes)
Prelude Lib Main> len3 xs
10000000
(0.48 secs, 1,680,361,272 bytes)
Prelude Lib Main> lenFold3 xs
10000000
(0.31 secs, 1,040,361,240 bytes)
Prelude Lib Main> length xs
10000000
(0.18 secs, 720,361,272 bytes)
对此我还有几个问题。
- 为什么
lenFold3的表现优于len3?我跑了几次 -
length如何仍然优于所有这些实现?
【问题讨论】:
-
您正在对解释代码和编译代码进行基准测试,并想知道为什么编译代码更快?
-
@Carl 这点很有用,但无论你是否有意,你都用居高临下的措辞,我认为这不是很有帮助。
-
@Carl 这是一种有趣的说法,“GHCI 默认解释代码。尝试使用
-fobject-code再次运行它。”不过谢谢你的建议!更新测量值。 -
不要在 GHCi 内部进行基准测试。使用
-O2编译并使用类似标准的东西。
标签: performance haskell recursion performance-testing fold