Hallo,
ich versuche eine Bitmanipulation wie sie beim Zoomen für ein LC-Display
vorkommt zu beschleunigen.
gewünscht: aus einem 7-bit Input (i6...i0) soll ein 28-bit Output
(o27...o0) erzeugt werden nach folgender Regel:
i0 -> o0=o1=o2=03
i1 -> o4=o5=o6=07
i2 -> o8=o9=o10=011
i3 -> o12=o13=o14=15
i4 -> o16=o17=o18=019
i5 -> o20=o21=o22=023
i6 -> o24=o25=o26=027
also in Worten: jedes Bit im Input soll Gruppen von 4 gleichen Bits im
Output ergeben, die dort entsprechend nebeneinander liegen.
Folgende Code-Varianten fallen mir ein:
Variante 1: trivial, nur zu Veranschaulichung was ich will.
Benötigt 564...648 Takte (je nachdem wie viel Einsen im Input
Variante 1a: nutzt mult (Host ist AVRmega mit intrinsischem mult8)
Benötigt 386...407 Takte (je nachdem wie viel Einsen im Input
Variante 2: nutzt Tabelle mit 128 dwords im ROM.
Benötigt 33 Takte, aber 128*4 = 512 Bytes im ROM - könnte ich gerade
noch mit leben.
Variante 3: nutzt Tabelle mit 128 dwords im RAM.
Benötigt 4 Takte, aber 128*4 = 512 Bytes im RAM: unschlagbar schnell,
aber leider habe ich nicht soviel RAM zur Verfügung.
Variante 4: nutzt GCC Funktion "insert bits".
Benötigt 95 Takte: das ist das schnellste, was mir ohne Tabelle
eingefallen ist.
Irgendwelche Ideen zur Beschleunigung von den (Assembler)-Gurus?
Jeder Tipp herzlich willkommen - Danke im Voraus.
/sigma9
Ich kenne mich nur mit PICs aus aber das wäre mein Ansatz (für 8biter):
1
//-------------------------------
2
uint32_tvariant_jan(uint8_tin)
3
//-------------------------------
4
{
5
union{
6
uint32_tall;
7
uint8_tarray[4];
8
}result;
9
10
result.all=0;
11
12
13
if(in&0x01){
14
result.array[0]+=0x0F;
15
}
16
17
if(in&0x02){
18
result.array[0]+=0xF0;
19
}
20
21
if(in&0x04){
22
result.array[1]+=0x0F;
23
}
24
25
if(in&0x08){
26
result.array[1]+=0xF0;
27
}
28
29
if(in&0x10){
30
result.array[2]+=0x0F;
31
}
32
33
if(in&0x20){
34
result.array[2]+=0xF0;
35
}
36
37
if(in&0x40){
38
result.array[3]+=0x0F;
39
}
40
41
if(in&0x80){
42
result.array[3]+=0xF0;
43
}
44
45
return(result.all);
46
}
Da jeweils nur auf 8 bit zugegriffen wird sollt es recht zügig gehen.
Bei einem PIC würde ich im List file noch nachschauen, dass bei den IF
Abfragen der Befehl "Is bit set" verwendet wird und nicht die verundung
ausgeführt wird.
@derJan
sehr gute Idee! Der wesentliche Trick ist, das man alles auf 8 bit
Arithmetik runterbrechen kann. Das wird sehr schnell. ausprobiert -> 35
Takte. Respekt!
btw der GCC macht es sehr pfiffig:
@Dr Google:
verstehe ich nicht. Ich will ja am liebsten keine Tabelle benutzen wegen
des zu hohej RAM/ROM Verbrauchs.
Oder ich habe dich komplett missverstanden...
@Johann L:
ich verstehe komplett was da passiert - nur wäre ich da nie drauf
gekommen.
Das ist halt der Unterschied zwischen Gurus und Bemühten ;=)
Was ich bis jetzt übersehen hatte - das hängt natürlich auch alles vom
optimization level ab. Gemessen:
o3 02 o1 oS
v4 38 34 95 94
jan 34 33 44 42
dunno 65 358 377 463
johann 32 31 34 32
Da ich fast immer mit -oS arbeite, ist dein Algorithmus noch
überzeugender.
Chapeau!
Wenn ich das richtig verstanden habe, wird dadurch die Variable 'out'
direkt in ein Register geschoben und auf diese darf gelesen und
geschrieben werden. Und das ist am Ende auch das Ergebnis (ret).
Nico W. schrieb:> Wenn ich das richtig verstanden habe, wird dadurch die Variable 'out'> direkt in ein Register geschoben und auf diese darf gelesen und> geschrieben werden. Und das ist am Ende auch das Ergebnis (ret).
das erwarte ich als Selberverständlichkeit von einem Compiler.
@ Peter II (Gast)
>> Wenn ich das richtig verstanden habe, wird dadurch die Variable 'out'>> direkt in ein Register geschoben und auf diese darf gelesen und>> geschrieben werden. Und das ist am Ende auch das Ergebnis (ret).>das erwarte ich als Selberverständlichkeit von einem Compiler.
Das macht der gcc ja auch ohne den Hack, der macht es nur noch einen
TICK schneller. Ist eine OptimierungsoptimierungOptimierung. Ich würde
sie einfach weglassen.
Mampf F. schrieb:> //-------------------------------> uint32_t variant1 (uint8_t in)> //-------------------------------> {> uint32_t result = 0L;>> for (uint8_t bit = 0; bit < 7; bit++)> result |= (in & (1<<bit)) ? (0xfL << (bit << 2)) : 0;>> return result;> }
war (fast) meine Version 0. Braucht wegen der 32bit-Arithmetik 797
Takte...
Falk B. schrieb:> @ Peter II (Gast)>>>> Wenn ich das richtig verstanden habe, wird dadurch die Variable 'out'>>> direkt in ein Register geschoben und auf diese darf gelesen und>>> geschrieben werden. Und das ist am Ende auch das Ergebnis (ret).>>>das erwarte ich als Selberverständlichkeit von einem Compiler.>> Das macht der gcc ja auch ohne den Hack, der macht es nur noch einen> TICK schneller. Ist eine OptimierungsoptimierungOptimierung. Ich würde> sie einfach weglassen.
Offensichtlich hilft man dem GCC beim Optimieren:
-oS: 32 (mit asm"") <-> 44 (ohne) ist 25% Unterschied!
-o2: 31 <-> 33 fast gleich!
Der Hack bewirkt also, dass trotz generellem Optimeren auf Grösse
die gewünschte Routine schnell wird.
GCC macht mit -oS:
Falk B. schrieb:> Ist eine OptimierungsoptimierungOptimierung. Ich würde sie> einfach weglassen.
Jau.
Zur Erklärung:
> asm ("" : "+r" (out));verhindert eine Optimierung :-)
Die Routine beginnt mit
1
uint32_tout=0;
2
if(in&(1u<<0))out|=0xful<<(4*0);
und avr-gcc macht daraus effektiv
1
uint32_tout=(in&1)?0xful:0ul;
was schlechter ist als erste 0ul zu laden und darauf ein OR 0xf zu
veranstalten.
Das asm beschreibt eine Operation, die out als Eingabe und Ausgabe hat;
gcc weiß nach dem asm also nicht mehr, dass out=0 ist, und daher kann
die o.g. "Optimierung" nicht angewandt werden.
Wenn man aber wirklich darauf angewiesen ist, dass der Code diesen Grad
an Effizienz hat, dann sollte man besser gleich die ganze Routine in
Assembler schreiben...
@Johann:
> Wenn man aber wirklich darauf angewiesen ist, dass der Code diesen Grad> an Effizienz hat, dann sollte man besser gleich die ganze Routine in> Assembler schreiben...
vollkommen d'accord.
Ob man nun 32 Takte oder 44 erreicht, ist - zumindest bei meiner
Anwendung - wirklich egal, verglichen mit ~750 unoptimiert dagegen
sicher nicht.
Das Zoomen war nur ein Hotspot...
Hab jedenfalls wieder was gelernt - danke.
Peter II schrieb:> Johann L. schrieb:>> verhindert eine Optimierung :-)>> danke für die Erläuterung.>> könnte man dann nicht gleich> uint32_t out = 0;> if (in & (1u << 0)) out = 0xful << (4*0);>> also ohne das oder.
kommt nicht ganz des gleiche heraus:
1
00000444 <variant5_mit_asm>:
2
444: 28 2f mov r18, r24 ; 1
3
446: 60 e0 ldi r22, 0x00 ; 0 ; 1
4
448: 70 e0 ldi r23, 0x00 ; 0 ; 1
5
44a: cb 01 movw r24, r22 ; 1
6
44c: 20 fd sbrc r18, 0 ; 2
7
44e: 6f 60 ori r22, 0x0F ; 15 ; 1
8
450: 21 fd sbrc r18, 1 ; 2
9
452: 60 6f ori r22, 0xF0 ; 240 ; 1
10
454: 22 fd sbrc r18, 2 ; 2
11
456: 7f 60 ori r23, 0x0F ; 15 ; 1
12
458: 23 fd sbrc r18, 3 ; 2
13
45a: 70 6f ori r23, 0xF0 ; 240 ; 1
14
45c: 24 fd sbrc r18, 4 ; 2
15
45e: 8f 60 ori r24, 0x0F ; 15 ; 1
16
460: 25 fd sbrc r18, 5 ; 2
17
462: 80 6f ori r24, 0xF0 ; 240 ; 1
18
464: 26 fd sbrc r18, 6 ; 2
19
466: 9f 60 ori r25, 0x0F ; 15 ; 1
20
468: 27 fd sbrc r18, 7 ; 2
21
46a: 90 6f ori r25, 0xF0 ; 240 ; 1
22
46c: 08 95 ret ; 4 = 32"
23
24
25
000004a2 <variant5_ohne_asm_ohne_or>:
26
4a2: 28 2f mov r18, r24 ; 1
27
4a4: 80 ff sbrs r24, 0 ; 2
28
4a6: 05 c0 rjmp .+10 ; 2
29
4a8: 6f e0 ldi r22, 0x0F ; 15 ; 1
30
4aa: 70 e0 ldi r23, 0x00 ; 0 ; 1
31
4ac: 80 e0 ldi r24, 0x00 ; 0 ; 1
32
4ae: 90 e0 ldi r25, 0x00 ; 0 ; 1
33
4b0: 03 c0 rjmp .+6 ; 2
34
4b2: 60 e0 ldi r22, 0x00 ; 0 ; 1
35
4b4: 70 e0 ldi r23, 0x00 ; 0 ; 1
36
4b6: cb 01 movw r24, r22 ; 1
37
4b8: 21 fd sbrc r18, 1 ; 2
38
4ba: 60 6f ori r22, 0xF0 ; 240 ; 1
39
4bc: 22 fd sbrc r18, 2 ; 2
40
4be: 7f 60 ori r23, 0x0F ; 15 ; 1
41
4c0: 23 fd sbrc r18, 3 ; 2
42
4c2: 70 6f ori r23, 0xF0 ; 240 ; 1
43
4c4: 24 fd sbrc r18, 4 ; 2
44
4c6: 8f 60 ori r24, 0x0F ; 15 ; 1
45
4c8: 25 fd sbrc r18, 5 ; 2
46
4ca: 80 6f ori r24, 0xF0 ; 240 ; 1
47
4cc: 26 fd sbrc r18, 6 ; 2
48
4ce: 9f 60 ori r25, 0x0F ; 15 ; 1
49
4d0: 27 fd sbrc r18, 7 ; 2
50
4d2: 90 6f ori r25, 0xF0 ; 240 ; 1
51
4d4: 08 95 ret ; 4 = 39"
In jedem Fall bin ich jedes Mal verblüfft über die Tricks des GCC.
Peter II schrieb:> könnte man dann nicht gleich
Unter -Os zumindest erzeugt das einen identischen Code.
Zumindest unter GCC 4.9.2.
GCC 6.1.1 merkt bei mir, wenn die beiden Funktionen in der gleichen
Datei stehen, dass diese Identisch sind, und ersetzt den Aufruf von
version5 mit version6 automatisch.
Nico W. schrieb:> Peter II schrieb:>> könnte man dann nicht gleich>> Unter -Os zumindest erzeugt das einen identischen Code.
hmmm - bei mir nicht (s.o.)
habe allerdings nicht allerneueste Toolchain (4.9.2) umd avrlib (1.8.2)
Nico W. schrieb:> sigma9 schrieb:>> hmmm - bei mir nicht (s.o.)>> Ich habe jetztuint32_t out = 0;> if (in & (1u << 0)) out |= 0xful << (4*0);> mituint32_t out = 0;> if (in & (1u << 0)) out = 0xful << (4*0);> verglichen.
hast Recht: "ohne asm() + ohne or" ist bei mir auch code-identisch mit
"ohne asm()" und ja, der 4.9.2 merkt nicht, dass die gleich sind.
> d = __builtin_avr_insert_bits (0xffff6666, in, 0);
aha, nach 6 kommt f
Eine andere Idee:
1
int8_tc=PORTD;//zum Test, damit nichts wegoptimiert wird
2
registerunion{uint32_ti32;uint8_tb[4];}u;
3
switch(c&3){
4
case0:u.b[0]=0x00;break;
5
case1:u.b[0]=0x0f;break;
6
case2:u.b[0]=0xf0;break;
7
case3:u.b[0]=0xff;break;
8
}
9
switch(c&12){//oder switch((in>>=2)&3){ //case 0, case 1, case 2, case 3
10
case0:u.b[1]=0x00;break;
11
case4:u.b[1]=0x0f;break;
12
case8:u.b[1]=0xf0;break;
13
case12:u.b[1]=0xff;break;
14
}
15
switch(c&48){
16
case0:u.b[2]=0x00;break;
17
case16:u.b[2]=0x0f;break;
18
case32:u.b[2]=0xf0;break;
19
case48:u.b[2]=0xff;break;
20
}
21
switch(c&192){
22
case0:u.b[3]=0x00;break;
23
case64:u.b[3]=0x0f;break;
24
case128:u.b[3]=0xf0;break;
25
case192:u.b[3]=0xff;break;
26
}
Wenn der gcc das gut optimiert (Jump-Adressberechnung aus den cases) ...
Außerdem stellt sich die Frage, ob ein uint32_t sinnvoll ist, denn er
muss ja wieder auf die einzelnen Bytes im Memory aufgeteilt werden.
Besser direkt die Bytes (mittels Pointer?) schreiben.
eProfi schrieb:>> d = __builtin_avr_insert_bits (0xffff6666, in, 0);> aha, nach 6 kommt f>
in diesem Fall schon: Input hat 7 (nicht 8!) Bits, Output daher 28
(nicht 32). Daher wollte ich in diesem Fall die obersten 4 Bits auf 0
setzen - genau das macht dieser Code.
> Eine andere Idee: int8_t c=PORTD; //zum Test, damit nichts wegoptimiert> wird> register union{uint32_t i32;uint8_t b[4];}u;> switch(c&3){> case 0:u.b[0]=0x00;break;> case 1:u.b[0]=0x0f;break;> case 2:u.b[0]=0xf0;break;> case 3:u.b[0]=0xff;break;> }> switch(c&12){ //oder switch((in>>=2)&3){ //case 0, case 1, case 2,> case 3> case 0:u.b[1]=0x00;break;> case 4:u.b[1]=0x0f;break;> case 8:u.b[1]=0xf0;break;> case 12:u.b[1]=0xff;break;> }> switch(c&48){> case 0:u.b[2]=0x00;break;> case 16:u.b[2]=0x0f;break;> case 32:u.b[2]=0xf0;break;> case 48:u.b[2]=0xff;break;> }> switch(c&192){> case 0:u.b[3]=0x00;break;> case 64:u.b[3]=0x0f;break;> case 128:u.b[3]=0xf0;break;> case 192:u.b[3]=0xff;break;> }>> Wenn der gcc das gut optimiert (Jump-Adressberechnung aus den cases) ...>
aha... das ist mal ne völlig andere Idee: werde ich probieren.
> Außerdem stellt sich die Frage, ob ein uint32_t sinnvoll ist, denn er> muss ja wieder auf die einzelnen Bytes im Memory aufgeteilt werden.> Besser direkt die Bytes (mittels Pointer?) schreiben.
ja und nein: zum Aufsammeln schon - da macht die lib alle shifts etc
schon richtig. Zum Reinschreiben sicher nicht - aber das Umcasten
(direkt oder mittels union) ist ja nix anderes als "direkt die Bytes
mittels Pointer schreiben". So machen das die (bisher) schnellsten
Lösungen ja auch.
Johann L. schrieb:> Mampf F. schrieb:>> Was ist hiermit?>> Kein gültiger C-Code :-)
Hö? Der Compiliert aber bei mir mit meinem AVRGCC ?
Was ist nicht gültig?
Danke fürs Ausprobieren!
Da muss man natürlich noch Hand anlegen!
Kann man ein Register auf den PC (Program Counter) addieren?
Habe ich nichts gefunden, nur über den Umweg Z-Register jmp(z).
IJMP Indirect Jump to (Z) PC ← Z None 2
aber wie kriegt man den PC ins Z? Push PC, Pop Z
@mampf, ja, auch eine sehr gute Idee!
> uint32_t result = 0;
= 0 ist überflüssig
> *rptr = lut[in & 0xf];> rptr++; in >>= 4;> *rptr = lut[in & 0xf];
besser / kürzer:
*rptr++ = lut[in & 15];
*rptr = lut[in >> 4];
Oder mit einer union (weiß nicht, ob das register was bringt, lieber den
Hack von oben probieren):
register union{uint32_t L;uint16_t w[2];}u;
u.w[0]=lut[in & 15];
u.w[1]=lut[in >> 4];
return u.L;
Oder wenn der GCC sich wegen der uint16_t recht anstellt:
uint8_t lut0[16]={
0x00, 0x0f, 0xf0, 0xff, 0x00, 0x0f, 0xf0, 0xff,
0x00, 0x0f, 0xf0, 0xff, 0x00, 0x0f, 0xf0, 0xff
};
uint8_t lut1[16]={
0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f,
0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff
};
register union{uint32_t L; uint16_t b[4];}u; uint8_t tmp;
u.b[0]=lut0[tmp = in & 15];
u.b[1]=lut1[tmp];
u.b[2]=lut0[tmp = in >> 4];
u.b[3]=lut1[tmp];
return u.L;
Die 32 Cycles schaffen wir damit aber nicht.
Die Skip-Befehle haben es schon in sich!
Mampf F. schrieb:> Johann L. schrieb:>> Mampf F. schrieb:>>> Was ist hiermit?>>>> Kein gültiger C-Code :-)>> Hö? Der Compiliert aber bei mir mit meinem AVRGCC ?>> Was ist nicht gültig?
>> sollte der GCC als ein Sprung umsetzen. Kostet halt 512byte Flash.
Wird gerne auch als LUT umgesetzt, und die liegt dam im RAM *autsch*
(außer für AVR_TINY oder mit -fno-tree-switch-conversion). Und die
Metrik des TO ist -Os, was durch die LUT oder Jump-Tabelle komplett
geshreddert wird :-)
Und der o.g. Code von
Beitrag "Re: Bitmanipulation beschleunigen"
wird ja noch etwas kleiner, da nur 7 Bits benötigt werden, d.h.
> if (in & (1u << 7)) out |= 0xful << (4*7);
kann entfallen.
> Kostet halt 512byte Flash.
Oh nein, das reicht nicht. Wir brauchen ja 4 Bytes pro Eintrag, und die
müssen geladen werden und einen Absprung brauchen wir ja auch für jeden
Case, also 256*(8+2)=2,5 K.
Da ist eine echte Table (im progmem oder wie das heißt) besser:
uint32 lut[256] PROGMEM ={0x00000000, 0x0000000f, 0x000000f0,
0x000000ff,
.....}
return lut[in];
Kurze Zwischenfrage, wenn viele Experten hier "zusammensitzen":
Ich habe auf diesem PC nur einen uralten (2010) GCC 4.4.4 drauf.
Vielleicht ist das bei moderneren besser gelöst:
#define MOSI (1<<PCx)
for(uint8_t m=128;m;DDRD&m?PORTC|=MOSI:(PORTC&=~MOSI),m=m/2);
GCC checkt genau, was ich will, übersetzt es aber sehr umständlich, er
nimmt einen 16-bit Schleifenzähler und zählt von 0000 bis 0008.
1
e2a: 90 e8 ldi r25, 0x80 ; 128
2
e2c: 20 e0 ldi r18, 0x00 ; 0 LoopCntL
3
e2e: 30 e0 ldi r19, 0x00 ; 0 LoopCntH
4
e30: 8a b1 in r24, 0x0a ; 10
5
e32: 89 23 and r24, r25
6
e34: 09 f4 brne .+2 ; 0xe38 <__stack+0x539>
7
e36: 21 c1 rjmp .+578 ; 0x107a <__stack+0x77b>
8
e38: 47 9a sbi 0x08, 7 ; 8
9
e3a: 88 b1 in r24, 0x08 ; 8
10
e3c: 96 95 lsr r25 ;m>>=1
11
e3e: 2f 5f subi r18, 0xFF ; 255 CntL++
12
e40: 3f 4f sbci r19, 0xFF ; 255 CntH++
13
e42: 28 30 cpi r18, 0x08 ; 8 8 Loops?
14
e44: 31 05 cpc r19, r1 ;r1 ist ZERO
15
e46: a1 f7 brne .-24 ; 0xe30 <__stack+0x531>
16
...
17
107a: 47 98 cbi 0x08, 7 ; 8
18
107c: 88 b1 in r24, 0x08 ; 8
19
107e: de ce rjmp .-580 ; 0xe3c <__stack+0x53d>
Anstatt dass er die Maske m mit als "Zähler" verwendet und nach dem
LSR m gleich mit bne zurückspringt.
Mag das bitte jemand mit aktueller GCC-Version für mich probieren?
@eProfi:
>> Die 32 Cycles schaffen wir damit aber nicht.> Die Skip-Befehle haben es schon in sich!
Die LUT mit 16/32 Bytes wäre zu tolerieren, doch es gibt für mich ein
anderes Problem: ich brauche diesen Code nicht nur für 4-fach aufblähen
(danach hatte ich zu Anfang gefragt), sondern auch für 3 und 2. Die (bis
jetzt) Rekordlösung von Johann hat zudem den Charme, dass sie sich ganz
leicht auch für 3 oder 2 modifizieren lässt. Alle hier diskutierten
Varianten mit LUT leider nicht - die nutzen aus, dass 4 = 8/2 ist.
Im Moment tendiere ich daher zu dieser.
@alle:
klar ist ne Tabelle mit einem 7-bit Index unschlagbar hinsichtlich
Geschwindigkeit - aber auch viel zu gross für mich.
@Johann:
Ich liebe auch den Begriff kaputtoptimieren.
@alle,leicht OT:
Ich bin dankbar für die hier gebotene Hilfe der Schwarm-Intelligenz.
Allerdings stelle ich mir immer wieder die Frage, wie ich meine eigenen
Fähigkeiten verbessern könnte. Viel hat mir geholfen, den Sourcecode
eines nicht-trivialen Programmes zu studieren, ich erwähne als Beispiel
einen TCP/IP-Stack. Den habe ich solange {angeschaut, modifiziert, durch
den Compiler gejagt}loop, bis ich (denke ich...) jede Zeile verstanden
hatte.
Frage also: wo finde ich anderen Quellcode, der frei ist, zum AVR passt
und an dem man was lernen kann? Irgendwelche Vorschläge?
sigma9 schrieb:> Ich bin dankbar für die hier gebotene Hilfe der Schwarm-Intelligenz.> Allerdings stelle ich mir immer wieder die Frage, wie ich meine eigenen> Fähigkeiten verbessern könnte.
Naja, das ist so eine Sache. Es gibt immer wieder irgendwelche
hochoptimierten Lösungen - zum Glück werden sie rel. selten tatsächlich
benötigt. Wenn sie nötig sind, muss man danach suchen - meistens sind
sie aber gar nicht erforderlich. Ich beschäftige mich dann damit, wenn
Engpässe auftauchen. Manchmal macht es auch einfach Spass, ohne jeden
Nutzen. Ist auch ein Argument, allerdings nicht wirtschaftlich. Wenn es
rein passt und ich mit der Laufzeit leben kann, ist die Lösung die
beste, die ich am schnellsten hingeschrieben haben oder die erste
Optimierung, die die Anforderungen erschlägt. Man kann sich da endlos
verzetteln, ist aber akademisch. Man fühlt sich gut, erreicht
letztendlich aber nur, dass statt x% x+y% im Leerlauf verbracht werden.
Nutzen Null, Aufwand gross. Persönliche "Investitionen" in den grossen
Überblick sind hilfreicher als gesuchte und vielleicht gefundene
Optimierungen in Kleinigkeiten.
Ich erinnere mich noch gut an meine Z80-Zeit, Thema sich selbst
modifizierende Programme. Damit konnte man scheinbar unmögliches möglich
machen, Speicherbedarf und Laufzeit war oft genug ein Thema - da konnte
man sich richtig austoben. Heute nimmt man einfach den ausreichenden
Prozessor, fertig. Einen gewissen Bedarf an Optimierungen gibts, wenn
man auf fertiger Hardware irgendwas nachträglich reinbasteln muss. Und
da muss man wieder unterscheiden: ist es Hobby oder Geschäft?
H.Joachim S. schrieb:> Naja, das ist so eine Sache. Es gibt immer wieder irgendwelche> hochoptimierten Lösungen - zum Glück werden sie rel. selten tatsächlich> benötigt. Wenn sie nötig sind, muss man danach suchen - meistens sind> sie aber gar nicht erforderlich.
Völlig einverstanden. Im hier diskutierten Fall habe ich bei 800 Takten
angefangen: zääääääher Display-Aufbau, nicht zu gebrauchen. Ob ich nun
bei 45 (optimiert) oder 32 (hochoptimiert) ende, ist idT nur von
akademischem Interesse und der Kitzel, herausfinden, was geht.
> Ich beschäftige mich dann damit, wenn> Engpässe auftauchen. Manchmal macht es auch einfach Spass, ohne jeden> Nutzen.
Meine Erfahrung ist: ja, es macht Spaß, aber es ist eben nicht ohne
jeden Nutzen. Kurzfristig mag das so aussehen, aber es kommt immer der
Tag, an dem einem eine früher (mit Spaß!) erworbene Erkenntnis den A.sch
rettet.
> Ist auch ein Argument, allerdings nicht wirtschaftlich. Wenn es> rein passt und ich mit der Laufzeit leben kann, ist die Lösung die> beste, die ich am schnellsten hingeschrieben haben oder die erste> Optimierung, die die Anforderungen erschlägt.
Korrekt - leider. Dieses Denken ist weit verbreitet und auch
nachvollziehbar. Nur: wirkliche neue Erkenntnisse/Ergebnisse gibt es
damit nicht. Man lese zB über die Entwicklung des ersten Transistors bei
IBM - hoch spannend.
> Man kann sich da endlos> verzetteln, ist aber akademisch. Man fühlt sich gut, erreicht> letztendlich aber nur, dass statt x% x+y% im Leerlauf verbracht werden.> Nutzen Null, Aufwand gross. Persönliche "Investitionen" in den grossen> Überblick sind hilfreicher als gesuchte und vielleicht gefundene> Optimierungen in Kleinigkeiten.
Zustimmung.
> Ich erinnere mich noch gut an meine Z80-Zeit, Thema sich selbst> modifizierende Programme. Damit konnte man scheinbar unmögliches möglich> machen, Speicherbedarf und Laufzeit war oft genug ein Thema - da konnte> man sich richtig austoben.
Oh ja: ich erinnere mich, dass ich riesige Z80-ASM Sourcen
umstrukturiert habe, um möglichst viele JP durch JR ersetzen, bringt
jeweils EIN Byte Speicher. Die logische Struktur der Programme ging
dabei völlig vor die Hunde.
> Heute nimmt man einfach den ausreichenden> Prozessor, fertig. Einen gewissen Bedarf an Optimierungen gibts, wenn> man auf fertiger Hardware irgendwas nachträglich reinbasteln muss. Und> da muss man wieder unterscheiden: ist es Hobby oder Geschäft?
Auch wahr. Heute hat niemand mehr Zeit=Geld, etwas wirklich zu
verstehen.
Dass dabei riesige Verluste entstehen, nur weil das Rad jedes Mal neu
erfunden werden muss - egal, Hauptsache der Quartalsbericht stimmt.
Trotzdem kann ich für mich privat (Hobby) den Anspruch haben, Zeit
"sinnlos" zu investieren und etwas zu lernen/verstehen. Dahin ging meine
Frage.
/s
Tim T. schrieb:> Also ohne groß drüber nachzudenken:> uint32_t variant(uint8_t input) {>
....
> +00000034: 01CA MOVW R24,R20> +00000035: 9508 RET>> Sind dann mit Rücksprung 25 Takte...
na also: ist der GCC als doch intelligent genug zu erkennen, dass
trotz 32bit Variable über 4 Register beim verodern mit solchen
Konstanten jeweils nur ein Register angefasst werden muss - nichts
anderes erreichen die anderen Lösungen (z.B. mit type casting oder
union) von Hand.
Sehr schön. Nur Nachteil für mich (ja, ich weiß, neue Bedingungen, nicht
im TO): ich brauche das auch für 3-fach und 2-fach aufblähen.
Johanns Lösung ist portierbar und bleibt bei ca 35 Takten.
Trotzdem Danke für den Beitrag: hält jetzt den Rekord für das 4er
Problem
Tim T. schrieb:> Also ohne groß drüber nachzudenken:>> uint32_t variant(uint8_t input) {> ...> }
Und worin liegt jetzt der wesentliche Unterschied zu Johanns Code?
Johann L. schrieb:> In function 'variant1':> warning: dereferencing type-punned pointer might break strict-aliasing> rules [-Wstrict-aliasing]> uint16_t *rptr = (uint16_t*) &result;> ^~~~~~~~
Ach, das ist doch nur für Leute, die nicht wissen, was sie tun ;-)
Mampf F. schrieb:> Johann L. schrieb:>> In function 'variant1':>> warning: dereferencing type-punned pointer might break strict-aliasing>> rules [-Wstrict-aliasing]>> uint16_t *rptr = (uint16_t*) &result;>> ^~~~~~~~>> Ach, das ist doch nur für Leute, die nicht wissen, was sie tun ;-)
...und wenn man statt type-casting beides in eine union steckt,
resultiert das gleiche (siehe andere Lösungen) und der Compiler ist auch
glücklich.
sigma9 schrieb:> Kurzfristig mag das so aussehen, aber es kommt immer der> Tag, an dem einem eine früher (mit Spaß!) erworbene Erkenntnis den A.sch> rettet.
Exakt dieses Problem wirst du wahrscheinlich nie wieder haben bzw. wenn
du es irgendwann mal wieder brauchst die Lösung zumindest halb vergessen
:-).
Wichtiger ist die Erkenntnis, dass im Zweifelsfall (fast immer) noch was
geht wenn es nötig wird.