Gibt es einen Leistungsgewinn / -verlust durch die Verwendung von Ganzzahlen ohne Vorzeichen gegenüber Ganzzahlen mit Vorzeichen?
Wenn ja, geht das auch kurz und lang?
Gibt es einen Leistungsgewinn / -verlust durch die Verwendung von Ganzzahlen ohne Vorzeichen gegenüber Ganzzahlen mit Vorzeichen?
Wenn ja, geht das auch kurz und lang?
Antworten:
Die Division durch Potenzen von 2 ist mit schneller unsigned int
, da sie in einen einzelnen Schichtbefehl optimiert werden kann. Mit signed int
, bedarf es in der Regel mehr Maschinenbefehle, weil Division Runden gegen Null , sondern auf die richtigen Runden Verschiebung nach unten . Beispiel:
int foo(int x, unsigned y)
{
x /= 8;
y /= 8;
return x + y;
}
Hier ist der relevante x
Teil (signierte Abteilung):
movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax
Und hier ist der relevante y
Teil (vorzeichenlose Unterteilung):
movl 12(%ebp), %edx
shrl $3, %edx
shrl
sollte ein Literal sein?
In C ++ (und C) ist der vorzeichenbehaftete Ganzzahlüberlauf undefiniert, während der vorzeichenlose Ganzzahlüberlauf zum Umschließen definiert ist. Beachten Sie, dass Sie z. B. in gcc das Flag -fwrapv verwenden können, um einen signierten Überlauf zu definieren (um ihn zu umbrechen).
Durch einen undefinierten Überlauf mit vorzeichenbehafteten Ganzzahlen kann der Compiler davon ausgehen, dass keine Überläufe auftreten, was zu Optimierungsmöglichkeiten führen kann. Siehe zB diesen Blog-Beitrag zur Diskussion.
unsigned
führt zu gleicher oder besserer Leistung als signed
. Einige Beispiele:
signed
Zahlen implementiert ; gcc macht es mit 1 Anweisung, genau wie im unsigned
Fall).short
führt normalerweise zu der gleichen oder schlechteren Leistung als int
(unter der Annahme sizeof(short) < sizeof(int)
). Eine Leistungsverschlechterung tritt auf, wenn Sie einer Variablen vom Typ , die im Register des Prozessors gespeichert ist (das ebenfalls vom Typ ist ) , ein Ergebnis einer arithmetischen Operation (normalerweise int
nie short
) zuweisen . Alle Konvertierungen von nehmen Sie sich Zeit und sind ärgerlich.short
int
short
int
Hinweis: Einige DSPs verfügen über schnelle Multiplikationsanweisungen für den signed short
Typ. in diesem speziellen Fall short
ist schneller als int
.
Was den Unterschied zwischen int
und long
betrifft, kann ich nur raten (ich bin nicht mit 64-Bit-Architekturen vertraut). Wenn int
und long
haben dieselbe Größe (auf 32-Bit-Plattformen), ist natürlich auch ihre Leistung gleich.
Eine sehr wichtige Ergänzung, auf die mehrere Personen hingewiesen haben:
Was für die meisten Anwendungen wirklich wichtig ist, ist der Speicherbedarf und die genutzte Bandbreite. Sie sollten die kleinsten erforderlichen Ganzzahlen ( short
möglicherweise sogar signed/unsigned char
) für große Arrays verwenden.
Dies führt zu einer besseren Leistung, aber die Verstärkung ist nichtlinear (dh nicht um den Faktor 2 oder 4) und etwas unvorhersehbar - dies hängt von der Cache-Größe und der Beziehung zwischen Berechnungen und Speicherübertragungen in Ihrer Anwendung ab.
short
Ihnen / anderen)
short
heutige Verwendung (wobei der Nicht-Cache-RAM praktisch unendlich ist) und ein sehr guter Grund.
short
ist schneller als int
wenn Speicher gebunden . Nach meiner Erfahrung haben sie auf x86 die gleiche Leistung und short
sind auf ARM langsamer.
Dies hängt von der genauen Implementierung ab. In den meisten Fällen gibt es jedoch keinen Unterschied. Wenn Sie sich wirklich darum kümmern, müssen Sie alle Varianten ausprobieren, die Sie in Betracht ziehen, und die Leistung messen.
+1
für "wenn du wissen willst, musst du messen". Es ist sehr ärgerlich, dass dies fast wöchentlich beantwortet werden muss.
Dies hängt ziemlich stark vom jeweiligen Prozessor ab.
Auf den meisten Prozessoren gibt es Anweisungen sowohl für vorzeichenbehaftete als auch für vorzeichenlose Arithmetik. Der Unterschied zwischen der Verwendung von vorzeichenbehafteten und vorzeichenlosen Ganzzahlen hängt also davon ab, welche vom Compiler verwendet wird.
Wenn einer der beiden schneller ist, ist er vollständig prozessorspezifisch, und höchstwahrscheinlich ist der Unterschied winzig, wenn überhaupt vorhanden.
Der Leistungsunterschied zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlen ist tatsächlich allgemeiner als die Akzeptanzantwort vermuten lässt. Die Division einer vorzeichenlosen Ganzzahl durch eine Konstante kann schneller erfolgen als die Division einer vorzeichenbehafteten Ganzzahl durch eine Konstante, unabhängig davon, ob die Konstante eine Zweierpotenz ist. Sehen http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
Am Ende seines Beitrags enthält er den folgenden Abschnitt:
Eine natürliche Frage ist, ob dieselbe Optimierung die vorzeichenbehaftete Teilung verbessern könnte. Leider scheint dies aus zwei Gründen nicht der Fall zu sein:
Die Erhöhung der Dividende muss zu einer Erhöhung der Höhe führen, dh zu einer Erhöhung, wenn n> 0 ist, zu einer Verringerung, wenn n <0. Dies führt zu einem zusätzlichen Aufwand.
Die Strafe für einen nicht kooperativen Teiler ist in der signierten Teilung nur etwa halb so hoch, so dass ein kleineres Fenster für Verbesserungen übrig bleibt.
Es scheint also, dass der Rundungsalgorithmus in vorzeichenbehafteter Division arbeiten könnte, aber den Standard-Rundungsalgorithmus nicht erfüllt.
Nicht nur die Division durch Zweierpotenzen ist beim Typ ohne Vorzeichen schneller, auch die Division durch andere Werte ist beim Typ ohne Vorzeichen schneller. Wenn Sie sich die Anweisungstabellen von Agner Fog ansehen, werden Sie feststellen, dass nicht signierte Abteilungen eine ähnliche oder bessere Leistung aufweisen als signierte Versionen
Zum Beispiel mit dem AMD K7
╔═════════════╤══════════╤═════╤═════════╤═══════════════════════╗
║ Instruction │ Operands │ Ops │ Latency │ Reciprocal throughput ║
╠═════════════╪══════════╪═════╪═════════╪═══════════════════════╣
║ DIV │ r8/m8 │ 32 │ 24 │ 23 ║
║ DIV │ r16/m16 │ 47 │ 24 │ 23 ║
║ DIV │ r32/m32 │ 79 │ 40 │ 40 ║
║ IDIV │ r8 │ 41 │ 17 │ 17 ║
║ IDIV │ r16 │ 56 │ 25 │ 25 ║
║ IDIV │ r32 │ 88 │ 41 │ 41 ║
║ IDIV │ m8 │ 42 │ 17 │ 17 ║
║ IDIV │ m16 │ 57 │ 25 │ 25 ║
║ IDIV │ m32 │ 89 │ 41 │ 41 ║
╚═════════════╧══════════╧═════╧═════════╧═══════════════════════╝
Gleiches gilt für Intel Pentium
╔═════════════╤══════════╤══════════════╗
║ Instruction │ Operands │ Clock cycles ║
╠═════════════╪══════════╪══════════════╣
║ DIV │ r8/m8 │ 17 ║
║ DIV │ r16/m16 │ 25 ║
║ DIV │ r32/m32 │ 41 ║
║ IDIV │ r8/m8 │ 22 ║
║ IDIV │ r16/m16 │ 30 ║
║ IDIV │ r32/m32 │ 46 ║
╚═════════════╧══════════╧══════════════╝
Natürlich sind diese ziemlich alt. Neuere Architekturen mit mehr Transistoren schließen möglicherweise die Lücke, aber die grundlegenden Dinge gelten: Im Allgemeinen benötigen Sie mehr Makrooperationen, mehr Logik und mehr Latenz, um eine signierte Division durchzuführen
Kurz gesagt, stören Sie sich nicht vor der Tatsache. Aber mach dir danach die Mühe.
Wenn Sie Leistung erzielen möchten, müssen Sie Leistungsoptimierungen eines Compilers verwenden, die möglicherweise gegen den gesunden Menschenverstand arbeiten. Beachten Sie, dass verschiedene Compiler Code unterschiedlich kompilieren können und selbst unterschiedliche Optimierungen aufweisen. Wenn es sich um einen g++
Compiler handelt und es darum geht, seine Optimierungsstufe durch Verwendung -Ofast
oder zumindest eines -O3
Flags zu maximieren, kann er meiner Erfahrung nach long
Typ in Code kompilieren, der eine noch bessere Leistung aufweist als jeder andere unsigned
Typ oder sogar nurint
.
Dies ist aus eigener Erfahrung und ich empfehle Ihnen, zuerst Ihr vollständiges Programm zu schreiben und sich erst danach um solche Dinge zu kümmern, wenn Sie Ihren eigentlichen Code zur Hand haben und ihn mit Optimierungen kompilieren können, um zu versuchen, die Typen auszuwählen, die tatsächlich funktionieren Beste. Dies ist auch ein guter, sehr allgemeiner Vorschlag zur Codeoptimierung für die Leistung. Schreiben Sie zuerst schnell, versuchen Sie, mit Optimierungen zu kompilieren, und optimieren Sie die Dinge, um herauszufinden, was am besten funktioniert. Sie sollten auch versuchen, verschiedene Compiler zu verwenden, um Ihr Programm zu kompilieren und den auszuwählen, der den leistungsstärksten Maschinencode ausgibt.
Ein optimiertes lineares Algebra-Berechnungsprogramm mit mehreren Threads kann leicht einen> 10- fachen Leistungsunterschied aufweisen, der fein optimiert und nicht optimiert ist. Das ist also wichtig.
Die Ausgabe des Optimierers widerspricht in vielen Fällen der Logik. Zum Beispiel hatte ich einen Fall, in dem ein Unterschied zwischen a[x]+=b
und a[x]=b
die Programmausführungszeit fast zweimal geändert wurde. Und nein, a[x]=b
war nicht der schnellere.
Hier ist zum Beispiel NVidia, die dies für die Programmierung ihrer GPUs angibt :
Hinweis: Wie bereits empfohlen, sollte vorzeichenbehaftete Arithmetik vorzugsweise vorzeichenloser Arithmetik vorgezogen werden, um den bestmöglichen Durchsatz bei SMM zu erzielen. Der C-Sprachstandard schränkt das Überlaufverhalten für vorzeichenlose Mathematik stärker ein und schränkt die Möglichkeiten zur Compileroptimierung ein.
IIRC auf x86 signiert / nicht signiert sollte keinen Unterschied machen. Short / Long ist dagegen eine andere Geschichte, da die Datenmenge, die in den / aus dem RAM verschoben werden muss, für Longs größer ist (andere Gründe können Cast-Vorgänge wie das Erweitern von Short zu Long sein).
Vorzeichenbehaftete und vorzeichenlose Ganzzahlen arbeiten immer beide als Einzeluhrbefehle und haben die gleiche Lese- / Schreibleistung. Laut Dr. Andrei Alexandrescu werden vorzeichenlose Ganzzahlen gegenüber vorzeichenbehafteten vorgezogen. Der Grund dafür ist, dass Sie die doppelte Anzahl von Zahlen in dieselbe Anzahl von Bits einpassen können, da Sie das Vorzeichenbit nicht verschwenden und weniger Anweisungen verwenden, um nach negativen Zahlen zu suchen, was zu Leistungssteigerungen aufgrund des verringerten ROM führt. Nach meiner Erfahrung mit der Kabuki-VM , die über eine Ultrahochleistungsimplementierung verfügt - Script, ist es selten, dass Sie beim Arbeiten mit Speicher tatsächlich eine signierte Nummer benötigen. Ich habe viele Jahre damit verbracht, Zeigerarithmetik mit vorzeichenbehafteten und vorzeichenlosen Zahlen durchzuführen, und ich habe keinen Vorteil für die vorzeichenbehafteten Zahlen gefunden, wenn kein Vorzeichenbit benötigt wird.
Vorzeichenbehaftet kann bevorzugt werden, wenn die Bitverschiebung zur Multiplikation und Division von Potenzen von 2 verwendet wird, da Sie negative Potenzen von 2 Division mit den Komplement-Ganzzahlen von vorzeichenbehafteten 2 durchführen können. Weitere Optimierungstechniken finden Sie in weiteren YouTube-Videos von Andrei . In meinem Artikel finden Sie auch einige gute Informationen zum weltweit schnellsten Integer-to-String-Konvertierungsalgorithmus .
Traditionell int
ist das native Ganzzahlformat der Zielhardwareplattform. Jeder andere ganzzahlige Typ kann zu Leistungseinbußen führen.
BEARBEITEN:
Bei modernen Systemen sieht das etwas anders aus:
int
kann auf 64-Bit-Systemen aus Kompatibilitätsgründen tatsächlich 32-Bit sein. Ich glaube, dass dies auf Windows-Systemen passiert.
Moderne Compiler können int
in einigen Fällen implizit verwendet werden, wenn Berechnungen für kürzere Typen durchgeführt werden.
int
immer noch 32 Bit breit, aber 64-Bit-Typen ( long
oder long long
, je nach Betriebssystem) sollten mindestens genauso schnell sein.
int
ist auf allen mir bekannten Systemen (Windows, Linux, Mac OS X, unabhängig davon, ob der Prozessor 64-Bit ist oder nicht) immer 32 Bit breit. Es ist der long
Typ, der anders ist: 32 Bit unter Windows, aber ein Wort unter Linux und OS X.
int
nicht immer 32 Bit breit sein.
Eine vorzeichenlose Ganzzahl ist insofern vorteilhaft, als Sie beide als Bitstrom speichern und behandeln. Ich meine nur Daten ohne Vorzeichen, sodass die Multiplikation und Teilung mit Bitverschiebungsoperationen einfacher (schneller) wird