Hallo zusammen,
neulich in einem Projekt (C++): es wurde ein Bottleneck ausgemacht, und
zwar in der naiven Realisierung einer Skalierungsfunktion scale():
uint8_t:[min, max] -> [0, 100]:
1
uint8_tscale(uint8_tvalue,uint8_tmin,uint8_tmax){
2
return((value-min)*100)/(max-min);
3
}
Nachdem das klar war, wurde zunächst behauptet, dass man das nun in
AVR-Assembler codieren müsse. Alle Einwände, doch zunächst ggf. andere
Algorithmen in Betracht zu ziehen, wurden ignoriert.
Nun zur Frage, weil ich selbst kein AVR-Assembler-Experte bin: wer kann
diese simple Skalierung in Assembler wesentlich performanter
(CPU-Zyklen) machen? Alle (auch die fiesesten) Tricks sind erlaubt! Das
ganze muss allerdings als (C)-Funktion aufrufbar sein, um als direkter
Ersatz zu dienen.
Wer möchte, kann dies auch einfach als Coding-Challenge auffassen ...
Sorry, ich kann ja nur C++ ... Ich bin mir sicher, dass die
Assemblerfraktion über dieses Problemchen nur müde lächelt!
Schon jetzt danke für Eure Hilfe!
so viel potenzial sehe ich da nicht. Wenn min und max konstanten sind
könnte man etwas machen.
An der stelle frage ich mich viel mehr, warum so eine Funktion so oft
aufgerufen wird, das sie ein Bottleneck ist. Da sollte man viel mehr
darüber nachdenken die Umrechnung gar nicht erst zu machen.
Muss das so dynamisch sein? Können min / max als Konstanten zur
Compilezeit festgelegt werden? Werden viele Aufrufe mit gleichem min /
max nacheinander durchgeführt? Könnte man min max also für eine große
Anzahl von Aufrufen vorher nur einmal übergeben?
Wenn min und max über viele (alle?) Aufrufe hinweg gleich bleiben und es
schnell sein muss würde ich auch mal über ne Lookup Tabelle nachdenken.
Sind ja maximal nur 256 Werte.
Tipps wurden dir jetzt genügend genannt. Keiner davon ist auf assembler
beschränkt, das geht alles mit c(++).
Also lieber TO, jetzt ist das DEIN coding-challenge
Zeig uns was du kannst!
:-)
FrickelFranz schrieb:> Muss das so dynamisch sein? Können min / max als Konstanten zur> Compilezeit festgelegt werden?
min und max sind bezogen auf einen Aufruf zwar konstant, d.h. sie
entstammen aus festen Konfigurationswerten (je Modul), es kommen aber
ganz unterschiedliche Konfigurationen in einem Compilat vor.
> Werden viele Aufrufe mit gleichem min /> max nacheinander durchgeführt?
ja
> Könnte man min max also für eine große> Anzahl von Aufrufen vorher nur einmal übergeben?
Das wäre dann eine stateful-Lösung: naja, ungern.
Peter II schrieb:> so viel potenzial sehe ich da nicht. Wenn min und max konstanten sind> könnte man etwas machen.>> An der stelle frage ich mich viel mehr, warum so eine Funktion so oft> aufgerufen wird, das sie ein Bottleneck ist. Da sollte man viel mehr> darüber nachdenken die Umrechnung gar nicht erst zu machen.
Das stimmt (hatte ich oben eigentlich gesagt), doch die Frage war hier
nach einem direkten Assembler Ersatz.
Naja. Wenn es wirklich ein Bottleneck ist und da sich eh alles im 8Bit
Raum, und vielleicht sogar <100 abspielt, würde ich bei der Division mit
einer Tabelle arbeiten.
return ( (value - min) * table[max - min] / 256);
Meine Erfahrung bei so einem Beispiel ist, dass es in Assembler 100%
genauso schnell wie in C geht. Es gibt ein paar Außnahmen, wo es in
Assembler etwas schneller wird. Hier wahrscheinlich nicht.
Detlev T. schrieb:> Ich vermute einmal, max und min ändern sich nicht ständig. Wie wäre es> mit einer Tabelle für die 256 Möglichkeiten von value? (Geht auch in> C++)
Klar geht auch Lookup-Tabelle. Im Flash, ins RAM kopieren. Trade-off:
Speed <-> Space?
Wilhelm M. schrieb:> Das stimmt (hatte ich oben eigentlich gesagt), doch die Frage war hier> nach einem direkten Assembler Ersatz.
aber was bring ein Assembler Ersatz der nicht schneller ist?
Der Andere schrieb:> Tipps wurden dir jetzt genügend genannt. Keiner davon ist auf assembler> beschränkt, das geht alles mit c(++).> Also lieber TO, jetzt ist das DEIN coding-challenge>> Zeig uns was du kannst!>> :-)
Bezogen auf die Posts vor Deinem, wurde hier nur Lookup-Tabelle und
min/max als Konstanten genannt. Damit habe ich kein Problem ... meine
Lösung kommt, sobald ich mal eine Assembler-Lösung als baseline habe.
Wilhelm M. schrieb:> Sorry, ich kann ja nur C++
Ich sehe da oben sogar nur C.
Übrigens: Wenn es möglich ist, vor die Definition der Funktion das
Wörtchen "static" zu schreiben, dann tue es. Dann wird der gcc bzgl.
Inlining wesentlich aggressiver.
Das kann ich übrigens generell empfehlen: Funktionen, die nur in einer
Übersetzungseinheit verwendet werden, sollten man auch immer static
definieren. Das bringt für den Compiler dann ganz andere
Optimierungsmöglichkeiten.
(Okay, bei LTO ist es wieder schnuppe, aber nur dann).
Peter II schrieb:> Wilhelm M. schrieb:>> Das stimmt (hatte ich oben eigentlich gesagt), doch die Frage war hier>> nach einem direkten Assembler Ersatz.>> aber was bring ein Assembler Ersatz der nicht schneller ist?
Natürlich nix! Es geht ja nur darum zu schauen, ob eine Assembler-Lösung
schneller sein kann.
Ich gehe sogar noch einen Schritt weiter als static: Für sowas könnte
man auch die Funktion böse mit 'inline' in den Header packen, Variablen
an Register binden. Braucht alles kein Assembler, aber kann den Overead
des Aufrufs optimieren.
Frank M. schrieb:> Wilhelm M. schrieb:>> Sorry, ich kann ja nur C++>> Ich sehe da oben sogar nur C.
In diesem Fall identisch ...
> Übrigens: Wenn es möglich ist, vor die Definition der Funktion das> Wörtchen "static" zu schreiben, dann tue es. Dann wird der gcc bzgl.> Inlining wesentlich aggressiver.
Auch klar, es ging aber hier eher um den Körper der Funktion ...
FrickelFranz schrieb:> Ich gehe sogar noch einen Schritt weiter als static: Für sowas könnte> man auch die Funktion böse mit 'inline' in den Header packen, Variablen> an Register binden. Braucht alles kein Assembler, aber kann den Overead> des Aufrufs optimieren.
Auch ok. Würdest Du das mal ausformulieren?
FrickelFranz schrieb:> Braucht alles kein Assembler, aber kann den Overead> des Aufrufs optimieren.
aber wie viel macht der Aufruf im Verhältnis zu Division aus? 0.1%??
Wilhelm M. schrieb:> Auch klar, es ging aber hier eher um den Körper der Funktion ...
Ehrlich gesagt: Seitdem ich die STM32 für mich entdeckt habe, kommen mir
solche Problemstellungen wie "Da ist ein Bottleneck im AVR-Code, das
dringend mit Assembler gefixt werden muss" einfach nur noch lächerlich
vor.
Solche Anstrengungen kosten nur viel Zeit, sonst nichts.
Wilhelm M. schrieb:> Es geht ja nur darum zu schauen, ob eine Assembler-Lösung> schneller sein kann.
Wird dieser Unsinn jemals auszurotten sein? Wiese sollte von Hand
erstellter Assembler schneller sein, als es die Ausgabe eines Compilers
ist? Soll der Compiler absichtlich langsamen Code generieren oder welche
Vorstellung steckt hinter solchen Ideen?
Es gibt genau eine Möglichkeit, wie das funktionieren kann: Man
berücksichtigt in Assembler zusätzliche, vereinfachende Randbedingungen,
die man (aus reiner Bosheit - muss ich annehmen) dem C/C++-Compiler
vorenthalten hat. Das hat aber nichts mit Assembler zu tun.
Solche Randbedingungen machen hier im günstigsten Fall die
Multiplikation und Division obsolet und sind aus der abstrakten
Problembeschreibung natürlich nicht zu entnehmen.
Pete K. schrieb:> Hast Du Dir denn mal das Assemblerlisting zu Deinem Programm angeschaut?> Auch C++ wird vor der Ausführung in Assembler übersetzt.
Na klar. Die Funktion wird generiert mit Prolog und Epilog, aber auch
brav inline'd. Sie macht ein call __divmodhi4 bzw. mul.
Assemblator schrieb:> Wird dieser Unsinn jemals auszurotten sein? Wiese sollte von Hand> erstellter Assembler schneller sein, als es die Ausgabe eines Compilers> ist? Soll der Compiler absichtlich langsamen Code generieren oder welche> Vorstellung steckt hinter solchen Ideen?
ja, der ASM code ist fast nie perfekt. Man kann so gut wie immer etwas
verbessern nur macht es wenig sinn.
Ich behaupte mal in jeden größeren Programm gibt es stellen die der
Mensch besser mit ASM machen könnte, nur lohnt sich es da nicht.
In fast jeder neue Compiler Version, wird die code kleiner, also war er
vorher zu groß. Das zeigt doch schon, das Compiler noch lange nicht
perfekt sind.
Assemblator schrieb:> Wilhelm M. schrieb:>> Es geht ja nur darum zu schauen, ob eine Assembler-Lösung>> schneller sein kann.>> Wird dieser Unsinn jemals auszurotten sein? Wiese sollte von Hand> erstellter Assembler schneller sein, als es die Ausgabe eines Compilers> ist? Soll der Compiler absichtlich langsamen Code generieren oder welche> Vorstellung steckt hinter solchen Ideen?
Ganz meine Meinung!
Es gibt aber hier wie auch in dem besagten Projekt Leute, die von dieser
Meinung nicht abzubringen sind. Und deswegen die Frage an diejenigen,
die Assembler-Spezis sind, da mal einen Ansatz zu liefern.
Peter II schrieb:> Assemblator schrieb:>> Wird dieser Unsinn jemals auszurotten sein? Wiese sollte von Hand>> erstellter Assembler schneller sein, als es die Ausgabe eines Compilers>> ist? Soll der Compiler absichtlich langsamen Code generieren oder welche>> Vorstellung steckt hinter solchen Ideen?>> ja, der ASM code ist fast nie perfekt. Man kann so gut wie immer etwas> verbessern nur macht es wenig sinn.> Ich behaupte mal in jeden größeren Programm gibt es stellen die der> Mensch besser mit ASM machen könnte, nur lohnt sich es da nicht.
Ist dies so eine Stelle?
Wilhelm M. schrieb:> Ich behaupte mal in jeden größeren Programm gibt es stellen die der> Mensch besser mit ASM machen könnte, nur lohnt sich es da nicht.
Ich behaupte mal: Je größer der Programmumfang ist, desto schlechter
wird der Assembler-Programmierer gegenüber dem C-Compiler abschneiden.
Der Assembler-Programmierer verliert irgendwann den Überblick, der
Compiler nicht - ganz im Gegenteil.
Frank M. schrieb:> Ehrlich gesagt: Seitdem ich die STM32 für mich entdeckt habe, kommen mir> solche Problemstellungen wie "Da ist ein Bottleneck im AVR-Code, das> dringend mit Assembler gefixt werden muss" einfach nur noch lächerlich> vor.
Aber Hallo! Hier die ARM Version: (arm-none-eabi-gcc -mcpu=cortex-m3
-mthumb -O3 -S scale.c, gcc 6.3.1)
1
scale:
2
@ args = 0, pretend = 0, frame = 0
3
@ frame_needed = 0, uses_anonymous_args = 0
4
@ link register save eliminated.
5
movs r3, #100
6
subs r0, r0, r1
7
mul r3, r3, r0
8
subs r2, r2, r1
9
sdiv r3, r3, r2
10
uxtb r0, r3
11
bx lr
Hier die AVR (avr-gcc -O3 -S scale.c, leider nur 4.9.2)
Frank M. schrieb:> Ich behaupte mal: Je größer der Programmumfang ist, desto schlechter> wird der Assembler-Programmierer gegenüber dem C-Compiler abschneiden.> Der Assembler-Programmierer verliert irgendwann den Überblick, der> Compiler nicht - ganz im Gegenteil.
Beispiel:
beim AVR nutzt der Compiler keine Register für globale variablen. Er hat
ein paar Register wo feste werte drin stehe, das war's aber auch.
Wenn man ein uint8_t an viele stellen im Programm verwendet (und auch
noch in der ISR) dann muss man mit volatile arbeiten - was ganz
schlechten code erzeugt. Ein ASM-Coder nimmt dafür ein festes Register.
2⁵ schrieb:> Frank M. schrieb:>> Ehrlich gesagt: Seitdem ich die STM32 für mich entdeckt habe, kommen mir>> solche Problemstellungen wie "Da ist ein Bottleneck im AVR-Code, das>> dringend mit Assembler gefixt werden muss" einfach nur noch lächerlich>> vor.>> Aber Hallo! Hier die ARM Version: (arm-none-eabi-gcc -mcpu=cortex-m3> -mthumb -O3 -S scale.c, gcc 6.3.1)
Danke, den AVR-Code habe ich ja ... und der Vergleich zu ARM ist zwar
interessant, aber weder anders zu erwarten noch an dieser Stelle gefragt
;-(
Peter II schrieb:> Wenn man ein uint8_t an viele stellen im Programm verwendet (und auch> noch in der ISR) dann muss man mit volatile arbeiten - was ganz> schlechten code erzeugt. Ein ASM-Coder nimmt dafür ein festes Register.
Ja. Man kann dem Compiler aber ein wenig helfen.
In einer *.h Datei:
1
register uint8_t min asm("r3");
2
register uint8_t max asm("r4");
3
4
inline uint8_t scale(const uint8_t value) {
5
return ((value - min) * 100) / (max - min);
6
}
Der obige Code führt selbstverständlich zu einem Desaster, wenn sich
verschiedene Programmteile wie Mainprogramm, ISR ... drum kloppen.
FrickelFranz schrieb:> Ja. Man kann dem Compiler aber ein wenig helfen.> register uint8_t max asm("r4");
nicht wirklich. Zum rechnen kopiert der Compiler R4 in ein anderes
Register und kopiert es dann zurück. (war zumindest bei Gcc 4.x der
fall)
Die erste Frage ist sowieso, wo kommt dieses elende 100 her
die zweite Frage ist, wie oft ändert sich min und max.
Wahrscheinlich lohnt es sich, daraus on the fly eine direkte
Lookup-Table zu erstellen und danach gar nicht mehr zu rechnen.
Dazu braucht man aber mehr Infos und Code.
Allgemein: Optimieren kann man manuell immer, das muss nicht nur
Assembler sein, sondern betrifft den Lösungsansatz und die ganze
Programmstruktur.
eProfi schrieb:> Wahrscheinlich lohnt es sich, daraus on the fly eine direkte> Lookup-Table zu erstellen und danach gar nicht mehr zu rechnen.> Dazu braucht man aber mehr Infos und Code.
Das wurde schon in den ersten Beiträgen vorgeschlagen.
Der TO hat hier aber schon mehr Zeit investiert den Thread am Leben zu
erhalten, als es gebraucht hätte das Ganze mal schnell in seiner
Programmiersprache auszutesten.
Auch die seltsamen Erklärungen warum es unbedingt in ASM sein muss
weisen stark auf einen Troll hin
Viel Spass also beim weiter auf den Leim gehen.
:-)
Wilhelm M. schrieb:> Ich bin mir sicher, dass die> Assemblerfraktion über dieses Problemchen nur müde lächelt!
Was fürn Quatsch.
Schau Dir mal die Assembler-Division in den AVR-Notes an. Die ist so,
wie es jeder Anfänger in der Schule gelernt hat. Also viel langsamer,
als der Compilercode. Den haben nämlich pfiffige Kerlchen gebaut.
Wilhelm M. schrieb:> Das> ganze muss allerdings als (C)-Funktion aufrufbar sein, um als direkter> Ersatz zu dienen.
Ja nach welchen calling conventions? Gerade da verplempern
Objektorientierte Ansätze u.U. viel. (bspw. stack statt registeroperand)
Und Assembler kann gegenüber den suboptimalen register allocation algos
der Compiler punkten.
Peter II schrieb:> Zum rechnen kopiert der Compiler R4 in ein anderes> Register und kopiert es dann zurück. (war zumindest bei Gcc 4.x der> fall)
Ja, dieser Quatsch ist mir auch schon mal aufgestoßen. Aber vielleicht
kann man den Compiler so wenigstens davon abhalten mit dem Register den
Stack zu poppen.
Wenn 'min' und 'max' pro Modul konstant sind und der TO ohnehin mit C++
arbeitet, dann ist die Lösung doch offensichtlich: Templates.
Für jedes Modul wird einfach eine optimale (weil konstante
min/max-Werte) scale()-Funktion instantiiert, gut ist. Die eine, die
sich als Performance-Problem erweist, wird dann noch als LUT speziell
implementiert, fertig.
Für das gegebene Problem ist Assembler die Antwort auf die falsche
Frage.
Die 100 wird wohl nur für eine dem Menschen passende Skalierung
gebraucht.
Und da ein Mensch eh nur in der Lage ist, ein Dutzend Werte die Sekunde
zu erkennen, ist das alles völlig überflüssig.
Einfach nur die Rohwerte binär speichern. Und wenn wirklich mal eine
Ausgabe gebraucht wird, dann reicht die Leistung auch für die paar
Werte.
Macht lieber was schönes, als bei sowas noch Takte zu sparen.
Nachdem viel über Sinn oder Unsinn der Optimierung dieser Routine
geschrieben worden ist (ich selber stimme auch eher für Unsinn :)),
habe ich trotzdem mal ein Stückchen Code geschrieben, das (hoffentlich)
Wilhelms Vorstellungen entspricht.
Unter der Annahme, dass für value, min und max sinnvolle Werte übergeben
werden, also
min < max
und
min ≤ value ≤ max
sollte sie exakt die gleichen Ergebnisse liefern wie das Original¹.
1
.global scale
2
scale:
3
sub r20,r22
4
sub r24,r22
5
ldi r25,100
6
mul r24,r25
7
movw r30,r0
8
ldi r25,0x40
9
ldi r24,0
10
loop:
11
mov r21,r24
12
or r21,r25
13
mul r21,r20
14
movw r22,r0
15
cp r30,r22
16
cpc r31,r23
17
brlo skip
18
mov r24,r21
19
breq leave
20
skip:
21
lsr r25
22
brne loop
23
leave:
24
clr r1
25
ret
Sie ist – abhängig von den Argumenten – etwa um den Faktor 2 bis 9
schneller als das Original. Im Mittel wird der Faktor wohl bei knapp 3
liegen, das ist aber nur geschätzt.
Sie ist so geschrieben, dass sie – in eine .s-Datei gepackt – direkt in
ein AVR-GCC-Projekt eingebunden und von C aus aufgerufen werden kann.
————————————
¹) Falls nicht (ich konnte sie leider nicht vernünftig testen), bitte
Beispiele posten, für die das Ergebnis falsch ist, ich werde dann
nachbessern :)
PS: Natürlich kann man diese Routine zurück nach C übersetzen, dann ist
sie immer noch schneller als das Original, aber trotzdem nicht ganz so
schnell wie in Assembler. Die Frage ist halt immer, wieviel Aufwand man
– zusätzlich zu einer C-Optimierung – in eine Assembler-Optimierung
stecken möchte, die den Code vielleicht gerade mal um 20% schneller
macht.
Yalu X. schrieb:> Nachdem viel über Sinn oder Unsinn der Optimierung dieser Routine> geschrieben worden ist (ich selber stimme auch eher für Unsinn :)),
Wie gesagt: ich halte es ja auch für Unsinn, doch andere bestanden
darauf.
@Yalu: Du bist also der erste, der wirklich mal was in ASM codiert hat!
Danke dafür! Ich baue das gleich in den Test ein und sende dann die
Ergebnisse.
S. Landolt schrieb:> Weshalb erfolgt in der Schleife das Umspeichern mit>> movw r22,r0
Gute Frage :)
Ja, da kann man nochmals bis zu 7 Zyklen einsparen. Danke für den Tipp!
Das ganze geht übrigens auch ohne MUL-Befehl (für die kleinen AVRs) und
ist damit nur unwesentlich langsamer. Bevor ich etwas dazu poste, warte
ich aber erst mal Wilhelms Testbereicht ab.
return uint8(((value - min) * merker)>>8); # Nur oberes Byte der Multiplikation
9
}
10
11
12
Also sowas in ASM:
13
SUB value, min
14
MUL value, high(merker)
15
MOV tmp, R0
16
MUL value, low(merker)
17
ADD R1, tmp
18
return R1
... sollte auch ein C-Compiler etwas brauchbares draus zaubern ...
Allerdings bezweifele ich auch ein wenig, dass das der Flaschenhals ist
...
**Edit: Korrektur R0/R1
Gruß
Jobst
Ingo Less schrieb:> Ich bin ja mal gespannt nach wie vielen Stunde unser c-hater den> perfekten asm-code raushaut. Der benötigt dann warscheinlich 1ms> Ausführungszeit
Ja, ich auch ... (aber vllt sollte er den Namen dann auf C++-Hater
ändern)
Wenn die Ursprungs-Rechnung in Assembler schlagbar ist, dann doch nur
aufgrund der Probleme der Integral Promotion --> signed -->
Sonderbehandlung.
Die also entweder abschalten oder ins leere laufen lassen. und natürlich
das delta von
Gu. F. schrieb:> delta
a)
uint8_t scale(uint16_t value, uint16_t min, uint16_t delta) {
return ((value - min) * 100) / (delta);
}
b) ggf. noch die *100 aufgedröselt, je nach Rechenkosten des µC
uint8_t scale(uint16_t value, uint16_t min, uint16_t delta) {
uint16_t v = (value-min)<<2;
uint16_t v32 = v<<3;
v+=v32;
v+=v32;
v+=v32;
return v/delta;
}
Wilhelm M. schrieb:> (simavr@12Mhz)
Gut, das hier geht dann in unter 1µs (7Takte):
Jobst M. schrieb:> Also sowas in ASM:> SUB value, min> MUL value, high(merker)> MOV tmp, R0> MUL value, low(merker)> ADD R1, tmp> return R1
Die Frage ist eben einfach, wie häufig Du
Jobst M. schrieb:> uint16_t merker = 25600 / (max - min);
ausrechnen musst. Also wie oft sich Dein max und min verändert.
Gruß
Jobst
Jobst M. schrieb:>> Die Frage ist eben einfach, wie häufig Du>> Jobst M. schrieb:>> uint16_t merker = 25600 / (max - min);>> ausrechnen musst. Also wie oft sich Dein max und min verändert.
Mach einfach - ggf. auch eine statefull - Asm-Routine, dann baue ich
sie in den Benchmark ein.
Wilhelm M. schrieb:> Pete K. schrieb:>> Hast Du Dir denn mal das Assemblerlisting zu Deinem Programm angeschaut?>> Auch C++ wird vor der Ausführung in Assembler übersetzt.>> Na klar. Die Funktion wird generiert mit Prolog und Epilog, aber auch> brav inline'd. Sie macht ein call __divmodhi4 bzw. mul.
Das liegt daran, dass du signed rechnest. Falls eine unsigned
berechnung ok ist, dann nimm unsigned. Da ist dann die Chance besser,
dass der Compiler ne Division dirch Konstante als Multiplikation
abbildet. Dazu:
1) Als static inline schreiben, wurde ja schon genannt. Falls das nicht
zu Inlining führt dann zusätzlich __attribute__((_always_inline_)).
2) Alle betroffenen Module mit -O2 übersetzen.
Wenn das alles nicht hilft kann man einen anderen Algorithmus überlegen,
da gibt's i.w. 2 Möglichkeiten. Die Division in Assembler machen ist
Käse da __[u]divmodXX4 bereits in Assembler steht.
Der Andere schrieb:> Der TO hat hier aber schon mehr Zeit investiert den Thread am Leben zu> erhalten, als es gebraucht hätte das Ganze mal schnell in seiner> Programmiersprache auszutesten.
Ich habe lediglich auf Fragen geantwortet. Und das hat nichts damit zu
tun, ein Thema künstlich am Leben zu erhalten.
>> Auch die seltsamen Erklärungen warum es unbedingt in ASM sein muss> weisen stark auf einen Troll hin
Die Aussage war doch klar. Was soll das?
Wilhelm M. schrieb:> Ich bin mir sicher, dass die> Assemblerfraktion über dieses Problemchen nur müde lächelt!
Hast du mal geschaut wie unsere "Altvorderen" das gemacht haben? Vor
30-50 Jahren war es ja Gang und Gäbe alles in Assembler zu schreiben, da
wäre doch zu erwarten das die Jungs damals auch ausgefuchste Routinen
zur Division entwickelt haben, welche um jedes Byte/Zyklus kämpften.
Ad hoc fallen wir da die Seiten
- http://www.6502.org/source/
- http://codebase64.org/doku.php?id=base:6502_6510_maths
ein, bei denen man mal schauen könnte ob es dort etwas brauchbares gibt.
Die sind zwar nicht für AVR-Assembler, aber die Ideen sollte man doch
übertragen können.
Johann L. schrieb:> Wilhelm M. schrieb:>> Pete K. schrieb:>>> Hast Du Dir denn mal das Assemblerlisting zu Deinem Programm angeschaut?>>> Auch C++ wird vor der Ausführung in Assembler übersetzt.>>>> Na klar. Die Funktion wird generiert mit Prolog und Epilog, aber auch>> brav inline'd. Sie macht ein call __divmodhi4 bzw. mul.>> Das liegt daran, dass du signed rechnest. Falls eine unsigned> berechnung ok ist, dann nimm unsigned.
Ok, habe die Konstanten noch auf 100U geändert. Dann wird auch call
__udivmodhi4, was aber nichts an der messbaren Laufzeit ändert.
> Da ist dann die Chance besser,> dass der Compiler ne Division dirch Konstante als Multiplikation> abbildet. Dazu:>> 1) Als static inline schreiben, wurde ja schon genannt. Falls das nicht> zu Inlining führt dann zusätzlich __attribute__((_always_inline_)).
Ich hatte schon geschrieben, dass das Inlining stattfindet. Bei den
templates ja eh ...
> 2) Alle betroffenen Module mit -O2 übersetzen.
Sind alle mit O3 übersetzt.
> Wenn das alles nicht hilft kann man einen anderen Algorithmus überlegen,> da gibt's i.w. 2 Möglichkeiten. Die Division in Assembler machen ist> Käse da __[u]divmodXX4 bereits in Assembler steht.
s.a. Div/Add Methode.
Wilhelm M. schrieb:> Was soll das?
Der "Andere" sieht seine Aufgabe vornehmlich darin, Andere zu
unbedingten Sprüngen zu animieren, in dem er so lange stichelt, bis
dieses Ziel erreicht ist. Ein äußerst unangenehmer Stil. Vertue Deine
Zeit nicht damit.
Wilhelm M. schrieb:> Mach einfach - ggf. auch eine statefull - Asm-Routine, dann baue ich> sie in den Benchmark ein.
Geht es hier um ein Benchmark oder ein Problem?
Das Ding kannst Du 1x in C berechnen und dann 1000x das andere Teil.
Gruß
Jobst
Springer schrieb:> Wilhelm M. schrieb:>> Was soll das?>> Der "Andere" sieht seine Aufgabe vornehmlich darin, Andere zu> unbedingten Sprüngen zu animieren, in dem er so lange stichelt, bis> dieses Ziel erreicht ist. Ein äußerst unangenehmer Stil. Vertue Deine> Zeit nicht damit.
Dann sollte erst sich mal die MetaFunktion in FastScale (s. obiger Code)
ansehen ...
Wilhelm M. schrieb:> Ok, habe die Konstanten noch auf 100U geändert. Dann wird auch call> __udivmodhi4, was aber nichts an der messbaren Laufzeit ändert.
Ich hab mal ca. duzend Werte für max-min durchprobiert, bei allen seh
ich ein __umulhisi3. Wieviel der originalen Divisionen werden denn
nicht durch mal abgebildet, und welche Werte von max-min sind das?
Johann L. schrieb:> Das liegt daran, dass du signed rechnest. Falls eine unsigned> berechnung ok ist, dann nimm unsigned. Da ist dann die Chance besser,> dass der Compiler ne Division dirch Konstante als Multiplikation> abbildet. Dazu:>> 1) Als static inline schreiben, wurde ja schon genannt. Falls das nicht> zu Inlining führt dann zusätzlich __attribute__((always_inline)).>> 2) Alle betroffenen Module mit -O2 übersetzen.>> Wenn das alles nicht hilft kann man einen anderen Algorithmus überlegen,> da gibt's i.w. 2 Möglichkeiten. Die Division in Assembler machen ist> Käse da __[u]divmodXX4 bereits in Assembler steht.
Stellschrauben über Stellschrauben. Eine zweite, notwendige Art der
Programmierung quasi.
Wilhelm M. schrieb:> ; /home/lmeier/Projekte/wmucpp/doc/bmcpp20/bm30a.cc:86:> avr-g++ V8 -O3
Irgendwas machst du falsch. Der Code ist nicht geinlinet oder du
starrst nur auf die nicht-geinlinte, globale Instanz. Außerdem
übersetzt du für ein Device ohne MUL. Ist das wirklich für einen
Winzling ohne MUL?
Uwe schrieb:> Johann L. schrieb:>> 1) Als static inline schreiben, wurde ja schon genannt. Falls das nicht>> zu Inlining führt dann zusätzlich __attribute__((always_inline)).>>>> 2) Alle betroffenen Module mit -O2 übersetzen.>>>> Wenn das alles nicht hilft kann man einen anderen Algorithmus überlegen,>> da gibt's i.w. 2 Möglichkeiten. Die Division in Assembler machen ist>> Käse da __[u]divmodXX4 bereits in Assembler steht.>>> Stellschrauben über Stellschrauben. Eine zweite, notwendige Art der> Programmierung quasi.
Genau. Meine template-Funktion macht eigentlich alles richtig ...
Johann L. schrieb:> Die Division in Assembler machen ist Käse da __[u]divmodXX4 bereits in> Assembler steht.
Hier wird aber eine Division mit einem 16-Bit-Dividend, -Divisor und
-Ergebnis ausgeführt, obwohl eine mit 16-Bit-Dividend, 8-Bit-Divisor und
8-Bit-Ergebnis ausreichen würde. Letztere gibt es in der GCC-Lib nicht,
müsste also neugeschrieben werden, wenn man den naiven Rechenweg mit der
Division beibehalten wollte.
Wilhelm M. schrieb:> O:3 scale d/a : 21ms> O:4 fast : 22ms> O:5 loUp Var : 38ms> O:6 loUp Fixed : 17ms> O:7 asm : 98ms
Naja wenn der Assembler-code langsamer ist, dann ist der C-Code
fehlerhaft. Oder der asm sehr suboptimal. Oder Cache-Hits verfälschen
das Ergebnis (sollte beim AVR aber nicht der Fall sein).
Michael H. schrieb:> Wilhelm M. schrieb:>> Ich bin mir sicher, dass die>> Assemblerfraktion über dieses Problemchen nur müde lächelt!>> Hast du mal geschaut wie unsere "Altvorderen" das gemacht haben?
Das interessiert mich ja eigentlich gar nicht! Und ich bin der Meinung,
dass die heutigen Compilerbauer hier einen ausgezeichneten Job machen.
Und die Ergebnisse sprechen für sich ...
Wilhelm M. schrieb:> Und ich bin der Meinung,> dass die heutigen Compilerbauer hier einen ausgezeichneten Job machen.> Und die Ergebnisse sprechen für sich ...
Sicher. Aber wie von mir schon weiter oben angedeutet sind zur Erzielung
der gleichen Asm-Ergebnisse viel mehr mehr sprachliche C(++)Konstrukte
zu kennen und auch richtig einzusetzen. Die richtig zu verwendenden
Compiler-Optionen (eine imposante Übersicht wurde ja schon geliefert)
kommen dann noch oben drauf. Das sind alles wie zusätzlich nötige
Programmierebenen. Dagegen kostet übersichtlicher Asm-Text herzlich
wenig- und ist auch noch wunderbar passgenau.
Wilhelm M. schrieb:> Und ich bin der Meinung,> dass die heutigen Compilerbauer hier einen ausgezeichneten Job machen.
Da fehlt dir offensichtlich das tiefere Verständnis für Compilerbau.
Heute Compiler sind nicht schlecht, aber auch nicht perfekt. Man
erinnere Linus Thorvalds der über den gcc als Oppossum-baby das auf den
Kopf gefallen ist wetterte.
https://www.heise.de/developer/meldung/Linus-Torvalds-wettert-gegen-Compiler-Collection-GCC-4-9-2268920.html
Und egal wie gut der Compiler ist, c++ macht es den User leicht
ineffizenten Code zu schreiben, da ist der Compiler machtlos.
Und beweisen die Test zwischen den Compilern verschiedener Hersteller
nicht, das es die perfekten Compilerbauer nicht gibt. Mal ist der Kail
gut, dann der IAR, ...
Uwe schrieb:> Wilhelm M. schrieb:>> Und ich bin der Meinung,>> dass die heutigen Compilerbauer hier einen ausgezeichneten Job machen.>> Und die Ergebnisse sprechen für sich ...>> Sicher. Aber wie von mir schon weiter oben angedeutet sind zur Erzielung> der gleichen Asm-Ergebnisse viel mehr mehr sprachliche C(++)Konstrukte> zu kennen und auch richtig einzusetzen.
FastScale ist nur deswegen so umfangreich, weil es generisch ist und für
uint8_t, uint16_t, uint32_t, ... gleichermaßen arbeitet.
> Die richtig zu verwendenden> Compiler-Optionen (eine imposante Übersicht wurde ja schon geliefert)> kommen dann noch oben drauf. Das sind alles wie zusätzlich nötige> Programmierebenen.
Die kommen einfach durch
-Os -fno-exceptions -fno-unwind-tables -fno-rtti -fno-threadsafe-statics
zustande.
> Dagegen kostet übersichtlicher Asm-Text herzlich> wenig- und ist auch noch wunderbar passgenau.
Der geht allerdings auch nur mit uint8_t und ist auch noch langsamer ...
Ordner schrieb:> Wilhelm M. schrieb:>> Und ich bin der Meinung,>> dass die heutigen Compilerbauer hier einen ausgezeichneten Job machen.>> Da fehlt dir offensichtlich das tiefere Verständnis für Compilerbau.> Heute Compiler sind nicht schlecht, aber auch nicht perfekt.
Habe ich nicht behauptet (eigene ASM Routinen sind aber auch nicht
perfekt, s.o.)
>Man> erinnere Linus Thorvalds der über den gcc als Oppossum-baby das auf den> Kopf gefallen ist wetterte.> https://www.heise.de/developer/meldung/Linus-Torvalds-wettert-gegen-Compiler-Collection-GCC-4-9-2268920.html>> Und egal wie gut der Compiler ist, c++ macht es den User leicht> ineffizenten Code zu schreiben, da ist der Compiler machtlos.
Ineffizienten Code kann ich auch in Assembler schreiben.
Ich warte immer noch auf eine schnellere Assembleroutine ...
Peter Hofbauer schrieb:> Hallo Wilhelm M.>> Deine Zeitangaben können nicht stimmen, wie ermittelst Du diese> eigentlich?>> Gruß Peter
Schon mal in den Code geschaut ...?
Es ist immer die Zeit für 10000 Skalierungen (plus etwas Boilerplate,
was aber immer daselbe ist).
C. A. Rotwang schrieb:> Wilhelm M. schrieb:>>> O:3 scale d/a : 21ms>> O:4 fast : 22ms>> O:5 loUp Var : 38ms>> O:6 loUp Fixed : 17ms>> O:7 asm : 98ms>> Naja wenn der Assembler-code langsamer ist, dann ist der C-Code> fehlerhaft. Oder der asm sehr suboptimal.
Nein, die schnellen C-Varianten gehen davon aus, dass min und max zur
Compilezeit bekannt sind, und/oder verwenden eine Lookup-Table, um auf
die Division verzichten zu können. Siehe hier:
Wilhelm M. schrieb:> Folgende Varianten:>> 1) Naiv, uint8_t, variabel> 2) Naiv, template, variabel> 3) Division/Addieren, uint8_t, fest> 4) FastScale, template, fest (wie Division/Addieren)> 5) Lookup, template, variabel> 6) Lookup, template, fest> 7) asm, uint8_t, variabel (ohne range check)
Die Assembler-Routine nimmt alle drei Argumente (min, max, und value)
als Variablen entgegen, deren Werte evtl. erst zur Laufzeit bekannt
sind. Ihre Funktionalität, Flexibilität und der Speicherverbrauch
entspricht somit der Variante aus dem Eröffungsbeitrag (Nr. 1 in den
Benchmarkergebnissen) und ist im Vergleich zu dieser immerhin um den
Faktor 2 schneller.
Natürlich kann man auch in Assembler eine Lookup-Table implementieren,
aber ich hatte Wilhelm ursprünglich so verstanden, dass er eine
"rechnende" Implementierung bevorzugt.
Wilhelm M. schrieb:> Ineffizienten Code kann ich auch in Assembler schreiben.
Nee, ich glaube nicht dass Du irgendwelchen lauffähigen Assemblercode
schreiben kannst.
Und völlig ohne Ironie, man in C++ viel leichter ineffizienten Code
schreiben als in Assembler, weil man in Assembler 1:1 sieht wie die CPU
den Code ausführt, aber in C++ nicht.
> Ich warte immer noch auf eine schnellere Assembleroutine ...
Bist du unfähig selbst deinen C-code in Assembler übersetzen zu lassen
und auf Schwachstellen hin zu analysieren? Und dann deinen
C-code,Compiler-Pragmas, makefile-scripts so umzuschreiben das da
optimaler Assembler-code rauskommt?
Yalu X. schrieb:> Die Assembler-Routine nimmt alle drei Argumente (min, max, und value)> als Variablen entgegen, deren Werte evtl. erst zur Laufzeit bekannt> sind. Ihre Funktionalität, Flexibilität und der Speicherverbrauch> entspricht somit der Variante aus dem Eröffungsbeitrag (Nr. 1 in den> Benchmarkergebnissen) und ist im Vergleich zu dieser immerhin um den> Faktor 2 schneller.
Genau! Da bin ich aber froh, dass jemand sich die Mühe gemacht hat, die
Ergebnisse zu lesen!
> Natürlich kann man auch in Assembler eine Lookup-Table implementieren,> aber ich hatte Wilhelm ursprünglich so verstanden, dass er eine> "rechnende" Implementierung bevorzugt.
Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,
erlaubt sind ;-)
Yalu X. schrieb:> Die Assembler-Routine nimmt alle drei Argumente (min, max, und value)> als Variablen entgegen, deren Werte evtl. erst zur Laufzeit bekannt> sind. Ihre Funktionalität und Flexibilität entspricht somit der Variante> aus dem Eröffungsbeitrag (Nr. 1 in den Benchmarkergebnissen) und ist im> Vergleich zu dieser immerhin um den Faktor 2 schneller.
Genau das ist mit Fehlerhaften C-Code gemeint, die Umsetzung geht von
andern Programmspezifikationen aus als die Assemblerumsetzung, sie setzt
die Spezifikation nicht korrekt um.
Wilhelm M. schrieb:> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,> erlaubt sind ;-)
Da hast aber auch geschrieben das es als C-Funktion aufrufbar sein soll.
Das impliziert die Übergabe von Argumenten. Daran hält sich das
Assenblerprogramm aber nicht die C-Implementierung. Das ist m.E.
hinterfotzig.
Ordner schrieb:> Yalu X. schrieb:>> Die Assembler-Routine nimmt alle drei Argumente (min, max, und value)>> als Variablen entgegen, deren Werte evtl. erst zur Laufzeit bekannt>> sind. Ihre Funktionalität und Flexibilität entspricht somit der Variante>> aus dem Eröffungsbeitrag (Nr. 1 in den Benchmarkergebnissen) und ist im>> Vergleich zu dieser immerhin um den Faktor 2 schneller.>> Genau das ist mit Fehlerhaften C-Code gemeint, die Umsetzung geht von> andern Programmspezifikationen aus als die Assemblerumsetzung, sie setzt> die Spezifikation nicht korrekt um.
Blödsinn. Schau nach, welche Varianten realisiert sind ...
Ordner schrieb:> Wilhelm M. schrieb:>> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,>> erlaubt sind ;-)>> Da hast aber auch geschrieben das es als C-Funktion aufrufbar sein soll.> Das impliziert die Übergabe von Argumenten. Daran hält sich das> Assenblerprogramm aber nicht die C-Implementierung.
Blödsinn!
> Das ist m.E.> hinterfotzig.
Danke!
Baldrian schrieb im Beitrag #5020297:
> Wilhelm M. schrieb:>>> Ich warte immer noch auf eine schnellere Assembleroutine ...>> Was gibt es zu verdienen?
Vielleicht Beschimpfungen?
Hallo Wilhelm,
kann leider als reiner Asm-Freak die Problemstellung hier nicht
nachvollziehen. Wenn Du Deine C-Funktion nochmal in einfachen Worten
formulieren, den konkreten Einsatzfall beschreiben und die
Takt-Ausführungsdauer Deiner C-Funktion für genau diesen Fall nennen
könntest?
Desweiteren ist mir nicht klar welche konkreten Anforderungen an den
Asm-Code gestellt werden damit er aus einer C-Funktion aufrufbar ist...
Danke.
Wilhelm M. schrieb:> Baldrian schrieb im Beitrag #5020297:>> Wilhelm M. schrieb:>>>>> Ich warte immer noch auf eine schnellere Assembleroutine ...>>>> Was gibt es zu verdienen?>> Vielleicht Beschimpfungen?
Ist von einem fordernden Selbstdarsteller - wie du es bist - maximal zu
erwarten.
Lars schrieb:> Wenn Du Deine C-Funktion nochmal in einfachen Worten> formulieren, den konkreten Einsatzfall beschreiben und die
das ist doch nur eine mathematische Formel, dafür muss man C nicht
verstehen.
Wilhelm M. schrieb:> Ich warte immer noch auf eine schnellere Assembleroutine ...
Unverschämter geht es kaum.
Wie ich gestern schon gesagt habe Troll, aber ihr füttert ihn fleisig
durch.
Wenn euch Spass macht
:-)
Wilhelm M. schrieb:> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,> erlaubt sind ;-)
Ich hätte jetzt die Annahme, dass die Funktionsargumente zur Compilezeit
bekannt sind, nicht zu den Tricks gerechnet, da sich diese Annahme ja
nicht auf die Implementierung, sondern auf die Randbedingungen bezieht.
Unter der Annahme, das sogar alle drei Argumente zur Compilezeit bekannt
sind, ist die Aufgabe trivial, da dann zur Laufzeit überhaupt nichts
mehr ausgeführt werden muss. D.h. die Rechenzeit ist 0, egal ob in C
oder in Assembler programmiert. Auch für min=0, max=100 und variablem
value muss nichts gerechnet werden.
Ich kann nicht umhin, aber irgendwie erinnert mich dieser Thread gerade
ein wenig an die C-vs-Assembler-Diskussionen mit Moby, nur andersherum.
Beispiel:
Beitrag "Assembler wieder auf dem Weg nach vorn"
:)
Yalu X. schrieb:> Wilhelm M. schrieb:>> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,>> erlaubt sind ;-)>> Ich hätte jetzt die Annahme, dass die Funktionsargumente zur Compilezeit> bekannt sind, nicht zu den Tricks gerechnet, da sich diese Annahme ja> nicht auf die Implementierung, sondern auf die Randbedingungen bezieht.
Zur Compilezeit bekannt heisst aber nicht, für jede Instanz gleich
(bezogen auf min und max). Die Template-Lösung kann das natürlich (und
ja auch generisch für den Input-Typ). Der fiese Trick besteht dann hier
darin, dass natürlich für uint8_t N^2 Instanzen erzeugt werden, wenn die
Anzahl der unterschiedlichen Paare (min, max) ist ... damit wieder der
Speed <-> Space Trade-Off. Wie auch bei den LT Varianten.
> Unter der Annahme, das sogar alle drei Argumente zur Compilezeit bekannt> sind, ist die Aufgabe trivial, da dann zur Laufzeit überhaupt nichts> mehr ausgeführt werden muss. D.h. die Rechenzeit ist 0, egal ob in C> oder in Assembler programmiert. Auch für min=0, max=100 und variablem> value muss nichts gerechnet werden.
Gut bemerkt ;-)
Wilhelm M. schrieb:> Ordner schrieb:>> Wilhelm M. schrieb:>>> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,>>> erlaubt sind ;-)>>>> Da hast aber auch geschrieben das es als C-Funktion aufrufbar sein soll.>> Das impliziert die Übergabe von Argumenten. Daran hält sich das>> Assenblerprogramm aber nicht die C-Implementierung.>> Blödsinn!
Schau wie hättest du den die Argumentenübergabe? Werd doch endlich mal
konkret. Dazu bist du schon gestern gefragt worden calling convention.
Weist du überhaupt was damit gemeint ist?
Yalu X. schrieb:> Ich kann nicht umhin, aber irgendwie erinnert mich dieser Thread gerade> ein wenig an die C-vs-Assembler-Diskussionen mit Moby, nur andersherum.
Das geht mir auch schon so die ganze Zeit. Außerdem fühle ich mich als
Leser ein wenig verarscht. Das mag daran liegen, dass ich das
ursprüngliche Posting so auffasste, dass hier jemand ein Problem hat,
das er lösen muss.
Wie sich dann im Verlauf des Threads rausstellte, gehts hier aber eher
um einen geschickt eingefädelten Programmierwettbewerb, um die
Assembler-Programmierer (zu denen ich nicht gehöre) mal gehörig in ihre
Schranken zu verweisen.
Ich mag solche Spielchen nicht. Wie Yalu schon bemerkte, hatten wir
genau diese Diskussion (nur andersherum) bereits mit Moby. Und das ist
überhaupt nicht gut ausgegangen.
Wilhelm M. schrieb:> Der fiese Trick besteht dann hier> darin, dass natürlich für uint8_t N^2 Instanzen erzeugt werden, wenn die> Anzahl der unterschiedlichen Paare (min, max) ist ... damit wieder der> Speed <-> Space Trade-Off. Wie auch bei den LT Varianten.
Ja mein meinst Du inline Funktion? Das kann jeder Makroassembler auch.
Das ist auch nicht fies sondern das 1x1 der maschinennahen
Programmierung.
Mal abgesehen das inline m.W. nicht zum urprünglichen C++ Standard
gehört sondern eine Erweiterung ist Und mit dem eigentlichen
Sprachkonzept nichts zu tun hat weil man es genausogut mit dem
Präprozessor erschlagen.
Frank M. schrieb:> Wie sich dann im Verlauf des Threads rausstellte, gehts hier aber eher> um einen geschickt eingefädelten Programmierwettbewerb, um die> Assembler-Programmierer (zu denen ich nicht gehöre) mal gehörig in ihre> Schranken zu verweisen.
Sehe ich nicht so, eher im Gegenteil.
Hier wird gezeigt das man ein C-Programm nur mit Kenntniss der
Assemblerumsetzung und gehörig Compiler-Feinsteuerung bestenfalls
genauso schnell machen kann wie in direkter Assemblerprogrammierung.
Es werden also eher den C-programmieren ihre Grenzen aufgezeigt. Und das
diese Grenzen erst entstehen wenn man sich weigert irgendwas von dem
Wissen zu nutzen das auch ein Assemblerprogrammier hat. Also aus Sicht
des TO ein Schuß ins eigene Knie. Klar, das er jetzt eingeschnappt ist.
und Die Spielregeln zu seinen Gunsten ändert.
Ordner schrieb:> Wilhelm M. schrieb:>> Der fiese Trick besteht dann hier>> darin, dass natürlich für uint8_t N^2 Instanzen erzeugt werden, wenn die>> Anzahl der unterschiedlichen Paare (min, max) ist ... damit wieder der>> Speed <-> Space Trade-Off. Wie auch bei den LT Varianten.>> Ja mein meinst Du inline Funktion?
Nein: template-Instanzen sind immer inline (müssen sie sein!). Die
Bedeutung von inline ist nämlich ein klein wenig anders ...
> Das kann jeder Makroassembler auch.> Das ist auch nicht fies sondern das 1x1 der maschinennahen> Programmierung.> Mal abgesehen das inline m.W. nicht zum urprünglichen C++ Standard> gehört sondern eine Erweiterung ist
Blödsinn.
> Und mit dem eigentlichen> Sprachkonzept nichts zu tun hat weil man es genausogut mit dem> Präprozessor erschlagen.
Nein!
Ordner schrieb:> Frank M. schrieb:>> Wie sich dann im Verlauf des Threads rausstellte, gehts hier aber eher>> um einen geschickt eingefädelten Programmierwettbewerb, um die>> Assembler-Programmierer (zu denen ich nicht gehöre) mal gehörig in ihre>> Schranken zu verweisen.>> Sehe ich nicht so, eher im Gegenteil.>> Hier wird gezeigt das man ein C-Programm nur mit Kenntniss der> Assemblerumsetzung und gehörig Compiler-Feinsteuerung bestenfalls> genauso schnell machen kann wie in direkter Assemblerprogrammierung.
Überhaupt nicht!
> Es werden also eher den C-programmieren ihre Grenzen aufgezeigt.
Da stimmt: aber es ging auch nicht um C (s.o.).
Wilhelm M. schrieb:> dass man das nun in> AVR-Assembler codieren müsse.
Ja und dann? Hat dann die Firma den Assembler-experten rangesetzt? Was
kam dabei raus? Oder muss erstmal ein Externer eingekauft werden?
Du hast doch nicht etwas die Aufgabe bekommen in einem Forum ein Lösung
zu finden.
Also: schwache Story, Trollfaktor hoch
Wilhelm M. schrieb:>> Hier wird gezeigt das man ein C-Programm nur mit Kenntniss der>> Assemblerumsetzung und gehörig Compiler-Feinsteuerung bestenfalls>> genauso schnell machen kann wie in direkter Assemblerprogrammierung.>> Überhaupt nicht!
Doch, doch.
Ordner schrieb:> Es werden also eher den C-programmieren ihre Grenzen aufgezeigt.
Ich als C-Programmierer sehe da keine Grenzen. Ich werfe den teuren AVR
in die Tonne und nehme einen STM32 für dasselbe Geld. Damit läuft es
schneller, als jeder Assembler-Programmierer auf der Welt es mit einem
AVR schafft. Das erzeugte Assembler-Listing mit schlappen 7 Zeilen Code
für den STM32 wurde ja oben schon gezeigt. Das ist auch durch einen
Assembler-Programmierer für den STM32 nicht mehr zu toppen.
Ich sehe da nur eine Grenze: Den AVR selbst. Aber man kann sich ja noch
tage-, nein wochenlang mit diesem fiktiven Problem herumschlagen, wenns
Spaß macht. Es gibt halt Leute, die wissen nichts besseres mit ihrer
wertvollen Zeit anzufangen.
Frank M. schrieb:>> Es werden also eher den C-programmieren ihre Grenzen aufgezeigt.>> Ich als C-Programmierer sehe da keine Grenzen. Ich werfe den teuren AVR> in die Tonne und nehme einen STM32 für dasselbe Geld. Damit läuft es> schneller, als jeder Assembler-Programmierer auf der Welt es mit einem> AVR schafft.
Ja klar mit Birne Äpfel gewinnt man jeden Vergleich. Oder mit C auf
32bit@50MHz versus Assembler auf 8bit@12MHz.
> Das erzeugte Assembler-Listing mit schlappen 7 Zeilen Code> für den STM32 wurde ja oben schon gezeigt. Das ist auch durch einen> Assembler-Programmierer für den STM32 nicht mehr zu toppen.
Genau mein Reden, mit C wird's nicht schneller als mit Assembler, das
scheint beim TO aber noch nicht angekommen.
Ordner schrieb:>> Das erzeugte Assembler-Listing mit schlappen 7 Zeilen Code>> für den STM32 wurde ja oben schon gezeigt. Das ist auch durch einen>> Assembler-Programmierer für den STM32 nicht mehr zu toppen.>> Genau mein Reden, mit C wird's nicht schneller als mit Assembler, das> scheint beim TO aber noch nicht angekommen.
das mag bei diesen einfachen Beispiel so sein, aber warum glaubst wird
selbst auf einem PC noch mit ASM Programmiert wenn der C code schon
perfekt ist? Auch auf einen STM32 wird es code geben, den man mit ASM
schneller hinbekommt, aber dann nimmst du vermutlich lieber den nächsten
größere Prozessor.
Wilhelm M. schrieb:> Ich warte immer noch auf eine schnellere Assembleroutine ...
Darauf kannst Du bei Asm-only Programmierern wie mir lange warten
solange Du den hingeworfenen Brocken hier nicht allgemeinverständlich
erklären kannst, was dort an Takten zu unterbieten ist und wie die
Parameterübergabe denn nun erfolgen soll.
Kryptischer C-Code samt Compileroptions-Wissenschaften kann mir
persönlich gestohlen bleiben.
Peter II schrieb:> Ordner schrieb:>>> Das erzeugte Assembler-Listing mit schlappen 7 Zeilen Code>>> für den STM32 wurde ja oben schon gezeigt. Das ist auch durch einen>>> Assembler-Programmierer für den STM32 nicht mehr zu toppen.>>>> Genau mein Reden, mit C wird's nicht schneller als mit Assembler, das>> scheint beim TO aber noch nicht angekommen.>> das mag bei diesen einfachen Beispiel so sein, aber warum glaubst wird> selbst auf einem PC noch mit ASM Programmiert wenn der C code schon> perfekt ist? Auch auf einen STM32 wird es code geben, den man mit ASM> schneller hinbekommt, aber dann nimmst du vermutlich lieber den nächsten> größere Prozessor.
Mich musst Dich nicht überzeugen, das Assembler resp.
Assemblerkenntnisse immer noch ihre Berechtigung haben. Und auch nicht
das für Anwendungen den passenden µC und nicht eine Übermotorisierte
Lösung um ineffiziente Programmierung und mangelnde Detailkenntnis von
Compileroptionen, Programmierstilen und Instruction set auszugleichen.
Rudi schrieb im Beitrag #5020538:
> Das war auch auf die "klugen" Aussagen des Herrn Moderator Frank M.> gemünzt.
Moby, Deine Posts wurden (übrigens nicht von mir) gelöscht, weil Du
unbefristetes Hausverbot hast. Der Grund für das Hausverbot war
Vandalismus, weil Du mehrere Wiki-Artikel hier einfach gelöscht bzw. mit
Unsinn überschrieben hast. Dein ganzes Verhalten hinterlässt bei den
Lesern hier lediglich den Eindruck enormer krimineller Energie. Das ist
bestimmt nicht das, was Du mit solchen Aktionen bezweckst.
Deine Posts können noch so sinnvoll sein, gelöscht werden sie trotzdem.
Deine wiederholten Beiträge, die Du hier auf mich münzt, obwohl ich
persönlich bis dato hier überhaupt nichts gelöscht habe, zeugt nur von
Deiner Blindheit, wie Du hier vorgehst und bekräftigt uns Moderatoren
lediglich in der Ansicht, das Hausverbot auch konsequent durchzusetzen.
Uwe schrieb:> Kryptischer C-Code samt Compileroptions-Wissenschaften kann mir> persönlich gestohlen bleiben.
Ist aber interessant. Diese inline-Geschichten hab ich mir beim avr-gcc
auch mal angeschaut. Funktioniert am Anfang ganz gut, aber dann macht es
der Compiler einfach nicht mehr. Er schmiss auch trotz -Wall keine
Meldung, weil es als Info-Ausgegeben wurde. Da stand so etwa: Ich mach
kein inline hier weil das mit der globalen Optimierung kollidiert. Also
muss da vor dem inline ein pragma rein das die Optimierung für diesen
abschnitt ausschaltet, damit er sich den Funktionsauruf mit temporären
Sichern der Register spart.
Dann ist man wieder geneigt statt inline die verpönten
Präprozessor-Makros zu nehmen. Und damit kommt man wieder auf das Niveau
das man schon von der Assemblerprogrammierung kennt:
Selber eine Vortstellung zu haben welche Möglichkeiten es bei der
Umsetzung gibt und sicherstellen das auch die vom User akzeptierte
Variante verwendet wird.
>> ergibt dann:>> O:1 naiv : 196ms> ...> O:8 scale1 : 197ms
Das du übersetzbaren Code dazu? Offenbar wird da immer noch dividiert.
Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Ordner schrieb:> Diese inline-Geschichten hab ich mir beim avr-gcc auch mal angeschaut.> Funktioniert am Anfang ganz gut, aber dann macht es der Compiler einfach> nicht mehr.
Die Dinger funktionieren durchaus, aber das primäre Publikum dafür sind
eher nicht Endanwender, sondern Implementierer von Systembibliotheken
und dergleichen. Das Ziel des Inline-Assemblers ist eine möglichst
optimale Integration in das Compilat, ohne den Compiler unnütz in seinen
Freiheiten bezüglich der Optimierung einschränken zu müssen. Damit
dieses Ziel erreicht wird, sollte man nicht gerade schreibfaul sein,
sondern wirklich alles als formale Parameter in das inline asm statement
reingeben, was man darin benötigt (einschließlich Hilfsregistern etc.).
Für eine Aufgabe wie hier ist sowas nicht sinnvoll geeignet. Da kann
man dann auch direkten Assemblercode dazu linken.
Aber dass dieser ganze Wettbewerb hier eher von zweifelhaftem Nährwert
ist, wurde ja bereits konstatiert.
Johann L. schrieb:>>>> O:1 naiv : 196ms>> ...>> O:8 scale1 : 197ms>> Das du übersetzbaren Code dazu? Offenbar wird da immer noch dividiert.
Ich habe aus dem Testcode alle Zeilen auskommentiert, die Du nicht ohne
weiteres übersetzen kannst. Sollte also auch bei Dir compilierbar sein.
Anbei auch der generierte asm-Code. Es ist also nur noch Deine Version
drin.
Jörg W. schrieb:> Für eine Aufgabe wie hier ist sowas nicht sinnvoll geeignet. Da kann> man dann auch direkten Assemblercode dazu linken.
Wurde ja auch schon gemacht: s.o. Code von Yalu
> Aber dass dieser ganze Wettbewerb hier eher von zweifelhaftem Nährwert> ist, wurde ja bereits konstatiert.
Wenn dem so wäre, hätte ich erwartet, dass einfach eine klare Antwort
kommt. Diese Diskussion hier zeigt jedoch etwas anderes.
Im übrigen bin ich mit einem modifizierten div/add Algorithmus als
template ganz zufrieden. Da bzgl. der Skalierung nicht allzu viele
unterschiedliche instanziiert werden, ist der Code-Overhead nicht so
schlimm. Wichtiger ist die reduzierte Laufzeit (spave vs time). Zudem
fasst der Compiler auch noch unterschiedliche Instanziierungen zusammen.
Danke für die (eine) Realisierung in Assembler an Yalu!!! Und die
freundlichen Worte einiger anderer ...
Wilhelm M. schrieb:> Johann L. schrieb:>>>>>> O:1 naiv : 196ms>>> ...>>> O:8 scale1 : 197ms>>>> Das du übersetzbaren Code dazu? Offenbar wird da immer noch dividiert.>> Ich habe aus dem Testcode alle Zeilen auskommentiert, die Du nicht ohne> weiteres übersetzen kannst. Sollte also auch bei Dir compilierbar sein.
Ja. Irgendein Unterschied zwischen <= v6 und >= v7. Die alte(n)
Version(en) machen keine Division, die neue(n) aber schon.
Jörg W. schrieb:> Ordner schrieb:>> Diese inline-Geschichten hab ich mir beim avr-gcc auch mal angeschaut.>> Funktioniert am Anfang ganz gut, aber dann macht es der Compiler einfach>> nicht mehr.>> Die Dinger funktionieren durchaus, aber das primäre Publikum dafür sind> eher nicht Endanwender, sondern Implementierer von Systembibliotheken> und dergleichen. Das Ziel des Inline-Assemblers ist eine möglichst> optimale Integration in das Compilat, ohne den Compiler unnütz in seinen> Freiheiten bezüglich der Optimierung einschränken zu müssen. Damit> dieses Ziel erreicht wird, sollte man nicht gerade schreibfaul sein,> sondern wirklich alles als formale Parameter in das inline asm statement> reingeben, was man darin benötigt (einschließlich Hilfsregistern etc.).
Ich hab mich unpräzise ausgedrückt, ich meine nicht den "inline
Assembler" sondern das Codewort "inline" vor einer (C/C++-)function, um
zu verhindern das das zu einem subprogromm call mit Parameterübergabe
übersetzt wird. Im konkreten fall wohl sogar auch komplett ohne
variablen , respektive nur mit konstanten Parametern. Das ist auch genau
der TO-Fall der hier benutzt wird um die "naive" Implementierung zu
optimieren. Er nennt das nicht inline obwohl er das Codewort inline
benutzt, sondern "template".
Diese function-inline sehe ich als tool für die systemprogrammierer
allein, wie du es für den "inline-Assembler" darstellst. Nach meiner
Erfahrung funktioniert diese iunline-function auch für den fall ohne
Parameter unzuverlässig, es wird gelöegentlich trotz des codeworts
inline als CALL/RETURN umgesetzt und nicht Makroexpansion an Ort und
Stelle. Auch wenn keine Parameter übergeben und das ganze PUSH/POP etc.
entfällt ist ein CALL/Return wie jeder Sprung langsamer (2 cycles per
CALL/RET).
In meiner Anwendung war diesen verloren 2 cycle wirklich essential, da
ich eine schnelle SPI Verbindung über zwei Kanäle gleichzeitig abwickeln
musste, aber nur ein SPI-Module im AVR ist. Deshalb war ich auch
ziemlich frustiert das inline-function bei avr-gcc nur mit tricks sicher
so umgesetzt wird wie ich es brauchte.
Wilhelm M. schrieb:> Johann L. schrieb:>>>>>> O:1 naiv : 196ms>>> ...>>> O:8 scale1 : 197ms>>>> Das du übersetzbaren Code dazu? Offenbar wird da immer noch dividiert.>> Ich habe aus dem Testcode alle Zeilen auskommentiert, die Du nicht ohne> weiteres übersetzen kannst.
In deinem Assemblerlisting lese ich etwas von -Os (die anderen Optionen
habe ich mir nicht angeschaut). Die Umwandlung einer Konstantendivision
in eine Konstantenmultiplikation mit Shiften des Ergebnisses geschieht
nicht bei -Os, weil diese Umwandlung den Programmcode zwar schneller,
aber auch etwas größer macht. Hast du mal -O2 probiert?
Wilhelm M. schrieb:> Im übrigen bin ich mit einem modifizierten div/add Algorithmus als> template ganz zufrieden.
Meinst du diesen hier?
Der ist zwar sauschnell, liefert aber i.Allg. ungenauere Ergebnisse als
der Originalcode mit min=0 und max=255 und macht zudem größere Sprünge
zwischen aufeinanderfolgenden Werten für value.
Beispiele:
Ordner schrieb:> Diese function-inline sehe ich als tool für die systemprogrammierer> allein, wie du es für den "inline-Assembler" darstellst.
Aargh, typo. ;-( Es soll heissen
Diese function-inline sehe ich NICHT als tool für die
Systemprogrammierer
allein, wie du es für den "inline-Assembler" darstellst sondern als
legitimen Ersatz für die verpönten Präprocessor-Makros.
Ordner schrieb:> Ich hab mich unpräzise ausgedrückt, ich meine nicht den "inline> Assembler" sondern das Codewort "inline" vor einer (C/C++-)function, um> zu verhindern das das zu einem subprogromm call mit Parameterübergabe> übersetzt wird.
Dafür ist das Schlüsselwort gar nicht gedacht. Es ist lediglich ein
Hinweis an den Compiler, diese Funktion fürs Inlining möglichst in
Betracht zu ziehen. Eine Verpflichtung entsteht ihm daraus nicht.
Der GCC kennt allerdings ein Funktionsattribut (“always_inline”), bei
dem das Inlining erzwungen wird.
Jörg W. schrieb:> Dafür ist das Schlüsselwort gar nicht gedacht. Es ist lediglich ein> Hinweis an den Compiler, diese Funktion fürs Inlining möglichst in> Betracht zu ziehen. Eine Verpflichtung entsteht ihm daraus nicht.
Ah, das ist mir neu; aber nicht unerwartet, der C-Compiler macht eben
das was gut für ihn ist und nicht unbedingt das was der User verlangt.
Das erklärt auch, warum das nur als Info und nicht als Warning
ausgegeben wurde. Was ich aber als Fehler betrachtete; wenn etwas nicht
so umgesetzt wurde wie verlangt, dann wäre das schon eine Warning wie
"Code has no effect" wert.
> Der GCC kennt allerdings ein Funktionsattribut (“always_inline”), bei> dem das Inlining erzwungen wird.
Mal schauen ob der gcc-avr das auch hat. Sollte der TO auch in seinem
Code verwenden, nicht das sein geschwindigkeitsvorteil nach Compiler
Gutdünken in nichts auflöst.
Wilhelm M. schrieb:> Im übrigen bin ich mit einem modifizierten div/add Algorithmus als> template ganz zufrieden.
Den hättest du auch ohne deine Seitenhiebe ins Assemblerlager und
"benchmarktuning" erhalten
> Und die> freundlichen Worte einiger anderer ...
Es wären mehr wenn du dir die Seitenhiebe und den trollhaften Ton
gespart hättest. Du weist ja, "Wie man in den Wald hinruft, so tönt es
heraus".
Ordner schrieb:> Ah, das ist mir neu; aber nicht unerwartet, der C-Compiler macht eben> das was gut für ihn ist und nicht unbedingt das was der User verlangt.
Es steht einfach so im Standard.
1
A function declared with an inline function specifier is an inline function. The
2
function specifier may appear more than once; the behavior is the same as if it appeared
3
only once. Making a function an inline function suggests that calls to the function be as
4
fast as possible. The extent to which such suggestions are effective is
5
implementation-defined. 124)
Fußnote 124 ist:
“For example, an implementation might never perform inline substitution,
or might only perform inline substitutions to calls in the scope of an
inline declaration.”
Letzteres ist nahezu unabdingbar, denn sonst müsste der Compiler ja
außer den Headerfiles (scope of declaration) auch noch alternative
Implementierungsfiles komplett analysieren.
Ersteres suggeriert, dass es eben ein Compiler auch komplett ignorieren
darf.
GCC geht einen Mittelweg, bei dem er die Komplexität des davon
generierten Codes relativ zu den aktuellen Optimierungseinstellungen
bewertet. Mit -O3 beispielsweise bekommst du auch Funktionen inline
erweitert, für die es gar nicht verlangt worden ist, mit -O1/-Os
passiert sowas nur dann, wenn eine "static"-Funktion lediglich ein
einziges Mal benutzt wird.
> Was ich aber als Fehler betrachtete;
Was du als Fehler betrachtest, unterscheidet sich halt davon, was der
Standard als Fehler betrachtet; nur letzteres darf ein Compiler jedoch
als Fehler generieren. Bezüglich Warnungen hingegen ist der Compiler
freier in seinen Entscheidungen.
>> Der GCC kennt allerdings ein Funktionsattribut (“always_inline”), bei>> dem das Inlining erzwungen wird.>> Mal schauen ob der gcc-avr das auch hat.
Der AVR-GCCist ein GCC. Das Frontend ist grundsätzlich erstmal
zwischen den einzelnen Zielplattformen gleich.
Jörg W. schrieb:>> Was ich aber als Fehler betrachtete;>> Was du als Fehler betrachtest, unterscheidet sich halt davon, was der> Standard als Fehler betrachtet; nur letzteres darf ein Compiler jedoch> als Fehler generieren. Bezüglich Warnungen hingegen ist der Compiler> freier in seinen Entscheidungen.
ich hab mich unpräzise ausgedrückt, ich meine nicht, der Compiler hätte
das als " Error" melden sollen. sondern er hätte das als "Warning"
ausgeben sollen, was er aber trotz -Wall nicht tat. sondern nur als
"Info". Das ist natürlich blöd wenn man das Compiler-log auf warning
grept und garnicht anschaut wenn warnings: 0 ausgegeben wird. Also der
Standard hat hier meines Erachtens einen Error, das Ignorieren des
Keywords inline hätte als Warning ins log gehört.
> Letzteres ist nahezu unabdingbar, denn sonst müsste der Compiler ja> außer den Headerfiles (scope of declaration) auch noch alternative> Implementierungsfiles komplett analysieren.
Möglicherweise hat mir das dem inline das Genick gebrochen, ich meine
ich habe die Funktionen mit dem inline als .c ausgelagert. Und da das
nicht wollte wieder main() und die meisten files bis auf die eigenen
Prototypen in ein file gepackt. Dann tat das inline wieder. Das hat mich
auch bei C++ gestört, das es unter Umständen abhängig ist wie man seinen
Code auf verschiedene files aufteilt.
Ordner schrieb:> ich hab mich unpräzise ausgedrückt, ich meine nicht, der Compiler hätte> das als " Error" melden sollen. sondern er hätte das als "Warning"> ausgeben sollen, was er aber trotz -Wall nicht tat.
Entgegen dem, was der Optionsname suggeriert, umfasst “-Wall” keineswegs
alle Warnungen, sondern nur die, die am häufigsten interessant
sind. Zusätzlich sollte man sich ohnehin angewöhnen, auch gleich
noch “-Wextra” mit anzugeben.
Hätte allerdings hier auch nicht geholfen, denn die von dir
gewünschte Warnung wird mit “-Winline” eingeschaltet, welches auch
bei “-Wextra” nicht mit dabei ist.
> sondern nur als> "Info".
Du sprichst in Rätseln: ich kenne nur Warnungen oder Fehler, aber
wüsste nicht, was eine „Info“ des Compilers sein soll.
>> Letzteres ist nahezu unabdingbar, denn sonst müsste der Compiler ja>> außer den Headerfiles (scope of declaration) auch noch alternative>> Implementierungsfiles komplett analysieren.>> Möglicherweise hat mir das dem inline das Genick gebrochen, ich meine> ich habe die Funktionen mit dem inline als .c ausgelagert.
Wenn du mal ein bisschen nachdenkst, wie ein Compiler arbeitet, dann
sollte ganz schnell offensichtlich werden, dass etwas, was er inline
erweitern soll, ihm auch an der Stelle, wo es benötigt wird, bereits
bekannt sein muss. Dafür kann man es eigentlich nur in einer
Headerdatei angeben … gewöhn' dir dann aber bitte gleich noch an, nicht
nur “inline” zu schreiben, sondern “static inline”, denn das Verhalten
von “extern inline” (was es ohne “static” wäre) ist seit C99, ähem,
nicht sonderlich intuitiv spezifiziert, um's mal vorsichtig zu
formulieren.
Wilhelm M. schrieb:> dann baue ich sie in den Benchmark ein.
hast Du denn schon die uint16_t Versionen getestet?
uint8_t scale(uint16_t value, uint16_t min, uint16_t delta) {
return ((value - min) * 100) / (delta);
}
Hi
Habe den Thread jetzt nur überflogen ...
Warum wurde das C-Konstrukt bisher noch nicht als ASM-Listing händisch
optimiert?
Wenn Der Compiler doch mit allen (noch so fiesen Tricks) die
Geschwindigkeit erhöhen darf, kann man doch auch diese ASM-Version
'Korrektur-Lesen'.
Fiel mir in den ganzen Posts nicht auf, daß Das schon gemacht wurde -
vorgeschlagen JA, aber dann auch nur wieder ignoriert.
Selber spiele ich in Assembler, was den großen 'Vorteil' der
Hardware-Nähe mitbringt ... ein anderes Steinchen und das Listing ist
(mindestens nahezu) unbrauchbar.
Bei C/C++ sehe ich dem Vorteil, daß das Programm auf den Chip passend
'zusammen gespaxt' wird - was, meinem Verständnis nach, nicht immer zu
100%igem Code führen kann.
Warum aber nicht hier anfassen, wo der Compiler aufgehört hat?
Habe selber aber mit C/C++ nicht wirklich was am Hut, weshalb meine
Sicht der Dinge durchaus arg subjektiv sein mag.
MfG
Patrick J. schrieb:> Warum wurde das C-Konstrukt bisher noch nicht als ASM-Listing händisch> optimiert?
Weil Du Dich bisher nicht eingemischt hast?
Mach mal ...
Hi
Dann Mal her mit dem Listing ... da, so wie ich schrieb, ich selber NIX
mit C 'am Hut' habe, ist das Compilieren und den ASM-Code selber
erstellen - nicht drin.
Wäre halt schön, daß dann der ultimativ schnellste, beste und so_oder_so
tollste Code, Der in C schon rennt 'wie Lumpi', kompiliert werden
sollte.
Aus einem Fiat 500 wird halt nur schwer ein echter Ferrari, wenn auch
die Schmiede identisch ist ;)
Noch mische ich also mit :)
MfG
Wilhelm M. schrieb:> Ich warte immer noch auf eine schnellere Assembleroutine ...Wilhelm M. schrieb:> Jein, ich hatte eigentlich gesagt, dass alle Tricks, auch die fiesesten,> erlaubt sind ;-)
Naja, meine pfiffige Lösung, welche mit 7 Takten zufrieden ist, möchtest
Du ja nicht testen. Nein, ich werde mir für Dich nicht aneignen, wie man
das in C hinein bekommt.
Außerdem bist Du nicht auf meine Frage nach der Bedingung eingegangen:
Jobst M. schrieb:> Also wie oft sich Dein max und min verändert.
Sollte es jetzt notwendig sein, das JEDES Mal neu zu berechnen, dann
muss dafür eben auch etwas pfiffiges gefunden werden. Sollte auch in
unter 100 Takten (8,3µs@12MHz µs, nicht ms!) zu erledigen sein. Aber
nicht mehr von mir ...
Frank M. schrieb:> Das geht mir auch schon so die ganze Zeit. Außerdem fühle ich mich als> Leser ein wenig verarscht.Frank M. schrieb:> Wie sich dann im Verlauf des Threads rausstellte, gehts hier aber eher> um einen geschickt eingefädelten Programmierwettbewerb, um die> Assembler-Programmierer (zu denen ich nicht gehöre) mal gehörig in ihre> Schranken zu verweisen.
Den Eindruck habe ich mittlerweile auch, da die fiesen Tricks erstmal
von ihm ignoriert werden.
Ordner schrieb:> Ja klar mit Birne Äpfel gewinnt man jeden Vergleich. Oder mit C auf> 32bit@50MHz versus Assembler auf 8bit@12MHz.
Dann nimm eben einen 8051. Der kann teilen. 4 Takte. 8Bit/8Bit.
Bei dem berechne ich 64 Bit Orthodrome in ASM schneller, als der TO
teilt ...
BTW: ist die DIV/8 Fuse eigentlich gesetzt? ...
Gruß
Jobst
Ich persönlich glaube ja, dass der Themenersteller uns hier was vom
Pferd erzählt.
In einer normalen Firma verlangt kein Projektleiter und kein
Vorgesetzter die Umsetzung eines bestimmten Algorithmus in Assembler.
Was gefordert wird, ist die Erfüllung der Anforderungen. Punkt.
Abgesehen davon wurde das Problem bzw. dessen Randbedingungen nicht
richtig beschrieben. Dazu gehört zum Beispiel eine Angabe, wieviel
(Rechen-)Zeit die Ausführung des Algorithmus denn benötigen darf.
Mark B. schrieb:> Ich persönlich glaube ja, dass der Themenersteller uns hier was vom> Pferd erzählt.
Sehe ich auch so.
Bevor ich irgendwas optimiere, muß ich wissen, ob das überhaupt der
Flaschenhals ist.
In diesem Fall ist aber der Kontext völlig unklar, warum das ein
Flaschenhals sein soll.
Und sobald man den Kontext kennt, kann man oft den Programmablauf so
umstellen, daß sich der Flaschenhals ganz von selbst auflöst ohne
jedwede Optimierung oder gar Assembler.
Es wird nur unnütz Entwicklungszeit verplempert, wenn man an der völlig
falschen Stelle ansetzt, weil entscheidende Informationen vorenthalten
werden.
Vor allem wäre da noch die Frage welchr Garantien die Funktion verwenden
darf. Ist z.B min immer <= zahl? Darf der Assemblercode von dem
C-Fragment abweichendes Verhalten haben falls das nicht der Fall ist?
Ich hatte mal den Fall auf einem 386SX.
Damals hatte es sich gelohnt "den 4-Byte float" in einen Bruch zu
verwandeln, also 2 8-Bit Zahlen daraus zu machen.
Diese Rationalzahl konnte schneller über Addition und Subtraktion
verarbeitet werden.
Für AVR lohnt sich das nicht recht, weil der schnell multiplizieren
kann. Die Division macht man dann über Schiebebefehl.