2010-05-30 7 views
27

Ich bin in einer dieser Herausforderungen beteiligt, wo Sie versuchen, die kleinstmögliche Binärdatei zu erstellen, also ich bin mein Programm ohne die C oder C++ ausführen -Zeitbibliotheken (RTL). Ich verlinke nicht auf die DLL-Version oder die statische Version. Ich habe nicht einmal #include die Header-Dateien. Ich habe das gut funktioniert.Wie VC++ intrinsische Funktionen ohne Laufzeitbibliothek verwenden

Einige RTL-Funktionen, wie memset(), können nützlich sein, also habe ich versucht, meine eigene Implementierung hinzuzufügen. Es funktioniert gut in Debug-Builds (sogar für die Stellen, an denen der Compiler implizite Aufruf an memset() generiert). Aber in Release Builds bekomme ich eine Fehlermeldung, dass ich keine intrinsische Funktion definieren kann. Sie sehen, dass in Release-Builds intrinsische Funktionen aktiviert sind und memset() ein intrinsischer Wert ist.

Ich würde gerne die intrinsische für memset() in meinem Release-Build verwenden, da es wahrscheinlich inlined und kleiner und schneller als meine Implementierung ist. Aber ich scheine ein In-Catch-22 zu sein. Wenn ich memset() nicht definiere, beschwert sich der Linker, dass es nicht definiert ist. Wenn ich es definiere, beschwert sich der Compiler, dass ich keine intrinsische Funktion definieren kann.

Kennt jemand die richtige Kombination aus Definition, Deklaration, #pragma und Compiler- und Linker-Flags, um eine intrinsische Funktion zu erhalten, ohne RTL-Overhead einzuziehen?

Visual Studio 2008, x86, Windows XP +.

Um das Problem ein wenig konkreter zu machen:

extern "C" void * __cdecl memset(void *, int, size_t); 

#ifdef IMPLEMENT_MEMSET 
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) { 
    char *p = reinterpret_cast<char *>(pTarget); 
    while (cbTarget > 0) { 
     *p++ = static_cast<char>(value); 
     --cbTarget; 
    } 
    return pTarget; 
} 
#endif 

struct MyStruct { 
    int foo[10]; 
    int bar; 
}; 

int main() { 
    MyStruct blah; 
    memset(&blah, 0, sizeof(blah)); 
    return blah.bar; 
} 

Und ich so bauen:

cl /c /W4 /WX /GL /Ob2 /Oi /Oy /Gs- /GF /Gy intrinsic.cpp 
link /SUBSYSTEM:CONSOLE /LTCG /DEBUG /NODEFAULTLIB /ENTRY:main intrinsic.obj 

Wenn ich mit meiner Implementierung memset() kompilieren, ich einen Compiler-Fehler:

error C2169: 'memset' : intrinsic function, cannot be defined 

Wenn ich dies ohne meine Implementierung vonkompilieren, erhalte ich einen Linker-Fehler:

error LNK2001: unresolved external symbol _memset 
+1

Es ist '/ GL', das ist das Problem, siehe meine Antwort unten. – egrunin

Antwort

16

Ich glaube, ich fand schließlich eine Lösung:

Zuerst in einer Header-Datei, erklärt memset() mit einem Pragma, etwa so:

extern "C" void * __cdecl memset(void *, int, size_t); 
#pragma intrinsic(memset) 

, dass Ihr Code ermöglicht memset() zu nennen. In den meisten Fällen wird der Compiler die intrinsische Version inline erstellen.

Zweitens, in einer separaten Implementierungsdatei eine Implementierung bereitstellen. Der Trick, den Compiler daran zu hindern, sich darüber zu beschweren, eine intrinsische Funktion neu zu definieren, besteht darin, zuerst ein anderes Pragma zu verwenden. Wie folgt aus:

#pragma function(memset) 
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) { 
    unsigned char *p = static_cast<unsigned char *>(pTarget); 
    while (cbTarget-- > 0) { 
     *p++ = static_cast<unsigned char>(value); 
    } 
    return pTarget; 
} 

Dies stellt eine Implementierung für die Fälle, in denen das Optimierungsprogramm nicht zu verwenden, um die intrinsische Version entscheidet.

