2012-05-25 5 views
5

Neulich habe ich in Verilog einen coolen Trick gelernt. Wenn Sie wiederholt etwas tun müssen. Sie könnten ein Schieberegister verwenden, um die Anzahl der Inkrementierungen zu zählen. Verschieben Sie einfach eine 1 von LSB zu MSB, und wenn es die MSB erreicht, sind Sie fertig.Hardware inspirierte Schleife. Unsinn?

In C würde es so etwas wie diese:

for(j=0b1; !(j & (1<<16)); j=j<<1) 
{ 
/*do a thing 16 times*/ 
} 

Ich weiß, es Nutzung wegen der Bit-Breite begrenzt ist, aber es ist keine Beteiligung hinaus, so dass es schnell ist. Also meine Frage: Gibt es eine Verwendung von diesem? Lohnt es sich, in C oder einer anderen Hochsprache zu verwenden?

Vielleicht in eingebetteten Systemen, wo Ressourcen begrenzt sind.

Dank

+5

Was lässt Sie denken, dass die Zugabe langsamer als Verschiebung? Es ist sicherlich nicht auf irgendeiner modernen CPU, nicht einmal eingebetteten Kernen. Noch ist der Bittest. Also ja, Unsinn. –

+0

interessant, aber ich sehe nicht viel CPU-Zyklus hier zu gewinnen. ! –

+0

@HansPassant Ich dachte, ein Addiermechanismus benötigt mehr Ressourcen als das Umstellen einiger Drähte. Und als ich diese Technik auf einem FPGA verwendete, habe ich etwas Bodenfläche gewonnen. Aber dann habe ich 2048 Bit breite Register verwendet. – Stiggo

Antwort

1

es ist beteiligt keine Zugabe, so dass es schnell ist

, für die CPU-Architektur verschieben schneller als Zugabe? Was bringt Sie auch dazu, zu glauben, dass der Compiler für diese spezifische Architektur die Optimierung von Addition zu Verschiebung nicht automatisch durchführt, wenn sich herausstellen sollte, dass die Verschiebung schneller ist?

Gibt es eine Verwendung von diesem?

Für Optimierungszwecke, nein, es gibt keine Verwendung davon.

Für andere Zwecke, ja, Code wie dieser wird häufig zum Ausblenden einzelner Bits eines Bytes verwendet.Ich glaube, die beiden häufigsten Ansätze sind diese:

uint8_t mask; 

for(mask = 0x01; mask != 0x00; mask<<=1) 
{ 
    do_something (data & mask); 
} 

oder

for(i=0; i<8; i++) 
{ 
    do_something (data & (1<<i)); 
} 
+0

Das einzige, was mich dazu gebracht hat zu glauben, dass die Verschiebung effizienter ist als das Hinzufügen, ist Verilog, wo standardmäßig + einen 32-Bit-Addierer aufruft, während << nur eine Neuanordnung von Leitungen ist. Also kann dieser Code verwendet werden, um Bit für Bit durch einen PORT eines Mikrocontrollers zu iterieren? Lesen Sie einen Stift, machen Sie etwas mit dem Lesen und gehen Sie zum nächsten. – Stiggo

+0

@Stiggo Ja, ein Port, ein Flag-Register, ein Teil eines Datenprotokolls, einige Eeprom-Einstellungen Variable usw. usw. – Lundin

8

Das ist sehr nicht wert. Es macht den Code viel weniger sauber und schwieriger zu lesen, und der Leistungsunterschied ist vernachlässigbar.

Ihr Compiler kann diese Arten von Optimierungen viel besser als Sie können. Kurze Loops wie diese könnten sogar aus Performance-Gründen abgerollt werden. Wenn Sie die Schleife jedoch so schreiben, könnte ein Compiler das nicht so einfach herausfinden, und Sie könnten das Programm sogar verlangsamen.

Dies ist wirklich ein Fall von Mikro-Optimierung, die fast nie einen spürbaren Unterschied auf der Laufzeit Ihres Programms machen wird.

1

In einer echten CPU ist die Addition eines der schnellsten Dinge, die Sie tun können; ein Bitshift ist nicht schneller. Und Sie werden es dem Compiler schwerer machen, effizient zu optimieren.

+1

Auch _much_ schwerer zu lesen und zu verstehen, was das eigentliche Problem ist. – Oleksi

1

Schneller? Bist du dir da sicher? Zumindest in der MIPS-Architektur dauert eine Bitverschiebung genau so lange wie eine Addition. Ich wäre überrascht, wenn dies nicht auch für die gängigsten Consumer-orientierten Prozessorarchitekturen gelten würde.

Außerdem, wie Oleksi bemerkt, ist dies ziemlich schwer zu lesen. Wahrscheinlich nicht wert eine nicht vorhandene Geschwindigkeitszunahme.

0

In der Regel, wenn Sie eine bestimmte Anzahl von Zeiten immer Schleife auf> 0 und Loop-Overhead minimieren, dann denke ich, das die „beste“ sein wird:

unsigned i = 16; 

do { 
// do something here 
} while (--i); 



You might get the same result with: 

unsigned i = 0x8000; 

do { 
// do something here 
} while (i>>=1); 

An diesem Punkt, den Sie suchen haben würden bei der Montage.

+0

Der Grund dafür, dass die erste Version schneller ist, besteht darin, dass viele Architekturen einen einzigen Befehl zum Dekrementieren und Verzweigen, wenn nicht Null, haben. –

5

