Ich möchte nichts optimieren, ich schwöre, ich möchte diese Frage nur aus Neugier stellen. Ich weiß , dass auf den meist Hardware gibt es einen Montag Befehl von Bit-Verschiebung (zB shl
, shr
), die ein einziger Befehl ist. Aber spielt es eine Rolle (in Bezug auf Nanosekunden oder CPU-Takt), wie viele Bits Sie verschieben? Mit anderen Worten, ist eine der folgenden Funktionen auf einer CPU schneller?
x << 1;
und
x << 10;
Und bitte hasse mich nicht für diese Frage. :) :)
Antworten:
Hängt möglicherweise von der CPU ab.
Alle modernen CPUs (x86, ARM) verwenden jedoch einen "Barrel Shifter" - ein Hardwaremodul, das speziell für beliebige Verschiebungen in konstanter Zeit entwickelt wurde.
Das Endergebnis ist also ... nein. Kein Unterschied.
quelle
60000 mod register_size
. Beispielsweise verwendet ein 32-Bit-Prozessor nur die 5 niedrigstwertigen Bits der Verschiebungsanzahl.Einige eingebettete Prozessoren haben nur eine "Shift-by-One" -Anweisung. Auf solchen Prozessoren, würde der Compiler ändert
x << 3
in((x << 1) << 1) << 1
.Ich denke, das Motorola MC68HCxx war eine der beliebtesten Familien mit dieser Einschränkung. Glücklicherweise sind solche Architekturen mittlerweile recht selten, die meisten enthalten jetzt einen Barrel Shifter mit variabler Schaltgröße.
Der Intel 8051, der über viele moderne Derivate verfügt, kann auch keine beliebige Anzahl von Bits verschieben.
quelle
Es gibt viele Fälle dazu.
Viele Hochgeschwindigkeits-MPUs verfügen über eine Multiplexer-ähnliche elektronische Schaltung mit Barrel Shifter, die jede Verschiebung in konstanter Zeit ausführt.
Wenn MPU nur eine 1-Bit-Verschiebung haben, ist
x << 10
dies normalerweise langsamer, da dies meistens durch 10 Verschiebungen oder Byte-Kopieren mit 2 Verschiebungen erfolgt.Es ist jedoch ein häufiger Fall bekannt, bei dem
x << 10
noch schneller alsx << 1
. Wenn x 16 Bit ist, sind nur die unteren 6 Bit davon betroffen (alle anderen werden herausgeschoben), sodass die MPU nur ein niedrigeres Byte laden muss, um nur einen einzelnen Zugriffszyklus auf den 8-Bit-Speicher durchzuführen, währendx << 10
zwei Zugriffszyklen erforderlich sind. Wenn der Zugriffszyklus langsamer als die Verschiebung ist (und das untere Byte löscht),x << 10
ist er schneller. Dies kann für Mikrocontroller mit schnellem Programm-ROM gelten, die auf langsamen externen Daten-RAM zugreifen.Zusätzlich zu Fall 3 kann sich der Compiler um die Anzahl der signifikanten Bits kümmern
x << 10
und weitere Operationen auf solche mit geringerer Breite optimieren, z. B. das Ersetzen der 16x16-Multiplikation durch die 16x8-Eins (da das untere Byte immer Null ist).Beachten Sie, dass einige Mikrocontroller überhaupt keine Anweisung zum Verschieben nach links haben, sondern
add x,x
stattdessen verwenden.quelle
Auf ARM kann dies als Nebeneffekt einer anderen Anweisung erfolgen. Daher gibt es für beide möglicherweise überhaupt keine Latenz.
quelle
ADD R0, R1, R2 ASL #3
addiert R1 und R2 um 3 Bits nach links verschoben.Hier ist meine Lieblings-CPU , die
x<<2
doppelt so lange dauert wiex<<1
:)quelle
Das hängt sowohl von der CPU als auch vom Compiler ab. Selbst wenn die zugrunde liegende CPU eine willkürliche Bitverschiebung mit einem Barrel Shifter aufweist, geschieht dies nur, wenn der Compiler diese Ressource nutzt.
Beachten Sie, dass das Verschieben von Daten außerhalb der Breite in Datenbits in C und C ++ "undefiniertes Verhalten" ist. Die Rechtsverschiebung signierter Daten wird ebenfalls als "Implementierung definiert" bezeichnet. Anstatt sich über die Geschwindigkeit Gedanken zu machen, sollten Sie sich Sorgen machen, dass Sie bei verschiedenen Implementierungen dieselbe Antwort erhalten.
Zitat aus ANSI C Abschnitt 3.3.7:
So:
"<<": y × 2 z ( undefiniert, wenn ein Überlauf auftritt);
">>": implementierungsdefiniert für signiert (meistens das Ergebnis der arithmetischen Verschiebung: y / 2 z ).
quelle
1u << 100
es UB ist. Es ist nur 0.1u << 100
als Bitverschiebung kann ein Überlauf sein.1u << 100
als arithmetische Verschiebung ist 0. Unter ANSI C<<
ist eine Bitverschiebung. en.wikipedia.org/wiki/Arithmetic_shiftx << (y & 31)
Kann weiterhin ohne UND-Befehl zu einem einzelnen Shift-Befehl kompiliert werden, wenn der Compiler weiß, dass der Shift-Befehl der Zielarchitektur die Anzahl maskiert (wie bei x86). (Codieren Sie die Maske vorzugsweise nicht fest, sondern holen Sie sie abCHAR_BIT * sizeof(x) - 1
oder so.) Dies ist nützlich, um eine Rotationssprache zu schreiben, die unabhängig von den Eingaben zu einem einzelnen Befehl ohne C UB kompiliert wird. ( stackoverflow.com/questions/776508/… ).Es ist denkbar, dass auf einem 8-Bit-Prozessor
x<<1
tatsächlich viel langsamer sein könnte alsx<<10
bei einem 16-Bit-Wert.Zum Beispiel kann eine vernünftige Übersetzung von
x<<1
sein:wohingegen
x<<10
einfacher wäre:Beachten Sie, wie
x<<1
sich häufiger und sogar weiter verschiebt alsx<<10
. Darüber hinausx<<10
hängt das Ergebnis von nicht vom Inhalt von byte1 ab. Dies könnte den Betrieb zusätzlich beschleunigen.quelle
Bei einigen Generationen von Intel-CPUs (P2 oder P3? Nicht AMD, wenn ich mich recht erinnere) sind die Bitshift-Operationen lächerlich langsam. Bitshift um 1 Bit sollte jedoch immer schnell sein, da nur Addition verwendet werden kann. Eine weitere zu berücksichtigende Frage ist, ob Bitverschiebungen um eine konstante Anzahl von Bits schneller sind als Verschiebungen mit variabler Länge. Selbst wenn die Opcodes die gleiche Geschwindigkeit haben, muss auf x86 der nicht konstante rechte Operand einer Bitverschiebung das CL-Register belegen, was der Registerzuweisung zusätzliche Einschränkungen auferlegt und das Programm möglicherweise auch auf diese Weise verlangsamt.
quelle
shlx
/shrx
/ verwendensarx
(Haswell und höher und Ryzen). Die CISC-Semantik (Flags unverändert, wenn count = 0) hat hier x86 verletzt.shl r32, cl
ist 3 Uops in der Sandybridge-Familie (obwohl Intel behauptet, dass es eines der Uops abbrechen kann, wenn das Flag-Ergebnis nicht verwendet wird). AMD hat Single-Uopshl r32, cl
(aber langsame Doppelschaltung für erweiterte Präzisionshld r32, r32, cl
)shl r32, cl
oder mit einer anderen als 1 blockiert das Front-End, bis die Schicht in den Ruhestand geht! ( stackoverflow.com/questions/36510095/… ). Compiler wissen dies und verwenden einen separatentest
Befehl, anstatt das Flag-Ergebnis einer Verschiebung zu verwenden. (Aber dies verschwendet Anweisungen auf CPUs, wo es kein Problem ist, siehe stackoverflow.com/questions/40354978/… )Wie immer hängt es vom umgebenden Codekontext ab : Verwenden Sie z.
x<<1
B. einen Array-Index? Oder es zu etwas anderem hinzufügen? In beiden Fällen kleine Verschiebung Zählungen (1 oder 2) kann oft optimize sogar mehr , als wenn die Compiler Enden bis zu mit nur verschieben muss. Ganz zu schweigen vom Kompromiss zwischen Durchsatz und Latenz und Front-End-Engpässen. Die Leistung eines winzigen Fragments ist nicht eindimensional.Eine Hardware-Shift-Anweisung ist nicht die einzige Option eines Compilers zum Kompilieren
x<<1
, aber die anderen Antworten gehen meistens davon aus.x << 1
ist genau gleichbedeutend mitx+x
für vorzeichenlose und für 2-Komplement-vorzeichenbehaftete Ganzzahlen. Compiler wissen beim Kompilieren immer, auf welche Hardware sie abzielen, damit sie solche Tricks nutzen können.Auf Intel Haswell ,
add
verfügt über 4 pro Takt Durchsatz, abershl
mit einer sofortigen Zählung hat nur 2 pro Takt Durchsatz. (Sehen Anweisungen und andere Links finden http://agner.org/optimize/x86Tag Wiki). SIMD-Vektorverschiebungen betragen 1 pro Takt (2 in Skylake), aber SIMD-Vektor-Integer-Additionen betragen 2 pro Takt (3 in Skylake). Die Latenz ist jedoch dieselbe: 1 Zyklus.Es gibt auch eine spezielle Shift-by-One-Codierung, bei der angegeben wird,
shl
wo die Anzahl im Opcode enthalten ist. 8086 hatte keine Schichten mit sofortiger Zählung, nur nacheinander und nachcl
Register. Dies ist hauptsächlich für Rechtsverschiebungen relevant, da Sie nur für Linksverschiebungen hinzufügen können, es sei denn, Sie verschieben einen Speicheroperanden. Wenn der Wert jedoch später benötigt wird, ist es besser, zuerst in ein Register zu laden. Aber trotzdemshl eax,1
oderadd eax,eax
ist ein Byte kürzer alsshl eax,10
, und die Codegröße kann direkt (Decodierungs- / Front-End-Engpässe) oder indirekt (L1I-Code-Cache-Fehler) die Leistung beeinträchtigen.Im Allgemeinen können kleine Verschiebungszahlen manchmal in einem Adressierungsmodus auf x86 in einen skalierten Index optimiert werden. Die meisten anderen heutzutage gebräuchlichen Architekturen sind RISC-Architekturen und verfügen nicht über Adressierungsmodi für skalierte Indizes. X86 ist jedoch eine Architektur, die häufig genug ist, um dies zu erwähnen. (Ei, wenn Sie ein Array von 4-Byte-Elementen indizieren, können Sie den Skalierungsfaktor um 1 erhöhen
int arr[]; arr[x<<1]
).Das Kopieren + Verschieben ist in Situationen üblich, in denen der ursprüngliche Wert von
x
noch benötigt wird. Die meisten x86-Integer-Anweisungen werden jedoch direkt ausgeführt. (Das Ziel ist eine der Quellen für Anweisungen wieadd
odershl
.) Die x86-64-System V-Aufrufkonvention übergibt Argumente in Registern mit dem ersten Argument inedi
und dem Rückgabewert ineax
, sodassx<<10
der Compiler bei einer zurückgegebenen Funktion auch copy + shift ausgibt Code.Mit der
LEA
Anweisung können Sie verschieben und hinzufügen (mit einer Verschiebungsanzahl von 0 bis 3, da die Maschinencodierung im Adressierungsmodus verwendet wird). Das Ergebnis wird in einem separaten Register abgelegt.gcc und clang optimieren diese Funktionen auf dieselbe Weise, wie Sie im Godbolt-Compiler-Explorer sehen können :
LEA mit 2 Komponenten hat eine Latenz von 1 Zyklus und einen Durchsatz von 2 pro Takt auf neueren Intel- und AMD-CPUs. (Sandybridge-Familie und Bulldozer / Ryzen). Unter Intel ist es nur 1 Durchsatz pro Takt mit 3c Latenz für
lea eax, [rdi + rsi + 123]
. (Siehe auch : Warum ist der C ++ Code schneller als meine handschriftliche Versammlung für die Vermutung Collatz testen? Geht in dieser im Detail.)Auf jeden Fall benötigt Kopieren + Verschieben um 10 eine separate
mov
Anweisung. Bei vielen neueren CPUs ist die Latenz möglicherweise null, es werden jedoch immer noch Front-End-Bandbreite und Codegröße benötigt. ( Kann der MOV von x86 wirklich "kostenlos" sein? Warum kann ich das überhaupt nicht reproduzieren? )Ebenfalls verwandt: Wie multipliziere ich ein Register mit 37 mit nur 2 aufeinanderfolgenden Leal-Anweisungen in x86? .
Dem Compiler steht es auch frei, den umgebenden Code so zu transformieren, dass keine tatsächliche Verschiebung erfolgt oder er mit anderen Operationen kombiniert wird .
Zum Beispiel
if(x<<1) { }
könnte ein verwendet werdenand
, um alle Bits außer dem hohen Bit zu überprüfen. Auf x86 würden Sie einetest
Anweisung wietest eax, 0x7fffffff
/jz .false
anstelle von verwendenshl eax,1 / jz
. Diese Optimierung funktioniert für jede Schichtanzahl und auch für Maschinen, bei denen große Schichten langsam (wie Pentium 4) oder nicht vorhanden (einige Mikrocontroller) sind.Viele ISAs verfügen über Anweisungen zur Bitmanipulation, die über das reine Verschieben hinausgehen. zB PowerPC hat viele Anweisungen zum Extrahieren / Einfügen von Bitfeldern. Oder ARM hat Verschiebungen von Quelloperanden als Teil eines anderen Befehls. (Verschiebungs- / Drehanweisungen sind also nur eine spezielle Form der
move
Verwendung einer verschobenen Quelle.)Denken Sie daran, C ist keine Assemblersprache . Achten Sie immer auf die optimierte Compilerausgabe, wenn Sie Ihren Quellcode so optimieren , dass er effizient kompiliert.
quelle