PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Compiler Multiplikation verbieten



Siro
01.11.2018, 08:45
Hallo zusammen,

in einem kleinen 8 Bitter ist eine Multiplikation ja nicht grad optimal, da sie in Software ausgeführt wird.
Wenn ich nun einen Wert mit 3 multiplizieren möchte, habe ich ja so einiges an Möglichkeiten.
Ich habe mal verschiedene Möglichkeiten compiliert und war doch sehr erstaunt über das Ergebnis:
Um den Compiler auszutricksen habe ich folgenden Code verwendet und genau der ist laufzeitmässig der absolut Schlechteste:

Compiler ist der XC8 Version V2.00

char cnt;


cnt = cnt + cnt + cnt; // 57 Zyklen

So wie ich es in Assembler gemacht hätte, hat auch der Compiler die beste Laufzeit.

cnt = (cnt << 1) + cnt; // 5 Zyklen

Hier mal die Übersicht:

cnt *=3; // 45 Zyklen
cnt = (cnt << 1) + cnt; // 5 Zyklen
cnt = cnt + cnt + cnt; // 57 Zyklen
cnt += (cnt + cnt); // 56 Zyklen


Ich möchte halt vermeiden, dass der schlaue C-Compiler mir eine Multiplikation erzeugt.
Kann man das noch irgendwie steuern ?

Siro

Wie so oft macht der C-Compiler nicht das was da steht, er konzentriert sich nur auf das Ergebnis. Hier fehlt eigentlich eine Option: Bitte das so kodieren wie es dort steht. Natürlich ironisch gemeint....;)

Klebwax
01.11.2018, 17:31
Ein paar Sachen fallen mir dazu ein, die die Laufzeit bei einem 8-Bitter beeinflussen könnte.

Was für ein Typ ist cnt? Unsigned oder signed? Und welche Größe hat es? Char oder Int? Und zu guter letzt, ist cnt lokal oder global?


Wie so oft macht der C-Compiler nicht das was da steht, er konzentriert sich nur auf das Ergebnis. Hier fehlt eigentlich eine Option: Bitte das so kodieren wie es dort steht. Natürlich ironisch gemeint....

Alle von dir gezeigten Ausdrücke brauchen eigentlich eine zusätzliche temporäre Variable, da es mathematische Operationen mit 3 Operanden nicht gibt. Der Compiler erledigt das schon richtig, Seine Strategie mag da aber nicht so leicht zu erkennen sein. Lös das mal in mehrere Zeilen so auf, daß rechts nur eine Operation steht. Dann bleibt dem Compiler nichts anderes übrig, genau das zu tun, was du hinschreibst. So als Beispiel




t = cnt + cnt;
cnt = cnt + t;


MfG Klebwax

Siro
01.11.2018, 18:08
Hallo Klebwax,
ertsmal Danke für deine Info:

ich habe deinen Code ausprobiert.
Ich habe beide Variablen als "unsigned char" definiert. GLOBAL
ohne volatile klaut er den gesamten Code, völlig okay, weil er ihn ja nicht braucht
Er ruft schon bei der ersten Zeile ein Unterprogramm (Multiplier) auf.

33 Zyklen

volatile unsigned char t,cnt;

void main(void)
{
t = cnt + cnt; // hier ruft er wieder eine Multiplikation auf.
cnt = cnt + t;
}


so macht er aber besseren Code ohne Multiplikation: 15 Zyklen


t = cnt;
t = t + cnt;
cnt = t + cnt;

Ich hoffe, das hängt nicht mit der Lizenz zusammen, da ich ja nur die freie Version vom XC8 benutze...:p

Klebwax
01.11.2018, 18:32
Er ruft schon bei der ersten Zeile ein Unterprogramm (Multiplier) auf.

33 Zyklen

volatile unsigned char t,cnt;

void main(void)
{
t = cnt + cnt; // hier ruft er wieder eine Multiplikation auf.
cnt = cnt + t;
}


Ist schon lustig, daß der Compiler in cnt + cnt 2*cnt erkennt. Ich hab anderes ähnliches gefunden: für t = 1 erzeugt er clear t und dann inc t.



so macht er aber besseren Code ohne Multiplikation: 15 Zyklen


t = cnt;
t = t + cnt;
cnt = t + cnt;
Das wäre das, was ich in Assembler schreiben würde, wobei t dann der Akku wäre.

