Forum: Compiler & IDEs Code langsamer durch Löschen einer Instruktion


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich habe mich gerade durch das Buch "Software-Test für Embedded Systems: 
Ein Praxishandbuch für Entwickler, Tester und technische Projektleiter" 
von Stefan Grünfelder gearbeitet.

Dort wird behauptet, daß eine Instrumentierung für die Messung der 
CPU-Last auch im Produktivcode verbleiben solle und auch der "CPU-Test 
bei jeder Code-Änderung zu wiederholen [sei], weil auch das Löschen 
einer Instruktion den Code deutlich langsamer machen kann, wenn durch 
die neuen Adressen einer häufig ausgeführten Schleife mehr Cache-Misses 
zu verzeichnen sind als vorher."

Irgendwie verstehe ich diese Begründung nicht:
- Cache misses müßten doch die weitere Rechnung eher verlangsamen und 
nicht beschleunigen?
- Wenn die Begründung stimmt: Bei welchen Architekturen tritt sie 
überhaupt auf?

Viele Grüße
W.T.

P.S.: Die Frage bezieht sich zwar nicht rein auf den GCC, bezieht sich 
aber vermutlich auf derart tiefe Implementierungedetails, daß die 
anderswo noch schlechter untergebracht wäre...

von (prx) A. K. (prx)


Lesenswert?

Walter Tarpan schrieb:
> Irgendwie verstehe ich diese Begründung nicht:

Neben Cache Misses ist hier das Branch Target Alignment wichtig. Das 
dürfte auch der interessante Fall sein, zumindest bei kleinen Schleifen.

Instruction fetches erfolgen bei schnellen Prozessoren meist entlang 
grösserer Blöcke, wie z.B. 16 Bytes am Stück. Diese Blöcke haben 
alignment Beschränkungen, müssen z.B. an einer Adresse (MOD 16 = 0) 
liegen. Liegt ein Sprungziel am Anfang des Blocks, dann sind alle Bytes 
nützlich. Liegt aber bereits das zweite Byte im nächsten Block, dann 
braucht man bereits für den ersten Befehl 2 Zugriffe, was den Befehl 
verzögert.

Wobei dieser Effekt zunächst nicht direkt mit Caches zu tun hat, sondern 
mit dem Verfahren von Instruction fetches. Einen Bezug zu Cache Misses 
gibt es beispielhaft, wenn ein entfallender Befehl dazu führen kann, 
dass die ersten Bytes einer dahinter liegenden Schleife nun auf die 
letzten Bytes einer Cache Line rutschen und die Schleife eine Line mehr 
belegt, obwohl der Code gleich lang ist.

> - Wenn die Begründung stimmt: Bei welchen Architekturen tritt sie
> überhaupt auf?

Über den Daumen gepeilt bei allen mit Cache und Fetch Buffers jeglicher 
Art, und alle Prozessoren, deren Befehle über den Grenzen von 
Fetchzyklen liegen können (also bereits 8086). Die LPC2000 ARM7 von NXP 
beispielsweise haben keinen Cache und holen 128 Bits parallel aus dem 
mit Waitstates beaufschlagten Flash, weshalb es ganz nützlich ist, wenn 
ein Sprungziel am Anfang davon liegt.

Genaueres verrät gelegentlich ein Optimization Guide des 
Prozessorherstellers.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Nachdem der Mechanismus, wie auf (DDR-) RAM zugegriffen wird, ein sehr 
ähnlicher ist (bloß mit mehr "Strafpunkten", wenn ein Zugriff über 
Bank-Grenzen hinweg erfolgen muß und deutlich weniger, wenn der 
Prozessor "bursten" kann), kann man die Argumentation m.E. getrost 
erweitern auf "jede Plattform, die Code oder Daten aus DDRx-RAM holen 
muß".

Also heutzutage fast alles, was größer als ein Fingernagel ist.

von (prx) A. K. (prx)


Lesenswert?

Markus F. schrieb:
> kann man die Argumentation m.E. getrost
> erweitern auf "jede Plattform, die Code oder Daten aus DDRx-RAM holen
> muß".

Im Prinzip richtig, aber finde mal eine Plattform, auf die dein 
Kriterium zutrifft, aber meines nicht, was normale Codezugriffe angeht. 
Diese Burst-Zugriffe ergeben nämlich ohne Caches oder Buffer keinen 
Sinn. ;-)

: Bearbeitet durch User
von foobar (Gast)


Lesenswert?

> Irgendwie verstehe ich diese Begründung nicht:
> - Cache misses müßten doch die weitere Rechnung eher verlangsamen und
> nicht beschleunigen?

Schreibt er ja auch.

> - Wenn die Begründung stimmt: Bei welchen Architekturen tritt sie
> überhaupt auf?

Solche Effekte kann man bei fast allen CPUs finden.  Selbst auf der 
alten 6502 CPU brauchen z.B. bedingte Sprünge einen Takt mehr, wenn das 
Ziel nicht im gleichen 256-Byte-Block liegt wie der Sprungbefehl selbst. 
Ändert sich durch Codeänderungen woanders die Lage ein Schleife im 
Speicher, kann das deren Laufzeit ordentlich beeinflussen.

Was man sagen kann: je einfacher (oder älter) die CPU desto höher die 
Chance, dass sie solche Effekte nicht hat.

Die 8-Bit-Controller sind in der Beziehung meist beherrschbar - die 
Laufzeit ist deterministisch und statisch analysierbar.  Ein "Feature", 
das dazu führt, dass bei AVR & Co häufig I/O-Protokolle taktgenau in 
Software implementiert werden. (Btw, das hat per se nichts mit 8-Bit zu 
tun - auch die 32-bittige 68000 war in der Beziehung gutmütig.)

Bei den komplexeren Microcontrollern wie z.B. Cortex-M sind statische 
Laufzeitanalysen schon sehr schwierig bis unmöglich (von zu vielen 
dynamischen Parametern abhängig - Caches, Pipeline stalls, 
Bus-Contention, etc).  Bei den M0 noch bedingt, bei den M3/M4 schon der 
Horror.  Aus diesen Grund werden übrigens auch bei den Cortex-M-MCUs 
soviele I/O-Funktionen in Hardware implementiert - eine 
Softwareimplementation wäre zu ungenau/schwierig.

Bei Desktop-CPUs kann man nur noch statistische Aussagen machen; ein 
einzelner Cache-Miss führt dazu, dass ein Befehl, der normalerweise 
einen halben Taktzyklus braucht, auf einmal 1000 oder sogar Millionen 
Takte braucht; eine falsche Branch-Prediction führt zu Dutzenden von 
zusätzlichen Takten, etc.

Um mal die Kurve Richtung GCC zu bekommen: wenn man so zeitkritische 
Bereiche hat, dass der ein oder andere Taktzyklus relevant wird, sollte 
man die Finger vom C-Compiler lassen.  Auch wenn der GCC einige der 
Probleme kennt und passende Optimierungen in der Codegenerierung 
bereithält, ist das Ganze doch ein Vabanquespiel - die nächste 
Compilerversion generiert anderen Code und das Kartenhaus fällt 
zusammen.  Hier ist Assembler angesagt.  Der in C geschriebene Teil 
sollte immer genügend "Luft" haben, um geringe Laufzeitunterschiede 
auffangen zu können.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.