Der herausragende Nachteil ist, dass Sie die gesamte Programmoptimierung (/ GL und/LTCG) deaktivieren müssen. Ich bin mir nicht sicher warum. Wenn jemand einen Weg findet, dies zu tun, ohne die globale Optimierung zu deaktivieren, bitte klingeln.

+0

Was sind all diese Güsse dort tun? Zeigerkonvertierungen zu und von 'void *' sind normalerweise 'static_cast'-s, nicht' reininterpret_cast'-s. – AnT

+0

@AndreyT: Ich habe den Cast von 'void *' geändert, um einen 'static_cast' zu verwenden. Zu der Zeit, als ich das ursprünglich geschrieben habe, war die Frage, welche Besetzung in dieser Situation zu verwenden war, unklar und heiß debattiert. (http://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever) Ich bin mir nicht sicher, was du meinst "alle" diese Fälle. Es gibt zwei. Die erste ist notwendig, weil Sie nicht über einen Zeiger auf void schreiben können (was "memset" benötigt). Die zweite ist so, dass der Compiler nicht davor warnt, einem unsignierten Zeichen einen Int zuzuordnen. –

+1

Sie können die Deaktivierung der Ganzprogrammoptimierung nur auf die Intrinsics beschränken, indem Sie diese Intrins in eine separate statische Bibliothek kompilieren. –

5
  1. Ich bin ziemlich sicher, dass ein Compiler-Flag gibt es die VC erzählt ++ nicht intrinsics wird mit dem Compiler installiert

  2. Die Quelle zu der Laufzeitbibliothek zu verwenden, . Sie haben die Wahl von Auszugsfunktionen, die Sie benötigen/brauchen, obwohl Sie sie oft umfangreich modifizieren müssen (weil sie Funktionen und/oder Abhängigkeiten enthalten, die Sie nicht brauchen/brauchen).

  3. Es sind auch andere Open-Source-Laufzeitbibliotheken verfügbar, die möglicherweise weniger angepasst werden müssen.

  4. Wenn Sie das wirklich ernst meinen, müssen Sie die Assemblersprache kennen (und vielleicht auch verwenden).

Edited hinzufügen:

Ich habe Ihren neuen Testcode und Link zu kompilieren.Dies sind die gewünschten Einstellungen vor:

Enable Intrinsic Functions: No 
Whole Program Optimization: No 

Es ist die letzte, die „Compiler Helfer“, wie die eingebaute in Memset unterdrückt.

Edited hinzufügen:

Jetzt, wo es entkoppelt ist, können Sie die asm-Code aus memset.asm in Ihr Programm kopieren - es eine globale Referenz hat, aber Sie können das entfernen. Es ist groß genug, so dass es nicht inline ist, obwohl, wenn Sie alle Tricks entfernen, die es verwendet, um Geschwindigkeit zu gewinnen, könnten Sie in der Lage sein, es klein genug dafür zu machen.

Ich habe Ihr obiges Beispiel und ersetzt die memset() mit diesem:

void * __cdecl memset(void *pTarget, char value, size_t cbTarget) { 
    _asm { 
    push ecx 
    push edi 

    mov al, value 
    mov ecx, cbTarget 
    mov edi, pTarget 
    rep stosb 

    pop edi 
    pop ecx 
    } 
    return pTarget; 
} 

Es funktioniert, aber die Version der Bibliothek ist viel schneller.

+0

Aber das funktioniert gegen das ultimative Ziel zu versuchen, die kleinste mögliche binäre zu machen. In vielen Fällen, einschließlich "memset", ist die eingebaute intrinsische Funktion kleiner als der Funktionsaufruf. –

+0

Die lib-Version ist schneller, nur weil sie den Zielzeiger auf 4 Bytes (in 32-Bit-Maschinen, 8 Bytes in 64 Bits) ausrichtet und rep stosd anstelle von rep stosb verwendet und die nicht ausgerichteten Bytes am Anfang und am Ende separat schreibt. Dadurch würde memset noch größer werden. Wiederum (wie ich in den Kommentaren zu meiner Antwort gesagt habe) glaube ich nicht, dass dein Compiler wirklich das intrinsische erzeugt. Egrunins Implementierung ist so klein wie möglich. In bestimmten Fällen könnte der Intrinsic die Pushs/​​Pops schonen, wenn ecx & edi verfügbar sind. Würdest du einen Nettogewinn haben? Selten, schätze ich. –

+0

Der Code in egrunin der zweiten Bearbeitung ist im Wesentlichen der gleiche wie der Code, der vom Compiler generiert wird, wenn er das intrinsische verwendet. Der Compiler kann oft ein paar Bytes speichern, wenn er weiß, dass er ecx und edi nicht beibehalten muss. Die Bibliotheksversion zahlt sich aus, wenn die Anzahl der zu löschenden Bytes größer wird. Es gibt einen Overhead im Umgang mit dem möglicherweise nicht ausgerichteten Anfang und Ende. –

1

Ich denke, Sie müssen die Optimierung auf "Minimize Size (/ O1)" oder "Disabled (/ Od)" setzen, um die Release-Konfiguration zu kompilieren; zumindest hat mir das mit VS 2005 geholfen. Intrinsics sind auf Geschwindigkeit ausgelegt, daher ist es sinnvoll, sie für die anderen Optimierungsstufen (Speed ​​und Full) zu aktivieren.

+0

Ich habe bereits/O1, und/Od irgendwie besiegt das Ziel, die kleinste mögliche binäre. Geschwindigkeit ist auch ein Problem. –

+0

Nun, ich habe VS2008 nicht vor mir, vielleicht haben sie etwas verändert. In VS2005 war dies die einzige Änderung, die ich vornehmen musste, um es erfolgreich zu bauen. – Luke

0

Benennen Sie die Funktion etwas anders.

+0

Gute Idee, aber es funktioniert nicht. Ich habe meine eigene Version namens 'ClearMemory()' geschrieben, die einen Namespace verwendet, um sicherzustellen, dass es mit nichts anderem kollidiert. Der Optimierer ersetzte meine Implementierung von 'ClearMemory()' durch einen Aufruf von 'memset()' (mit einem Byte-Wert von 0)! Zu schlau für sein eigenes Wohl. :-) –

