2016-03-11 6 views
19

Ich erkannte, dass die neueste Version von GHC (7.10.3) erheblich langsamer Code als eine ältere Version produziert. Meine aktuelle Version ab sofort:GHC 7.10 generiert langsameren Code als ältere Versionen

$ ghc --version 
The Glorious Glasgow Haskell Compilation System, version 7.10.3 

Ich habe auch zwei andere alte Versionen auf meinem lokalen Rechner installiert.

My Testcode wird entnommen aus here (der collatz1.hs code):

import Data.Word 
import Data.List 
import System.Environment 

collatzNext :: Word32 -> Word32 
collatzNext a = (if even a then a else 3*a+1) `div` 2 

-- new code 
collatzLen :: Word32 -> Int 
collatzLen a0 = lenIterWhile collatzNext (/= 1) a0 

lenIterWhile :: (a -> a) -> (a -> Bool) -> a -> Int 
lenIterWhile next notDone start = len start 0 where 
    len n m = if notDone n 
       then len (next n) (m+1) 
       else m 
-- End of new code 

main = do 
    [a0] <- getArgs 
    let max_a0 = (read a0)::Word32 
    print $ maximum $ map (\a0 -> (collatzLen a0, a0)) [1..max_a0] 

Kompilieren mit GHC 7,4, 7,6 und 7,10 ergibt die folgenden Zeiten:

$ ~/Tools/ghc-7.4.2/bin/ghc -O2 Test.hs 
[1 of 1] Compiling Main    (Test.hs, Test.o) 
Linking Test ... 

$ time ./Test 1000000 
(329,837799) 

real 0m1.879s 
user 0m1.876s 
sys  0m0.000s 
$ ~/Tools/ghc-7.6.1/bin/ghc -O2 Test.hs 
[1 of 1] Compiling Main    (Test.hs, Test.o) 
Linking Test ... 

$ time ./Test 1000000 
(329,837799) 

real 0m1.901s 
user 0m1.896s 
sys  0m0.000s 
$ ~/Tools/ghc/bin/ghc -O2 Test.hs 
[1 of 1] Compiling Main    (Test.hs, Test.o) 
Linking Test ... 

$ time ./Test 1000000 
(329,837799) 

real 0m10.562s 
user 0m10.528s 
sys  0m0.036s  

Wir können sagen, dass es keinen Zweifel gibt, dass die neueste Version von GHC schlechteren Code als die älteren zwei Versionen produziert. Ich kann nicht die gleiche Effizienz wie der Blog reproduzieren, aber wahrscheinlich, weil ich kein LLVM habe und nicht die genaue Version, die der Autor verwendet hat. Trotzdem glaube ich, dass die Schlussfolgerung offensichtlich ist.

Meine Frage ist im Allgemeinen, warum dies passieren könnte? Irgendwie wird GHC schlechter als früher. Und speziell, wenn ich untersuchen will, wie soll ich mich selbst anfangen lassen?

+4

Das ist eine Geschwindigkeitsregression und sollte als ein Problem gemeldet werden. Auf diese Weise können die Autoren des Compilers das Problem überprüfen und/oder Ihnen sagen, woher die Regression stammt.Wenn Sie mit GHC nicht vertraut sind, können Sie sich zunächst den erzeugten Kern nach der Vereinfachung ansehen, z. '-ddump-simpl 'und auf Unterschiede prüfen. – Zeta

+1

@ Zeta klingt gut. Ich werde diesen Bericht erhalten – HuStmpHrrr

+2

Hatte einen schnellen Blick, sie scheinen sich hauptsächlich darin zu unterscheiden, ob Funktionen inline oder floated sind, aber ich bin mir nicht sicher darüber. Für diejenigen, die stack verwenden: 'stack --install-ghc --resolver = ghc-7.8 ghc - Test.hs -O2 -ddump-simpl-suppress-all-effekt-rekomp> 7.8.dump; Stapel --install-ghc --resolver = ghc-7.10 ghc - Test.hs -O2 -ddump-simpl-suppress-all-effekt-rekomp> 7.10.dump; vimdiff 7.8.dump 7.10.dump' (Ersetzen Sie Vimdiff durch Ihren bevorzugten Diff-Befehl). Scheint wie der Unterschied ist während der Optimierung, obwohl. – Zeta

