Warum ändert ghc den Auswertungsweg aufgrund des Optimierungs-Flags?

8

Hallo, ich habe ein verdrahtetes Verhalten der Optimierungsflags von ghc festgestellt. Die Optimierungsflags scheinen die Art der Auswertung zu verändern. Zusammenfassend,

  • Ich habe einen Code geschrieben, der primes und isPrime enthält, die durch gegenseitige Bezugnahme definiert wurden.
  • Ich habe festgestellt, dass das Programm mit ghc -O3 gut funktioniert, aber ich konnte runhaskell nicht verwenden, um das Ergebnis zu erhalten. Es kostet zu viel Zeit.
  • Ich habe festgestellt, dass bei Verwendung von ghc -O1 das Ergebnis sofort als -O3 angezeigt wird, aber die von ghc -O0 kompilierte ausführbare Datei das Ergebnis in einer Minute nicht berechnet.
  • Ich habe Debug.Trace.trace verwendet, um festzustellen, dass primes von Anfang an immer dann ausgewertet wird, wenn isPrime aufgerufen wird.
  • Ich habe die Definition von primes und isPrime in eine andere Datei Prime.hs verschoben. In der Hauptdatei habe ich meine Prime-Bibliothek importiert. Leider berechnet die ausführbare Datei, die von ghc -O3 erstellt wurde, das Ergebnis nicht in einer Minute.

Hier ist die Beschreibung. Bitte beachten Sie den folgenden Code.

%Vor%

Wenn ich den Code mit ghc -O3 kompiliere, berechnet die ausführbare Datei das korrekte Ergebnis 68906 in 2 Sekunden.

%Vor%

Wenn ich jedoch -O0 verwendet habe, konnte ich das Ergebnis nicht in einer Minute erhalten. Stellen Sie sicher, dass Sie die generierten Dateien im Voraus entfernen.

%Vor%

Ich habe abgebrochen. Das Flag -O1 funktioniert genauso gut wie -O3 .

Sehen wir uns also die Untersuchung an. Ich habe Debug.Trace.trace verwendet. Ich habe das Argument von isPrime nachverfolgt.

%Vor%

Wenn das Optimierungs-Flag -O3 ist (oder -O1 ), lautet die Ausgabe wie folgt.

%Vor%

Dieses Ergebnis ist vernünftig (beachten Sie, dass die letzte Zeile die Anzahl der Primzahlen ausgibt; 11, 13, 17, 19, 23, 29).

Hier ist das Ergebnis mit -O0 (oder runhaskell )

%Vor%

Dieses Ergebnis ist interessant zu untersuchen. 2 ist bereits an der Spitze von primes angeordnet. 3 und 5 werden immer wieder auf isPrime geprüft. Wenn isPrime 11 aufgerufen wird, wird 3 geprüft, ob eine Primzahl und 5 ebenfalls markiert ist, isPrime 3 wird erneut aufgerufen. Ebenso wird für fast alle ungeraden Zahlen isPrime 3 und isPrime 5 immer wieder aufgerufen.

Also dachte ich, wenn ich -O0 verwende, wird primes nicht jedes Mal zwischengespeichert und aus [2] konstruiert, wenn isPrime aufgerufen wird. Die erste Frage ist, warum -O0 und -O1 das Verhalten der Auswertung ändert.

Hier ist ein anderes Problem. Okay, okay, aber ich verwende selten -O0 flag. In den meisten Fällen verwende ich -O2 oder -O3 optimization flag, also dachte ich, dass das obige Problem nicht in vielen Anwendungsfällen auftritt.

Aber wenn ich die Codes in eine andere Datei verschoben habe, taucht das Problem wieder auf. Ich habe gerade primes und isPrime auf Prime.hs verschoben.

test.hs:

%Vor%

Prime.hs:

%Vor%

In dieser Zeit konnte ich das Ergebnis nicht mit -O1 flag oder sogar mit -O3 flag erhalten.

%Vor%

hmm, ich habe wieder abgebrochen. Ich weiß nicht, ob sich dieser Weg auf das Ergebnis auswirkt, ich habe Prime.hs vorab mit -O3 vorkompiliert, aber vergebens. Ich habe hierbei Debug.Trace.trace verwendet und ich habe 2 und 3 immer wieder mit -O3 flag gesehen. Kurz gesagt, ich konnte keine Prime-Bibliothek erstellen, da sich der Auswertungsweg ändert, wenn primes und isPrime in ein Modul verschoben werden (was mich überrascht hat) und -O3 lässt es nicht funktionieren.

Also ist die zweite Frage, trotz der -O3 -Flag, warum die Stoffe in einem Modul als von -O0 flag kompiliert ausgewertet werden?

Ich bin es endlich leid, dieses verkabelte Verhalten zu untersuchen. Ich kam zu dem Schluss, dass ich keine querverweisende Definition in einem Modul verwenden sollte. Ich habe es aufgegeben, meine Prime-Bibliothek zu erstellen und habe Data.Numbers.Primes verwendet.

Vielen Dank im Voraus.

    
itchyny 21.09.2014, 09:59
quelle

1 Antwort

10

Was hier vor sich geht, ist in der folgenden Signatur:

%Vor%

Die Typklasse verhindert, dass primes naiv memoisiert wird. primes :: [Int] ist nicht identisch mit primes :: [Integer] . Und es können keine Berechnungen geteilt werden, weil GHC nicht davon ausgehen kann, dass alle Instanzen von Num derselben Logik folgen. Aus diesem Grund wird bei jeder Verwendung von primes die Liste am ausgewählten Typ neu berechnet.

Wenn Sie jedoch Optimierungen aktivieren, wird GHC ein gutes Stück intelligenter. Wenn sich primes nur im selben Modul wie die Definition befindet, kann GHC es auf den konkreten Typ, in dem es verwendet wird, optimieren. Dann werden die Berechnungen für die Verwendung der Liste gemeinsam genutzt.

Dies geschieht jedoch nur innerhalb von Modulgrenzen. Getrenntes Kompilieren von Modulen bedeutet, dass wenn primes exportiert wird, kann es nicht auf einen konkreten Typ spezialisiert werden - GHC weiß nie, ob das nächste Modul, das kompiliert wird, primes bei einem anderen Typ verwendet.

Der einfachste Weg, dies zu lösen, ist primes einen konkreten Typ zu geben. Dann notiert selbst der naive Gebrauch davon.

    
Carl 21.09.2014, 15:36
quelle

Tags und Links