Im CUDA C Best Practices Guide gibt es einen kleinen Abschnitt über die Verwendung von Ganzzahlen mit und ohne Vorzeichen.
Im C-Sprachstandard sind vorzeichenlose Integer-Überlaufsemantiken wohldefiniert, während vorzeichenbehafteter Integer-Überlauf zu undefinierten Ergebnissen führt. Daher kann der Compiler mit Vorzeichenarithmetik aggressiver optimieren als mit vorzeichenloser Arithmetik. Dies ist besonders bei Schleifenzählern zu beachten: Da Schleifenzähler häufig Werte annehmen, die immer positiv sind, könnte es verlockend sein, die Zähler als vorzeichenlos zu deklarieren. Für eine etwas bessere Leistung sollten sie jedoch stattdessen als signiert deklariert werden.
Betrachten Sie zum Beispiel den folgenden Code:
%Vor%Hier könnte der Unterausdruck
stride*i
eine 32-Bit-Ganzzahl überlaufen. Wenn i als vorzeichenlos deklariert wird, verhindert die Überlaufsemantik, dass der Compiler einige Optimierungen verwendet, die ansonsten angewendet worden wären, wie z. Wenn stattdessen i als signed deklariert wird, wo die Überlaufsemantik nicht definiert ist, hat der Compiler mehr Spielraum, um diese Optimierungen zu verwenden.
Die ersten beiden Sätze verwirren mich besonders. Wenn die Semantik nicht vorzeichenbehafteter Werte wohldefiniert ist und vorzeichenbehaftete Werte zu undefinierten Ergebnissen führen können, wie kann dann der Compiler besseren Code für diesen erzeugen?
Der Text zeigt dieses Beispiel:
%Vor%Es erwähnt auch "Stärkeabbau". Der Compiler darf dies durch den folgenden "pseudo-optimized-C" -Code ersetzen:
%Vor% Stellen Sie sich nun einen Prozessor vor, der nur Gleitkommazahlen (und Ganzzahlen als Teilmenge) unterstützt. tmp
würde vom Typ "sehr große Zahl" sein.
Nun sagt der C-Standard, dass Berechnungen mit unsignierten Operanden niemals überlaufen können, sondern modulo um den größten Wert + 1 reduziert werden. Das bedeutet, dass im Fall von unsigned i
der Compiler dies tun muss:
Aber im Fall von vorzeichenbehafteten Ganzzahlen kann der Compiler tun, was er will. Es muss nicht nach einem Überlauf gesucht werden - wenn es überläuft, ist es das Problem des Entwicklers (es könnte eine Ausnahme verursachen oder fehlerhafte Werte erzeugen). So kann der Code schneller sein.
Es ist, weil die Definition von C begrenzt, was der Compiler-Schreiber im Fall der vorzeichenlosen Ganzzahlen tun kann. Es gibt mehr Spielraum, um herumzualbern mit dem, was passiert, wenn vorzeichenbehaftete Ganzzahlen überlaufen. Die Autoren des Compilers haben sozusagen mehr Bewegungsspielraum.
So lese ich es.
Der Unterschied zwischen der Semantik von signed
und unsigned
wird für die Leistung auf Prozessoren relevant, die nicht alle von C definierten Wortgrößen unterstützen. Nehmen wir beispielsweise an, Sie haben eine CPU, die nur 32-Bit-Operationen unterstützt und hat 32-Bit-Register, und Sie schreiben eine C-Funktion, die sowohl int
(32-Bit) als auch char
(8-Bit *) verwendet:
Da die CPU char
nur in 32-Bit-Registern speichern und nur 32-Bit-Werte arithmetisch ausführen kann, verwendet sie ein 32-Bit-Register, um b zu halten, und eine 32-Bit-Multiplikation.
Da der C-Standard angibt, dass vorzeichenbehafteter Integerüberlauf nicht definierte Ergebnisse verursacht, kann der Compiler Code für die obige Funktion erstellen, der einen höheren Wert als 127 zurückgibt, wenn a
größer als 2 ist.
Wenn jedoch vorzeichenlose Werte verwendet werden:
%Vor% Der C-Standard definiert die Überlaufsemantik für vorzeichenlose Operationen. Daher muss der Compiler eine Maskierungsoperation hinzufügen, um sicherzustellen, dass die Funktion keine Werte höher als 255 zurückgibt, auch wenn a
größer als 2 ist.
* Die C-Spezifikation lässt char
auf mehr als 8 Bits zu, aber das würde viele Programme sprengen. Daher nehmen wir einen Compiler an, der in diesem Beispiel 8-Bit-Werte für char
verwendet.