2015-04-03 8 views
6

Ich möchte einige lange Ganzzahl Mathe (128 Bit) mit Intel I64 Assembler tun und müssen ein Zweierkomplement erstellen. Nehmen wir an, mein positiver Wert liegt in RDX: RAX.Zweierkomplement der langen Ganzzahl

2er Komplement erfolgt durch "Flip die Bits und add 1". Also die meisten naive Implementierung (4 Anweisungen und 14 Byte-Code):

NOT RAX 
    NOT RDX 
    ADD RAX,1 ; Can't use INC, it doesn't set Carry 
    ADC RDX,0 

Wenn ich die NEG-Befehl auf RAX statt NICHT verwenden, ist es nicht die „+1“ für mich, aber die Carry ist falsch, NEG RAX löscht Carry, wenn RAX gleich Null war, aber ich brauche NUR in diesem Fall. So könnte der nächstbeste Ansatz sein (4 Instruktionen und 11 Bytes Code):

NOT RDX 
    NEG RAX 
    CMC 
    ADC RDX,0     ; fixed, thanks lurker 

Noch 4 Anweisungen. Aber anstatt +1 zu addieren, kann ich -1 subtrahieren und da SBB das Carry-Bit zum Subtrahend addiert, werde ich +1 addieren, wenn Carry klar ist. Also mein nächster bester Versuch das ist, mit 3 Anweisungen und 10 Byte Code:

NOT RDX 
    NEG RAX 
    SBB RDX,-1 

Wie Sie aus meinem langen umständlichen Text sehen können, ist dies nicht offensichtlich zu verstehen. Gibt es eine bessere, verständlichere Möglichkeit, ein kaskadiertes Zweierkomplement in Assembler zu erstellen?

+4

Sie scheinen davon auszugehen, dass "besser" gleich "kürzerer Code" ist, und das ist etwas, das nicht für einen Multi-skalaren Out-or-order-Prozessor gelten muss, wie der x86-64 ist. Ich würde sagen, dass die unterste Tabelle Ihrer Implementierungen die erste ist, und ich wäre nicht überrascht, wenn alle die gleiche Zeit zur Ausführung benötigen. –

+0

BTW: Haben Sie überlegt, die XMM-Register zu verwenden?Sie sind breit genug, um eine 128-Bit-Nummer zu halten und (ich habe nicht überprüft) sie haben möglicherweise Integer-Anweisungen, um mit der ganzen Zahl –

+2

@mcleod_ideafix sie nicht tun, so dass Sie immer noch mit dem Problem der Durchführung der tragen übrig manuell. – harold

Antwort

3

Kürzere Anweisungen oder weniger Anweisungen bedeutet nicht unbedingt schnellere Ausführung. Die Latenz und der Durchsatz für jede Anweisung sind unterschiedlich. Überholte Anweisungen wie enter, dad, aam ... werden viel langsamer und sie sind nur aus Gründen der Abwärtskompatibilität da. Sogar inc is sometimes slower than add. Das Gleiche gilt für cmc, die Sie oben verwendet haben. Eine längere Reihe von Befehlen mit niedriger Latenz, die parallel ausgeführt werden können, werden viel schneller arbeiten. Die Optimierer des Compilers wissen dies immer und werden die am besten geeigneten Anweisungen zum Emittieren auswählen.

Für diesen Code

__int128 x = some_value; 
__int128 y = -x; // line 12 

Bei -O2ICC will generate the following instructions den Wert

xor  esi, esi          #12.17 
    xor  edx, edx          #12.17 
    sub  rdx, r15          #12.17 
    sbb  rsi, rbx          #12.17 

Ich habe die nicht verwandten Linien entfernt negieren dazwischen, so können Sie sehen, dass es den Wert mit 0 durch sub subtrahiert/sbb. Das ist schneller als deine zweite Lösung.

Sie können den Compiler in gcc.godbolt Link oben schalten verschiedene Möglichkeiten, um zu sehen von verschiedenen Compilern

GCC zu negieren:

neg rdx 
adc rcx, 0 
neg rcx 

Clang:

neg rbx 
mov esi, 0 
sbb rsi, r14 

Wie Sie sehen können Sie benutzen auch nur 3 Anweisungen. Ob es schneller ist oder nicht, bedarf eines sorgfältigen Benchmarks. Aber auf Intel-CPUs erreicht der Intel-Compiler (ICC) oft eine höhere Leistung als andere, weil er die Architektur besser versteht.

+0

'Papa' ist kein x86-Kürzel,' aam' und dergleichen sind in x86-64 ungültig. Laut den Anweisungstabellen von Ager Fog ist "cmc" seit der P4 auf jeder Intel/AMD-Mikroarchitektur schnell. – EOF

+0

Vielleicht sollte ich GCC mal ausprobieren, zumindest um den erzeugten Code zu betrachten. Ich verwendete Visual Studio 2013. – Rolf

+0

https://gcc.godbolt.org/ wird Ihnen helfen, die Assembly-Ausgabe von mehreren der häufigsten Compiler zu sehen –