2011-09-13 23 views
8

Rozważmy następujący fragment kodu:Dlaczego korzystanie z funkcji zdefiniowanych w tym samym module jest szybsze niż ta sama funkcja zdefiniowana w innym?

isPrime primes' n = foldr (\p r -> p * p > n || (n `rem` p /= 0 && r)) True primes' 

primes = 2 : filter (isPrime primes) [3..] 

main = putStrLn $ show $ sum $ takeWhile (< 1000000) primes 

który oblicza sumę wszystkich liczb pierwszych poniżej jednego miliona. Potrwa to 0.468 sekund, aby wydrukować wynik na moim komputerze. Ale jeśli definicje isPrime i primes są wyodrębniane do innego modułu, koszt czasu wynosi 1,23 sekund, jest prawie 3 razy wolniejszy.

Oczywiście mogę kopiować/wklejać wszystkie wymagane obszary, ale jestem również ciekawy, dlaczego tak się dzieje i jak go rozwiązać.


[Edytuj] Używam GHC 7.0.3 (Windows 7 + MinGW). Kod jest zapisany w EclipseFP (używa Scion jako back-end IDE) i wbudowany w plik wykonywalny z flagami -O2.

Próbowałem też budowania pakietu poza IDE:

executable test 
    hs-source-dirs: src 
    main-is:   Main.hs 
    build-depends: base >= 4 
    ghc-options:  -O2 
    other-modules: Primes 

executable test2 
    hs-source-dirs: src2 
    main-is:   Main.hs 
    build-depends: base >= 4 
    ghc-options:  -O2 

Oto wynik:

$ time test/test 
37550402023 

real 0m1.296s 
user 0m0.000s 
sys  0m0.031s 

$ time test2/test2 
37550402023 

real 0m0.520s 
user 0m0.015s 
sys  0m0.015s 

Odpowiedz

7

Mogę odtworzyć to, jeśli wstawię isPrime i primes w różnych modułach. (Jeśli są w tym samym module, ale nadal są oddzielone od main, nie widzę różnicy).

Dodanie {-# INLINE isPrime #-} daje taką samą wydajność, jak wszystkie trzy w jednym module, więc wydaje się, że GHC wymagało szturchnięcia, aby w tym przypadku wstawić krzyże modułowe.

To jest na GHC 7.0.2, Ubuntu 11.04, 64-bitowy

+0

to działa! Dzięki! – claude

+5

GHC wykona bardzo agresywną inline w module, szczególnie jeśli funkcja, która ma być wbudowana, nie jest eksportowana. O wiele mniej chętnie inline funkcje na granicach modułów, chyba że INLINE je ręcznie. –

1

Czy działa to wewnątrz GHCi lub kompilacji poprzez GHC? Właśnie próbowałem eksperymentu, zachowując wszystkie definicje w tym samym pliku, przenosząc pierwsze dwa wyjścia i kompilując przez GHC z włączoną i wyłączoną opcją -O. Nie ma dostrzegalnej różnicy między różnymi kombinacjami na moim komputerze (wszystkie działają tylko kilka milisekund przez 1 sekundę, używając GHC 7).

+0

Używasz '-O' lub' -O2'? IMHO wiele optymalizacji, na które może wpływać ruch kodu, jest wyzwalanych przez drugą flagę. – fuz

+0

Informacje o środowisku kompilacji dodane do oryginalnego wpisu, dzięki! – claude

+0

@FUZxxl Tak naprawdę wypróbowałem oba. W obu przypadkach żadna zauważalna różnica. Ogólne najszybsze wykonanie było bez flag optymalizacji przekazanych do GHC, ale mówimy o ogólnym rozpowszechnieniu około 100ms w czasie wykonywania pomiędzy wszystkimi wartościami Cobminations na moim komputerze. –