Forum: Compiler & IDEs GCC bei vielen Bitshiftingfunktionen sehr suboptimal?


von Matthias Larisch (Gast)


Lesenswert?

Hi!

Ich programmiere erst seit gestern mit WinAVR in neuster Version, vorher 
habe ich so einige Projekte in Assembler realisiert, aber ich wollte mir 
das Leben auch mal einfacher & übersichtlicher gestalten ;-) Deshalb 
mache ich gerade ein Spaßprojekt, bei dem ein BlinkenLEDs Display von 
einem Mega 8 angesteuert wird.

Nun ist mir aufgefallen, dass der Compiler bei Operationen, wo ich 
diverse Bits in anderer Reihenfolge in andere Variablen bringe (kurz: 
Bitshifting betreibe) keine sonderlich gute Arbeit leistet. Mit Inline 
ASM hab ich einige Stellen innerhalb kürzester Zeit auf 35% Zyklenbedarf 
bei ~20% Flash ersparnis reduziert... Nun eine Zeile, welche ich euch 
einmal zeigen möchte:
1
228:          LCD_XTRA |= (lcd_framebuffer[3*(7-i)+2] & 64) >> (6 - LCD_D8);
2
+00000172:   B328        IN      R18,0x18         
3
+00000173:   8182        LDD     R24,Z+2          
4
+00000174:   E090        LDI     R25,0x00         
5
+00000175:   7480        ANDI    R24,0x40         
6
+00000176:   7090        ANDI    R25,0x00         
7
+00000177:   E036        LDI     R19,0x06         
8
+00000178:   9595        ASR     R25              
9
+00000179:   9587        ROR     R24              
10
+0000017A:   953A        DEC     R19              
11
+0000017B:   F7E1        BRNE    PC-0x03          
12
+0000017C:   2B28        OR      R18,R24          
13
+0000017D:   BB28        OUT     0x18,R18

Das steht in einer Schleife, i ist der Schleifenzähler. LCD_D8 ist ein 
#define LCD_D8 0.

Merkt der Compiler an der Stelle nicht, dass ich lediglich 1 Bit von 
einer variablen in ne andere bringen muss? Gibt es eine sinnvollere 
Schreibweise, wie ich ihm das mitteilen kann? Ich würde in ASM einfach 
folgendes machen:

ldd r24, z+2
bld r24, 6
brtc weiter
sbi 0x18, 0
weiter:

das sind grad mal 4 Instruktionen, welche exakt das gleiche machen. 
Warum kann mein gcc nicht auch wenigstens Ansatzweise solchen Code 
generieren?

Ich hoffe ja immernoch, dass der Fehler bei mir zu suchen ist... Denn 
mir vergeht die Lust an C auf AVR, wenn mir jedes mal, wenn ich mir das 
List file angucke, 1000 Verbesserungsmöglichkeiten auffallen.

Mir ist klar, dass der Compiler nicht perfekt arbeitet, aber ein 
weeeeenig kann ich ja wohl von ihm erwarten? Teilweise klappts ganz gut 
mit der Optimierung, aber eben an einigen Ecken und Kanten nicht.

Ich benutze übrigens -Os als Optimierungsstufe.

Vielen Dank,

Matthias

von eProfi (Gast)


Lesenswert?

Ich glaube, das merkt er nicht. Das musst Du ihm schon vorkauen:

LCD_XTRA |= (lcd_framebuffer[3*(7-i)+2] & 64) >> (6 - LCD_D8);
-->
if (lcd_framebuffer[3*(7-i)+2] & 64){LCD_XTRA |= 1;}

Und ich wette, lcd_framebuffer[] ist 16 bit breit, also int.
Bei 8 Bit (char) würde der Code schon etwas kürzer sein.

von Matthias Larisch (Gast)


Lesenswert?

Hi!

Danke erstmal, das mit dem if... oehm, irgendwie logisch, so könnts 
gehen :-)

Und nein, ich vergaß zu schreiben, lcd_framebuffer ist ein uint8_t 
Array. Werde morgen früh mal auf die If-Abfrage umstellen und mal 
schauen, was bei raus kommt, jetzt gehts erstmal ins Bett.

Vielen Dank!

Matthias

von holger (Gast)


Lesenswert?

>Gibt es eine sinnvollere
>Schreibweise, wie ich ihm das mitteilen kann?

Ja, sicher. Erstmal schreibst du nicht alle Berechnungen
in eine Zeile. Und welchen Variablentyp hat i ? int i ?
Ohne etwas mehr Code kann man dazu kaum was sagen.

von Matthias Larisch (Gast)


Lesenswert?

Guten Morgen!

Okay, hier dann mal alle relevanten Code-Abschnitte, dachte, aus oben 
genanntem kann man das bereits rausziehen:
1
#define LCD_DATA PORTD
2
#define LCD_XTRA PORTB
3
#define LCD_D8 PB0
4
#define LCD_CLK PB3
5
6
#define LCD_CLK_HIGH LCD_XTRA |= (1 << LCD_CLK);
7
#define LCD_CLK_LOW LCD_XTRA &= ~(1 << LCD_CLK);
8
uint8_t lcd_framebuffer[24];
9
10
[...]
11
12
for (uint8_t i = 0; i < 8; i++)
13
  {
14
    uint8_t tempo, temp;
15
    /* An dieser Stelle wird noch temp und tempo berechnet, lcd_framebuffer enthält eigentlich die
16
Pixeldaten in einem anderen Format, welches hier umgewandelt wird. Das dürfte aber irrelevant sein.
17
In der folgenden Zeile wird dann ein Teil, welcher erst nach den ersten 8 Durchläufen gebraucht wird,
18
in einen Teil des Buffers geschrieben, der schon konvertiert wurde (sozusagen Doppelbenutzung zum
19
Speichersparen). Aber auch das dürfte nicht relevant sein, funktionieren tut es*/
20
    lcd_framebuffer[3*(7-i)] = temp; 
21
    LCD_DATA = tempo;
22
    LCD_XTRA &= ~( 1 << LCD_D8);
23
    LCD_XTRA |= (lcd_framebuffer[3*(7-i)+2] & 64) >> (6 - LCD_D8);
24
    LCD_CLK_HIGH;
25
    LCD_CLK_LOW;
26
  }
