Hallo, ich habe ein verdrahtetes Verhalten der Optimierungsflags von ghc festgestellt. Die Optimierungsflags scheinen die Art der Auswertung zu verändern. Zusammenfassend,
primes
und isPrime
enthält, die durch gegenseitige Bezugnahme definiert wurden. ghc -O3
gut funktioniert, aber ich konnte runhaskell
nicht verwenden, um das Ergebnis zu erhalten. Es kostet zu viel Zeit. 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. Debug.Trace.trace
verwendet, um festzustellen, dass primes
von Anfang an immer dann ausgewertet wird, wenn isPrime
aufgerufen wird. 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.
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.
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.
Wenn das Optimierungs-Flag -O3
ist (oder -O1
), lautet die Ausgabe wie folgt.
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
)
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.
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.
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.