Sven schrieb:> Ich habe folgenden Code, den AVRGCC erzeugt hat:
Also schonmal keine Assemblerfrage. Vieles von dem Assemblercode, den
der GCC erzeugt, ist nicht sinnvoll. Eben deswegen lohnt ja
Assembleroptimierung auch immer noch...
>
1
> while(delay--) asm volatile("nop");
2
> 1d4: 00 00 nop
3
> 1d6: 01 97 sbiw r24, 0x01 ; 1
4
> 1d8: 00 97 sbiw r24, 0x00 ; 0
5
> 1da: e1 f7 brne .-8 ; 0x1d4
6
>
>> Im Grunde verstehe ich den Code: Er zählt Register r24 runter. Aber> warum ist da das zweite sbiw, welches 0 subtrahiert?
Kein Ahnung. Eigentlich kann es nur drei Gründe dafür geben.
1) "sbiw R24,0" muß sinngemäß eigentlich als "NOP2" gelesen werden
2) es ist aus irgendeinem Grunde für den verschissenen C-Compiler
wichtig, die MCU-Flags beim Austritt aus der Warteschleife in einem
bestimmten Zustand zu haben, eben in dem, der sich nach einer
Subtraktion 0-0 einstellt.
3) eine Kombination aus 1) und 2)
Ich würde auf 3) tippen...
Das wurde vielleicht nicht wegoptimiert: das Abziehen von 1 könnte vom
-- herrühren, das Abziehen von 0 zum Test für das while.
(Wobei ja eigentlich das Postfix-Dekrement nach dem Test kommen sollte,
aber zum Ausgleich findet es ja nach dem Schleifenrumpf statt - dann
müsste die Anzahl wieder hinkommen.)
Probiere doch mal mit mehr Optimierung zu kompilieren, vielleicht
verschwindet die eine Subtraktion dann ja?
Das zweite sbiw ist schlicht das noch nicht optimierte Testen auf 0.
Aber da der GCC so blöd ist, die Anweisung "nicht so stark optimieren"
ernst zu nehmen, sollt man besser die ganzen 32k Code in ASM von Hand
schreiben. Da lernt man was für's Leben!
(gefundene Ironie darf behalten werden)
c-hater schrieb:> Ich würde auf 3) tippen...
Und ich auf ein krasses Unverständnis von der Arbeitsweise eines solch
komplexen Werkzeuges wie GCC.
Es ist beim GCC (und bei allen anderen ernsthaften Compilern) nicht mehr
sinnvoll, willkürlich nach dem Sinn irgendeines Schnippels zu fragen.
Ganz einfach deshalb, weil zwischen C-Quelltext und ASM-Emission viel zu
viele Schritte liegen.
Wenn du also in deinem Beispiel fragst, warum deine Schleife nicht
effizienter (lies: ohne die scheinbar überflüssige Subtraktion)
compiliert wurde, dann lautet die einzige richtige Antwort: Weil du es
offenbar nicht vom Compiler verlangt hast (-O0)...
Im Übrigen erzeugt der GCC schon ziemlich effizienten Code. Betrachtet
man neben der Effizienz des Codes auch die Effizienz des Programmierens
('Time-to-market'), dann ist der Einsatz eines guten Compilers heute
fast immer effizienter, als Hand-Optimiererei in Assembler.
Nett, was man so lernt...
Es ist ein mega16. Ich hatte mit -Os compiliert. Bei -O1 kommt dann
raus:
1
while(delay--) asm volatile("nop");
2
1d2: 00 97 sbiw r24, 0x00 ; 0
3
1d4: 19 f0 breq .+6 ; 0x1dc <ow_delay+0xa>
4
1d6: 00 00 nop
5
1d8: 01 97 sbiw r24, 0x01 ; 1
6
1da: e9 f7 brne .-6 ; 0x1d6 <ow_delay+0x4>
7
1dc: 08 95 ret
Ob das nun besser ist?
Bei 2/3/s dann das von oben wieder.
Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären
könnte. Es geht mir dabei mehr ums Verständnis, als um Effizienzfragen
etc. Man will ja wissen, was da so unter der Motorhaube passiert...
Sven schrieb:>> Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären> könnte. Es geht mir dabei mehr ums Verständnis, als um Effizienzfragen> etc. Man will ja wissen, was da so unter der Motorhaube passiert...
Dazu muss man kein Assembler-Guru sein, sondern nur verstehen was
1
while(delay--)asmvolatile("nop");
für beliebige Anfangswerte einschlisselich 0 von delay machen soll. In
deiner erster Variante steht vermutlich noch irgendwo ein Spung auf
0x1d8.
Versuch mal statt dessen ein
1
while(--delay)asmvolatile("nop");
dann dürfte der angeblich überflüssige Vergleich auf 0 weg sein.
c-hater schrieb:> Kein Ahnung. Eigentlich kann es nur drei Gründe dafür geben.>> 1) "sbiw R24,0" muß sinngemäß eigentlich als "NOP2" gelesen werden> 2) es ist aus irgendeinem Grunde für den verschissenen C-Compiler> wichtig, die MCU-Flags beim Austritt aus der Warteschleife in einem> bestimmten Zustand zu haben, eben in dem, der sich nach einer> Subtraktion 0-0 einstellt.> 3) eine Kombination aus 1) und 2)>> Ich würde auf 3) tippen...
Weder 1) 2) noch 3) treffen zu. Was hingegen zutrifft sind 4) und 5)
4) Mit einem halbwegs aktuellen Compiler (4.7, 4.8, ...) und aktivierter
Optimierung (etwa -Os) ist der erzeugte Code mit 8-Bit bzw. 16-Bit
unsigend delay:
1
.L2:
2
sbiw r24,1
3
brcs .L5
4
/* #APP */
5
nop
6
/* #NOAPP */
7
rjmp .L2
8
.L5:
bzw.
1
subi r24,1
2
brcs .L9
3
/* #APP */
4
nop
5
/* #NOAPP */
6
rjmp .L7
5) Das einzige, was hier verschissen ist, sind deine Umgangsformen.
Sven schrieb:> Bei 2/3/s dann das von oben wieder.>> Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären> könnte.
es ist ganz einfach die kürzeste Version, um eine 16 Bit Zahl auf 0 zu
testen. Zieh 0 ab und sieh dir nachher die Flags an.
In deiner Urversion von ganz oben steht die 2.te Subtraktion deswegen
drinnen, weil
1
while(delay--)
ja nichts anderes als die Kurzform von
1
while(delay--!=0)
ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann
entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je
nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.
Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.
Karl Heinz schrieb:> Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.
Ich tippe eher auf die Erklärung von "Lattice User".
Wenn man
while (delay--)...
zeitoptimiert umsetzt, dann landet man bei
if (delay) do ... while (--delay)
und genau das macht er bei -O1.
Bei -Os hingegen verkürzt er den Code zu Lasten der Laufzeit zu
goto test;
loop:
--delay;
test:
if delay != 0 goto loop;
und da lässt sich der Test nicht rausoperieren. Den "goto test"
allerdings hatte er zu erwähnen vergessen.
Karl Heinz schrieb:>> In deiner Urversion von ganz oben steht die 2.te Subtraktion deswegen> drinnen, weil>
1
>while(delay--)
2
>
> ja nichts anderes als die Kurzform von>
1
>while(delay--!=0)
2
>
> ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann> entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je> nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.
Tut mir leid, das ist nicht richtig
1
delay--
ist post decrement, also zuerst vergleichen und dann decrementieren.
>> Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.
Die Variante war laut Sven mit Os d.h. Platzbedarf. Der Vergleich auf 0
ist für den Vergleich beim allerersten Durchlauf enthalten.
Die Variante, die sich am besten optimieren lässt dürfte
1
do{
2
asmvolatile("nop");
3
}while(--delay);
sein, da sie ohne Sonderbehandlung beim Schleifeneintritt auskommt. Ein
Startwert von 0 macht dann aber nicht mehr das gleiche wie beim Orginal.
>> ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann>> entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je>> nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.>> Tut mir leid, das ist nicht richtig>
1
>delay--
2
>
> ist post decrement, also zuerst vergleichen und dann decrementieren.
Das hat mich auch zunächst verblüfft.
Aber wenn du dir den genauen Code ansiehst und wir davon ausgehen, dass
da am Anfang noch ein Sprung auf den 2.ten SBIW fehlt, dann hat der
Compiler das gar nicht so unclever gelöst (Jetzt mal davon abgesehen,
dass der 2.te SBIW nicht notwendig wäre). Er hat den NOP vorgezogen
wodurch die Anzahl der ausgeführten NOP wieder dieselbe ist. Interessant
wäre es jetzt, wie es im C Code weiter geht, denn delay hat nach der
Schleife einen falschen Wert (0 anstelle von -1), was aber unter
Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso
schon zu Ende ist bzw. delay nicht mehr benutzt wird.
> do {> asm volatile("nop");> } while( --delay );
in genau so eine ähnliche Form dürfte sich der Compiler die Schleife
zunächst gebracht haben
PseudoCode, kein echtes C
1
gotocheck;
2
do{
3
asmvolatile("nop");
4
--delay;
5
}check:while(delay!=0);
und das hat er dann 1:1 übersetzt.
Aber ich stimme zu: das alles ist Spekulation.
Johann L. schrieb:> 5) Das einzige, was hier verschissen ist, sind deine Umgangsformen.
Was hat denn dich gebissen? Was soll diese Ansage? Mir unverständlich.
Oder beziehst Du das auf Dich?
LUGS schrieb:> Was hat denn dich gebissen? Was soll diese Ansage? Mir unverständlich.
c-hater (nomen est omen) hatte vom "verschissenen C-Compiler"
geschrieben, und Johann reagierte darauf verständlicherweise etwas
empfindlich.
Karl Heinz schrieb:> Schleife einen falschen Wert (0 anstelle von -1), was aber unter> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso> schon zu Ende ist bzw. delay nicht mehr benutzt wird.
Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung
der Schleife im Zwischencode die fällige Angleichoperation hinter der
Schleife zunächst erzeugt, diese aber im weiteren Verlauf der
Optimierung rausfliegt, weil erkennbar ungenutzt.
A. K. schrieb:> Karl Heinz schrieb:>> Schleife einen falschen Wert (0 anstelle von -1), was aber unter>> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso>> schon zu Ende ist bzw. delay nicht mehr benutzt wird.>> Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung> der Schleife im Zwischencode die fällige Angleichoperation hinter der> Schleife zunächst erzeugt, diese aber im weiteren Verlauf der> Optimierung rausfliegt, weil erkennbar ungenutzt.
Kopfklatsch.
Richtig, natürlich.
Ich hab mir schon die ganze Zeit den Kopf darüber zerbrochen, wie er
dieses Problem hätte lösen können.
Wie man sieht, kann ein Post-Dekrement ganz schön unangenehm sein.
Pre-Decrement ist einfacher zu handhaben, wenn man nicht nur auf den
Nebeneffekt des Dekrementierens aus ist, sondern mit dem Wert selbst
auch noch was machen muss.
Aber seit 4.7 ist ja (laut Johann) da sowieso mit einem komplett anderen
Ergebnis zu rechnen. Und an dem Ergebnis kann noch nicht mal c-hater
rummäkeln. Nicht das es irgendjemand interessieren würde, was c-hater so
meint.
A. K. schrieb:> Karl Heinz schrieb:>> Schleife einen falschen Wert (0 anstelle von -1), was aber unter>> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso>> schon zu Ende ist bzw. delay nicht mehr benutzt wird.>> Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung> der Schleife im Zwischencode die fällige Angleichoperation hinter der> Schleife zunächst erzeugt, diese aber im weiteren Verlauf der> Optimierung rausfliegt, weil erkennbar ungenutzt.
Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten
Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber
warum hat er dass dann in der -O1 Variante nicht gemacht?
Übrigens finde ich es verwunderlich, einerseits von Hand ein NOP
einzufügen, sich andererseits über eine zusätzliche vom Compiler
eingebaute Instruktion aufzuregen.
Lattice User schrieb:> Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten> Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber> warum hat er dass dann in der -O1 Variante nicht gemacht?
Vorsicht. Das mit der Konstante war nicht von Sven sondern von Fred.
A. K. schrieb:> Vorsicht. Das mit der Konstante war nicht von Sven sondern von Fred.
Ist aber mit einer Variablen nicht anders (-O1, gcc 3.4.2/Atmel Studio
6.1):
Source:
Hätte ich geahnt, was meine Frage für Wellen schlägt...
Lattice User schrieb:> A. K. schrieb:> Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten> Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber> warum hat er dass dann in der -O1 Variante nicht gemacht?
Der C Code war trivial:
Also egal, was delay danach für'n Wert hat. Übergeben wird eine
Konstante.
Sorry, dass ich nicht so weit ausgeholt hatte, aber konnte ja keiner
Ahnen, dass das wichtig wird. Und für die Vergleichbarkeit: Compiliert
mit avr-gcc (WinAVR 20100110) 4.3.3
Rolf Magnus schrieb:> Übrigens finde ich es verwunderlich, einerseits von Hand ein NOP> einzufügen, sich andererseits über eine zusätzliche vom Compiler> eingebaute Instruktion aufzuregen.
Mooooment: Ich wollte nur wissen, was da vor sich geht. ist ist völlig
Schnuppe, wie Optimal das hinsichtlich von Laufzeit oder Größe ist.
Eigentlich wollte ich nur die Ticks ermitteln und bin dabei über -0
gestolpert und da ich von Assembler nur Grundkenntnisse habe aus einer
Zeit, als man noch mit 8 Schaltern programmierte und das auch nur ein
paar Stunden lang gemacht habe, fragte ich die Community, damit ich mein
Verständnis auffrischen kann.
Hi,
mal eine Frage:
Wenn der TO wirklich Ticks zählen will, wäre es dann nicht besser die
Func. immer mit der gleichen Optimierung zu kompilieren! Unabhängig vom
Rest des Programms!
nur eine Idee: (nicht getestet)
Sven schrieb:> Mooooment: Ich wollte nur wissen, was da vor sich geht. ist ist völlig> Schnuppe, wie Optimal das hinsichtlich von Laufzeit oder Größe ist.> Eigentlich wollte ich nur die Ticks ermitteln
Compiler sind komplexe Gebilde. Speziell dann, wenn es um Optimierungen
geht. Es ist schwierig im Detail nachzuvollziehen, wie der Compiler ganz
konkret in einer ganz bestimmten Situation zu einer spezifischen Lösung
kommt, weil meistens viele Faktoren zusammenspielen und dabei eine
Unmenge an Code im Compiler in der einen oder anderen Form mitspielt.
Auch in deinem Code ist es sehr wahrscheinlich nicht eine einzige
Optimierung, die für das Ergebnis verantwortlich ist, sondern da sind
viele Optimierungen am Werk, die sukzessive und in Schritten den
Ausgangscode auf das letztendlich generierte Ergebnis transformieren.
Manchmal optimieren Compiler Dinge nicht, die eigentlich recht
offensichtlich sind, dafür gibt es wieder andere Fälle, in denen sie mit
wirklich cleveren Lösungen hochkommen.
Über alles gesehen ist aber das Ergebnis bei einem durchschnittlichen
Compiler meistens so, dass er auf dem Level eines guten
Assembler-Programmierers operiert. D.h. um den Compiler zu schlagen,
muss man schon recht gut in Assembler sein und viel Erfahrung haben. Bei
kleinen Programmen geht das noch recht problemlos aber wenn die Dinge
größer werden (und ich rede da jetzt nicht von 50 Zeilen Code, 50 Zeilen
ist Micky Mouse) hast du als Assembler Programmierer kaum mehr eine
Chance, weil du das Programm in seiner Gesammtheit in allen Details
einfach nicht mehr überblicken kannst. Die einzige "Chance" die du hast
ist die, Fehler einzubauen, weil du wieder mal irgendwo ein Register
versemmelt hast oder dir die Flags mit einer Instruktion zerschossen
hast, die dann den Conditional Branch 3 Instruktionen weiter falsch
springen lassen. Derartige Fehler passieren einem Compiler nun mal
nicht. Daher nimmt man die paar Prozentpunkte, die das compilierte
Produkt gegenüber einem handoptimierten Assemblercode langsamer ist,
gerne in Kauf. Man kann seine Zeit auch besser verbringen, als
stundenlang in 30 Bildschirmseiten Code den kritischen Pfad durch alle
Conditional Branches zu suchen, der zu einem Pop zu wenig führt, der
dann letzten Endes den Stack zerschiesst. Den in den wenigsten
Programmen kommt es tatsächlich auf jede µs an. Ob ein Relais 2 µs
früher oder später nach einem Tastendruck schaltet, interessiert keinen.
Was aber interessiert ist, dass es das zuverlässig und korrekt
entsprechend der vorgegebenen Logik und den Nebenbedingungen tut.
Stephan schrieb:> Hi,> mal eine Frage:> Wenn der TO wirklich Ticks zählen will, wäre es dann nicht besser die> Func. immer mit der gleichen Optimierung zu kompilieren! Unabhängig vom> Rest des Programms!>> nur eine Idee: (nicht getestet)> inline __attribute__((gnu_inline, optimize("O1")) void ow_delay> (uint16_t delay)> {> while(delay--) asm volatile("nop");> }
Das ist eine gute Idee, brauche ich aber nicht.
Ich danke wirklich für die vielen Kommentare. Aber wie gesagt: es geht
mir nicht um die Optimierung. Ich sah, verstand nicht und fragte. Ich
dachte einfach, dass ich sbiw vielleicht nicht richtg verstanden habe
und brne danach nur dann funktioniert, wenn -0 vorher gemacht wird. Oder
irgendwas anderes, was sich meinem Assembler-Verstehen entzogen hat.
Mir ist im grunde schnuppe, was da steht und wie der Compiler sich das
zusammengefrickelt hat. Jetzt weiß ich, daß liegt am delay-- und das ist
ausreichend. und ich hatte sbiw und brne schon richtig verstanden und
die machen das so wie ich dachte und die Feinheiten warum das so optimal
ist überlasse ich den Kapazitäten, die dankenswerterweise am AVR GCC
entwickeln. Und ich werde den Code weder ändern noch in Assembler coden.
Das habe ich schon vor Jahrzehnten als unpraktikabel abgelehnt (wie
gesagt: 8x Klick und 1x Tipp war grausig genug) Ich bleibe schön bei C
(oder anderen Hochsprachen) und bin zufrieden mit dem was raus kommt.
Nur manchmal juckt es einen halt zu sehen, was da rauskommt. Ich bastle
ja auch am Auto und frage mich oft genug, was "der Scheiß" da soll.
Deshalb baue ich ja nicht gleich das Auto selber oder kaufe mir eine
neues und baue es erst mal um.
Sven schrieb:> Und ich werde den Code weder ändern
Das solltest Du aber doch in Betracht ziehen.
Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().
Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare
zu raufen, wenn man mal einen anderen Quarz anschließen will.
Handoptimierte Delayschleifen sind eine mehr Dirty als Quicklösung. Ein
anderer Quarz, andere Compilerversion schmeißen Dir alles wieder
komplett über den Haufen.
Peter Dannegger schrieb:> Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().> Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare> zu raufen, wenn man mal einen anderen Quarz anschließen will.
Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für
1-Wire muß man sich schon etwas verbiegen. Und es ging hier nicht ums
Optimum.
Sven schrieb:> Peter Dannegger schrieb:>> Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().>> Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare>> zu raufen, wenn man mal einen anderen Quarz anschließen will.>> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für> 1-Wire muß man sich schon etwas verbiegen. Und es ging hier nicht ums> Optimum.
Die zugrundelegenden Primitiven sind Zykel-genau. Die erreichbare
Genauigkeit hängt also nur noch von der Taktfrequenz ab: Je größer,
desto genauer. Ebenfalls hängt die erreichbare Genauigkeit von deiner
IRQ-Last ab.
Nehmen wir mal folgendes kleines Beispiel bei 20 MHz:
1
#define F_CPU 20000000
2
3
#include<util/delay.h>
4
5
voidwait(void)
6
{
7
_delay_us(10.3);
8
}
10.3µs entsprechen 10.3 / 1e6 * 20e6 = 206 Ticks.
Lassen wir das Beispiel mit avr-gcc übersetzen — hier mit 4.8 — und
setzen zusätzlich zu der für delay.h erforderlichen Optimierungsoption
noch -fdump-tree-optimized weil wir zu faul sind, die Ticks im erzeugten
Programm zu zählen. Das entsprechende .optimized enhält für wait:
1
wait ()
2
{
3
<bb 2>:
4
__builtin_avr_delay_cycles (206); [tail call]
5
return;
6
}
Das ist genau die oben errechnete Anzahl an Ticks.
Und wennst nicht glaubst daß avr-gcc in der Lage ist, das in eine
Sequenz (modulo IRQ-Last) umzusetzen, die genau 206 Ticks dauert, dann
studier gerne die GCC-Quellen oder zähl es im erzeugten Assembler-Code
nach:
1
wait:
2
ldi r24,lo8(68) ; 5 delay_cycles_1 [length = 3]
3
1: dec r24
4
brne 1b
5
rjmp . ; 6 *nopv/2 [length = 1]
6
ret ; 13 return [length = 1]
Wobei das ret am Ende nicht mehr zum delay gehört. Und selbst wenn es zu
Rundungsfehlern kommt weil die Anzahl der errechneten Ticks nicht
ganzzahlig ist: Genauer als hausbackener C-Code ist's allemal...
Sven schrieb:> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für> 1-Wire muß man sich schon etwas verbiegen.
Also bei mir funktioniert 1-Wire einwandfrei mit _delay_us() und vor
allem auf Anhieb und bei beliebiger Quarzfrequenz.
Beim 60µs Bitzugriff muß man aber in der Regel Interrupts verbieten.
Beim 480..960µs Resetpuls brauche ich das nicht, da meine Interrupts
kurz genug sind.
Sven schrieb:> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance.
Ich weiss nicht wie du es schaffst, die ziemlich gut entwickelten
Delay-Routinen der AVR-Lib derart ungenau werden zu lassen, dass 1-Wire
Code in die Binsen geht. Das geht eigentlich nur mit falscher
Taktvorgabe, störenden Interrupts oder unoptimierter Übersetzung.