2017-11-15 1 views
5

einen Code gegeben, die ein bisschen Mathe tut/für jede Zahl von 1 bis 500.000 Gießen, haben wir Optionen:Gibt es in Perl 6 eine schnelle parallele "for" -Schleife?

  1. einfache for-Schleife: for ^500000 -> $i { my $result = ($i ** 2).Str; }. In meinem unwissenschaftlichen Benchmark dauert das 2,8 Sekunden.

  2. Die kanonischste parallele Version erledigt jedes Stück Arbeit in einem Promise, dann wartet auf das Ergebnis. await do for ^500000 -> $i { start { my $result = ($i ** 2).Str; } } dauert 19 Sekunden. Das ist langsam! Das Erstellen eines neuen Versprechens muss zu viel Aufwand erfordern, um sich für solch eine einfache Berechnung zu lohnen.

  3. Mit einem parallelen map Operation ist ziemlich schnell. Mit 2,0 Sekunden scheint der Vorgang kaum langsam genug, um die Vorteile der Parallelisierung zu nehmen: (^500000).race.map: -> $i { my $result = ($i ** 2).Str; }

Die dritte Option scheint am besten. Leider liest es sich wie ein Hack. Wir sollten nicht map Code für die Iteration im Sink-Kontext schreiben, weil andere, die "map" in der Quelle lesen, annehmen, dass der Zweck darin besteht, eine Liste zu erstellen, was nicht unsere Absicht ist. Es ist schlechte Kommunikation, map auf diese Weise zu verwenden.

Gibt es einen kanonischen schnellen Weg, um die Parallelität von Perl 6 zu nutzen? Ein hyper Operator perfekt sein würde, wenn er einen Block statt nur Funktionen übernehmen könnte:

(^500000)».(-> $i { my $result = ($i ** 2).Str; }) # No such method 'CALL-ME' for invocant of type 'Int' 
+0

Sollte die Schleife nicht in der Zeit Null laufen, da sie keine Auswirkungen hat? – BarneySchmale

+0

@BarneySchmale Sie haben gerade erklärt, warum ich 'map' anstelle von' for' nicht mag. In Perl 6 ist 'map' nicht faul, also funktioniert es wie' for'. (Genauer gesagt funktioniert es wie 'do for'.)' Map' wird für alle Listenelemente ausgeführt, auch wenn das Ergebnis nicht verwendet wird. Wenn Sie jedoch 'map' anstelle von' for' wählen, wird Ihnen mitgeteilt, dass das Ziel darin besteht, eine Liste zu erstellen, und in diesem Fall ist es nicht wahr. – piojo

+0

@piojo 'map' ist fast immer faul. Versuchen Sie 'say [1..Inf] .map (* + 100) [1..10]'. Aiui ...Standardmäßig ist "for" A) wie eine "Map", die eifrig und nicht faul ist, und B) eine Kontrollflussanweisung anstelle eines Ausdrucks. Ein "do" wandelt eine Anweisung in einen Ausdruck um, indem der nachfolgende Code ausgeführt und die vom Code generierte Liste der Werte zurückgegeben wird. (Ohne es gibt ein 'for' überhaupt keine Werte zurück.) – raiph

Antwort

6

Wenn Sie für eine Hyper- oder Rennoperation verwenden möchten, müssen Sie sie mit hyper for @blah.hyper(:batch(10_000)) oder race for @blah.race(:batch(10_000)) buchstabieren. Oder ohne Parameter: hyper for @blah, race for @blah.

Dies wurde entschieden, weil Sie Code wie for some-operation() { some-non-threadsafe-code } haben könnten, wo some-operation Teil einer Bibliothek oder etwas ist. Jetzt können Sie nicht mehr sagen, ob die for-Schleife thread-unsafe Code darin enthalten kann oder nicht, und selbst wenn Sie wissen, dass die Bibliothek zu diesem Zeitpunkt keine HyperSeq zurückgibt, was ist, wenn der Bibliotheksautor diese vorstellt tolle Ideesome-operation schneller zu machen, indem man es hyperte?

Deshalb ist ein Signifikant für "es ist sicher, dies für die Schleife parallel zu betreiben" genau dort erforderlich, wo der Code ist, nicht nur dort, wo die Sequenz erzeugt wird.

+0

Haben Sie die doppelte Verwendung von 'hyper' in' hyper für gedacht @ foo.hyper' (und ebenfalls mit 'race')? Das ist nicht, was Jnthn auf Kanal und in seinem Rakudo bis Ende Oktober gesagt hat. Wenn du das nicht dachtest, gibt es da irgendwo etwas, das ich anders als deine Antwort hier lesen könnte? TIA. – raiph

+0

Sie haben Recht. Sie müssen es nicht dort eingeben, wenn Sie nur über eine Liste oder etwas iterieren. Ich habe es bearbeitet, um eine Stapeleinstellung dort zu haben, also können Sie sehen, wie man dort mit einer for-Schleife Optionen erhält. – timotimo

3

Auf meinem PC, das ist ein bisschen (~ 15%) schneller als die naive Schleife:

(^500_000).hyper(batch => 100_000).map(-> $i { my $result = ($i ** 2).Str; }) 

Da die Berechnung innerhalb der Schleife sehr schnell ist, sind die Kosten für Parallelisierung und Synchronisation in der Regel gering. Das einzige Gegenmittel ist eine große Losgröße.

Update: mit einer Losgröße von 200_000 bekomme ich etwas bessere Ergebnisse (noch ein paar Prozent schneller).

+0

Der Grund, warum ich frage ist, weil ich ein reales Welt-Skript mit Versprechungen um 15% langsamer als mit '.hyper.map' wahrgenommen habe , aber immer noch viel schneller als die nicht parallele Version. Versprechungen sind also nicht nur eine Verlangsamung, wenn Sie sie in der falschen Situation verwenden. – piojo

+0

Darf ich aus dieser Antwort folgern, dass es keine eigentliche imperative Parallelschleife irgendeines Typs gibt ?: 'for @ a.hyper -> $ element {...}' – piojo

Verwandte Themen