Hallo allerseits,
ich möchte im Programm große Zahlen dividieren. 40 Bit / 40 Bit würde
ausreichen. Das Programm ist in AVRGCC geschrieben.
uint32_t Zaehlerwert;
Zaehlerwert = 10000000 / Zaehlerwert;
Program: 1390 bytes (67.9% Full) <<-- sehr schlank
(.text + .data + .bootloader)
Data: 23 bytes (18.0% Full)
(.data + .bss + .noinit)
funktioniert sehr gut und vergrößert das Programm nur geringfügig.
__________________________________________________
Wenn ich aber umstelle auf 64 bit, bläht sich der Code extrem auf:
uint64_t Zaehlerwert;
Zaehlerwert = 10000000 / Zaehlerwert;
Program: 5670 bytes (276.9% Full) <<<-- das geht gar nicht !!!
(.text + .data + .bootloader)
Data: 283 bytes (221.1% Full)
(.data + .bss + .noinit)
Kann mir bitte jemand nennen, ob man nur beim Conpilieren eine andere
Bibliothek einbinden muß, wie es mal für Float der Fall war, um die
Programmgröße in vernünftigem Maß zu halten? Oder muß beim Datentyp
uint64_t das Programm so groß werden?
siehe:
Beitrag "LCD float ausgeben"Beitrag "uint64_t verwendet und gleich wird 5kb mehr Ram gebraucht"
Ich habe früher mal eine 40-bit-Division in Assembler geschrieben für
den kleinen Controller, die nicht sonderlich viel Platz brauchte.
Es gibt auch hier noch eine Abhandlung, die das Thema streift. Es soll
mit einem Tiny2313 ein reziproker Frequenzzähler realisiert werden.
Beitrag "Reziproker Frequenzzähler+ Optimierte 64bit uint Routinen"
Die Anwendung der mitgelieferten uint64_ops.h erschließt sich mir nicht
ohne weiteres...
mit freundlichem Gruß
Christian S. schrieb:> Es soll> mit einem Tiny2313 ein reziproker Frequenzzähler realisiert werden.
Zum einen kannst Du problemlos auf einen ATtiny4313 umsteigen (4K Flash)
und sofern Deine Referenzfrequenz nicht mehr als 6-7 gültige Stellen
hergibt viel besser mit float rechnen.
Andernfalls nimm eine IAR-Kiskstart Version (4K limit) und rechne
double. Auch das geht sehr schnell und paßt in einen ATtiny4313. Als
Anregung: http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp2
GCC 4.9.3 bringt das gleiche Ergebnis.
Davon abgesehen, für rechenintensivere Dinge würde ich eher einen
ATmega nehmen, der in Hardware multiplizieren kann.
Jörg W. schrieb:> GCC 4.7.2:> $ avr-size foo.elf> text data bss dec hex filename> 320 0 8 328 148 foo.elf>> GCC 4.9.3 bringt das gleiche Ergebnis.
Kann das sein dass der Compiler die foo() wegoptimiert hat? 320 Bytes
ist für eine Softwaredivision einer 64Bit Zahl etwas zugut.
Christian S. schrieb:> Ich habe früher mal eine 40-bit-Division in Assembler geschrieben für> den kleinen Controller, die nicht sonderlich viel Platz brauchte.
Dann nimm doch die her ... Du meintest ja schon, 40Bit wären genug
Matthias x. schrieb:> Kann das sein dass der Compiler die foo() wegoptimiert hat? 320 Bytes> ist für eine Softwaredivision einer 64Bit Zahl etwas zugut.
Vermutlich ... Der Compiler weiß genau, dass mit dem Ergebnis nichts
mehr passiert :-)
Matthias x. schrieb:> Kann das sein dass der Compiler die foo() wegoptimiert hat? 320 Bytes> ist für eine Softwaredivision einer 64Bit Zahl etwas zugut.
Nö:
1
…
2
00000044 T foo
3
…
4
000000c2 T __udivdi3
5
000000c4 T __udivdi3_umoddi3
6
000000d8 T __udivmod64
7
000000be T __umoddi3
8
…
Ohne LTO kann er das auch nicht wegoptimieren, da es ja eine globale
Option ist, d. h. beim Compilieren von foo.c weiß er nicht, ob sie
noch jemand anders rufen könnte.
Matthias x. schrieb:> 320 Bytes ist für eine Softwaredivision einer 64Bit Zahl etwas zugut.
Einiges der 64-Bit Arithmetik wurde für GCC 4.7 überarbeitet bzw.
komplett neu geschrieben, siehe https://gcc.gnu.org/PR49313
Mit Peters Implementierung hat das übrigens nichts zu tun.
Die 64-Bit Division macht einen Deal zwischen Codegröße und Laufzeit:
Bei "großen" Devices (solchen mit JMP, also >= 16KiB Flash) kann das
dazu führen, dass eine 64-Bit Division merklich schneller arbeitet als
eine 32-Bit Division mit den gleichen Werten.
Wer sich für die konkrete Imlpementierung interessiert:
http://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/avr/lib1funcs.S?revision=219188&view=markup#l1741
Johann L. schrieb:> Mit Peters Implementierung hat das übrigens nichts zu tun.
Meine Version (9.11.2011) benennt die temporären Register t7 ... t0.
Deine Version (21.11.2011) benennt die temporären Register c7 ... c0.
Ansonsten ist der Code (1030 ;; The very Division + Remainder Routine)
völlig identisch, sogar das T-Bit als Flag für die Restausgabe ist
gleich.
Ist aber bestimmt alles nur reiner Zufall, wie auch die zeitliche Nähe.
Peter D. schrieb:> Johann L. schrieb:>> Mit Peters Implementierung hat das übrigens nichts zu tun.>> Meine Version (9.11.2011) benennt die temporären Register t7 ... t0.> Deine Version (21.11.2011) benennt die temporären Register c7 ... c0.> Ansonsten ist der Code (1030 ;; The very Division + Remainder Routine)> völlig identisch, sogar das T-Bit als Flag für die Restausgabe ist> gleich.
Ich wollte hier keinen Streit vom Zaun brechen. Generell kopiere ich
keinen Code, den ich irgendwo im Netz finde, in GCC.
Zum einen genügt der Code i.d.R. weder den Lizenzbedingungen (GPL), und
andererseits entspricht er selten den Coding-Rules oder dem ABI (libgcc
verwendet z.B. nie R2...R7).
Ausnahme ist der Code von Sean D'Épagnier für Fixed-Point, der aber auch
massiv angepasst wurde.
Die Nähe des (Binär)Codes liegt darin begründet, dass es nicht wirklich
viele unterschiedliche Implementierungen für eine 64-Bit Division gibt:
Bitweise Schieben, Vergleichen und abhängig davon das Ergebnis bitweise
aufbauen. Und die Übergabe- und Return-Register, call-saved und
call-clobbered Register sind vom ABI vorgegeben.
Zum Vergleich hab ich die beiden Versionen mal angehängt; viel
Ähnlichkeit sehe ich da nicht.
> Ist aber bestimmt alles nur reiner Zufall, wie auch die zeitliche Nähe.
Die zeitliche Nähe ergab sich auch daraus, dass damals auf AVRfreaks
ziemlich viel über die ineffiziente 64-Bit Arithmetik genölt wurde.
PR49131 ist übrigens von Juni 2011 und nennt bereits explizit die
libgcc-Funktionen in der GCC-Nomenklatur: __muldi3, __[u]divdi3,
__[u]moddi3.
Zumindest kam das Thema in 11-2011 auch hier auf, und ich hab da
unterschiedliche Versionen implementiert und auch gebenchmarkt:
Beitrag "Re: Stack Überschreiber beim Rechnen mit uint64_t"
Meine Arbeitsversion dort anbei sieht auch schon fast so aus wie das,
was jetzt in der libgcc ist:
Beitrag "Re: Stack Überschreiber beim Rechnen mit uint64_t"
Und nein, ich arbeitete an avr-gcc weder haupberuflich noch
nebenberuflich oder freiberuflich. Von daher war das alles recht zäh
und langwierig und im Ausmaß sehr beschränk. Gelciher gilt auch für
meine Verwendung von avr-gcc.
Und ich fänd's toll, wenn mehr Leute am GCC mitarbeiten würden.
Erfahrene Entwickler gibt's ja viele, und eine Toolchain ist zentraler
Teil der Softwareentwicklung -- zumindest wenn man in C/C++ schreibt.
...hier noch die 64-Bit Multiplikation. Ohne MUL bleibt da nicht viel
Spielraum für die Kür, aber mit MUL ist die Implementierung schon was
aufwändiger.
Hallo,
nochmals Danke für die Ausführungen. Meine Lösung gestaltet sich im
Upgrade des AVR-GCC. Einen Assembler-Code-Teil in den C-Code einzufügen,
gelingt mir bislang noch nicht. Bestimmt ist das ganz einfach, wenn man
weiß, wie es geht :-) Diese Lernschwelle mußte ich also nicht
überwinden, obwohl das interessant und fortschritlich wäre.
Bei Atmel kann man die neue Toolchain
"avr8-gnu-toolchain-installer-3.5.0.85-win32.any.x86.exe" erhalten, die
den AVR-GCC enthält.
Diesen entpackt man in ein beliebiges Verzeichnis. Damit AVR-Studio
diesen auch benutzt, muß man im AVR-Studio in
Project - Project options - custom options - external tools
vom Pfad, der bisher zu winavr20100110 zeigte, zum Pfad des neuen
Verzeichnisses umstellen. Die Zeile bei Make kann bleiben, da in der
neuen Toolchain kein Make enthalten ist.
Hier beispielsweise hat schon mal jemand auf andere Weise das neue GCC
eingebunden:
Beitrag "Re: WinAVR 2010 / AVR-GCC und Atmega48PA"
Hier mal ein paar Größenangaben. Realisiert wurde ein Programm, das
einen normalen Frequenzzähler und einen Fequenzzähler mittels
Periodendauermessung umschaltbar auf einfache Weise mit Ausgabe aufs LCD
realisiert: (mal wieder ein Fall für die gut abgelagerten Exemplare, die
wie am ersten Tag funktionieren)
AVR Memory Usage
----------------
Device: at90s2313 mit neuem GCC compiler (avr8-gnu-toolchain)
Program: 1442 bytes (70.4% Full) <<-- ist größer, nicht kleiner!!!
(.text + .data + .bootloader) Zaehlerwert = 10000000 / Zaehlerwert;
Data: 23 bytes (18.0% Full)
nochmals mit der alten Version (Winavr mit AVR GNU Compiler Collection
(GCC) 4.3.3 ) und 32-Bit-Division zum Vergleich getestet:
Program: 1390 bytes (67.9% Full)
(.text + .data + .bootloader)
Data: 23 bytes (18.0% Full)
(.data + .bss + .noinit)
___________________________________________
mit neuem GCC compiler (avr8-gnu-toolchain 3.5.0.85) und
64-Bit-Division:
AVR Memory Usage
----------------
Device: at90s2313
Program: 1630 bytes (79.6% Full) <<--- sehr brauchbare Größe
(.text + .data + .bootloader)
Data: 27 bytes (21.1% Full)
(.data + .bss + .noinit)
mit neuem GCC compiler (avr8-gnu-toolchain), aber ohne die Zeile mit der
Division:
Program: 1442 bytes (71.4% Full) <<-- 188 bytes benötigt die
Zeile mit der Divison
(.text + .data + .bootloader)
Data: 27 bytes (21.1% Full)
(.data + .bss + .noinit)
_________________________________________
mit "zaehlerwert = 10000000000ULL / Zaehlerwert;"
Program: 1584 bytes (77.3% Full)
(.text + .data + .bootloader)
Data: 23 bytes (18.0% Full)
(.data + .bss + .noinit)
Es bleibt also noch Platz für eine Mittelwertbildung und sonstige Späße
wie UART-Ausgabe...
mit freundlichem Gruß