MfG Klebwax

Siro
01.11.2018, 18:44
Die Algorithmen sind teils schon lustig.
für dein Beispiel t=1 kann er alles direkt in den Registern abarbeiten. Er muss dazu keine Konstante (die 1) aus dem Speicher laden. So ist es bei einigen Prozessoren eventuell schneller.

Ein echtes "Steuern" für den Code scheint aber nicht direkt möglich zu sein, bzw. nicht sicher nachvollziehbar.
Am schnellsten geht es in diesem Falle (unsigned multiplikation mit 3) immer noch mit
cnt = (cnt << 1) + cnt;

mit signed geht das natürlich dann nicht mehr.

Siro

HaWe
02.11.2018, 05:57
moinmoin!
für cnt=3 geht das ntl mit 1x shiften und 1x addieren, aber für cnt >= 4 brauchst du ja wieder Zähler und/oder eine Fallunterscheidung ob cnt gerade oer ungerade.


Aber nur interessehalber,
was macht er per

for(int i=1; i<cnt;i++) t+=x;

:?:

Siro
02.11.2018, 06:14
Guten Morgen,
hab ich auch mal getestet:


for(i=0; i<3;i++) cnt+=cnt; // 44 Zyklen, normaler Schleifencode, wenn i ein unsigned char ist

for(int i=0; i<3;i++) cnt+=cnt; // 76 Zyklen, normaler Schleifencode, weil i ein int ist,

wobei mir grad auffällt, da kommt ja was anderes raus...

Siro

HaWe
02.11.2018, 06:32
Hatte mich vertippt
Ntl
... t+=x

cnt ist bei mir der Multiplikator

PS
Welche Compileroptimierungsstufe hast du?
Offenbar musst den Schleifenzähler wirklich auch global definieren

Siro
02.11.2018, 06:36
t=cnt;
for(i=0; i<2;i++) cnt+=t;


hier benötigt er 34 Zyklen.

The current licence does not permit the selected optimization level, using level -O1 :(

HaWe
02.11.2018, 06:42
Edit
Stimmt
Hatte mich vertan

cnt war bei mir der Multiplikator
x der Multiplikant
t das Ergebnis

Also anstelle
t=x*cnt
Alternativ:


int i, x, cnt;
t=x;
for( i=1; i<cnt;i++) t+=x;

Siro
02.11.2018, 06:53
Fazit:
Es lonht sich in jedem Falle, ab und zu in den Assembler Code reinzuschauen, sofern es notwenidig ist und man zeitkritische Anpassungen machen muss.

Siro

PS: zudem ist mir heute das zweite Mal die IDE abgestürzt. Sie lässt sich vermeintlich im Taskmanager beenden, das tut sie aber nicht.
Ich muss tatsächlich den Rechner neu starten. Immer beim Umstellen der Projekt Properties.

HaWe
02.11.2018, 06:58
(PS: sry, handy ist blöd zum schreiben ;) )

Siro
15.08.2020, 16:28
Hallo zusammen,
ein kleines Update der beschriebenen Problematik
"Multiplikation verhindern"

Ich wollte eben einen kleines PIC Progrämmchen schreiben
und bin unter anderem wieder über die Multiplikation mit 3 gestoßen.
Der ursprünglich "erfolgreiche" Code mit
count = (count << 1) + count;
war ja am kürzesten,
doch plötzlich weigert sich der Compiler einen entsprechenden Code zu erzeugen.
er benutzt generell ein Unterprogramm mit einer Multiplikation...

Nach einigem herumprobieren habe ich nun festgestellt, dass ich eine neue Version vom XC8 auf dem Rechner habe
und genau da liegt das Problem.

V2.00 erzeugt "guten" kompakten Code
V2.10 ungünstig, Berechnung generell über Unterprammaufruf Multiplikation
V2.20 ungünstig, Berechnung generell über Unterprammaufruf Multiplikation

Ich habe es auch mit verschiedenen Optimierungseinstellungen probiert,
leider bisher ohne Erfolg.

Anbei:
hat sich auch mein Ausschiebecode für meine RGB LEDs verändert und funktioniert auch nicht mehr.
Hier treten plötzlich erheblich längere Zeiten auf, weil der erzeugt Code anders und langsamer ist.

