Guten Abend zusammen!
Ich verzweifle gerade etwas an meinem 8051 Programm mit Keil.
Der macht immer so schrecklich langen Code :( Ich will nicht alles in
Assembler schreiben.
1
unsignedcharcodePeriodTable[]={0,128,0,164,0,196,0,255};/* will be much longer later! */
Ich habe auch mal lokale temporäre Variablen eingeführt, trotzdem macht
er die Adressrechnung für den Zugriff in das Array nochmal.
Ähnliches Problem in einer etwas komplexeren Funktion, dort fällt es
aber nicht so auf (hier nur ein Ausschnitt):
1
fx=tempSongPosition[0]&0x0F;
2
period=tempSongPosition[1];
3
fxParam=tempSongPosition[2];
1
R MOV DPL,tempSongPosition+01H
2
R MOV DPH,tempSongPosition
3
MOVX A,@DPTR
4
MOV R7,A
5
ANL A,#0FH
6
R MOV fx,A
7
; SOURCE LINE # 293
8
INC DPTR
9
MOVX A,@DPTR
10
R MOV period,A
11
; SOURCE LINE # 294
12
R MOV DPL,tempSongPosition+01H
13
R MOV DPH,tempSongPosition
14
INC DPTR
15
INC DPTR
16
MOVX A,@DPTR
17
R MOV fxParam,A
1x klappt das mit dem INC DPTR super, danach lädt er trotzdem neu...
Warum?!
Ich habe häufiger diese Spielchen mit dem kleinen :( von überflüssigen
MOV Instructions möchte ich garnicht anfangen zu reden, weil man da
immer nur einen Zyklus verliert. Hier sind es aber 3*2 = 6! Mein
Prozessor ist (leider) recht beschäftigt.
Hat jemand ein paar Tipps?
Dankeschön!
Matthias
Man muß sich etwas abhärten, wenn man aus der Assemblerecke nach C
kommt. Man findet immer einige Zyklen, die eingespart werden könnten.
Dafür punktet der Compiler an anderer Stelle. Z.B. kann er Variablen
überlagern, was man in Assemmbler nicht kann und das spart dann ne Menge
PUSH/POP ein.
Matthias Larisch schrieb:> Mein> Prozessor ist (leider) recht beschäftigt.
Dann muß man den Flaschenhals suchen und genau diesen optimieren.
Auf breiter Front zu optimieren, bringt wenig, kostet aber viel Zeit.
Und wenns wirklich eng wird, kann man ja immer noch schnellere 8051-er
nehmen. Also statt dem alten 12-clocker einen 6-, 4-, 2- oder 1-clocker.
Die 1-clocker gibts von Silabs bis 100MHz hoch (1 NOP = 10ns).
Peter
;---- Variable 'idx' assigned to Register 'R6/R7' ----
14
; SOURCE LINE # 223
15
MOV A,R7 ; ----- was soll das?
16
R MOV DPTR,#PeriodTable
17
MOVC A,@A+DPTR
18
MOV R5,A
19
R MOV A,#LOW ASIncr
20
ADD A,R4
21
MOV R0,A
22
MOV @R0,AR5
23
; SOURCE LINE # 224
24
INC R7
25
R CJNE R7,#00H,?C0058 ; ?!? bitte was? Hängt wohl mit dem 2-byte-increment zusammen, wobei er beim Multiplizieren das 2. byte mal diskret weggelassen hat
26
?C0058:
27
; SOURCE LINE # 225
28
MOV A,R7
29
R MOV DPTR,#PeriodTable ; ---- was soll das?
30
MOVC A,@A+DPTR
31
MOV R7,A
32
R MOV A,#LOW ASIncrFrac
33
ADD A,R4
34
MOV R0,A
35
MOV @R0,AR7
36
; SOURCE LINE # 226
37
RET
38
; FUNCTION _setSampleTone (END)
Somit: Etwas besser (2 Zyklen, wenn ich mich nicht verzählt hab), aber 3
weitere offensichtlich einsparbar.
Außerdem habe ich gewisse Erwartungen an die Optimieriung eines
Compilers. Diese 3 offensichtlichen Stellen zählen für mich dazu. Jede
statische Analyse erkennt, dass die Register vorher garantiert den
richtigen Wert enthalten.
Auch die Transformation meines Ursprungscodes in diesen hier sollte der
Compiler doch hinkriegen.
Hallo Peter,
Auf eine Antwort von dir hatte ich gehofft :)
Peter Dannegger schrieb:> Man muß sich etwas abhärten, wenn man aus der Assemblerecke nach C> kommt. Man findet immer einige Zyklen, die eingespart werden könnten.
Das schlimme ist ja: Eigentlich mache ich sehr wenig Assembler. Ich
möchte es auch garnicht. Ich habe aber gewisse Erwartungen an ein
Produkt, was dann auch noch so teuer sein soll. Klar ist der SDCC noch
schlechter, aber solche Codestücke fallen mir dann einfach sehr übel ins
Auge.
> Dafür punktet der Compiler an anderer Stelle. Z.B. kann er Variablen> überlagern, was man in Assemmbler nicht kann und das spart dann ne Menge> PUSH/POP ein.
Das sehe ich ein und achte ich hoch :)
>>> Matthias Larisch schrieb:>> Mein>> Prozessor ist (leider) recht beschäftigt.>> Dann muß man den Flaschenhals suchen und genau diesen optimieren.> Auf breiter Front zu optimieren, bringt wenig, kostet aber viel Zeit.
Ich halte mich hier in einem Codestück auf, was mehr oder weniger alle
20-50ms aufgerufen wird. Es ist nicht direkt der Flaschenhals, die lange
Ausführungszeit führt aber zu unschönem Jitter in der Ausgabe
(Tonerzeugung; Hier wird die Frequenz geändert; Außerdem muss eben noch
ne ganze Ecke mehr passieren vor/nach diesem Codeschnipsel).
Die Ausgabe an sich erfolgt alle 256 Takte im Interrupt, den habe ich
schon per Hand geschrieben.
> Und wenns wirklich eng wird, kann man ja immer noch schnellere 8051-er> nehmen. Also statt dem alten 12-clocker einen 6-, 4-, 2- oder 1-clocker.> Die 1-clocker gibts von Silabs bis 100MHz hoch (1 NOP = 10ns).
Ja, ja...
Es geht hier um ne Uniaufgabe, die ich mir mal wieder aufwändiger
gemacht habe, als sie sein müsste :) Vorgabe ist der AT89S52, ich möchte
halt ein MOD-ähnliches Tracker Format abspielen. Nebenbei gibts noch ne
20x14 Pixel Matrix(alle 2ms im Interrupt -> hier kann man auch noch
optimieren) und ein Spiel, was darauf läuft. Das ist aber beides
deutlich unkritischer als der Ton. Wir takten mit 20 MHz.
Schonmal an einen neueren uC mit besserer Architektur gedacht als der
8051 mit seinem singulärem DPTR ?
Irgendwann nach 30 Jahren sollte man umstellen !
Gruss
> Was ist hiermit?
Oder hiermit (wenn du deinen Code schon nicht mit einer vernünftigen
struct versehen willst, was softwaretechnisch sowieso die bessere Lösung
wäre, weil es den Zusammenhang von ganzzahligem und fraktionellem Teil
betont und es nicht einfach nur um irgendwelche eigenständige Arrays
geht, die halt zufällig irgendwie zusammengehören)
Karl Heinz Buchegger schrieb:> Was ist hiermit?> struct IncValue {> unsigned char Whole;> unsigned char Frac;> }; /* semikolon von mir */>> struct IncValue code PeriodTable[] = /* code segment */> {> { 0, 128 },> { 0, 164 },> { 0, 196 },> { 0, 255 }> };>> data struct IncValue ASInc[ AUDIO_MAX_PARALLEL ];>> static void setSampleTone(unsigned char channel, unsigned char period)> {> ASInc[channel] = PeriodTable[period]; /* korrigiert von mir */> }
[x] du möchtest nicht wissen, was nun passiert ist :)
Der Code ist 300 Byte größer geworden. Das Struct möchte der Herr Keil
gerne mit einer Library Funktion kopieren. Die Multiplikation mit der
Struct Größe erfolgt mit MUL.
Ich hätte noch den Vorschlag mit einem unsigned short für die Tabelle.
Kann man dann ja selbst auseinanderpflücken
-> Sieht nicht gut aus.
Letzter Versuch: Beides als unsigned short (also ein kombiniertes Incr,
IncrFrac).
Das ist die beste Lösung:
1
; FUNCTION _setSampleTone (BEGIN)
2
; SOURCE LINE # 220
3
;---- Variable 'period' assigned to Register 'R5' ----
4
;---- Variable 'channel' assigned to Register 'R7' ----
Außerdem kann ich nun im Abspiel-Interrupt vermutlich noch ein oder zwei
Instruktionen sparen, muss ich nochmal reinschauen. Super! (Wir
erinnern uns: Abgespielt wird alle 256 CPU Takte - hier lohnt sich also
jeder Takt)
Somit auch einen Dank an dich, Karl Heinz.
und Erich:
Ja, ich würde das privat auch nie machen :) Hier freue ich mich aber
dennoch sehr, auch mal mit dem 8051 in Berührung zu kommen. Es macht
Spaß :) Sonst würde ich mir vermutlich den Assemblercode nichtmal
anschauen und einfach ein Pong auf dem Display implementieren (das würde
die Aufgabenstellung voll erfüllen)
Damit wäre der Thread hier erstmal erledigt. Danke an alle
Beteiligungen!
PS: Meine Compile-Optionen sind
Matthias Larisch schrieb:> [x] du möchtest nicht wissen, was nun passiert ist :)> Der Code ist 300 Byte größer geworden. Das Struct möchte der Herr Keil> gerne mit einer Library Funktion kopieren. Die Multiplikation mit der> Struct Größe erfolgt mit MUL.
Autsch.
In der struct die beiden unsigned char noch mit einer union zu einem
unsigned int (oder short) zusammenfassen, hätt ich noch im Angebot.
Allerdings wirds jetzt wirklich schon ein wenig absurd.
> Letzter Versuch: Beides als unsigned short (also ein kombiniertes Incr,> IncrFrac).
Hats du mal den Vorschlag mit dem eigenständigen Pointer probiert? Sowas
sollte der Compiler eigentlich selber rausfinden, aber na ja.
Noch von vorhin:
Karl Heinz Buchegger schrieb:>> Was ist hiermit?>> Oder hiermit (wenn du deinen Code schon nicht mit einer vernünftigen> struct versehen willst, was softwaretechnisch sowieso die bessere Lösung> wäre, weil es den Zusammenhang von ganzzahligem und fraktionellem Teil> betont und es nicht einfach nur um irgendwelche eigenständige Arrays> geht, die halt zufällig irgendwie zusammengehören)
Ich sehe das vollkommen ein. Ich bin aber (leider) derzeit überzeugt
davon, dass in dieser niedrigen Ebene heutzutage softwaretechnisch
sinnvolle Lösungen meist kein gutes Ergebnis liefern. Daher habe ich
kein besonders schlechtes Gewissen, wenn ich diese Grundregel missachte.
Die Lösung mit einem gemeinsamen unsigned short kann man quasi als
Fixkomma-Lösung auffassen. Somit wäre das softwaretechnisch auch wieder
in Ordnung :)
Ich bin gespannt, ob wir es schaffen, in einigen Jahren auch auf
niedrigster Ebene vernünftigen Hochsprachencode schreiben zu können.
Vermutlich bräuchte man dazu eher eine Art Hardwarebeschreibungssprache,
welche die niedrigste Ebene implementiert, aber keine Hardware sondern
eben Instruktionen erzeugt.
Karl Heinz Buchegger schrieb:>> Hats du mal den Vorschlag mit dem eigenständigen Pointer probiert? Sowas> sollte der Compiler eigentlich selber rausfinden, aber na ja.> static void setSampleTone(unsigned char channel, unsigned char period)> {> unsigned char* pTable = &PeriodTable[2*period];> // Alternativ: unsigned char* pTable = PeriodTable + 2*period;> ASIncr[channel] = *pTable++;> ASIncrFrac[channel] = *pTable;> }
Spendieren wir noch ein Code-Attribut für den lokalen Zeiger (sonst
gibts einen generischen und ne Menge Library-Code), dann erhalte ich
komischerweise die gleiche Codegröße (fürs Gesamtprojekt) wie bei der
unsigned short Variante, gleichzeitig aber eine etwas längere Funktion.
Unter Umständen habe ich hier gerade was übersehen.
Aufgrund des Einsparpotenzials am größten "Rechenzeitverschwender" werde
ich weiterhin die unsigned short Variante bevorzugen.
Also nochmals vielen Dank für die Rege Beteiligung hier, ihr habt mir
sehr geholfen.
Gruß!
Matthias
Edit: Ich vergaß: In spätestens 4 Wochen, dann ist nämlich Abgabe, gibts
das ganze Projekt natürlich offen für alle.
Hallo,
wäre es nicht eine Idee, im Array PeriodTable einfach 16Bit-Werte
abzulegen, und die dann in einem Rutsch auszulesen?
Ich nehme mal an, die Werte landen später in den Timer-Registern?? Da
verwende ich oft eine temporäre Variable zum Datenaustausch. Ich
definiere eine Int-Variable und an den selben Adressen 2 Byte-Variablen.
Die Auftrennung in Low- und High-Byte geht über 2 Zugriffe. Int
schreiben, 2x Char lesen.
z.B. code at 0x08 unsigned int uiTemp;
code at 0x08 unsigned char ucHighByte, ucLowByte;
Die belegen jetzt zusammen die Bytes 0x08 und 0x09, der Compiler meckert
etwas, aber das ist ja gewollt.
Für einen Timer-Reload mache ich dann z.B.
uiTemp = (65536-Zeit);
TH1 = ucHighByte;
TL1 = ucLowByte;
tschuessle
Bernhard