Antwort

18

Hier ist ein Vergleich beider Profile (diff Test-GHC-7-8-4.prof Test-GHC-7-10-3.prof)

1c1        
<  Fri Mar 11 19:58 2016 Time and Allocation Profiling Report (Final) 
---        
>  Fri Mar 11 19:59 2016 Time and Allocation Profiling Report (Final) 
5,6c5,6        
<  total time =  2.40 secs (2400 ticks @ 1000 us, 1 processor) 
<  total alloc = 256,066,744 bytes (excludes profiling overheads) 
---        
>  total time =  10.89 secs (10895 ticks @ 1000 us, 1 processor) 
>  total alloc = 15,713,590,808 bytes (excludes profiling overheads) 
10,13c10,12        
< lenIterWhile.len Main  93.8 0.0      
< collatzMax  Main  2.2 93.7 
< collatzNext  Main  2.0 0.0 
< lenIterWhile  Main  1.5 6.2 
---         
> collatzNext  Main  79.6 89.4 
> lenIterWhile.len Main  18.9 8.8 
> collatzMax  Main  0.8 1.5 

Es ist etwas sehr Seltsames passiert. Während in GHC lenIterWhile.len war die meiste Zeit, collatzNext ist jetzt der Schuldige. Werfen wir einen Blick auf den entkernten Kern:

Scheint mehr oder weniger sinnvoll. Jetzt über GHC 7.10.3:

