Generieren von CMOV-Anweisungen mit Microsoft-Compilern

Lesezeit: 6 Minuten

Benutzer-Avatar
Max Deliso

Um einige cmov-Anweisungen auf einem Intel Core 2 mit Windows 7 Pro herauszubekommen, habe ich den folgenden Code geschrieben. Alles, was es tut, ist, einen String von der Konsole als Eingabe zu nehmen, einige Verschiebungsoperationen anzuwenden, um einen zufälligen Startwert zu generieren, und diesen Startwert dann an srand weiterzugeben, um ein kleines Array von Pseudozufallszahlen zu generieren. Die Pseudozufallszahlen werden daraufhin ausgewertet, ob sie die Prädikatfunktion (eher willkürliches Bitshuffling) erfüllen, und ein ‘*’ oder ein ‘_’ ausgeben. Der Zweck des Experiments besteht darin, cmov-Anweisungen zu generieren, aber wie Sie in der folgenden Disassemblierung sehen können, gibt es keine.

Irgendwelche Tipps, wie man den Code oder die Cflags ändert, damit sie generiert werden?

#include <iostream>
#include <algorithm>
#include <string>
#include <cstdlib>

bool blackBoxPredicate( const unsigned int& ubref ) {
   return ((ubref << 6) ^ (ubref >> 2) ^ (~ubref << 2)) % 15 == 0;
}

int main() {
   const unsigned int NUM_RINTS = 32;
   unsigned int randomSeed = 1;
   unsigned int popCount = 0;
   unsigned int * rintArray = new unsigned int[NUM_RINTS];
   std::string userString;

   std::cout << "input a string to use as a random seed: ";
   std::cin >> userString;

   std::for_each( 
      userString.begin(), 
      userString.end(), 
      [&randomSeed] (char c) {
         randomSeed = (randomSeed * c) ^ (randomSeed << (c % 7));
   });

   std::cout << "seed computed: " << randomSeed << std::endl;

   srand(randomSeed);

   for( int i = 0; i < NUM_RINTS; ++i ) {
      rintArray[i] = static_cast<unsigned int> (rand());
      bool pr = blackBoxPredicate(rintArray[i]);
      popCount = (pr) ? (popCount+1) : (popCount);

      std::cout << ((pr) ? ('*') : ('_')) << " ";
   }

   std::cout << std::endl;

   delete rintArray;
   return 0;
}

Und habe dieses Makefile verwendet, um es zu erstellen:

OUT=cmov_test.exe
ASM_OUT=cmov_test.asm
OBJ_OUT=cmov_test.obj
SRC=cmov_test.cpp
THIS=makefile

CXXFLAGS=/nologo /EHsc /arch:SSE2 /Ox /W3

$(OUT): $(SRC) $(THIS)
   cl $(SRC) $(CXXFLAGS) /FAscu /Fo$(OBJ_OUT) /Fa$(ASM_OUT) /Fe$(OUT)

clean:
   erase $(OUT) $(ASM_OUT) $(OBJ_OUT)

Und doch, als ich nachsehen wollte, ob welche generiert wurden, sah ich, dass die Compiler von Microsoft die folgende Assembly für diese letzte for-Schleife generiert hatten:

; 34   :       popCount = (pr) ? (popCount+1) : (popCount);
; 35   :       
; 36   :       std::cout << ((pr) ? ('*') : ('_')) << " ";

  00145 68 00 00 00 00   push    OFFSET $SG30347
  0014a 85 d2        test    edx, edx
  0014c 0f 94 c0     sete    al
  0014f f6 d8        neg     al
  00151 1a c0        sbb     al, al
  00153 24 cb        and     al, -53            ; ffffffcbH
  00155 04 5f        add     al, 95         ; 0000005fH
  00157 0f b6 d0     movzx   edx, al
  0015a 52       push    edx
  0015b 68 00 00 00 00   push    OFFSET ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::cout
  00160 e8 00 00 00 00   call    ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@D@Z ; std::operator<<<std::char_traits<char> >
  00165 83 c4 08     add     esp, 8
  00168 50       push    eax
  00169 e8 00 00 00 00   call    ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >
  0016e 46       inc     esi
  0016f 83 c4 08     add     esp, 8
  00172 83 fe 20     cmp     esi, 32            ; 00000020H
  00175 72 a9        jb  SHORT $LL3@main

Als Referenz sind hier meine CPU-ID-Strings und die Compiler-Version.

