Fast konstante Zeitauswertung von "x == 7" zu 1 (wahr) oder 0 (falsch) in Java

7

Ich möchte eine Crypto-Funktion von C nach Java portieren. Die Funktion muss in konstanter Zeit ausgeführt werden, daher sind keine bedingten Verzweigungen (und keine Tabellensuchen basierend auf x) zulässig.

Der ursprüngliche C-Code lautet:

%Vor%

Also ist 'result' auf 1 gesetzt, wenn 'x == 7' und sonst auf 0. Die Variable 'result' wird dann in weiteren Berechnungen verwendet.

Ich suche jetzt nach der besten Möglichkeit, dies auf Java zu übertragen. Da in Java Ausdrücke zu Booleschen Werten und nicht zu ganzen Zahlen ausgewertet werden, muss man das obige mit Operatoren simulieren.

Ich verwende derzeit

%Vor%

was für mich gut funktioniert, da mein x im Bereich {0, ..., 15} liegt. (Beachten Sie, dass die Shift-Funktion nur die unteren 5 Bits verwendet, so dass Sie falsche Positive erhalten, wenn x zu groß ist.)

Der Ausdruck wird millionenfach ausgewertet. Wenn es beispielsweise eine clevere Lösung gibt, die nur zwei anstelle von drei Operatoren verwendet, würde dies die Gesamtberechnung beschleunigen.

    
Chris 04.09.2015, 23:13
quelle

5 Antworten

7

Die beste Option von @ Hosch250 ist der ternäre Operator. Werfen wir einen Blick auf den Assembler, der vom JIT-Compiler für diese Methode generiert wurde:

%Vor%

Es hängt tatsächlich von der Profilerstellung ab. Wenn Ihr x ziemlich oft den Wert 7 hat, ist es wie folgt kompiliert:

%Vor%

Sehen Sie, dass ternary durch cmovne ersetzt wurde, was nicht die Verzweigungsinstruktion ist.

Wenn Sie andererseits in sehr seltenen Fällen 7 übergeben (z. B. einmal in 5000 Aufrufen), dann ist die Verzweigung hier:

%Vor%

Jetzt wird fast nie verzweigt, also ist es umso schneller, die Bedingung zu halten, da der CPU-Verzweigungs-Prädiktor fast immer korrekt ist. Beachten Sie, dass <slowpath> nicht nur return 1; ist, sondern auch das Zweigprofil aktualisiert. Wenn also das Muster während der Programmausführung geändert wird ( 7 wird häufiger angezeigt), wird die Methode erneut an die erste Version.

Versuchen Sie im Allgemeinen nicht, in so einfachen Fällen schlauer zu sein als der JIT-Compiler.

    
Tagir Valeev 05.09.2015 04:18
quelle
6

OK, ich denke, der Grund dafür ist folgender: Wenn die Ausführungszeit einer Krypto-Funktion von den Eingaben der Funktion abhängt, kann ein Angreifer durch Messen der Ausführungszeit Hinweise auf diese Eingaben erhalten. (Daher gelten die normalen Vorschläge "vorzeitige Optimierung" und "Versuch, den Compiler nicht zu überlisten" nicht wirklich.)

In Anbetracht dessen, hier sind meine Vorschläge:

  • Wenn x zur Kompilierzeit (oder JIT-Kompilierzeit) eine Konstante ist, besteht die Möglichkeit, dass der Code für beide optimiert wird result = true; oder result = false;

  • Wenn x keine Konstante ist, aber es gibt einen kleinen Bereich möglicher Werte, dann wird wahrscheinlich einer der folgenden Ansätze funktionieren:

    %Vor%

Das Problem ist, dass bei jedem denkbaren Ansatz der JIT-Compiler nicht-verzweigenden Code rechtlich in Verzweigungscode optimieren könnte. Wenn dies sicherheitskritisch ist, müssen Sie den tatsächlich ausgegebenen systemeigenen Code für jede zu zertifizierende Plattform untersuchen.

Der andere Ansatz ist:

  1. Analysiere den Java-Code-Algorithmus,
  2. versuche Fälle zu erkennen, in denen eine bedingte Verzweigung wahrscheinlich ist,
  3. Design-Test-Eingaben, um diese Verzweigungspfade auszulösen,
  4. messen Sie die Ausführungszeit (auf allen Zielplattformen), um festzustellen, ob es einen erkennbaren Unterschied zwischen Ihren Testeingaben gibt.

Natürlich ist die andere Sache, die es zu beachten gilt, dass dies sowieso egal sein kann; z.B. Wenn result dann in einem anderen Teil der Crypto-Funktion verwendet wird, um zu entscheiden, ob der Ausführungspfad zu übernehmen ist.

Und ...

  

Der Ausdruck wird millionenfach ausgewertet. Wenn es beispielsweise eine clevere Lösung gibt, die nur zwei anstelle von drei Operatoren verwendet, würde dies die Gesamtberechnung beschleunigen.

Wenn das Ihre wahre Motivation ist ... dann ist mein Ratschlag Do not Bother . Dies ist eine vorzeitige Optimierung. Überlassen Sie es dem JIT-Compiler.

    
Stephen C 05.09.2015 04:18
quelle
3

Da ist das Ziel,

zu erreichen %Vor%

in einer Art algebraischer Art ohne Verzweigung,

%Vor%

Aber das funktioniert nicht, weil die Rechtsverschiebung nur auf ein paar Bits maskiert ist. Was Sie also tun können, ist

%Vor%

aber es ist immer noch maskiert für den Fall von -6 (alle Bits sind im Fall von -6 ^ 7 gesetzt), also,

%Vor%

Also, wie viel langsamer ist es als eine Verzweigung bedingt? Obige Lösung, die bitCount () verwendet, um den gesamten Bereich int mehr als einmal zu vergleichen,

%Vor%

Verzweigung verwenden, (x == 7? 1: 0),

%Vor%

Es ist also nicht so schlimm, wenn man einen konstanten Zeitvergleich hat, der für jeden Wert funktioniert, 7 nur ein Beispiel. Ja, Integer.bitCount () ist auch eine konstante Zeit.

    
user3427419 05.09.2015 05:13
quelle
2

Ein Ternär wäre hier eine gute Option:

%Vor%

Dieser Code weist 1 zu result zu, wenn der Ausdruck x == 7 zu true ausgewertet wird, und weist 0 andernfalls zu.

    
Hosch250 05.09.2015 00:09
quelle
2

Unter Ausnutzung des extrem begrenzten Bereichs von x, der in [0,15] liegt, schlage ich vor, eine In-Register-Tabelle zu verwenden, die ein Bit pro Tabelleneintrag verwendet. In der Tabelle ist Bit i für die Eingänge festgelegt, die eine 1 und andernfalls null ergeben sollen. Dies bedeutet, dass unsere Tabelle die Literalkonstante 2 7 = 128:

ist %Vor%     
njuffa 12.09.2015 21:42
quelle