Forum: Compiler & IDEs Ungenutzte Bibliotheksfunktionen aussortieren


von Florian O. (simuru)


Lesenswert?

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?

von 2⁵ (Gast)


Lesenswert?

Florian O. schrieb:
> Bliebe noch die Möglichkeit __always_inline

__always_inline dürfte bei einem CallBack keinen Sinn machen!

von Dr. Sommer (Gast)


Lesenswert?

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.

von Florian O. (simuru)


Lesenswert?

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.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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

von Florian O. (simuru)


Lesenswert?

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.

von Florian O. (simuru)


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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

von M. S. (nickfisher)


Lesenswert?

nimm Funktionszeiger und lass den nutzer NULL übergeben, wenn er sie 
nicht braucht.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
von Florian O (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.