Das stellt natürlich generell ein riesen Problem dar:
Wenn man solch zeitkritischen Code in C (oder generell in einer Hochsprache) implementiert
weiss man nicht was der Compiler daraus macht.
Durch ein Compilerupdate hat dies unter Umständen katastrophale Folgen.

Im Prinzip holt mich ein "ehemaliges" Problem wieder ein. Siehe Thread:
https://www.roboternetz.de/community/threads/70216-XC8-inline-Assembler
Da hatte ich meinen Assembler Code in C umgesetzt....




Compiler ist der XC8 Version V2.00
! // damit keine Multiplikation verwendet wird:
! count = (count << 1) + count; // 3 Bytes pro Led RGB
0x463: LSLF count, W
0x464: ADDWF count, W
0x465: MOVWF __pcstackCOMMON
0x466: MOVF __pcstackCOMMON, W
0x467: MOVWF count
-------------------
Compiler ist der XC8 Version V2.10
79: // damit keine Multiplikation verwendet wird:
80: count = (count << 1) + count; // 3 Bytes pro Led RGB !! er nimmt einen Multiplikations
001F 3003 MOVLW 0x3
0020 00C6 MOVWF 0x46
0021 0846 MOVF 0x46, W
0022 00C2 MOVWF multiplicand
0023 084B MOVF count, W
0024 20A4 CALL 0xA4
0025 00C7 MOVWF 0x47
0026 0847 MOVF 0x47, W
0027 00CB MOVWF count
------------------------------
Compiler ist der XC8 Version V2.20


305: // damit keine Multiplikation verwendet wird:
306: count = (count << 1) + count; // 3 Bytes pro Led RGB
03A9 3003 MOVLW 0x3
03AA 00F4 MOVWF i
03AB 0874 MOVF i, W
03AC 00F0 MOVWF __pcstackCOMMON
03AD 087A MOVF count, W
03AE 3185 MOVLP 0x5
03AF 25D0 CALL 0x5D0
03B0 3183 MOVLP 0x3
03B1 00F5 MOVWF counter
03B2 0875 MOVF counter, W
03B3 00FA MOVWF count



Siro

Moppi
15.08.2020, 18:54
Hallo Siro!

Mal anders gedacht:
Kannst Du nicht an der einen Stelle Inline-Assembler nutzen?

MfG

Siro
15.08.2020, 19:57
Ja Moppi, das kann ich natürlich und das mache ich jetzt auch.
Assembler scheint aber mehr oder minder verpöhnt innerhalb einer Hochsprache und darum versuche ich es auch, soweit als möglich, zu vermeiden.

Das Meiste kann man ja wirklich in Hochsprache erledigen, aber manchmal geht das halt doch nicht.

Ich war aber wirklich erstaunt, dass die neue Compilerversion doch so erheblich unterschiedlichen Code erzeugt.
Die Compilerbauer haben ja jegliche Freiheit, es geht nur darum den Code von Hochsprache auf Assemblercode umzusetzen,
wie er das macht ist nicht unbedingt vorgeschrieben. Der Code ist ja funktionell auch richtig.
nur das Timing halt nicht. Durch die eng gesteckten Parameter bei den WS2812 RGB Leds wird das tatsächlich zum Problem..
Die Multiplikation wollte ich natürlich auch vermeiden, da mein kleiner PIC nur 512 Byte bzw. Programwords hat.

Jetzt zu versuchen den C Code wieder so hinzufrickeln dass er evtl. keine Multiplikation daraus macht, gibt nicht wirklich Sinn,
beim nächsten Update könnte das dann wieder anders aussehen. Daher also "back to the roots (Assembler)" ;)

[nächtliches edit]
Nun kann ich mit dem 6 poligen PIC10F322 auch RGB Leds steuern. Timing stimmt dank ASM.

Siro

Moppi
16.08.2020, 08:06
nur das Timing halt nicht. Durch die eng gesteckten Parameter bei den WS2812 RGB Leds wird das tatsächlich zum Problem..
Die Multiplikation wollte ich natürlich auch vermeiden, da mein kleiner PIC nur 512 Byte bzw. Programwords hat.

