Welche Integer-Operationen haben in Rust alternative Methoden?

8

Beim Schreiben von Integer-Funktionen in Rust, die millionenfach ausgeführt werden (Pixelverarbeitung), ist es sinnvoll, Operationen mit der höchsten Leistung zu verwenden - ähnlich wie in C / C ++.

Während das Referenzhandbuch Änderungen im Verhalten erklärt, ist es nicht immer klar, welche Methoden eine höhere Leistung als die normalen arithmetischen Operationen des Typs (siehe Anmerkung 1) haben. Ich würde annehmen, wrapping_add kompiliert zu etwas, das dem Zusatz von C entspricht.

Von den Standardoperationen (addieren / subtrahieren / multiplizieren / modulo / division / shift / Bitmanipulation ...), welche Operationen haben höhere Leistungsalternativen, die nicht standardmäßig verwendet werden?

Hinweis:

  1. By standard Ich meine Ganzzahlarithmetik mit Symbolen a + b , i / k oder c % e ... usw.
    Was würden Sie verwenden, wenn Sie mathematische Ausdrücke schreiben - es sei denn, Sie haben eine besondere Notwendigkeit für die Verwendung einer der Methoden, die den Überlauf umschließt oder zurückgibt.
  2. Ich weiß, dass das Beantworten dieser Frage einige Nachforschungen erfordert. Daher bin ich froh, einige Überprüfungen durchzuführen, indem ich die resultierende Assembly ansehe, um zu sehen, welche Operationen ungeprüfte / primitive Operationen verwenden.
  3. Es kann sein, dass der Geschwindigkeitsunterschied zwischen aktivierten / nicht aktivierten Operationen nicht signifikant ist. Wenn das der Fall wäre, würde ich noch gerne in der Lage sein, eine 'schnelle' Version einer Funktion zu schreiben Vergleichen Sie mit der "sicheren" Version, um zu meiner eigenen Schlussfolgerung zu kommen, ob es eine vernünftige Wahl für eine gegebene Funktion ist.
  4. Nachdem wir die Pixelverarbeitung erwähnt haben, ist SIMD eine mögliche Lösung. Auch wenn das ein guter Vorschlag ist. Das lässt uns immer noch die Fälle übrig, die nicht mit SIMD optimiert werden können, so dass der allgemeine Fall der schnellen Ganzzahlarithmetik immer noch etwas zu berücksichtigen ist.
ideasman42 12.12.2016, 13:25
quelle

3 Antworten

6
  

Von den Standardoperationen (addieren / subtrahieren / multiplizieren / modulo / division / shift / Bitmanipulation ...), welche Operationen haben höhere Leistungsalternativen, die nicht standardmäßig verwendet werden?

Beachten Sie, dass Rust für Leistung entwickelt wurde; Wenn Integer-Operationen in Debug geprüft werden, werden sie daher in release definiert, sofern Sie den Compiler nicht anderweitig anweisen.

Daher gibt es im Veröffentlichungsmodus mit Standardoptionen strikt keinen Leistungsunterschied zwischen:

  • + und wrapping_add
  • - und wrapping_sub
  • * und wrapping_mul
  • / und wrapping_div
  • TODO: Prüfe auf Modulo, Verschiebung, Negation und absolute

Für vorzeichenlose Ganzzahlen ist die Leistung daher genau wie die von C oder C ++; Bei Ganzzahlen mit Vorzeichen kann der Optimierer jedoch andere Ergebnisse liefern, da Unter- / Überlauf bei Ganzzahlen mit Vorzeichen ein undefiniertes Verhalten in C und C ++ ist (gcc und Clang akzeptieren -fwrapv -Flag, um das Einschließen selbst für vorzeichenbehaftete Ganzzahlen zu erzwingen, aber es ist nicht der Standardwert) .

Ich erwarte, dass die Verwendung der Methoden checked_* , overflow_* und saturating_* jedoch im Allgemeinen langsamer ist.

Eine interessante Tangens ist dann, zu verstehen, was passiert, wenn Sie den Schalter umdrehen und ausdrücklich geprüfte Arithmetik erfordern.

Derzeit ist die Rust-Implementierung 1 eine genaue Implementierung der Unterlauf- / Überlaufprüfung. Jede Addition, Subtraktion, Multiplikation, ... wird unabhängig überprüft, und der Optimierer ist nicht gut darin, diese Zweige zu verschmelzen.

