Forum: Mikrocontroller und Digitale Elektronik uint64_t Division großer Zahlen benötigt zu viel Programmcode


von Christian S. (roehrenvorheizer)


Lesenswert?

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ß

von Peter D. (peda)


Lesenswert?

Christian S. schrieb:
> Wenn ich aber umstelle auf 64 bit, bläht sich der Code extrem auf:

Dann benutzt Du vermutlich noch den alten WINAVR 2010.
Ich hatte dafür mal eine optimierte Lib geschrieben:

http://www.avrfreaks.net/forum/c-util-efficient-64-bit-support?name=PNphpBB2&file=viewtopic&t=113673

Bei neueren AVR-GCC wurde die dann übernommen.

von msx (Gast)


Lesenswert?

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

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


Lesenswert?

1
#include <stdint.h>
2
3
uint64_t Zaehlerwert;
4
5
void foo(void)
6
{
7
  Zaehlerwert = 10000000 / Zaehlerwert;
8
}
9
10
int main(void)
11
{
12
  return 0;
13
}

Compiliert mit -Os für den ATtiny2313:

GCC 4.7.2:
1
$ avr-size foo.elf
2
   text    data     bss     dec     hex filename
3
    320       0       8     328     148 foo.elf

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.

von Matthias X. (current_user)


Lesenswert?

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.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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

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


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

: Bearbeitet durch User
von roehrenvorheizer (Gast)


Lesenswert?

Hallo,

Danke erst mal für die Hinweise und Zuschriften. Ich werde das alles 
noch durchsehen.

MfG

von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Angehängte Dateien:

Lesenswert?

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

von Christian S. (roehrenvorheizer)


Lesenswert?

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ß

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.