10 Jahre habe ich nur Maschinensprache und mit Assembler programmiert. Das sind die typischen Pro's für Assembler. Also auch Inline.
Übrigens habe ich noch nie davon gehört, dass Inline-Assembler verpönt sei. Ich hatte in den 90gern viel Kontakt auch zu Mitarbeitern bei SAP und habe mich da also viel in der Sphäre "rumgetrieben", inkl. Usenet. Ich habe das eigentlich gegenteilig kennengelernt, nämlich genau so, dass man dann sehr wohl Inline-Assembler auch nutzt. Kann vielleicht sein, dass sich das im Laufe der Zeit etwas geändert hat, weil heute viel systemübergreifend gemacht wird. Damals waren Cross-Compiler die Ausnahme.

MfG

HaWe
16.08.2020, 09:43
interessant....!
was macht/braucht er hier...?

t = cnt;
cnt+=cnt;
cnt+=t;

PS,
gibt es evt compiler options zur Optimierung, die man disablen kann ( -O0 ) ?

Siro
16.08.2020, 10:18
@HaWe:

XC8 Compiler V2.00

t = cnt;
0x8E: MOVF cnt, W
0x8F: MOVWF 0x51
0x90: MOVF 0x51, W
cnt+=cnt;
0x92: MOVF cnt, W
0x93: MOVWF 0x51
0x94: MOVF 0x51, W
0x95: ADDWF cnt, F
cnt+=t;
0x96: MOVF t, W
0x97: MOVWF 0x51
0x98: MOVF 0x51, W
0x99: ADDWF cnt, F
---------------------------
XC8 Compiler V2.10

t = cnt;
0x89: MOVF cnt, W
0x8A: MOVWF 0x51
0x8B: MOVF 0x51, W
0x8C: MOVWF t
cnt+=cnt;
0x8D: MOVF cnt, W
0x8E: MOVWF 0x51
0x8F: MOVF 0x51, W
0x90: ADDWF cnt, F
cnt+=t;
0x91: MOVF t, W
0x92: MOVWF 0x51
0x93: MOVF 0x51, W
0x94: ADDWF cnt, F
-------------------------

Compiler XC8 V2.20
compiliert meinen inline Assembler Code garnicht mehr, er meckert....
sehr merkwürdig.
-------------------------


übrigens: einen Wert * 3 geht auch so: Manuelle Codierung :)
cnt = cnt * 3;

asm("movf _cnt,W");
asm("addwf _cnt,F");
asm("addwf _cnt,F");

man benötigt nichteinmal eine Zwischenvariable

HaWe
16.08.2020, 12:27
@Siro
und das bedeutet jetzt...was?

Siro
16.08.2020, 13:12
Fazit:
Man weis nicht genau was für ein Code vom Compiler erzeugt wird
und der kann, je nach Version, auch völlig unterschiedlich ausfallen,
was im Normalfall aber keine wirkliche Rolle spielt. Funktional sollte man sich darauf verlassen können.
Lediglich bei sehr zeitkritischen Anwendungen und/oder wenn man Code sparen muss, sollte man sich unter Umständen den erzeugten Code mal genauer anschauen.
Ein Garant für gleichen Code bzw. Laufzeiten bei unterschiedlichen Compiler Versionen ist nicht gegeben.

Achja, wenn man Softwaredokumentation bzw. Abnahmen für Geräte benötigt, ist es in sicherheitsrelevanten Anwendungen
zwingend notwendig die gesamte Entwicklungsumgebung "einzufrieren" da durch ein Update nicht sichergestellt ist,
dass anhand der Sourcen ein identischer Code wieder hergestellt werden kann.

Siro

HaWe
16.08.2020, 13:22
ich meinte:
wie ist die Performance dieses C-Codes im Vergleich zu bisherigen C-Codes, um Multiplikation zu vermeiden?

t = cnt;
cnt+=cnt;
cnt+=t;

Siro
16.08.2020, 17:48
Zur Performance habe ich nochmal einige Tests durchgeführt:
unsigned char cnt; // 8 Bit ohne Vorzeichen

----------------
cnt = (cnt << 1) + cnt;
17 Zyklen bei XC8 V2.00
102 Zyklen bei XC8 V2.10

cnt*=3;
46 Zyklen bei XC8 V2.00
46 Zyklen bei XC8 V2.10

HaWe-Code:
t = cnt;
cnt+=cnt;
cnt+=t;
16 Zyklen bei XC8 V2.00
12 Zyklen bei XC8 V2.10

Siro's Assemblercode: Compiler Version egal
asm("movf _cnt,W");
asm("addwf _cnt,F");
asm("addwf _cnt,F");
3 Zyklen

Siro