Insbesondere eine genaue Implementierung schließt temporäre Überläufe aus: 5 + x - 5 kann nicht als x optimiert werden, weil 5 + x überlaufen könnte. Es schließt auch die automatische Vektorisierung im Allgemeinen aus.

Nur wenn der Optimierer die Abwesenheit eines Überlaufs nachweisen kann (was normalerweise nicht möglich ist), können Sie hoffen, einen verzweigungsfreien Pfad wiederzuerlangen, der für Optimierungen besser geeignet ist.

Man sollte beachten, dass die Auswirkungen auf allgemeine Software kaum bemerkbar sind, da arithmetische Anweisungen einen kleinen Teil der Gesamtkosten ausmachen. Wenn dieser Anteil jedoch ansteigt, kann er sehr auffällig sein, und er zeigt sich tatsächlich in Teilen des SPEC2006-Benchmarks mit Clang.

Dieser Overhead war ausreichend, um als ungeeignet für die standardmäßig zu aktivierenden Prüfungen zu gelten.

1 Dies liegt an den technischen Einschränkungen der LLVM-Seite; die Rust-Implementierung delegiert nur an LLVM.

In Zukunft besteht die Hoffnung, dass eine unscharfe Implementierung der Prüfungen verfügbar sein wird. Die Idee hinter einer Fuzzy-Implementierung ist, dass, anstatt jede einzelne Operation zu überprüfen, diese gerade ausgeführt wird und ein Flag gesetzt wird oder die Werte im Falle eines Unterlaufs / Überlaufs vergiftet werden. Vor der Verwendung des Ergebnisses wird dann eine Überprüfung (Verzweigung) ausgeführt.

Laut Joe Duffy hatten sie eine solche Implementierung in Midori und der Leistungseinfluss war kaum bemerkbar, also scheint es machbar zu sein. Mir ist jedoch noch keine Bemühung bekannt, etwas Ähnliches in LLVM zu haben.

    
Matthieu M. 12.12.2016 14:42
quelle
5

Rust gibt keine Garantie für die Geschwindigkeit seiner Operationen. Wenn Sie Garantien benötigen, müssen Sie in Assembler aufrufen.

Das heißt, derzeit leitet Rust an LLVM weiter, also können Sie einfach die intrinsics aufrufen, die 1: 1 auf LLVM-Intrinsics abbilden und diese Garantien verwenden. Was auch immer Sie tun, das ist nicht asm, beachten Sie, dass der Optimierer eine andere Meinung von dem hat, was Sie für optimal halten, und somit Ihre manuellen Aufrufe an LLVM-Intrinsics nicht optimieren.

Das heißt, Rust strebt danach, so schnell wie möglich zu sein, also können Sie annehmen (oder einfach die Implementierung der Standardbibliothek betrachten), dass alle Operationen, die einen intrinsischen LLVM haben, der dasselbe tut, diesem LLVM intrinsisch zugeordnet werden und somit sein werden so schnell wie LLVM es kann.

Es gibt keine allgemeine Regel, welche Operation für eine gegebene arithmetische Grundoperation am schnellsten ist, da sie vollständig von Ihrem Anwendungsfall abhängt.

    
oli_obk - ker 12.12.2016 13:36
quelle
3
  

Denk Pixelverarbeitung

Dann sollten Sie keine einwertigen Operationen überhaupt denken; Sie möchten stattdessen SIMD-Anweisungen verwenden. Diese sind derzeit im stabilen Rust nicht verfügbar, aber einige sind über Feature-Gated-Funktionen zugänglich und alle sind durch Assembly verfügbar.

  

Ist es möglich, LLVM Code in SIMD optimiert, wie es für Clang tut?

Als aochagavia hat bereits geantwortet , ja, LLVM wird bestimmte Codearten automatisch initialisieren. Wenn Sie jedoch die höchste Leistung verlangen, möchten Sie sich normalerweise nicht den Launen des Optimierers überlassen. Ich tendiere dazu, in meinem normalen Standardcode auf die automatische Sicherung zu hoffen, schreibe dann den geradlinigen Code für meine schwermathematischen Kernel, schreibe dann SIMD-Code und überprüfe die Korrektheit und Benchmark für Geschwindigkeit.

    
Shepmaster 12.12.2016 13:38
quelle

Tags und Links