Ich verwende einen int
Typ, um einen Wert zu speichern. Aufgrund der Semantik des Programms variiert der Wert immer in einem sehr kleinen Bereich (0 - 36) und wird int
(nicht a char
) nur wegen der CPU-Effizienz verwendet.
Es scheint, dass viele spezielle arithmetische Optimierungen für einen so kleinen Bereich von ganzen Zahlen durchgeführt werden können. Viele Funktionsaufrufe für diese Ganzzahlen können in kleinen "magischen" Operationen optimiert werden, und einige Funktionen können sogar in Tabellensuchen optimiert werden.
Kann man dem Compiler also mitteilen, dass dies int
immer in diesem kleinen Bereich liegt, und kann der Compiler diese Optimierungen vornehmen?
unsigned
Typen haben, da diese für den Compiler einfacher zu verstehen sind.var value: 0..36;
.int
undunsigned int
muss auf den meisten Systemen mit 64-Bit-Zeigern auch von 32 auf 64-Bit vorzeichen- oder null-erweitert werden. Beachten Sie, dass auf x86-64 Operationen an 32-Bit-Registern kostenlos auf 64-Bit null erweitert werden (keine Vorzeichenerweiterung, aber ein vorzeichenbehafteter Überlauf ist ein undefiniertes Verhalten, sodass der Compiler nur 64-Bit-vorzeichenbehaftete Mathematik verwenden kann, wenn er dies wünscht). Sie sehen also nur zusätzliche Anweisungen zum Null-Erweitern von 32-Bit-Funktionsargumenten, keine Berechnungsergebnisse. Sie würden für engere vorzeichenlose Typen.Antworten:
Ja, es ist möglich. Zum Beispiel können
gcc
Sie damit__builtin_unreachable
den Compiler über unmögliche Bedingungen informieren, wie zum Beispiel:Wir können die obige Bedingung in ein Makro einschließen:
Und benutze es so:
Wie Sie sehen können , werden
gcc
Optimierungen basierend auf diesen Informationen durchgeführt:Produziert:
Ein Nachteil ist jedoch, dass Sie undefiniertes Verhalten erhalten , wenn Ihr Code jemals solche Annahmen verletzt .
Es benachrichtigt Sie nicht, wenn dies geschieht, selbst bei Debug-Builds. Um Fehler mit Annahmen einfacher zu debuggen / testen / abzufangen, können Sie ein hybrides Annahme- / Assert-Makro (Credits für @David Z) wie das folgende verwenden:
In Debug-Builds (mit
NDEBUG
nicht definiert) funktioniert es wie ein gewöhnlichesassert
Druckfehler- undabort
Programmierprogramm, und in Release-Builds wird eine Annahme verwendet, die optimierten Code erzeugt.Beachten Sie jedoch, dass es kein Ersatz für reguläres ist
assert
-cond
verbleibt in Release-Builds, daher sollten Sie so etwas nicht tunassume(VeryExpensiveComputation())
.quelle
return 2
Zweig vom Compiler aus dem Code entfernt wurde.__builtin_expect
ist ein nicht strenger Hinweis.__builtin_expect(e, c)
sollte lauten als "e
wird am wahrscheinlichsten ausgewertet werdenc
" und kann nützlich sein, um die Verzweigungsvorhersage zu optimieren, aber es beschränkt sich nicht daraufe
, immer zu seinc
, sodass der Optimierer andere Fälle nicht wegwerfen kann. Sehen Sie, wie die Zweige in der Baugruppe organisiert sind .__builtin_unreachable()
.assert
, z. B. zu definierenassume
,assert
wannNDEBUG
nicht definiert ist und__builtin_unreachable()
wannNDEBUG
definiert ist. Auf diese Weise profitieren Sie von der Annahme im Produktionscode, aber in einem Debug-Build haben Sie immer noch eine explizite Prüfung. Natürlich muss man dann genug Tests durchführen , um sich zu versichern , dass die Annahme wird in der freien Natur erfüllt werden.Hierfür gibt es Standardunterstützung. Was Sie tun sollten, ist, include
stdint.h
(cstdint
) einzuschließen und dann den Typ zu verwendenuint_fast8_t
.Dies teilt dem Compiler mit, dass Sie nur Zahlen zwischen 0 und 255 verwenden, dass es jedoch kostenlos ist, einen größeren Typ zu verwenden, wenn dies schnelleren Code ergibt. Ebenso kann der Compiler davon ausgehen, dass die Variable niemals einen Wert über 255 haben wird, und dann entsprechende Optimierungen vornehmen.
quelle
uint_fast8_t
es sich tatsächlich um einen 8-Bit-Typ handelt (z. B.unsigned char
), wie dies bei x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ) der Fall ist . In der frühen DEC Alpha vor 21164A wurden Byte-Ladevorgänge / -Speicher nicht unterstützt, sodass jede vernünftige Implementierung verwendet werden würdetypedef uint32_t uint_fast8_t
. AFAIK, es gibt keinen Mechanismus für einen Typ, der bei den meisten Compilern (wie gcc) zusätzliche Bereichsbeschränkungen hat, daher bin ich mir ziemlich sicher,uint_fast8_t
dass er sichunsigned int
in diesem Fall genauso oder wie auch immer verhalten würde .bool
ist etwas Besonderes und auf 0 oder 1 beschränkt, aber es ist ein eingebauter Typ, der nicht durch Header-Dateien in Bezugchar
auf gcc / clang definiert wird. Wie gesagt, ich glaube nicht, dass die meisten Compiler einen Mechanismus haben das würde das möglich machen.)uint_fast8_t
ist dies eine gute Empfehlung, da auf Plattformen, auf denen dies genauso effizient ist wie 8-Bit, ein 8-Bit-Typ verwendet wirdunsigned int
. (Ich bin wirklich nicht sicher , was diefast
Typen sollen schnell sein für und ob der Cache - Fußabdruck tradeoff soll ein Teil davon sein.). x86 bietet umfassende Unterstützung für Byte-Operationen, selbst für das Hinzufügen von Bytes mit einer Speicherquelle, sodass Sie nicht einmal eine separate Last ohne Erweiterung ausführen müssen (was ebenfalls sehr billig ist). gcc machtuint_fast16_t
einen 64-Bit-Typ auf x86, was für die meisten Anwendungen verrückt ist (im Vergleich zu 32-Bit). godbolt.org/g/Rmq5bv .Die aktuelle Antwort ist gut für den Fall, dass Sie sicher wissen, um welchen Bereich es sich handelt. Wenn Sie jedoch weiterhin ein korrektes Verhalten wünschen, wenn der Wert außerhalb des erwarteten Bereichs liegt, funktioniert dies nicht.
In diesem Fall habe ich festgestellt, dass diese Technik funktionieren kann:
Die Idee ist , ein Code-Daten Kompromiss: Sie verschieben 1 Bit von Daten (ob
x == c
) in der Steuerlogik .Dies deutet auf den Optimierer hin, der
x
tatsächlich eine bekannte Konstante istc
, und ermutigt ihn, den ersten Aufruf vonfoo
getrennt vom Rest zu integrieren und zu optimieren , möglicherweise ziemlich stark.Stellen Sie jedoch sicher, dass der Code tatsächlich in einer einzigen Unterroutine berücksichtigt wird
foo
- duplizieren Sie den Code nicht.Beispiel:
Damit diese Technik funktioniert, müssen Sie ein wenig Glück haben - es gibt Fälle, in denen der Compiler beschließt, die Dinge nicht statisch zu bewerten, und sie sind willkürlich. Aber wenn es funktioniert, funktioniert es gut:
Verwenden Sie einfach
-O3
und beachten Sie die vorge ausgewertet Konstanten0x20
und0x30e
in Assembler ausgegeben .quelle
if (x==c) foo(c) else foo(x)
? Wenn nur umconstexpr
Implementierungen von zu fangenfoo
?constexpr
schon einmal ausgedacht und habe mich nie darum gekümmert, sie später zu "aktualisieren" (obwohl ich mirconstexpr
auch danach keine Sorgen gemacht habe ), aber der Grund, warum ich sie anfangs nicht gemacht habe, war, dass ich es wollte Erleichtern Sie dem Compiler das Herausfiltern als allgemeinen Code und das Entfernen des Zweigs, wenn er sie als normale Methodenaufrufe belassen und nicht optimieren möchte. Ich habe erwartet, dassc
es für den Compiler wirklich schwierig ist, c (sorry, schlechter Witz) zu erkennen, dass die beiden der gleiche Code sind, obwohl ich dies nie überprüft habe.Ich möchte nur sagen, dass Sie, wenn Sie eine Lösung mit mehr Standard-C ++ wünschen, das
[[noreturn]]
Attribut verwenden können, um Ihre eigene zu schreibenunreachable
.Also werde ich das hervorragende Beispiel von Deniss erneut verwenden , um zu demonstrieren:
Was, wie Sie sehen können , zu nahezu identischem Code führt:
Der Nachteil ist natürlich, dass Sie eine Warnung erhalten, dass eine
[[noreturn]]
Funktion tatsächlich zurückkehrt.quelle
clang
, wenn meine ursprüngliche Lösung nicht funktioniert , so schöner Trick und +1. Aber das Ganze ist sehr compilerabhängig (wie Peter Cordes uns gezeigt hat,icc
kann es die Leistung verschlechtern ), so dass es immer noch nicht universell anwendbar ist. Außerdem ein kleiner Hinweis: Dieunreachable
Definition muss für den Optimierer verfügbar und inline sein, damit dies funktioniert .