Es scheint mir, dass die meisten der Kommentatoren nicht wirklich verstehen, worüber der Fragesteller spricht. Verilog-Sprache ist für Hardware-Design und Hardware-Design ist sehr unterschiedlich, als Software-Design, keine CPU-Zyklen oder ähnliches. Aber kurze Antwort ist immer noch: Nein. Lange Antwort:

Für die sichere Verschiebung ist viel einfacher als Addition. Zum Schalten gibt es viel weniger Logik von FF (Flipflop) nach FF. Zusätzlich muss der Übertrag von dem LSB-Bit zu dem MSB-Bit übertragen werden, was log2 (N) -Niveaus der Logik bedeutet (N ist der oberste Wert, den der Zähler erreichen würde). Andererseits würde das Schieberegister N FFs verwenden, während der Addierer nur log2 (N) FFs verwenden würde. Es gibt also einen Performance/Area Trade Off, der auch stark von N abhängig ist.Einige 'unabhängige' Informationen über Addierer: http://en.wikipedia.org/wiki/Adder_%28electronics%29 Konnte ähnlichen Artikel für Verschiebung nicht finden, aber sobald Sie Addierer verstehen, sollte Shifter offensichtlich sein.

Dies ist möglicherweise wichtig, wenn Sie die Zustandsmaschine in RTL entwerfen. Aber der Code, den Sie vorgestellt haben, hat eigentlich nichts mit dem oben genannten zu tun. Diese "for" -Schleife in Verilog bedeutet, dass alle "Arbeiten" in einem einzigen Zyklus ausgeführt werden. Also wird es tatsächlich N Logiken geben. Diese Schleife hat nichts mit der Implementierung zu tun. Es könnte sogar den Verilog-Compiler verwirren, um etwas Seltsames auszuspucken und die Simulation zu beeinflussen (wo CPU-Zyklen wichtig sind und ob die obigen Antworten gültig wären). Jemand mit mehr Erfahrung mit Tools könnte dies kommentieren.

+0

Ich war mir ziemlich sicher, dass das Originalposter fragte, ob die C-Version nützlich sei, angesichts der Formulierung "inspiriert durch Hardware-Design" (was bedeutet, dass es kein Hardware-Design ist) und des Kommentars zu eingebetteten Systemen. Aber du hast Recht, es lohnt sich, etwas zu erklären. –

+0

Richtig, ich dachte, andere nicht, aber sieht aus, als hätte ich die Frage nicht sorgfältig genug gelesen ... – Stefan

+0

@Stefan Ich habe gerade einen Kurs an der Universität namens 'High-Performance-Berechnungen mit FPGA'. Aber ich hatte keine Vorkenntnisse auf FPGAs oder Electronic Design oder Verilog. Ich war nur neugierig. Es umfasste einige interessante Dinge wie Addierer, Multiplikator, Dividierer, Potenzierung. Wahrscheinlich trivial für einen Elektroingenieur. Zuerst, als ich etwas zählen musste, habe ich nur ein ** reg [n: 0] cnt ** erstellt und das inkrementiert wie ** cnt <= cnt + 1 **. Für mich war es nicht offensichtlich, dass ** + ** eine Addierschaltung aufruft. Dann habe ich dieses Shift-Register-Ding gelernt, von wo die Idee kam. – Stiggo

2

(Stand Stefan Antwort, ich nehme an, Sie über die C-Version von der Verilog-Version inspiriert sind gefragt, nicht dazu in Verilog zu tun.)

Auf vielen Architekturen, das ist eigentlich noch schlimmer, weil Die Bitverschiebung nimmt eine zusätzliche Anweisung, während die Addition für die Schleifenvariable vollständig frei ist.

Komplett?

Ja. Weil es auf vielen Architekturen einzelne Anweisungen gibt, die einen Zähler dekrementieren und einen Zweig verzweigen, wenn er nicht null ist - und diese Anweisungen nehmen genauso viel Zeit in Anspruch wie jeder andere Vergleichs- und Verzweigungsbefehl. Wenn Sie jedoch eine Schicht machen, erfordert das einen zusätzlichen Instruktionszyklus. Es ist noch schlimmer, wenn Ihre Plattform keine "Gleich- und Zweigniederlassung" -Anweisung hat - und nicht alle von ihnen; Einige machen Sie subtrahieren und in zwei Anweisungen mit Null vergleichen.

Auch auf einer RISC-Plattform ohne Dekrement-Vergleichs-Verzweigungsbefehl ist die Countdown-Schleife wahrscheinlich schneller, da Sie einfach (eine Anweisung) subtrahieren und die Verzweigung-Wenn-Nicht-Null-Anweisung verwenden können. Sie benötigen eine Verschiebung (eine Anweisung) und eine bitweise - und (eine Anweisung) vor der Verzweigung - wenn - Null. Und das setzt voraus, dass Sie sogar eine Verzweigung-wenn-Null haben.

Darüber hinaus ist es für eine einfache for (i = 0; i < N; i++)-Schleife trivial für den Compiler, es in eine "count down to 0" -Schleife umzuwandeln, wenn das schneller ist - Sie müssen selten selbst diese Klugheit selbst tun.

1

Inkrement ist ein ganz spezieller Fall der Addition. In den meisten Prozessoren und sicherlich den meisten RISC-Prozessoren werden eine Verschiebung und ein Inkrement in der Ausführungszeit identisch sein. Tatsächlich wird in den meisten Architekturen der Zusatz auch nicht länger dauern.

Wenn Sie Ihren Schleifencode idiomatisch halten, ist der Optimierer in der Lage, die Schleife einfach abzuwickeln und sie in jedem Fall schneller zu machen. Wenn Sie den Loop-Mechanismus "ungewöhnlich" machen, kann der Optimierer ihn möglicherweise nicht optimieren.