Leistung von vorzeichenlosen und vorzeichenbehafteten Ganzzahlen


76

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?


8
Nicht genug, dass Sie sich darum kümmern müssen.
JeremyP

17
@ JeremyP, könnte ich vorschlagen, dass Sie die Wahrheit nur für die Mehrheit der Entwickler und Anwendungen gesprochen haben ....
Brett

1
@Brett: Der Unterschied zwischen vorzeichenbehafteter und vorzeichenloser Arithmetik ist bei den meisten CPUs Null. Der Unterschied für verschiedene Größen ist gering, es sei denn, Sie rechnen viel.
JeremyP

Antworten:


106

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 xTeil (signierte Abteilung):

movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax

Und hier ist der relevante yTeil (vorzeichenlose Unterteilung):

movl 12(%ebp), %edx
shrl $3, %edx

11
Dies funktioniert nur, wenn der Divisor eine bekannte Konstante zur Erfüllungszeit ist, die eine Zweierpotenz ist, nicht wahr?
Scharfzahn

1
@ Sharptooth, für die Teilung, ja. Es gibt wahrscheinlich andere Tricks zur Bitmanipulation, die nur für unsignierte Tricks gültig sind. Oder unterschrieben. Ich denke nicht, dass der positive Effekt nur in eine Richtung geht.
AProgrammer

Warum kann der Trick für nicht konstante Teiler nicht ausgeführt werden? Der erste Operand von x86 shrlsollte ein Literal sein?
Manu343726

@ Manu343726 Was ist, wenn der Divisor keine Zweierpotenz ist? (Und selbst wenn es so wäre, müssten Sie zuerst den binären Logarithmus der Zahl berechnen, bevor Sie verschieben.)
Fredoverflow

1
In dieser Größenordnung bedeuten mehr Anweisungen für moderne Pipeline-CPU-Architekturen nicht immer eine langsamere Laufzeit . Das heißt, ich würde noch eine Messung durchführen, bevor ich zu weitreichenden Schlussfolgerungen komme.
Ulidtko

49

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.


19

unsignedführt zu gleicher oder besserer Leistung als signed. Einige Beispiele:

  • Division durch eine Konstante, die eine Potenz von 2 ist (siehe auch die Antwort von FredOverflow )
  • Division durch eine konstante Zahl (zum Beispiel implementiert mein Compiler die Division durch 13 unter Verwendung von 2 asm-Anweisungen für nicht signierte und 6 Anweisungen für signierte)
  • Überprüfen, ob eine Zahl gerade ist (ich habe keine Ahnung, warum mein MS Visual Studio-Compiler sie mit 4 Anweisungen für signedZahlen implementiert ; gcc macht es mit 1 Anweisung, genau wie im unsignedFall).

shortfü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 intnie short) zuweisen . Alle Konvertierungen von nehmen Sie sich Zeit und sind ärgerlich.shortintshortint

Hinweis: Einige DSPs verfügen über schnelle Multiplikationsanweisungen für den signed shortTyp. in diesem speziellen Fall shortist schneller als int.

