Word foldl 'не оптимизирован, а также Int foldl'
import Data.List
test :: Int -> Int
test n = foldl' (+) 0 [1..n]
main :: IO ()
main = do
print $ test $ 10^8
GHC оптимизирует вышеуказанный код до такой степени, что сборщик мусора даже не должен ничего делать:
$ ghc -rtsopts -O2 testInt && ./testInt +RTS -s
[1 of 1] Compiling Main ( testInt.hs, testInt.o )
Linking testInt ...
5000000050000000
51,752 bytes allocated in the heap
3,480 bytes copied during GC
44,384 bytes maximum residency (1 sample(s))
17,056 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 0 colls, 0 par 0.000s 0.000s 0.0000s 0.0000s
Gen 1 1 colls, 0 par 0.000s 0.000s 0.0001s 0.0001s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.101s ( 0.101s elapsed)
GC time 0.000s ( 0.000s elapsed)
EXIT time 0.000s ( 0.000s elapsed)
Total time 0.103s ( 0.102s elapsed)
%GC time 0.1% (0.1% elapsed)
Alloc rate 511,162 bytes per MUT second
Productivity 99.8% of total user, 100.9% of total elapsed
Однако, если я изменяю тип test
на test :: Word -> Word
, тогда появляется много мусора, а код работает на 40x медленнее.
ghc -rtsopts -O2 testWord && ./testWord +RTS -s
[1 of 1] Compiling Main ( testWord.hs, testWord.o )
Linking testWord ...
5000000050000000
11,200,051,784 bytes allocated in the heap
1,055,520 bytes copied during GC
44,384 bytes maximum residency (2 sample(s))
21,152 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 21700 colls, 0 par 0.077s 0.073s 0.0000s 0.0000s
Gen 1 2 colls, 0 par 0.000s 0.000s 0.0001s 0.0001s
INIT time 0.000s ( 0.000s elapsed)
MUT time 4.551s ( 4.556s elapsed)
GC time 0.077s ( 0.073s elapsed)
EXIT time 0.000s ( 0.000s elapsed)
Total time 4.630s ( 4.630s elapsed)
%GC time 1.7% (1.6% elapsed)
Alloc rate 2,460,957,186 bytes per MUT second
Productivity 98.3% of total user, 98.3% of total elapsed
Почему это происходит? Я ожидал, что производительность будет почти одинаковой?
(Я использую GHC версии 8.0.1 на x86_64 GNU/Linux)
edit: Я отправил ошибку: https://ghc.haskell.org/trac/ghc/ticket/12354#ticket
Ответы
Ответ 1
Это, вероятно, главным образом, хотя и не исключительно, из-за переписывания правил, которые существуют для Int, а не Word. Я говорю это, потому что, если мы используем -fno-enable-rewrite-rules
в случае Int
, мы получаем время, которое намного ближе, но не так плохо, как случай Word
.
% ghc -O2 so.hs -fforce-recomp -fno-enable-rewrite-rules && time ./so
[1 of 1] Compiling Main ( so.hs, so.o )
Linking so ...
5000000050000000
./so 1.45s user 0.03s system 99% cpu 1.489 total
Если мы сбрасываем правила перезаписи с помощью -ddump-rule-rewrites
и различаем эти правила, то мы видим правило, которое срабатывает в случае Int
, а не в случае Word
:
Rule: fold/build
Before: GHC.Base.foldr
...
Это конкретное правило находится в Base 4.9 GHC.Base line 823 (N.B. Я фактически использую GHC 7.10) и не упоминает Int
явно. Мне любопытно, почему он не стреляет в Word
, но сейчас у вас нет времени для дальнейшего расследования.
Ответ 2
Как указано в комментарии здесь, dfeuer, экземпляр Enum
для Int
лучше, чем для Word
:
Int
:
instance Enum Int where
{-# INLINE enumFromTo #-}
enumFromTo (I# x) (I# y) = eftInt x y
{-# RULES
"eftInt" [~1] forall x y. eftInt x y = build (\ c n -> eftIntFB c n x y)
"eftIntList" [1] eftIntFB (:) [] = eftInt
#-}
{- Note [How the Enum rules work]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Phase 2: eftInt ---> build . eftIntFB
* Phase 1: inline build; eftIntFB (:) --> eftInt
* Phase 0: optionally inline eftInt
-}
{-# NOINLINE [1] eftInt #-}
eftInt :: Int# -> Int# -> [Int]
-- [x1..x2]
eftInt x0 y | isTrue# (x0 ># y) = []
| otherwise = go x0
where
go x = I# x : if isTrue# (x ==# y)
then []
else go (x +# 1#)
{-# INLINE [0] eftIntFB #-}
eftIntFB :: (Int -> r -> r) -> r -> Int# -> Int# -> r
eftIntFB c n x0 y | isTrue# (x0 ># y) = n
| otherwise = go x0
where
go x = I# x `c` if isTrue# (x ==# y)
then n
else go (x +# 1#)
-- Watch out for y=maxBound; hence ==, not >
-- Be very careful not to have more than one "c"
-- so that when eftInfFB is inlined we can inline
-- whatever is bound to "c"
Теперь Word
фактически использует реализацию для Integer
enumFromTo n1 n2 = map integerToWordX [wordToIntegerX n1 .. wordToIntegerX n2]
который использует
instance Enum Integer where
enumFromTo x lim = enumDeltaToInteger x 1 lim
Теперь enumDeltaToInteger
установил правила перезаписи, но оказывается, что Word
s enumFromTo
никогда не встраивается, поэтому эта настройка не имеет шансов сплавиться здесь.
Копирование этой функции в мой тестовый код заставляет GHC встроить его, правило fold/build
запускать и сильно сокращать выделение, но преобразование из и в Integer
(которое выделяет) остается.