请注意以下Haskell计划:
-- (^) allocs memory so we define it using the native (**)
pow :: Int -> Int -> Int
pow x y = floor $ fromIntegral x ** fromIntegral y
-- tail recursive, div and mod are native, I believe, so, no alloc
isPalindrome :: Int -> Bool
isPalindrome x = go (pow 10 (floor $ logBase 10 $ fromIntegral x)) x
where go m x = x <= 0 || div x m == mod x 10 && go (div m 100) (div (x - m * mod x 10) 10)
-- go is tail recursive too... no obvious allocation here
wanderer :: Int -> Int
wanderer n = go 0 (pow 10 n - 1) (pow 10 n - 1) where
go p x y | p > 0 && div p x >= pow 10 n = p
go p x y | p > 0 && y < div p x || y < x = go p (x-1) (pow 10 n - 1)
go p x y | isPalindrome (x*y) = go (x*y) x (y-1)
go p x y = go p x (y-1)
main = print . wanderer $ 8
剖析它,我们得到:
Fri May 8 03:36 2015 Time and Allocation Profiling Report (Final)
aff +RTS -p -RTS
total time = 7.34 secs (7344 ticks @ 1000 us, 1 processor)
total alloc = 6,487,919,472 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
isPalindrome Main 41.9 18.5
isPalindrome.go Main 22.6 1.4
wanderer.go Main 20.0 67.8
pow Main 15.5 12.3
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 47 0 0.0 0.0 100.0 100.0
main Main 95 0 0.0 0.0 0.0 0.0
CAF:main1 Main 92 0 0.0 0.0 0.0 0.0
main Main 94 1 0.0 0.0 0.0 0.0
CAF:main2 Main 91 0 0.0 0.0 100.0 100.0
main Main 96 0 0.0 0.0 100.0 100.0
wanderer Main 98 1 0.0 0.0 100.0 100.0
pow Main 101 1 0.0 0.0 0.0 0.0
wanderer.go Main 99 49995002 20.0 67.8 100.0 100.0
isPalindrome Main 102 49985002 41.9 18.5 80.0 32.2
pow Main 104 49985002 15.5 12.3 15.5 12.3
isPalindrome.go Main 103 52207117 22.6 1.4 22.6 1.4
pow Main 100 1 0.0 0.0 0.0 0.0
pow Main 97 2 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 85 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding 78 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 76 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Handle.FD 69 0 0.0 0.0 0.0 0.0
CAF GHC.Event.Thread 55 0 0.0 0.0 0.0 0.0
据我所知,看起来我的所有函数都是尾递归的,而那些前奏函数都是asm操作。然而,这个简单的程序分配了7GB的内存。所有分配来自哪里?
答案 0 :(得分:13)
分配来自go
中的isPalindrome
:
go m x = x <= 0 || div x m == mod x 10 && go (div m 100) (div (x - m * mod x 10) 10)
我们右侧有一个||
。 ||
的短路语义是通过惰性评估实现的。如果m
评估为x <= 0
,GHC会发现True
参数未被使用,因此它不会取消m
,而是允许它保持未计算。当然,在这种情况下,我们最好取消装箱m
,所以让我们这样做。
{-# LANGUAGE BangPatterns #-}
go !m x = ...
现在使用ghc -O2
和+RTS -s
:
52,016 bytes allocated in the heap
3,408 bytes copied during GC
44,312 bytes maximum residency (1 sample(s))
17,128 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
答案 1 :(得分:2)
GHC有时被称为“分配评估”。更重要的是内存是否保留。只是眼睛看代码,我没有看到任何明显的空间泄漏。
你看过runtime stats了吗?您甚至不需要为此分析库。这里更感兴趣的是复制的GC字节和最大驻留时间......我猜这两者都相对较小。
除了一阶问题之外,大多数这些分配可能都是封闭式的。您是否正在编译ghc -O
以运行严格性分析器?即使你是,我也不确定严格性分析器是否能够消除第一个m
函数或go
和{{1}中x
参数的闭包分配第二个y
中的参数。我的猜测是爆炸模式会大大减少你的分配。
尾部递归的实际后果在Haskell中与严格的语言非常不同;所以,如果你熟悉严格语言的尾调用,你的直觉可能就不存在了。