27
28
for (uint8_t i = 0; i < 8; i++)
29
  {
30
    LCD_DATA = lcd_framebuffer[3*(7-i)];
31
    LCD_XTRA &= ~( 1 << LCD_D8);
32
    LCD_XTRA |= (lcd_framebuffer[3*(7-i)+2] & 128) >> (7 - LCD_D8);
33
    LCD_CLK_HIGH;
34
    LCD_CLK_LOW;
35
  }
In der 2. For-Schleife implementiert er das etwas besser:
1
237:          LCD_XTRA |= (lcd_framebuffer[3*(7-i)+2] & 128) >> (7 - LCD_D8);
2
+00000189:   B398        IN      R25,0x18         
3
+0000018A:   01ED        MOVW    R28,R26          
4
+0000018B:   818A        LDD     R24,Y+2          
5
+0000018C:   1F88        ROL     R24              
6
+0000018D:   2788        CLR     R24              
7
+0000018E:   1F88        ROL     R24              
8
+0000018F:   2B89        OR      R24,R25          
9
+00000190:   BB88        OUT     0x18,R24

Wobei der move von R26:R25 in R28:R27 keinen Sinn ergibt, denn R28:R27 
wird überhaupt nicht mehr verwendet, bevor es vor Ende der Funktion vom 
Stack gepopt wird :-) Er macht hier die Bitübertragung explizit mit 
rollen durchs carry. Warum nicht den kack sparen und:
1
LDD R24,Y+2
2
SBRS R24,7
3
rjmp nicht_setzen
4
sbi LCD_XTRA, LCD_D8
5
nicht_setzen:

eben genau wie an der anderen Stelle (der ASM Code der anderen Funktion 
steht oben)

Also, wäre nett, wenn ihr mir schreiben könntet, wie ich das anstellen 
soll :-) Ich probiere jetzt, wie gestern schon gesagt, erstmal die 
If-Anweisung aus.

Matthias

von Matthias Larisch (Gast)


Lesenswert?

Hm jetzt muss ich mir schon selbst antworten, kann man als registrierter 
Benutzer seine Beiträge editieren?

Also: If ist die Lösung. Sogar noch schlauer als ich, bin wohl grade 
nicht so ganz drin im ASM:
1
229:          if(lcd_framebuffer[3*(7-i)+2] & 64) LCD_XTRA |= (1 << LCD_D8);
2
+00000172:   8182        LDD     R24,Z+2          
3
+00000173:   FD86        SBRC    R24,6            
4
+00000174:   9AC0        SBI     0x18,0

Wunderbar, so solls sein ^^

Schade nur, dass er das nicht automatisch aus der anderen Zeile erkennt, 
denn durch das & ist ja klar, dass ich mich nur auf exakt 1 bit beziehe, 
durch das oder ist klar, dass es nur gesetzt werden kann und der shift 
zeigt an, um welches ziel-bit es sich handelt..

Immerhin zeigt das gut, dass man auch als C-Programmierer immer etwas 
mitdenken sollte :-)

Vielen Dank & noch einen schönen Sonntag,

Matthias

von Philipp B. (philipp_burch)


Lesenswert?

Matthias Larisch wrote:
> Hm jetzt muss ich mir schon selbst antworten, kann man als registrierter
> Benutzer seine Beiträge editieren?

Kann man.

> Immerhin zeigt das gut, dass man auch als C-Programmierer immer etwas
> mitdenken sollte :-)

Allerdings. Aber mich wundert teilweise schon, wie intelligent der GCC 
doch ist. Der ersetzt mir doch tatsächlich eine Subtraktionsschleife 
durch einen Aufruf von _divmodqi4...

von Matthias L. (matze88)


Lesenswert?

So, hab mich angemeldet.

Ja, ich war auch überrascht, wie er bei oben geschriebenen Code die 
for-schleife implementiert hat:
X Pointer wird auf höchste "base-adresse" gesetzt, also auf 
lcd_framebuffer[3*(7-i)] und dann für jeden loop um 3 verringert, dann 
einfach gegen den niedrigsten wert getestet, falls drunter -> loop zu 
ende... Also komplett ohne eigene Loopvariable sozusagen :-) Das gefällt 
mir wirklich sehr gut!

Naja, all das zeigt auch, das man sich den entstehenden ASM Code doch 
mal anschauen sollte. Wie eine kleine Veränderung so viel bewirken kann 
(das ersetzen der hier diskutierten Zeile durch gleichwertiges mit dem 
If verringert die Zyklenzahl für den kompletten Funktionsaufruf von 1126 
auf 822 - also benötigt nur 73% der Zeit!

Matthias

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.