+1

Das funktioniert auch nicht, wenn der Compiler "memset" an erster Stelle verwendet (wie in einem Klasseninitialisierer). –

+0

In dem speziellen Fall, in dem Sie Nullen schreiben möchten, scheint die SecureZeroMemory-Funktion zu funktionieren. (Es ist als eine erzwungene Inline-Funktion implementiert in Winnt.h implementiert.) –

-1

Der Weg zur "normalen" Laufzeitbibliothek besteht darin, eine Assemblydatei mit einer Definition von memset zu kompilieren und in die Laufzeitbibliothek zu verknüpfen (Sie finden die Assemblydatei in oder um C: \ Programme \ Microsoft Visual Studio 10.0 \ VC \ crt \ src \ intel \ memset.asm). So etwas funktioniert auch bei der Optimierung ganzer Programme.

Beachten Sie auch, dass der Compiler nur in bestimmten Fällen intrinsisch das memset verwenden wird (wenn die Größe konstant und klein ist?). Es wird normalerweise die von Ihnen zur Verfügung gestellte memset-Funktion verwenden, daher sollten Sie wahrscheinlich die optimierte Funktion in memset.asm verwenden, es sei denn, Sie werden etwas genauso optimiertes schreiben.

0

Dies funktioniert definitiv mit VS 2015: Fügen Sie die Befehlszeilenoption/Oi- hinzu. Dies funktioniert, weil "Nein" in Intrinsic-Funktionen kein Schalter ist, es ist nicht spezifiziert./ Oi- und alle deine Probleme gehen weg (es sollte mit der ganzen Programmoptimierung funktionieren, aber ich habe das nicht richtig getestet).

+1

Von MSDN: "/ Oi ist nur eine Anforderung an den Compiler, einige Funktionsaufrufe durch intrinsics zu ersetzen; der Compiler kann die Funktion aufrufen (und den Funktionsaufruf nicht durch einen intrinsischen ersetzen), wenn dies zu einer besseren Leistung führt." So könnte es oder könnte nicht in allen Fällen funktionieren. –