PROCESSOR_ARCHITECTURE=x86
PROCESSOR_IDENTIFIER=x86 Family 6 Model 58 Stepping 9, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=3a09

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86

  • Wenn Sie bestimmte Anweisungen wünschen, versuchen Sie nicht, den Compiler dazu zu bringen, sie abzuleiten, da sich das, was er tun wird, mit der Version, den Optimierungseinstellungen, Flags usw. ändern kann. Verwenden Sie stattdessen die für diesen Compiler oder Link geltende Inline-Assembly-Funktion eine echte Assembler-Quelldatei in das Ergebnis.

    – Chris Stratton

    1. Dezember 2012 um 16:30 Uhr


  • Unter welchen Bedingungen generieren Optimierungs-C++-Compiler normalerweise cmov-Anweisungen? dies ist eher ein Experiment als für den Produktionseinsatz; Ich würde gerne wissen, wie man C++ schreibt, das für Compiler einfach zu optimieren ist, um die Leistung der Verzweigungsvorhersage zu steigern.

    – Max Deliso

    1. Dezember 2012 um 16:54 Uhr

  • Früher war das so cmov war langsamer als cmp+jmp Wenn der Zweig sehr vorhersehbar wäre, würden Compiler ihn zu Recht nicht oft verwenden. Ebenfalls, cmov Abhängigkeiten erstellt, die dazu führen, dass es in einer engen Schleife langsamer läuft. Ich bin mir nicht sicher, ob dies immer noch der Fall ist. Vielleicht würde die Verwendung von PGO den Compiler dazu ermutigen, indem er hilft, falsch vorhergesagte Zweige zu finden?

    – Cory Nelson

    1. Dezember 2012 um 17:23 Uhr


  • Schreiben Sie Ihre DLL in Assembler.

    – Алексей Неудачин

    14. Dezember 2016 um 14:45 Uhr

  • (b + ((a - b) & -(a < b))) hat möglicherweise auch undefiniertes Verhalten, wenn a-b überläuft. Einige moderne Compiler könnten dann davon ausgehen, dass a-b kann beim Optimieren von späterem Code nicht überlaufen. Ob a oder b konstant sind (nach Inlining und Constant-Progation), wird dies zu einer einfachen Bereichsgrenze, die zu einem führen könnte if (a > 123456) als immer falsch oder immer wahr wegoptimiert werden. In der Praxis wird es meiner Meinung nach normalerweise kein Problem sein, aber modernes C++ ist leider sehr weit davon entfernt, eine portable Assemblersprache für Ziele mit vorzeichenbehafteten Ganzzahlen im Komplement von vernünftigen 2 zu sein.

    – Peter Cordes

    14. Dezember 2016 um 19:54 Uhr

  • Betreff: Inline-Asm: Ich habe eine ganze Antwort darüber geschrieben, wie schlecht Inline-Asm im MSVC-Stil zum Umschließen einzelner Anweisungen im Vergleich zur GNU-C-Syntax ist (aber selbst dann gcc.gnu.org/wiki/DontUseInlineAsm). Vielleicht möchten Sie es verlinken. Übrigens, mir war nie klar, wie es für MSVC-Inline-ASM funktionieren kann, einfach einen Wert in EAX zu belassen und das wegzulassen return in einer nicht leeren Funktion. Deaktiviert dies das Inlining der Funktion? Oder unterstützt MSVC dieses Idiom beim Inlining, um ein Speichern/Neuladen für Ergebnisse zu vermeiden?

    – Peter Cordes

    14. Dezember 2016 um 20:01 Uhr

  • Sicher, es hat möglicherweise ein undefiniertes Verhalten, @Peter. Nichts davon ist tragbar. Ich habe überprüft, ob es auf MSVC korrekt funktioniert. Es ist keiner dieser Compiler im Gnu-Stil, der von einem Gegner nicht zu unterscheiden ist. 🙂 Es ist vielleicht nicht das beste Beispiel, aber es ist Code, den ich tatsächlich verwende, wenn ich für 32-Bit auf MSVC 2010 baue. Ich wünschte oft, es gäbe eine wirklich portable Assemblersprache, aber weder C noch C++ sind diese Sprache. MSVCs Inline-asm wurde früher hauptsächlich für “Escapes” wie das Ausführen von DOS-Interrupts entwickelt. Es war nicht auf Geschwindigkeit ausgelegt und wurde nicht wie GAS erweitert.

    – Cody Grey

    16. Dezember 2016 um 9:47 Uhr

  • Das Belassen eines Werts in EAX (oder EDX:EAX) am Ende eines Inline-asm-Blocks ist vollkommen legal und in MSVC gültig. Ich denke, ältere Versionen des Compilers (wir sprechen von Pre-MSVC 6.0, vor sehr langer Zeit) haben früher eine Warnung ausgegeben, wenn Sie dies getan haben, etwas über die Ausführung fällt am Ende der Funktion ohne Rückgabewert ab, aber der generierte Code war in diesem Fall noch korrekt. Die Tatsache, dass die Warnung entfernt wurde, deutet für mich darauf hin, dass dies eine ausdrücklich unterstützte Redewendung ist. Das Ergebnis wird nie gespeichert/neu geladen, wenn Sie den Code richtig schreiben. Und nein, Inlining wird nicht deaktiviert.

    – Cody Grey

    16. Dezember 2016 um 9:49 Uhr

  • Überraschenderweise funktioniert Inlining sehr gut mit Funktionen, die Inline-asm-Blöcke enthalten, wenn nicht Sie versuchen, die Funktion zu zwingen, die zu verwenden __fastcall Konvention. Dann wird es nicht inline. (__cdecl und __stdcall beide inline in Ordnung). Das ist ziemlich enttäuschend, denn __fastcall würde erlauben, dass die ersten beiden Parameter in Registern übergeben werden, was es Ihnen ermöglichen würde, zumindest einige der Lade-/Speicherstrafen für das Lesen von Parametern zu vermeiden. Leider soll es nicht sein. Ich habe mit unzähligen verschiedenen Arten gespielt, den Code zu schreiben, und es führt einfach kein Weg daran vorbei, die Eingaben zu mischen.

    – Cody Grey

    16. Dezember 2016 um 9:51 Uhr

1012700cookie-checkGenerieren von CMOV-Anweisungen mit Microsoft-Compilern

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy