Forum: Mikrocontroller und Digitale Elektronik Assembler (AVR) Freaks bitte: der schnellste Weg, einen ganzzahligen Wert zu skalieren?


von Wilhelm M. (wimalopaan)


Lesenswert?

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_t scale(uint8_t value, uint8_t min, uint8_t max) {
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!

: Gesperrt durch Moderator
Beitrag #5019412 wurde von einem Moderator gelöscht.
von Peter II (Gast)


Lesenswert?

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.

von Detlev T. (detlevt)


Lesenswert?

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++)

von FrickelFranz (Gast)


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

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.

von Der Andere (Gast)


Lesenswert?

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!

:-)

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von o.p.x. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Peter II (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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).

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von FrickelFranz (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Alexander B. (Firma: brickwedde.dev) (alexbrickwedde)


Lesenswert?

o.p.x. schrieb:
> return ( (value - min) * table[max - min] / 256);

Statt "/ 256" aber lieber ">> 8", oder???

return ( ((uint16_t)(value - min)) * table[max - min]) >> 8;

von Peter II (Gast)


Lesenswert?

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%??

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Wilhelm M. (wimalopaan)


Lesenswert?

Alexander B. schrieb:
> o.p.x. schrieb:
>> return ( (value - min) * table[max - min] / 256);
>
> Statt "/ 256" aber lieber ">> 8", oder???
>
> return ( ((uint16_t)(value - min)) * table[max - min]) >> 8;

Soweit ich das bislang gesehen habe, wird die Division in 2'er-Potenzen 
vom Compiler eh als shift optimiert.

von Gu. F. (mitleser)


Lesenswert?

Wilhelm M. schrieb:
> uint8_t scale(uint8_t value, uint8_t min, uint8_t max) {
>     return ((value - min) * 100) / (max - min);
> }

Evtl. statt "max" das delta zu min übergeben, spart zumindest die 
Subtraktion
1
uint8_t scale(uint8_t value, uint8_t min, uint8_t delta) 
2
{
3
    return ((value - min) * 100) / delta;
4
}

von Pete K. (pete77)


Lesenswert?

Hast Du Dir denn mal das Assemblerlisting zu Deinem Programm angeschaut? 
Auch C++ wird vor der Ausführung in Assembler übersetzt.

von Amateur (Gast)


Lesenswert?

Hast Du mal probiert, das Ganze als Inline-Funktion zu deklarieren?

von Assemblator (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Peter II (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Ist dies so eine Stelle?

nein, wie schon oben geschrieben.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von 2⁵ (Gast)


Lesenswert?

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)
1
scale:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        mov r30,r22
7
        ldi r31,0
8
        mov r18,r24
9
        ldi r19,0
10
        sub r18,r30
11
        sbc r19,r31
12
        mov r24,r18
13
        mov r25,r19
14
        lsl r24
15
        rol r25
16
        add r24,r18
17
        adc r25,r19
18
        mov r22,r24
19
        mov r23,r25
20
        lsl r22
21
        rol r23
22
        swap r22
23
        swap r23
24
        andi r23,0xf0
25
        eor r23,r22
26
        andi r22,0xf0
27
        eor r23,r22
28
        add r24,r22
29
        adc r25,r23
30
        add r24,r18
31
        adc r25,r19
32
        mov r22,r20
33
        ldi r23,0
34
        sub r22,r30
35
        sbc r23,r31
36
        rcall __divmodhi4
37
        mov r24,r22
38
        ret

von Peter II (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 
;-(

von Wilhelm M. (wimalopaan)


Lesenswert?

Nicht, dass ihr denkt, der TO ist verschwunden: melde mich jetzt mal für 
die Heimfahrt ab ...

von FrickelFranz (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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)

von eProfi (Gast)


Lesenswert?

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.

von Der Andere (Gast)


Lesenswert?

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.
:-)

von Peter D. (peda)


Lesenswert?

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.

von Guardian (Gast)


Lesenswert?

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.

von FrickelFranz (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

Beitrag #5019708 wurde von einem Moderator gelöscht.
von PittyJ (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

: Bearbeitet durch Moderator
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von S. Landolt (Gast)


Lesenswert?

Weshalb erfolgt in der Schleife das Umspeichern mit
1
 movw r22,r0
?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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)

Hier die Codegrößen:

   text    data     bss     dec     hex filename
    338       2       1     341     155 bm31a.elf
    290       2       1     293     125 bm32a.elf
    212       2       1     215      d7 bm33a.elf
    212       2       1     215      d7 bm34a.elf
    520       2     143     665     299 bm35a.elf
    192     514       1     707     2c3 bm36a.elf
    234       2       1     237      ed bm37a.elf

Und hier die Ausführungszeiten:

O:1 : 196ms
O:2 : 201ms
O:3 : 21ms
O:4 : 22ms
O:5 : 38ms
O:6 : 17ms
O:7 : 99ms

Der Code ist angehängt.

: Bearbeitet durch User
von Jobst M. (jobstens-de)


Lesenswert?

Sinngemäß würde ich folgendes machen:
1
#1x - wenn nötig:
2
uint16_t merker = 25600 / (max - min);
3
4
5
Und dann viele Male:
6
7
uint8_t scale(uint8_t value, uint8_t min, uint16_t merker) {
8
    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

: Bearbeitet durch User
von Ingo Less (Gast)


Lesenswert?

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

von Jobst M. (jobstens-de)


Lesenswert?

Mit wie vielen kHz läuft der Controller? :-D


Gruß
Jobst

von Wilhelm M. (wimalopaan)


Lesenswert?

Jobst M. schrieb:
> Mit wie vielen kHz läuft der Controller? :-D

Ist doch egal: es geht doch um die relative Geschwindigkeit! 
(simavr@12Mhz)

von Wilhelm M. (wimalopaan)


Lesenswert?

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)

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

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;
}

von Jobst M. (jobstens-de)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Michael H. (dowjones)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...hier mal ein Beispiel unter obiger Annahme:
 
1
static inline
2
uint8_t scale (uint8_t value, uint8_t min, uint8_t max)
3
{
4
    return ((value - min) * 100u) / ((uint16_t) (uint8_t)(max - min));
5
}
6
7
uint8_t scale1 (uint8_t value)
8
{
9
    return scale (value, 100, 21);
10
}

 
wird qua avr-gcc v6 -O2 zu
 
1
scale1:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
  ldi r25,0   ;  28  movqi_insn/1  [length = 1]
7
  subi r24,100   ;  7  addhi3_clobber/2  [length = 2]
8
  sbc r25,__zero_reg__
9
  ldi r18,lo8(100)   ;  8  movqi_insn/2  [length = 1]
10
  mul r18,r24   ;  9  muluqihi3  [length = 5]
11
  movw r20,r0
12
  mul r18,r25
13
  add r21,r0
14
  clr __zero_reg__
15
  movw r18,r20   ;  10  *movhi/1  [length = 1]
16
  ldi r26,lo8(67)   ;  11  *movhi/5  [length = 2]
17
  ldi r27,lo8(114)
18
  call __umulhisi3   ;  12  *umulhi3_highpart_call  [length = 2]
19
  sub r20,r24   ;  14  subhi3/1  [length = 2]
20
  sbc r21,r25
21
  lsr r21   ;  32  *lshrhi3_const/2  [length = 2]
22
  ror r20
23
  add r24,r20   ;  16  *addhi3/1  [length = 2]
24
  adc r25,r21
25
  lsl r24   ;  33  *lshrhi3_const/5  [length = 5]
26
  mov r24,r25
27
  rol r24
28
  sbc r25,r25
29
  neg r25
30
  ret   ;  31  return  [length = 1]
 
Die Konstante in insn 11 willst du garantiert nicht in Assembler 
eruieren.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Springer (Gast)


Lesenswert?

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.

von Jobst M. (jobstens-de)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

Geändert:
1
std::percent scale_naiv(uint8_t value, uint8_t min, uint8_t max) {
2
    if (value < min) {
3
        return std::percent{0U};
4
    }
5
    else if (value > max) {
6
        return std::percent{100U};
7
    }
8
    return std::percent{(uint8_t)(((value - min) * 100U) / (max - min))};
9
}
1
.global scale_naiv(unsigned char, unsigned char, unsigned char)
2
        .type   scale_naiv(unsigned char, unsigned char, unsigned char), @function
3
scale_naiv(unsigned char, unsigned char, unsigned char):
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 0 */
7
.L__stack_usage = 0
8
 ;  /home/lmeier/Projekte/wmucpp/doc/bmcpp20/bm30a.cc:86:     if (value < min) {
9
        cp r24,r22       ;  value, min
10
        brlo .L7         ; ,
11
        cp r20,r24       ;  max, value
12
        brlo .L5         ; ,
13
        mov r30,r22      ;  _9, min
14
        ldi r31,0        ;  _9
15
        mov r18,r24      ;  value, value
16
        ldi r19,0        ;  value
17
        sub r18,r30      ;  tmp58, _9
18
        sbc r19,r31      ; , _9
19
        ldi r21,lo8(100)         ;  tmp60,
20
        mul r21,r18      ;  tmp60, tmp58
21
        movw r24,r0      ;  tmp59
22
        mul r21,r19      ;  tmp60, tmp58
23
        add r25,r0       ;  tmp59
24
        clr __zero_reg__
25
        mov r22,r20      ;  max, max
26
        ldi r23,0        ;  max
27
        sub r22,r30      ;  tmp62, _9
28
        sbc r23,r31      ; , _9
29
        call __udivmodhi4
30
        mov r24,r22      ;  SR.187, tmp67
31
        ret
32
.L5:
33
        ldi r24,lo8(100)         ;  SR.187,
34
/* epilogue start */
35
 ;  /home/lmeier/Projekte/wmucpp/doc/bmcpp20/bm30a.cc:93: }
36
        ret
37
.L7:
avr-g++ V8 -O3

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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?

von Uwe (Gast)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Ja, sorry. Die ge-inlined Version ist die hier (für atmega328p)
1
main:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        ldi r24,lo8(42)  ;  tmp47,
7
        sts y,r24        ;  y, tmp47
8
        lds r24,y        ;  y.0_1, y
9
        ldi r18,lo8(100)         ; ,
10
        mul r24,r18      ;  y.0_1,
11
        movw r24,r0      ;  tmp48
12
        clr __zero_reg__
13
        ldi r22,lo8(-1)  ; ,
14
        ldi r23,0        ; 
15
        call __divmodhi4
16
 ;  ../../include/units/percent.h:36:         mValue = rhs.mValue;
17
        sts z,r22        ;  z.mValue, tmp56
18
.L9:
19
        rjmp .L9         ; 
20
        .size   main, .-main

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Ja, sorry. Die ge-inlined Version ist die hier (für atmega328p)
>         call __divmodhi4

Nö, wir waren bei unsigned :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

In der globalen Instanz ist es unsigend, in der inlined signed
1
       .file   "bm31a.cc"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
 ;  GNU C++14 (GCC) version 8.0.0 20170518 (experimental) (avr)
8
 ;      compiled by GNU C version 8.0.0 20170515 (experimental), GMP version 6.1.2, MPFR version 3.1.5-p2, MPC version 1.0.3, isl version isl-0.15-GMP
9
10
 ;  GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=4096
11
 ;  options passed:  -I /usr/local/avr/avr/include -I . -I ../../include
12
 ;  -I ../include -I ../../../include -I /usr/include/simavr
13
 ;  -imultilib avr5 -D__AVR_ATmega328P__ -D__AVR_DEVICE_NAME__=atmega328p
14
 ;  -D F_OSC=12000000 -D F_CPU=12000000
15
 ;  /home/lmeier/Projekte/wmucpp/doc/bmcpp20/bm31a.cc -mn-flash=1
16
 ;  -mmcu=avr5 -auxbase-strip bm31a.s -O2 -Wall -Wextra -std=c++1z
17
 ;  -fno-unwind-tables -fno-threadsafe-statics -funsigned-char
18
 ;  -funsigned-bitfields -fshort-enums -fconcepts -fverbose-asm -mn-flash=1
19
 ;  -mno-skip-bug -fno-rtti -fno-enforce-eh-specs -fno-exceptions
20
 ;  options enabled:  -Wmisspelled-isr -faggressive-loop-optimizations
21
 ;  -falign-functions -falign-jumps -falign-labels -falign-loops
22
 ;  -fauto-inc-dec -fbranch-count-reg -fchkp-check-incomplete-type
23
 ;  -fchkp-check-read -fchkp-check-write -fchkp-instrument-calls
24
 ;  -fchkp-narrow-bounds -fchkp-optimize -fchkp-store-bounds
25
 ;  -fchkp-use-static-bounds -fchkp-use-static-const-bounds
26
 ;  -fchkp-use-wrappers -fcode-hoisting -fcombine-stack-adjustments
27
 ;  -fcommon -fcompare-elim -fcprop-registers -fcrossjumping
28
 ;  -fcse-follow-jumps -fdefer-pop -fdevirtualize
29
 ;  -fdevirtualize-speculatively -fdwarf2-cfi-asm -fearly-inlining
30
 ;  -feliminate-unused-debug-types -fexpensive-optimizations
31
 ;  -fforward-propagate -ffp-int-builtin-inexact -ffunction-cse -fgcse
32
 ;  -fgcse-lm -fgnu-runtime -fgnu-unique -fguess-branch-probability
33
 ;  -fhoist-adjacent-loads -fident -fif-conversion -fif-conversion2
34
 ;  -findirect-inlining -finline -finline-atomics
35
 ;  -finline-functions-called-once -finline-small-functions -fipa-bit-cp
36
 ;  -fipa-cp -fipa-icf -fipa-icf-functions -fipa-icf-variables
37
 ;  -fipa-profile -fipa-pure-const -fipa-ra -fipa-reference -fipa-sra
38
 ;  -fipa-vrp -fira-hoist-pressure -fira-share-save-slots
39
 ;  -fira-share-spill-slots -fisolate-erroneous-paths-dereference -fivopts
40
 ;  -fkeep-static-consts -fleading-underscore -flifetime-dse -flra-remat
41
 ;  -flto-odr-type-merging -fmath-errno -fmerge-constants
42
 ;  -fmerge-debug-strings -fmove-loop-invariants -fomit-frame-pointer
43
 ;  -foptimize-sibling-calls -foptimize-strlen -fpartial-inlining
44
 ;  -fpeephole -fpeephole2 -fplt -fprefetch-loop-arrays -freg-struct-return
45
 ;  -freorder-blocks -freorder-functions -frerun-cse-after-loop
46
 ;  -fsched-critical-path-heuristic -fsched-dep-count-heuristic
47
 ;  -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic
48
 ;  -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic
49
 ;  -fsched-stalled-insns-dep -fschedule-fusion -fsemantic-interposition
50
 ;  -fshow-column -fshrink-wrap -fshrink-wrap-separate -fsigned-zeros
51
 ;  -fsplit-ivs-in-unroller -fsplit-wide-types -fssa-backprop -fssa-phiopt
52
 ;  -fstdarg-opt -fstore-merging -fstrict-aliasing
53
 ;  -fstrict-volatile-bitfields -fsync-libcalls -fthread-jumps
54
 ;  -ftoplevel-reorder -ftrapping-math -ftree-bit-ccp
55
 ;  -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-coalesce-vars
56
 ;  -ftree-copy-prop -ftree-dce -ftree-dominator-opts -ftree-dse
57
 ;  -ftree-forwprop -ftree-fre -ftree-loop-if-convert -ftree-loop-im
58
 ;  -ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
59
 ;  -ftree-phiprop -ftree-pre -ftree-pta -ftree-reassoc -ftree-scev-cprop
60
 ;  -ftree-sink -ftree-slsr -ftree-sra -ftree-switch-conversion
61
 ;  -ftree-tail-merge -ftree-ter -ftree-vrp -funit-at-a-time -fverbose-asm
62
 ;  -fzero-initialized-in-bss
63
64
        .text
65
.global scale_naiv(unsigned char, unsigned char, unsigned char)
66
        .type   scale_naiv(unsigned char, unsigned char, unsigned char), @function
67
scale_naiv(unsigned char, unsigned char, unsigned char):
68
/* prologue: function */
69
/* frame size = 0 */
70
/* stack size = 0 */
71
.L__stack_usage = 0
72
{
73
        cp r24,r22       ;  value, min
74
        brlo .L7         ; ,
75
        cp r20,r24       ;  max, value
76
        brlo .L5         ; ,
77
        mov r30,r24      ; , value
78
        sub r30,r22      ; , min
79
        sbc r31,r31      ; 
80
        ldi r21,lo8(100)         ;  tmp60,
81
        mul r21,r30      ;  tmp60, tmp58
82
        movw r24,r0      ;  tmp59
83
        mul r21,r31      ;  tmp60, tmp58
84
        add r25,r0       ;  tmp59
85
        clr __zero_reg__
86
        sub r20,r22      ;  tmp61, min
87
        mov r22,r20      ;  tmp62, tmp61
88
        ldi r23,0        ; 
89
        call __udivmodhi4
90
        mov r24,r22      ;  SR.11, tmp67
91
        ret
92
.L5:
93
        ldi r24,lo8(100)         ;  SR.11,
94
/* epilogue start */
95
        ret
96
.L7:
97
        ldi r24,0        ;  D.9136
98
        ret
99
        .size   scale_naiv(unsigned char, unsigned char, unsigned char), .-scale_naiv(unsigned char, unsigned char, unsigned char)
100
        .section        .text.startup,"ax",@progbits
101
.global main
102
        .type   main, @function
103
main:
104
/* prologue: function */
105
/* frame size = 0 */
106
/* stack size = 0 */
107
.L__stack_usage = 0
108
        ldi r24,lo8(42)  ;  tmp47,
109
        sts y,r24        ;  y, tmp47
110
        lds r24,y        ;  y.0_1, y
111
        ldi r18,lo8(100)         ; ,
112
        mul r24,r18      ;  y.0_1,
113
        movw r24,r0      ;  tmp48
114
        clr __zero_reg__
115
        ldi r22,lo8(-1)  ; ,
116
        ldi r23,0        ; 
117
        call __divmodhi4
118
        sts z,r22        ;  z.mValue, tmp56
119
.L9:
120
        rjmp .L9         ; 
121
        .size   main, .-main
122
.global z
123
        .section .bss
124
        .type   z, @object
125
        .size   z, 1
126
z:
127
        .zero   1
128
.global y
129
        .data
130
        .type   y, @object
131
        .size   y, 1
132
y:
133
        .byte   42
134
        .ident  "GCC: (GNU) 8.0.0 20170518 (experimental)"
135
.global __do_copy_data
136
.global __do_clear_bss

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> In der globalen Instanz ist es unsigend, in der inlined signed

Dann hast du noch nen Wurm drin. Code hab ich oben gezeigt:

Beitrag "Re: Assembler (AVR) Freaks bitte: der schnellste Weg, einen ganzzahligen Wert zu skalieren?"

Und 255 als Divisor

>        ldi r22,lo8(-1)
>        ldi r23,0

wird deinen Code auf fast nichts eindampfen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Auch noch die Version von Johann (s.u, scale1) eingefügt:
1
static inline
2
uint8_t scale (uint8_t value, uint8_t min, uint8_t max)
3
{
4
    return ((value - min) * 100u) / ((uint16_t) (uint8_t)(max - min));
5
}
6
7
uint8_t scale1 (uint8_t value)
8
{
9
    return scale (value, 0, 255);
10
}

ergibt dann:

O:1 naiv : 196ms
O:2 scale var : 201ms
O:3 scale d/a : 21ms
O:4 fast : 22ms
O:5 loUp Var : 38ms
O:6 loUp Fixed : 17ms
O:7 asm : 98ms
O:8 scale1 : 197ms

von C. A. Rotwang (Gast)


Lesenswert?

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).

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

Beitrag #5020180 wurde von einem Moderator gelöscht.
von Uwe (Gast)


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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, ...

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Peter Hofbauer (Gast)


Lesenswert?

Hallo Wilhelm M.

Deine Zeitangaben können nicht stimmen, wie ermittelst Du diese 
eigentlich?

Gruß Peter

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Wilhelm M. (wimalopaan)


Lesenswert?

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).

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

: Bearbeitet durch Moderator
von Ordner (Gast)


Lesenswert?

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?

Beitrag #5020297 wurde von einem Moderator gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

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 ;-)

von Ordner (Gast)


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von Wilhelm M. (wimalopaan)


Lesenswert?

Baldrian schrieb im Beitrag #5020297:
> Wilhelm M. schrieb:
>
>> Ich warte immer noch auf eine schnellere Assembleroutine ...
>
> Was gibt es zu verdienen?

Vielleicht Beschimpfungen?

von Lars (Gast)


Lesenswert?

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.

von Baldrian (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Der Andere (Gast)


Lesenswert?

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
:-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

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"

:)

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ;-)

von Ordner (Gast)


Lesenswert?

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?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Ordner (Gast)


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von Wilhelm M. (wimalopaan)


Lesenswert?

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.).

von Dumdi D. (dumdidum)


Lesenswert?

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

von Ordner (Gast)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
Beitrag #5020463 wurde von einem Moderator gelöscht.
von Ordner (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

Beitrag #5020511 wurde von einem Moderator gelöscht.
von Uwe (Gast)


Lesenswert?

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.

Beitrag #5020522 wurde von einem Moderator gelöscht.
von Ordner (Gast)


Lesenswert?

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.

Beitrag #5020536 wurde von einem Moderator gelöscht.
Beitrag #5020538 wurde von einem Moderator gelöscht.
Beitrag #5020539 wurde von einem Moderator gelöscht.
Beitrag #5020544 wurde von einem Moderator gelöscht.
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
Beitrag #5020546 wurde von einem Moderator gelöscht.
Beitrag #5020552 wurde von einem Moderator gelöscht.
von Ordner (Gast)


Lesenswert?

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.

Beitrag #5020561 wurde von einem Moderator gelöscht.
Beitrag #5020573 wurde von einem Moderator gelöscht.
Beitrag #5020588 wurde von einem Moderator gelöscht.
Beitrag #5020589 wurde von einem Moderator gelöscht.
Beitrag #5020591 wurde vom Autor gelöscht.
Beitrag #5020595 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Auch noch die Version von Johann (s.u, scale1) eingefügt:
>
>
1
> static inline
2
> uint8_t scale (uint8_t value, uint8_t min, uint8_t max)
3
> {
4
>     return ((value - min) * 100u) / ((uint16_t) (uint8_t)(max - min));
5
> }
6
> 
7
> uint8_t scale1 (uint8_t value)
8
> {
9
>     return scale (value, 0, 255);
10
> }
11
>
>
> 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.

Beitrag #5020598 wurde von einem Moderator gelöscht.
Beitrag #5020612 wurde von einem Moderator gelöscht.
Beitrag #5020638 wurde von einem Moderator gelöscht.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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?

1
constexpr percent scale(uint8_t value) {
2
    return std::percent{(uint8_t)((uint8_t)(value / 4) + (uint8_t)(value / 8) + (uint8_t)(value / 64))};
3
}

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:
1
  value   Original   Div/Add
2
  ——————————————————————————
3
    63       24        22
4
   127       49        47 \___ großer Sprung
5
   128       50        50 /
6
   255      100        97
7
  ——————————————————————————

von Ordner (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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.

von Ordner (Gast)


Lesenswert?

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".

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Hast du mal -O2 probiert?

Ist wie gesagt ein Problem mit -O2 ab v7.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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-GCC ist ein GCC.  Das Frontend ist grundsätzlich erstmal 
zwischen den einzelnen Zielplattformen gleich.

von Ordner (Gast)


Lesenswert?

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.

Beitrag #5020792 wurde von einem Moderator gelöscht.
Beitrag #5020798 wurde von einem Moderator gelöscht.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

Beitrag #5020835 wurde von einem Moderator gelöscht.
Beitrag #5020839 wurde von einem Moderator gelöscht.
von A. S. (Gast)


Lesenswert?

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);
}

Beitrag #5020852 wurde von einem Moderator gelöscht.
Beitrag #5020885 wurde von einem Moderator gelöscht.
von Patrick J. (ho-bit-hun-ter)


Lesenswert?

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

von Dieter F. (Gast)


Lesenswert?

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 ...

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

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

Beitrag #5021029 wurde von einem Moderator gelöscht.
von Jobst M. (jobstens-de)


Lesenswert?

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

Beitrag #5021299 wurde von einem Moderator gelöscht.
von Mark B. (markbrandis)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

Beitrag #5021647 wurde von einem Moderator gelöscht.
von Dumdi D. (dumdidum)


Lesenswert?

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?

Beitrag #5021665 wurde von einem Moderator gelöscht.
Beitrag #5021670 wurde von einem Moderator gelöscht.
von Dr. Google (Gast)


Lesenswert?

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.

Beitrag #5021740 wurde von einem Moderator gelöscht.
Beitrag #5021798 wurde von einem Moderator gelöscht.
Beitrag #5022044 wurde von einem Moderator gelöscht.
Beitrag #5022051 wurde von einem Moderator gelöscht.
Beitrag #5022066 wurde von einem Moderator gelöscht.
Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.