Hallo Leute. Ich arbeite im Moment an einem Protokollstack in dem es viele Callback Funktionen gibt, die nicht unbedingt vom Nutzer des Stacks benötigt werden. Nicht benötigte Callbacks sollten im Idealfall keinen Speicher belegen. Wenn das unbedingt notwendig ist (der Callback also vorhanden sein muss) sollte er entweder nicht ausgeführt werden oder aber so wenig Zyklen wie irgend möglich verwenden - z.B. sofort ein return machen oder Register zu sichern etc. Meine erste Idee alle Callbacks über Funktionszeiger zu realisieren macht den Code unleserlich. Deswegen dachte ich das Attribute __weak zu nutzen, aber der Thread Beitrag "gcc: Suche Dokumentation: WEAK - Reihenfolge beim Linken" macht mich ein wenig stuzig. Wenn eine Optimierung dazu führen könnte, dass ein leerer, von mir definierter Callback, einen Usercallback überschreibt dann ist das vollkommen inakzeptabel und der falsche weg. Und somit komme ich wieder zu Funktionszeigern, die ich eigentlich vermeiden möchte. Dann Frage ich mich wie ich diese Callbacks am besten attribuiere. Da meine Funktionen leer sind, würde __naked sinn ergeben, aber das ist auf der x86 Architektur (gcc) scheinbar nicht definiert. Bliebe noch die Möglichkeit __always_inline Wie würdet ihr das Problem lösen?
Florian O. schrieb: > Bliebe noch die Möglichkeit __always_inline __always_inline dürfte bei einem CallBack keinen Sinn machen!
Florian O. schrieb: > Meine erste Idee alle Callbacks über Funktionszeiger zu realisieren > macht den Code unleserlich Im Gegenteil werden Funktionszeiger für so etwas bevorzugt weil sie flexibler sind. Callbacks die nur über ihren Namen gefunden werden kann man schlecht für mehrere Instanzen benutzen, man kann sie nicht ändern, und es ist dem API nicht anzusehen wie die Callbacks heißen müssen. Außerdem sind Dinge wie__weak unportabel. In OOP-Sprachen kann man das in virtuellen Funktionen schöner anlegen, in C kann man sich mit structs behelfen die alle F.-Zeiger enthalten.
2⁵ schrieb: > Florian O. schrieb: >> Bliebe noch die Möglichkeit __always_inline > > __always_inline dürfte bei einem CallBack keinen Sinn machen! Ich dachte mir folgendes dabei: Ich deklariere die Funktion als weak, damit sie von einem Usercallback überschrieben werden kann.
1 | header.h: |
2 | void user_callback(uint8_t param) __attribute__ ((weak, always_inline)); |
3 | |
4 | src.c |
5 | void user_callback(...) |
6 | {;}
|
Wird nun beim Linken nur meine Deklaration und meine Definition gefunden, dann tauscht der Linker den Aufruf zu dieser Funtion durch einen leeren Funktionsrumpf. Somit habe ich keinen Speicherverbrauch und es müssen auch keine Takzyklen aufgewendet werden. Zumindest habe ich mir das so vorgestellt. Das funktioniert mit Pointern aber nicht.
Hi Um mal den Donald (K. nicht T.) zu zitieren: premature optimization is the root of all evil (or at least most of it) in programming. Matthias
Dr. Sommer schrieb: > Florian O. schrieb: >> ... > > ... Callbacks die nur über ihren Namen gefunden werden kann Stimmt. Es sollte schon klar ersichtlich sein was Callback ist und was nicht und vor allem was der nächste wie zu implementieren hat. > man schlecht für mehrere Instanzen benutzen, man kann sie nicht ändern, > und es ist dem API nicht anzusehen wie die Callbacks heißen müssen. Ich benötige nur eine Instanz, und die Callbacks ändern sich auch nicht. Entschuldige die Salami-Taktikt - die ist ungewollt! > Außerdem sind Dinge wie__weak unportabel. In OOP-Sprachen kann man das > in virtuellen Funktionen schöner anlegen, in C kann man sich mit structs > behelfen die alle F.-Zeiger enthalten. Ein guter Grund mich doch den Zeigern zu zuwenden.
Μαtthias W. schrieb: > Hi > > Um mal den Donald (K. nicht T.) zu zitieren: > > premature optimization is the root of all evil (or at least most of it) > in programming. > > Matthias Dem stimme ich auch voll und ganz zu. Wenn mir aber der Nutzer des Stacks mitteilt, dass er von 36 möglichen Callbacks nur zwei benötigt, dann kann ich mir den rest sparen, was Platz und Takte angeht. Da alles statisch ist ist daran m.E. nichts premature.
Florian O. schrieb: > Ich benötige nur eine Instanz, und die Callbacks ändern sich auch nicht. Das denkt man am Anfang immer ;-) ein wichtiger Punkt beim Software Engineering ist die Wiederverwendbarkeit von Code. Dazu gehört auch, dass man einzelne Komponenten mehrfach instanzieren kann, also z.B. zwei IP Stacks wenn man zwei Netzwerk Karten hat. Früher oder später will man das dann doch machen, und dann ärgert man sich wenn es nicht geht weil man nur einen Satz Callbacks haben kann. Gilt äquivalent auch für globale/statische Variablen. Immer wenn ich Bibliotheken sehe (z.B. die von ST) wo man globale Funktionen überschreiben muss denk ich mir "muss das sein"... Für Unit Tests sind Funktionszeiger übrigens auch deutlich praktischer.
Wenn dir die Funktionszeiger zu viel Platz verbrauchen: In C++ kannst du dir mit Templates und SFINAE-logik auch die noch wegoptimieren. Aber ob der Aufwand lohnt...
nimm Funktionszeiger und lass den nutzer NULL übergeben, wenn er sie nicht braucht.
Florian O. schrieb: > Μαtthias W. schrieb: >> Hi >> >> Um mal den Donald (K. nicht T.) zu zitieren: >> >> premature optimization is the root of all evil (or at least most of it) >> in programming. >> >> Matthias > > Dem stimme ich auch voll und ganz zu. Wenn mir aber der Nutzer des > Stacks mitteilt, dass er von 36 möglichen Callbacks nur zwei benötigt, > dann kann ich mir den rest sparen, was Platz und Takte angeht. Da alles > statisch ist ist daran m.E. nichts premature. Doch, ist es. Du versuchst Takte für den Aufruf einer leeren Funktion (bzw. den Vergleich eines Funktionspointers auf NULL) zu sparen ohne auch nur den geringsten Nachweis zu haben das diese "vergeudeten" Takte ein Problem für deine Applikation darstellen. Genau das ist "premature optimization". Matthias
Florian O. schrieb: > Deswegen dachte ich das Attribute *__weak* > zu nutzen, aber der Thread Beitrag "gcc: Suche Dokumentation: WEAK - Reihenfolge beim Linken" > macht mich ein wenig stuzig. Ich bin mir nichtmal sicher, ob das ein Problem der Tools ist oder der dortigen Anwendung. Eine wirklich handfeste Spezi von .weak hab ich aber auch nicht gefunden. > Wenn eine Optimierung dazu führen könnte, > dass ein leerer, von mir definierter Callback, einen Usercallback > überschreibt dann ist das vollkommen inakzeptabel und der falsche weg. Üblicherweise werden .weak in Libs definiert (im verlinkten Beispiel hingegen in einem Objekt). Der Nachteil von .weak ist aber, dass der Compiler nicht dagegen optimieren kann, z.B. Cross-Module-Inlining (CMI). Bei Funktionszeigern ist das aber i.d.R. auch nicht möglich. > Nicht benötigte Callbacks sollten im Idealfall keinen Speicher belegen > [...] auf der x86 Architektur. Auf x86? Spielen paar Bytes da echt ne Rolle? > Wenn das unbedingt notwendig ist (der Callback also vorhanden sein muss) > sollte er entweder nicht ausgeführt werden oder aber so wenig Zyklen wie > irgend möglich verwenden Am effizientesten bist du mit mit non-weak extern, weil das am ehesten CMI ermöglicht. Allerdings hat das auch Nachteile und Einschränkungen: Der Callback (CB) kann nicht mehr zur Laufzeit geändert werden, und entsprechende Dynamik muss dann im CB selbt geschehen anstatt durch Austausch des CBs. Und der Anwender muss alle CBs zur Verfügung stellen — auch die trivialen. > Wie würdet ihr das Problem lösen? Ich hab was ähnliches aber einfacheres (Soft-I2C, GNU-C) mal wie oben implementiert, und die Optimierung war optimal :-) Ein paar Auszüge:
1 | // i2c-soft.h
|
2 | typedef struct |
3 | {
|
4 | ...
|
5 | } i2c_soft_t; |
6 | |
7 | enum
|
8 | {
|
9 | I2CS_sda, |
10 | I2CS_sda_get, |
11 | I2CS_scl
|
12 | };
|
13 | |
14 | extern uint8_t i2cs_port (i2c_soft_t*, uint8_t port, uint8_t val); |
15 | extern void i2cs_init (i2c_soft_t*, fifo_t*, fifo_t*); |
16 | ...
|
i2cs_port wird im I2C-Modul verwendet aber nicht definiert:
1 | // i2c-soft.c
|
2 | #include "i2c-soft.h" |
3 | |
4 | #define NOINLINE __attribute__((__noinline__,__noclone__))
|
5 | #define INLINE inline __attribute__((__always_inline__))
|
6 | #define FLATTEN __attribute__((__flatten__))
|
7 | |
8 | ...
|
9 | |
10 | static INLINE void |
11 | sda (i2c_soft_t *is, uint8_t val) |
12 | {
|
13 | i2cs_port (is, I2CS_sda, val); |
14 | }
|
15 | |
16 | static INLINE uint8_t |
17 | sda_get (i2c_soft_t *is) |
18 | {
|
19 | uint8_t val; |
20 | val = i2cs_port (is, I2CS_sda_get, 0 /* unused */); |
21 | return val; |
22 | }
|
23 | |
24 | static INLINE void |
25 | scl (i2c_soft_t *is, uint8_t val) |
26 | {
|
27 | i2cs_port (is, I2CS_scl, val); |
28 | }
|
29 | |
30 | void FLATTEN |
31 | i2cs_init (i2c_soft_t *is, fifo_t *fifo, fifo_t *fifo_in) |
32 | {
|
33 | ATOMIC_BLOCK (ATOMIC_RESTORESTATE) |
34 | {
|
35 | ...
|
36 | sda (is, 1); |
37 | scl (is, 1); |
38 | }
|
39 | }
|
40 | ...
|
Die Implementation von sda() etc. erfolgt dann im verwendenden Modul, und durch CMI wird der Code optimal umgesetzt, z.B. in eine einzige Instruktion für den Port-Zugriff, welche in i2cs_init() geinlinet wird:
1 | // modul.c
|
2 | inline __attribute__((always_inline)) |
3 | uint8_t i2cs_port (i2c_soft_t *ic, uint8_t port, uint8_t val) |
4 | {
|
5 | (void) ic; |
6 | |
7 | if (port == I2CS_sda) |
8 | {
|
9 | if (val) |
10 | CLR (PORT_SDA); |
11 | else
|
12 | SET (PORT_SDA); |
13 | }
|
14 | |
15 | if (port == I2CS_sda_get) |
16 | {
|
17 | val = 0; |
18 | if (IS_SET (PORT_SDA_IN)) |
19 | val = 1; |
20 | }
|
21 | |
22 | if (port == I2CS_scl) |
23 | {
|
24 | if (val) |
25 | CLR (PORT_SCL); |
26 | else
|
27 | SET (PORT_SCL); |
28 | }
|
29 | |
30 | return val; |
31 | }
|
.weak Defaults in der Library stören hier nicht; davon weiß der Compiler ja nix. Wesentlich ist, dass die Symbole im Modul nicht weak sind. Problem ist dann aber wie gesagt, dass gegen die Lib-Funktionen nicht optimiert werden kann (da .weak). Sie verhindern lediglich einen hässlichen Linkerfehler; wobei die sich auch durch Optionen abstellen ließen. Wenn aber auch in den trivialen Fällen optimiert werden soll, muß eh alles non-weak sein.
:
Bearbeitet durch User
Hallo Johann,
danke für das ausführliche Beispiel. Und danke auch an die Anderen für
ihre Tipps!
Kurzer Nachtrag an Johann:
> Auf x86? Spielen paar Bytes da echt ne Rolle?
Nein keineswegs :)
Da ging es nur darum, dass __naked dort nicht definiert zu sein
scheint und ich mit #ifdef das ganze zurecht rücken müsste - was mir
nicht gefällt. Macht den Code immer sehr unleserlich.
Grüße,
Florian
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.