Veränderbar, (möglicherweise parallel) Haskell-Code und Performance-Tuning

9

Ich habe jetzt ein anderes implementiert SHA3-Kandidat, nämlich Grøstl. Dies ist immer noch in Arbeit (sehr), aber im Moment übergibt eine 224-Bit-Version alle KATs. So, jetzt frage ich mich über die Leistung (wieder: - & gt;). Der Unterschied liegt diesmal darin, dass ich die (optimierte) C-Implementierung genauer nachgebildet habe, also aus der ich einen Port gemacht habe C nach Haskell. Die optimierte C-Version verwendet Tabellen-Lookups, um den Algorithmus zu implementieren. Außerdem basiert der Code stark auf der Aktualisierung eines Arrays, das 64-Bit-Wörter enthält. Daher entschied ich mich dafür, in Haskell veränderbare ungepackte Vektoren zu verwenden.

Mein Grøstl-Code finden Sie hier: Ссылка

Kurze Beschreibung des Algorithmus: Es ist eine Merkle-Damgård-Konstruktion, die eine Komprimierungsfunktion ( f512M in meinem Code) wiederholt, solange 512-Bit-Blöcke von Nachrichten übrig sind. Die Komprimierungsfunktion ist sehr einfach: Sie führt einfach zwei verschiedene unabhängige 512-Bit-Permutationen durch P und Q ( permP und permQ in meinem Code) und kombiniert ihre Ausgabe. Es sind diese Permutationen, die durch Nachschlagetabellen implementiert werden.

Q1) Das erste, was mich stört ist, dass die Verwendung von veränderbaren Vektoren meinen Code wirklich fugig aussehen lässt. Dies ist das erste Mal, dass ich irgendeinen änderbaren Hauptcode in Haskell schreibe, also weiß ich nicht wirklich, wie ich das verbessern kann. Irgendwelche Tipps, wie ich den monadischen Code besser strukturieren könnte, wären willkommen.

Q2) Die zweite ist die Leistung. Eigentlich ist es nicht so schlimm, denn im Moment ist der Haskell Code nur 3 mal langsamer. Verwenden von GHC-7.2.1 und Kompilieren als solche:

  

ghc-O2-Odph -flllm -optlo-O3 -optlo-loop-reduce -optlo-loop-deletion

Der Haskell-Code verwendet 60s. bei einer Eingabe von ~ 1GB, während die C-Version 21-22s verwendet. Aber es gibt einige Dinge, die ich seltsam finde:

(1) Wenn ich versuche, rnd512QM einzubinden, dauert der Code 4-mal länger, aber wenn ich inline rnd512PM nichts passiert! Warum passiert dies? Diese beiden Funktionen sind praktisch identisch!

(2) Das ist vielleicht schwieriger. Ich habe damit experimentiert, die beiden Permutationen parallel auszuführen. Aber momentan ohne Erfolg. Dies ist ein Beispiel für das, was ich versucht habe:

%Vor%

Beim Überprüfen der Laufzeitstatistiken und mithilfe von ThreadScope habe ich festgestellt, dass die richtige Anzahl von SPARKS erstellt wurde, aber fast keine wurde tatsächlich in nützliche parallele Arbeit umgewandelt. So habe ich an Beschleunigung nichts gewonnen. Meine Frage wird dann:

  1. Sind die P- und Q-Funktionen einfach zu klein, um die Laufzeit parallel laufen zu lassen?
  2. Wenn nicht, verwende ich par und pseq (und möglicherweise Vector.Unboxed.force) falsch?
  3. Würde ich durch den Wechsel zu Strategien etwas gewinnen? Und wie würde ich das machen?

Vielen Dank für Ihre Zeit.

BEARBEITEN:

Entschuldigen Sie, dass Sie keine echten Benchmark-Tests durchgeführt haben. Der Testcode im Repo war nur für mich gedacht. Für diejenigen, die den Code testen möchten, müssen Sie main.hs kompilieren und dann wie folgt ausführen:

  

./ main "Algorithmus" "testvariant" "Byte ausgerichtet"

Zum Beispiel:

  

./ main groestl short224 Falsch

oder

  

./ main groestl e Falsch

( e steht für "Extreme". Es ist die sehr lange Nachricht, die mit dem NIST KATS geliefert wird).

    
hakoja 16.11.2011, 17:24
quelle

2 Antworten

3

Ich habe das Repo überprüft, aber es gibt keinen einfachen Benchmark, mit dem man einfach nur laufen und spielen kann, also sind meine Ideen nur daran, den Code zu sehen. Die Nummerierung hat nichts mit Ihren Fragen zu tun.

1) Ich bin mir ziemlich sicher, dass force nicht das tut, was Sie wollen - es zwingt tatsächlich eine Kopie des zugrunde liegenden Vektors.

2) Ich denke, die Verwendung von unsicherem Tau und unsicherem Gefriergut ist irgendwie merkwürdig. Ich würde einfach f512M in die ST-Monade setzen und damit fertig sein. Dann führe es so aus:

%Vor%

3) V.foldM' ist irgendwie albern - Sie können einfach eine normale (strikte) foldM über eine Liste verwenden - Faltung über den Vektor im zweiten Argument scheint nichts zu kaufen.

4) Ich bin zweifelhaft wegen der Pony in columnM und für die unsafeReads.

Auch ...

a) Ich vermute, dass das Xoring von ungepackten Vektoren wahrscheinlich auf einer niedrigeren Ebene als zipWith implementiert werden kann, indem Data.Vector-Internals verwendet werden.

b) Es ist jedoch besser, dies nicht zu tun, da dies die Vektorfusion beeinträchtigen könnte.

c) Bei Betrachtung sieht extractByte etwas ineffizient aus? Anstatt fromIntegral zum Abschneiden zu verwenden, verwenden Sie möglicherweise mod oder quot und dann eine einzelne vonIntegral, um Sie direkt zu einem Int. Zu bringen.

    
sclv 16.11.2011 20:12
quelle
1
  1. Stellen Sie sicher, dass Sie mit -threaded -rtsopts kompilieren und mit +RTS -N2 ausführen. Ohne dies haben Sie nicht mehr als einen Betriebssystem-Thread, um Berechnungen durchzuführen.

  2. Versuchen Sie, Berechnungen zu generieren, auf die an anderer Stelle verwiesen wird, andernfalls könnten sie gesammelt werden:

_

%Vor%

_

3) Wenn Sie die Dinge ändern, so akzeptiert parseBlock strikte Bytestrings (oder Chunks und packt faule Bytes, wenn nötig), dann können Sie Data.Vector.Storable verwenden und möglicherweise einige Kopien vermeiden.

    
Thomas M. DuBuisson 16.11.2011 19:59
quelle