Rec { 
$wlen_r6Sy :: GHC.Prim.Word# -> GHC.Prim.Int# -> GHC.Prim.Int# 
[GblId, Arity=2, Str=DmdType <S,U><L,U>] 
$wlen_r6Sy = 
    \ (ww_s60s :: GHC.Prim.Word#) (ww1_s60w :: GHC.Prim.Int#) -> 
    case ww_s60s of wild_X1Z { 
     __DEFAULT -> 
     case even @ Word32 GHC.Word.$fIntegralWord32 (GHC.Word.W32# wild_X1Z) of _ [Occ=Dead] { 
      False -> 
      $wlen_r6Sy 
       (GHC.Prim.quotWord# 
       (GHC.Prim.narrow32Word# 
        (GHC.Prim.plusWord# 
         (GHC.Prim.narrow32Word# (GHC.Prim.timesWord# (__word 3) wild_X1Z)) 
         (__word 1))) 
       (__word 2)) 
       (GHC.Prim.+# ww1_s60w 1); 
      True -> 
      $wlen_r6Sy 
       (GHC.Prim.quotWord# wild_X1Z (__word 2)) (GHC.Prim.+# ww1_s60w 1) 
     }; 
     __word 1 -> ww1_s60w 
    } 
end Rec }

Gut, scheint wie es ist das gleiche. Außer dem Anruf von even. Lassen Sie uns even durch eine der Inline-Varianten von Integral ersetzen, z.B. x rem 2 == 0:

import Data.Word 
import Data.List 
import System.Environment 

collatzNext :: Word32 -> Word32 
collatzNext a = (if a `rem` 2 == 0 then a else 3*a+1) `div` 2 

-- rest of code the same 

ist es wieder mit einer Profilierung Lassen Sie kompilieren und überprüfen:

$ stack --resolver=ghc-7.10 ghc -- Test.hs -O2 -fforce-recomp -prof -fprof-auto -auto-all 
$ ./Test +RTS -s -p -RTS 
(329,837799) 
    416,119,240 bytes allocated in the heap 
      69,760 bytes copied during GC 
      59,368 bytes maximum residency (2 sample(s)) 
      21,912 bytes maximum slop 
       1 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  800 colls,  0 par 0.000s 0.002s  0.0000s 0.0001s 
    Gen 1   2 colls,  0 par 0.000s 0.000s  0.0002s 0.0003s 

    INIT time 0.000s ( 0.019s elapsed) 
    MUT  time 2.500s ( 2.546s elapsed) 
    GC  time 0.000s ( 0.003s elapsed) 
    RP  time 0.000s ( 0.000s elapsed) 
    PROF time 0.000s ( 0.000s elapsed) 
    EXIT time 0.000s ( 0.000s elapsed) 
    Total time 2.500s ( 2.567s elapsed) 

    %GC  time  0.0% (0.1% elapsed) 

    Alloc rate 166,447,696 bytes per MUT second 

    Productivity 100.0% of total user, 97.4% of total elapsed 

$ cat Test.prof 
     Fri Mar 11 20:22 2016 Time and Allocation Profiling Report (Final) 

      Test.exe +RTS -s -p -RTS 1000000 

     total time =  2.54 secs (2535 ticks @ 1000 us, 1 processor) 
     total alloc = 256,066,984 bytes (excludes profiling overheads) 

COST CENTRE  MODULE %time %alloc 

lenIterWhile.len Main  94.4 0.0 
main    Main  1.9 93.7 
collatzNext  Main  1.8 0.0 
lenIterWhile  Main  1.3 6.2 

                    individual  inherited 
COST CENTRE   MODULE      no.  entries %time %alloc %time %alloc 

MAIN     MAIN      44   0 0.0 0.0 100.0 100.0 
main     Main      89   0 1.9 93.7 100.0 100.0 
    main.\    Main      92  1000000 0.4 0.0 98.1 6.2 
    collatzLen   Main      93  1000000 0.2 0.0 97.8 6.2 
    lenIterWhile  Main      94  1000000 1.3 6.2 97.5 6.2 
    lenIterWhile.len Main      95 88826840 94.4 0.0 96.2 0.0 
     collatzNext  Main      96 87826840 1.8 0.0  1.8 0.0 
    main.max_a0   Main      90   1 0.0 0.0  0.0 0.0 
CAF     GHC.IO.Encoding.CodePage 73   0 0.0 0.0  0.0 0.0 
CAF     System.Environment   64   0 0.0 0.0  0.0 0.0 
CAF     GHC.IO.Handle.Text   62   0 0.0 0.0  0.0 0.0 
CAF     GHC.IO.Encoding    61   0 0.0 0.0  0.0 0.0 

Scheint so, dass es behoben. Also das Problem ist, dass GHC-7.8 Inlines even, während GHC-7.10 nicht. Dies geschieht aufgrund der hinzugefügten {-# SPECIALISE even :: x -> x -> Bool #-} Regeln für Int und Integer, die Inlining nicht zulassen.

Als issue's Diskussion Dokumente machen even und odd{-# INLINEABLE ... #-} würde dieses Problem beheben. Beachten Sie, dass die Spezialisierung selbst was added for perfomance reasons ist.

+0

'SPECIALISE' stört immer das Inlining! GHC wählt generell oder vielleicht immer eine vom Benutzer gewünschte Spezialisierung über Inlining. Ich kann mir keinen guten Grund vorstellen, * nicht * für 'Int' zu" inline "zu sein, also klingt es wie ein Fehler für mich. – dfeuer

+0

Aus Neugier, wie ich in der Frage sagte, ich kann immer noch nicht reproduzieren, was der ursprüngliche Autor in seinem Blog und ich kann nur sehen, der Unterschied kommt von llvm. Würde so ein signifikanter Unterschied entstehen? – HuStmpHrrr

+0

@HuStmpHrrr Ich habe nie versucht llvm, da ich auf Windows bin. Aber man könnte das IR und den resultierenden Assembler überprüfen. Der IR enthält immer noch "even" (oder einen Sprung an der erwarteten Stelle), aber die Assembly ist dann nicht llvm's Inliner scheint schlau genug. Wenn der IR keinen Sprung/Aufruf zu "even" enthält, könnte der Codegen für llvm ein bisschen schlauer sein. Ich kann morgen nachsehen. – Zeta