Was den Unterschied zwischen intund longbetrifft, kann ich nur raten (ich bin nicht mit 64-Bit-Architekturen vertraut). Wenn intund longhaben 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 ( shortmö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.


8
Ich würde mit der Aussage über die Leistung von Short im Vergleich zu Int vorsichtig sein. Während die Arithmetik mit int "möglicherweise" schneller ist, sollte man bedenken, dass die Ganzzahlarithmetik selten ein Engpass ist (zumindest bei modernen Desktop-CPUs), die Speicherbandbreite dagegen häufig, sodass bei großen Datenmengen kurze Daten tatsächlich eine erheblich bessere Leistung bieten können int. Darüber hinaus bedeutet die Verwendung kleinerer Datentypen für autovektorisierten Code häufig, dass mehr Datenelemente gleichzeitig verarbeitet werden können, sodass sich sogar die Rechenleistung erhöhen kann (obwohl dies angesichts des aktuellen Status der Autovektorisierer unwahrscheinlich ist).
Grizzly

1
@Grizzly Ich stimme zu (meine App ist tatsächlich rechenintensiv, daher unterscheidet sich meine Erfahrung mit der von shortIhnen / anderen)
Anatolyg

2
@martinkunev Auf jeden Fall! Dies ist möglicherweise der einzige Grund für die shortheutige Verwendung (wobei der Nicht-Cache-RAM praktisch unendlich ist) und ein sehr guter Grund.
Anatolyg

1
@anatolyg RAM ist zwar praktisch unendlich, aber vergessen Sie nicht, dass 32-Bit-Programme immer noch die 64-Bit-Programme um ein Vielfaches übersteigen. Unabhängig davon, wie viel RAM verfügbar ist, sind Sie häufig auf 2 GB verwendbare Adresse beschränkt -Raum.
bcrist

1
@ JoshParnell Ich denke du meinst shortist schneller als intwenn Speicher gebunden . Nach meiner Erfahrung haben sie auf x86 die gleiche Leistung und shortsind auf ARM langsamer.
Anatolyg

17

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.


20
+1für "wenn du wissen willst, musst du messen". Es ist sehr ärgerlich, dass dies fast wöchentlich beantwortet werden muss.
sbi

9

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.


7

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.


4

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


3

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 -Ofastoder zumindest eines -O3Flags zu maximieren, kann er meiner Erfahrung nach longTyp in Code kompilieren, der eine noch bessere Leistung aufweist als jeder andere unsignedTyp 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]+=bund a[x]=bdie Programmausführungszeit fast zweimal geändert wurde. Und nein, a[x]=bwar 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.


1

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).


1
Beachten Sie auch, dass bestimmte Compiler möglicherweise Optimierungen aufweisen, die nicht für alle Ganzzahltypen gelten. Zum Beispiel konnten zumindest alte Intel-Compiler keine Autovektorisierung anwenden, wenn der For-Loop-Zähler etwas anderes als ein signiertes Int war.
CAFxX


@ LưuVĩnhPhúc sprechen Sie über signierten Überlauf als UB? Wenn ja, ist mir nur der Fall bekannt, in dem es für die Optimierung von Compilern schwieriger ist, über vorzeichenlose Interger nachzudenken, die als Schleifenzähler / Induktionsvariablen verwendet werden (und dies wurde durch meinen Kommentar unmittelbar über
Ihrem behandelt

Nein, es gibt verschiedene andere Fälle, in denen die Signifikanz eine Rolle spielt. Hast du die anderen Antworten gelesen?
Phuclv

Ich tat. Hast du? Die meisten von ihnen sagen, dass es keine großen Unterschiede gibt, außer für Divisionen mit konstanter Kompilierungszeit und für Schleifeninduktionsvariablen (die ich in meinem Kommentar erwähnt habe). Auch in Sie Ihnen ein bisschen darauf hinweisen, dass der Unterschied in neueren Prozessoren ist nicht sehr groß (überprüfen zB die Sandy - Bridge - Tabellen)
CAFxX

1

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 .


0

Traditionell intist das native Ganzzahlformat der Zielhardwareplattform. Jeder andere ganzzahlige Typ kann zu Leistungseinbußen führen.

BEARBEITEN:

Bei modernen Systemen sieht das etwas anders aus:

  • intkann 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 intin einigen Fällen implizit verwendet werden, wenn Berechnungen für kürzere Typen durchgeführt werden.


ja, traditionell ;-) ist auf aktuellen 64-Bit-Systemen intimmer noch 32 Bit breit, aber 64-Bit-Typen ( longoder long long, je nach Betriebssystem) sollten mindestens genauso schnell sein.
Philipp

1
intist 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 longTyp, der anders ist: 32 Bit unter Windows, aber ein Wort unter Linux und OS X.
Philipp

@Philipp muss aber intnicht immer 32 Bit breit sein.
mercury0114

0

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.