Forum: Compiler & IDEs Was kostet eine Funktion?


von nik j. (mirkovolt)


Lesenswert?

Hi,

ich bin noch relativ unerfahren im Bereich 
Mikrocontroller-Programmierung und C im allgemeinen, habe allerdings 
einige berufliche Erfahrung in der Programmierung von 
Unternehmensanwendungen – primär in Java.

Ich versuche bei der Programmierung ein paar Prinzipien und Regeln zu 
beherzigen, die von manchen unter dem Begriff 'Clean Code' 
zusammengefasst werden. Was mir gerade schwerfällt, ist diese Prinzipien 
mit 'idiomatischem' C für Mikrocontroller zu verheiraten:

Funktionen:
- keine kryptischen Abkürzungen als Namen, gilt auch für Argumente
- tut genau EINE Sache (Single Responsibility Priciple)
- kurz, möglichst nicht mehr als 3 bis 5 Zeilen
- möglichst wenige Funktionsargumente
- alle Logik befindet sich auf dem selben Abstraktionslevel, keine 
Vermischung von high- und lowlevel-Logik.
- keine tiefen Verschachtelungen von if- und Schleifenkonstrukten.
- wiederkehrende Muster in Funktionen auslagern - Duplikation vermeiden

Boolsche Ausdrücke:
- Zuweisung an eine Variable mit aussagekräftigem Namen statt direkter 
Verwendung in einem if-Ausdruck
- Komplexen Ausdruck in mehrere kleine zerlegen, auch wieder jeden mit 
aussagekräftiger Bezeichnung versehen

Variablen:
- keine mehrfachen Zuweisungen
- keine kryptischen Abkürzungen, schon gar keine einzelnen Buchstaben

So eine Praxis führt ggf. zu vielen kleinen Funktionen, mehr Variablen, 
längeren Bezeichnern und generell mehr Text. Dafür kann man sich oftmals 
Kommentare sparen, da der Code für sich spricht.

In Java muss man sich um die Effizienz solchen Codes keine Gedanken 
machen. Der Compiler bzw. die virtuelle Maschine optimieren das alles 
weg. Hier könnte ich mir allerdings vorstellen, dass einige bei einer 
solchen Programmierpraxis die Hände über dem Kopf zusammenschlagen.

Mich würde eure Meinung interessieren. Wie sollte lesbarer, wartbarer 
Qualitätscode aussehen, der gleichzeitig die Performancebedingungen von 
Mikrocontrollern berücksichtigt und als idiomatisches C angesehen werden 
kann? Wie lässt sich der Begriff 'Clean Code' in die Embedded-Welt 
einordnen?

von Heinz (Gast)


Lesenswert?

Und was ist deine konrekte Frage?

von Kevin (Gast)


Lesenswert?

nik janusch schrieb:
> Was kostet eine Funktion?

Nun ich sage einmal das kommt auf die Menge an.
Für größere Mengen gibt es sicher Rabatt.

von Fabian O. (xfr)


Lesenswert?

nik janusch schrieb:
> In Java muss man sich um die Effizienz solchen Codes keine Gedanken
> machen.

In C eigentlich auch nicht. Kleine Funktionen in der gleichen 
Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es 
sich lohnt. Wenn nicht, kostet der Funktionsaufruf halt ein paar Takte 
(hängt von der Anzahl und Größe der Parameter und des Rückgabewerts ab), 
dafür wird der Code kleiner.

Zusätzliche lokale Variablen für Zwischenergebnisse kosten sowieso 
nichts. Im Gegenteil, in manchen Fällen kann der Code dadurch sogar 
besser optimiert werden, z.B. wenn man eine volatile-Variable zuerst in 
einer lokalen Variable zwischenspeichert und dann mehrmals verwendet.

Ist also imo schon genau richtig, wie Du es machst. Schreib den Code so, 
dass er lesbar, verständlich und wartbar ist und lass den Compiler den 
Rest machen.

von erdnuss (Gast)


Lesenswert?

nik janusch schrieb:
> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen

Das ist aber eine ziemlich starke Einschränkung, ne Schleife mit 
if-Abfrage passt da nicht mehr rein. 10 Zeilen sollte man schon 
mindestens erlauben.

von Christian B. (casandro)


Lesenswert?

Fabian O. schrieb:
> In C eigentlich auch nicht. Kleine Funktionen in der gleichen
> Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es
> sich lohnt.

Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline 
zu machen das Verhalten des Programmes stark verändert. Schau lieber in 
der Dokumentation des Compilers nach, da steht drin wann und ob er das 
macht.

Ganz grob kann man aber sagen, dass Prozessoren gerne auf 
Funktionsaufrufe optimiert werden. Sprich je optimierter der Prozessor 
auf Geschwindigkeit ist, um so weniger kostet ein Funktionsaufruf.

Auf modernen PCs sind Funktionsaufrufe sogar schneller als 
Inlinefunktionen, weil durch das letztere mehr Programmcode aus dem 
Speicher geladen werden muss, was dort meistens das Nadelör ist.

Fefe hat dazu mal einen Vortrag gemacht 
http://www.fefe.de/know-your-compiler.pdf

Oder als Video hier:
ftp://ftp.ccc.de/events/camp2007/video/m4v/cccamp07-en-1952-Know_your_co 
mpiler.m4v

von MS (Gast)


Lesenswert?

erdnuss schrieb:
> nik janusch schrieb:
>> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen
>
> Das ist aber eine ziemlich starke Einschränkung, ne Schleife mit
> if-Abfrage passt da nicht mehr rein. 10 Zeilen sollte man schon
> mindestens erlauben.

Eben nicht. Jede Funktion soll nur eine Sache machen. Du hättest hier 
schon zwei Dinge: das Iterieren und dann die if-Logik. Laut Clean-Code 
sollte in deiner Schleife also nur ein Funktionsaufruf stehen. Deine 
if-Abfrage kommt dann in diese Funktion.

von Christian B. (casandro)


Lesenswert?

Was sauberer Code ist musst du selber für Dich herausfinden. Les Dir mal 
"The Art of Unix Programming" durch. Das ist eine fundierte Meinung, auf 
der Du Deine eigene Meinung aufbauen kannst, oder von der Du Dich 
distanzieren kannst.

von Klaus (Gast)


Lesenswert?

nik janusch schrieb:
> Funktionen:
> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen

Ich versuche gerade mir vorzustellen, wie die libc oder die libmath im 
inneren aussieht, zerlegt in Scheibchen mit 3 bis 5 Zeilen

MfG Klaus

von Sven P. (Gast)


Lesenswert?

Christian Berger schrieb:
> Was sauberer Code ist musst du selber für Dich herausfinden. Les Dir mal
> "The Art of Unix Programming" durch.
Unbedingt.

Christian Berger schrieb:
> Fabian O. schrieb:
>> In C eigentlich auch nicht. Kleine Funktionen in der gleichen
>> Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es
>> sich lohnt.
>
> Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline
> zu machen das Verhalten des Programmes stark verändert.
Ne, eben gerade nicht. Sämtliche Optimierungen, die der Compiler 
vornehmen darf, dürfen das Verhalten des Programmes nicht verändern. 
Diese Voraussetzung funktioniert allerdings nur, wenn man auch 
verstanden hat, was das Verhalten eines Programmes in C überhaupt ist.

Christian Berger schrieb:
> Auf modernen PCs sind Funktionsaufrufe sogar schneller als
> Inlinefunktionen,
Das hingegen würde ich nicht unterschreiben. Auf der einen Seite 
forderst du, kurze Funktionen zu schreiben. Ich erlaube mir mal, das so 
zu erweitern, dass man auch lieber kurze Funktionen statt Makros 
verwenden soll. Aber eine Funktion, die gerade ein Bit im Register 
setzt, ist bei Umsetzung als inline mit Sicherheit schneller und 
kompakter, als ein Funktionsaufruf...

Zur Thematik insgesamt ist m.M.n. das gleiche zu sagen, wie zur 
Kommentierung (siehe Thread nebenan(1)): Verallgemeinerungne sind 
grundsätzlich falsch...
Es spricht überhaupt nichts dagegen, einen Schleifenzähler 'i' zu 
nennen. In einer maximal fünf Zeilen langen Funktion schon garnicht 
schmunzel Aber ob das Zerbröseln von Quelltext in unzählige 
Unterfunktionen immer sinnvoll ist, bezweifle ich stark.

von troll (Gast)


Lesenswert?

nik janusch schrieb:
> Was kostet eine Funktion?
Aktuell 7,32€ + MwSt., aber das ändert sich alle paar Stunden je nach 
Nachfrage.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Klaus schrieb:
> nik janusch schrieb:
>> Funktionen:
>> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen
>
> Ich versuche gerade mir vorzustellen, wie die libc oder die libmath im
> inneren aussieht, zerlegt in Scheibchen mit 3 bis 5 Zeilen

:-)

...und ich versuche gerade mit vorzustellen, wie GCC aussehen würde. 
Bei 4000000 Zeilen Code wäre man bei 100000 Funkionen oder mehr.

Dabei ist schon ein Faktor von 1/4 eingerechnet (Quelle besteht zu 75% 
aus Kommentaren, also 1000000 LOC netto) und eine Funktion darf 10 
Zeilen groß sein.

Bibliotheken wie libgcc, libada, libfortran, libstdc++, libsupc++, 
libiberty, libcpp, ...) sind da noch nicht eingerechnet.

Eine Coding-Rule starr nach einer Code-Metrik auszurichten scheint mir 
wenig zielführend zu sein, zumindest bei komplexen Aufgabenstellungen.

von Christian B. (casandro)


Lesenswert?

Sven P. schrieb:
> Das hingegen würde ich nicht unterschreiben. Auf der einen Seite
> forderst du, kurze Funktionen zu schreiben. ...
> Aber eine Funktion, die gerade ein Bit im Register
> setzt, ist bei Umsetzung als inline mit Sicherheit schneller und
> kompakter, als ein Funktionsaufruf...

Nein, Funktionen müssen nicht "kurz" sondern "in der richtigen Länge" 
sein. Da musst Du mich wohl mit jemanden verwechselt haben.

von Sven P. (Gast)


Lesenswert?

Christian Berger schrieb:
> Sven P. schrieb:
>> Das hingegen würde ich nicht unterschreiben. Auf der einen Seite
>> forderst du, kurze Funktionen zu schreiben. ...
>> Aber eine Funktion, die gerade ein Bit im Register
>> setzt, ist bei Umsetzung als inline mit Sicherheit schneller und
>> kompakter, als ein Funktionsaufruf...
>
> Nein, Funktionen müssen nicht "kurz" sondern "in der richtigen Länge"
> sein. Da musst Du mich wohl mit jemanden verwechselt haben.
Mea Culpa. Ja, ich war wieder beim OT.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Um zur Frage zurück zu kommen:

Die Kosten einer Funktion sind zunächst mal abhängig von:

• Dem verwendeten Compiler (Sprache, Zielarchitektur, Hersteller)

• Dessen Version und Verschalterung, insbesondere Inline-Heuristiken,
  Optimierung auf Codegröße, Geschwindigkeit, ...

• Der Kontext, in dem diese Funktion aufgerufen wird

• Was ist über die Funktion prinzipiell bekannt?

  - Wie oft wird sie (statisch) aufgerufen?
  - Ist der Aufruf direkt oder indirekt?
  - Ist dem Compiler der Code bekannt?
  - Wie groß ist die Funktion?
  - Welche Registerlast herrscht zum Zeitpunkt des Aufrufs?
  - Welche interprozeduralen Optimierungen werden gemacht?
    partial inlinig, interprecedural constant propagation, LTO, ...

• Wie genau sind die wirklichen Kosten im Compiler modelliert bzw.
  überhaupt modellierbar?  Und wo und wie finden diese Verwendung?

Bottom Line:

Nur Erfahrung wird dir helfen, zu beurteilen, ob der erzeugte Code 
deinen Ansprüchen an Geschwindigkeit, Codeverbrauch und Stack- bzw. 
Kontextverbrauch genügt.

Erschwerend kommt kinzu, daß die Anforderungen an den Code stark vom 
Einsatzfeld abhägen und i.d.R. selst innerhalb eine und derselben 
Anwendung stark variieren:

Eine GUI-Anwendung auf einem 64-Bit Rechner hat andere Anforderungen als 
die Firmware eines 8-Bit Mikrocontrollers, die harten 
Echtzeit-Anforderungen gerecht werden muß.  Als Beispiel sei die 
Darstellung einer Software-UART oder -PWM genannt.

von (prx) A. K. (prx)


Lesenswert?

Sven P. schrieb:
>> Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline
>> zu machen das Verhalten des Programmes stark verändert.
> Ne, eben gerade nicht. Sämtliche Optimierungen, die der Compiler
> vornehmen darf, dürfen das Verhalten des Programmes nicht verändern.

Eine Ausnahme: Die Laufzeit darf sich ändern.

Fefes Papier führt mit Recht auf, dass die Effizienz von Caching in 
erheblichem Umfang die Laufzeit bestimmt. Allerdings lässt sich daraus 
keine Regel bauen. Denn die die Laufzeit dominierenden Schleifen von 
Programmen können sehr gross sein (typisch für Datenbanksysteme), oder 
sehr klein (technisch/wissenschaftliche Aufgaben, Grafik). Abhängig 
davon kann aggressives Inlining schlecht oder gut sein.

Das Papier setzt aber voraus, dass man überhaupt Caches hat, tiefe 
Pipelines mit dynamischer Sprungvorhersage hat etc. Diese Gedanken 
spielen in der Welt von PC-Prozessoren. Mikrocontroller der hier im 
Forum üblichen Kategorie haben beides nicht oder nur rudimentär (z.B. 
für nichtsequentielle Flash-Zugriffe). Da muss der Code nur ins ROM 
passen und RAM-Daten verhalten sich gewissermassen stets wie permanent 
zu 100% gecached

In sehr einfach aufgebauten 10-100MHz Mikrocontrollern können die Regeln 
für laufzeiteffiziente Programmierung daher deutlich anders lauten als 
in hochkomplexen 3GHz PC-Systemen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

nik janusch schrieb:
> Funktionen:
> ...
> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen

Das ist zwar theoretisch machbar, trägt aber – konsequent umgesetzt –
garantiert nicht zur besseren Lesbarkeit des Codes bei.

Ich würde sagen: Eine Funktion sollte möglichst nicht länger sein als
die Größe eines Editorfensters auf einem nicht allzugroßen Bildschirm,
also etwa 50 Zeilen. Kürzer ist gut, wenn eine weitere Unterteilung der
Funktion logisch sinnvoll ist, aber nur dann.

MS schrieb:
> Jede Funktion soll nur eine Sache machen. Du hättest hier schon zwei
> Dinge: das Iterieren und dann die if-Logik. Laut Clean-Code sollte in
> deiner Schleife also nur ein Funktionsaufruf stehen. Deine if-Abfrage
> kommt dann in diese Funktion.

Im folgenden Beispiel wird in einem Graustufenbild, das durch ein zwei-
dimensionales Array gegeben ist, das Pixel mit der maximalen Helligkeit
ermittelt werden.

Ich würde das normalerweise etwa so schreiben:
1
unsigned char maxintensity(int height, int width, unsigned char image[height][width]) {
2
  unsigned char intens, maxintens;
3
  int y, x;
4
5
  maxintens = 0;
6
  for(y=0; y<height; y++) {
7
    for(x=0; x<width; x++) {
8
      intens = image[y][x];
9
      if(intens > maxintens)
10
        maxintens = intens;
11
    }
12
  }
13
  return maxintens;
14
}

Ich finde, man erkennt schon beim kurzen Überfliegen, was die Funktion
tut. Man würde die Funktion sogar dann verstehen, wenn sie einen weniger
passenden Namen (bspw. einfach nur f) hätte.

Allerdings hat die Funktion 7 Zeilen (ohne Deklarationen und Zeilen ohne
aktiven Inhalt), was nach obiger Regel schlecht ist. Außerdem enthält
sie zwei For-Schleifen und eine If-Abfrage, die auch noch ineinander
verschachtelt sind, was nach deiner Interpretation der Regeln ebenfalls
schlecht ist.

Teilt man die gleiche Funktionalität in mehrere Funktionen auf, von
denen jede maximal eine For- oder eine If-Anweisung enthält, sieht das
Ganze so aus:
1
unsigned char updatemaxlineintensity(unsigned char intens, unsigned char oldmaxintens) {
2
  unsigned char maxintens;
3
4
  maxintens = oldmaxintens;
5
  if(intens > maxintens)
6
    maxintens = intens;
7
8
  return maxintens;
9
}
10
11
unsigned char lineintensity(int width, unsigned char line[width]) {
12
  unsigned char maxintens;
13
  int x;
14
15
  maxintens = 0;
16
  for(x=0; x<width; x++)
17
    maxintens = updatemaxlineintensity(line[x], maxintens);
18
19
  return maxintens;
20
}
21
22
unsigned char updatemaxintens(int width, unsigned char line[width], unsigned char oldmaxintens) {
23
  unsigned char intens, maxintens;
24
25
  maxintens = oldmaxintens;
26
  intens = lineintensity(width, line);
27
  if(intens > maxintens)
28
    maxintens = intens;
29
30
  return maxintens;
31
}
32
33
unsigned char maxintensity(int height, int width, unsigned char image[height][width]) {
34
  unsigned char maxintens;
35
  int y;
36
37
  maxintens = 0;
38
  for(y=0; y<height; y++)
39
    maxintens = updatemaxintens(width, image[y], maxintens);
40
41
  return maxintens;
42
}

Der Code ist jetzt dreimal so lang, und ich für meinen Geschmack hat die
Lesbarkeit ganz klar gelitten.

Diese 3-bis-5-Zeilenregel mag für VHL-Sprachen wie Haskell gelten (und
ist dort auch relativ leicht einzuhalten), wo eine einzelne Codezeile
i.Allg. sehr viel mehr tut als in C, aber für C ist die Regel unsinnig.

von (prx) A. K. (prx)


Lesenswert?

Sven P. schrieb:
>> Auf modernen PCs sind Funktionsaufrufe sogar schneller als
>> Inlinefunktionen,
> Das hingegen würde ich nicht unterschreiben.

Das bezieht sich vermutlich eher auf den Rahmen von Fefes Papier: Wenn 
Inlining den Code so sehr aufbläht, dass die Effizienz des L1 Cache und 
des Branch Predictors darunter leidet. Andernfalls kann ich mir auch 
kaum vorstellen, dass Inlining schädlich ist.

Im Gegenteil: Nicht selten werden einige Optimierungen innerhalb der 
aufgerufenen Funktion überhaupt erst mit Inlining möglich. 
Beispielsweise wenn die Argumente des Aufrufs Konstanten sind und der 
Code der Funktion dadurch auf einen Bruchteil des Aufwands eindampft.

von Sven P. (Gast)


Lesenswert?

A. K. schrieb:
> Eine Ausnahme: Die Laufzeit darf sich ändern.
Das ist das Problem. Die Laufzeit gehört nämlich eigentlich nicht zum 
Verhalten des Programms. Zumindest nicht im Sinne der Sprachbeschreibung 
im C99-Standard.

von F. F. (foldi)


Lesenswert?

Gleich zu Anfang möchte ich mich entschuldigen, weil ich den ganzen 
Thread nicht gelesen habe. Da ein ähnliches Thema hier vor kurzem 
eröffnet wurde, ich selbst erst seit kurzem mit dem Programmieren von µC 
angefangen habe, fiel mir gerade eine Frage im Allgemeinen dazu ein.

Gibt es Lektüre dazu? Wie, nicht in welcher Sprache, programmiert werden 
sollte? Oder ist es grundsätzlich Sprachen abhängig? Gibt es eigentlich 
einen Industriestandard oder so was wie eine DIN (gibt es sogar fürs 
Klo)?

Das waren dann doch mehr als eine Frage.:-)

von (prx) A. K. (prx)


Lesenswert?

Frank O. schrieb:
> Oder ist es grundsätzlich Sprachen abhängig?

Definitiv ist es das. Einen 5-Zeiler in der extrem kompakten Sprache APL 
kann man schlecht mit einem 5-Zeiler in der extrem geschwätzigen Sprache 
COBOL vergleichen. ;-)

von F. F. (foldi)


Lesenswert?

A. K. schrieb:
> Frank O. schrieb:
>> Oder ist es grundsätzlich Sprachen abhängig?
>
> Definitiv ist es das. Einen 5-Zeiler in der extrem kompakten Sprache APL
> kann man schlecht mit einem 5-Zeiler in der extrem geschwätzigen Sprache
> COBOL vergleichen. ;-)

Irgendwie dachte ich mir das schon. Deshalb ist wahrscheinlich so wenig 
zu Grundsätzlichem zu lesen.
Gerade am Anfang ist es schwer die richtige Strategie zu entwickeln, wie 
ich finde.
So werden ganze Programme am Ende nur schwer lesbar, wartbar und Teile 
werden nur schwer portierbar, weil Bücher eigentlich nicht darauf 
eingehen.

Aber ich will diesen Thread nicht weiter kapern.

Also, schönen Sonntag!

von mahwe (Gast)


Lesenswert?

wieder ein punkt das wiki zu füttern

von MaWin (Gast)


Lesenswert?

> - keine kryptischen Abkürzungen als Namen, gilt auch für Argumente

Kürzt der Compiler weg, bei C sind keine Namen mehr im Kompilat.
Übrigens macht er das auch mit Kommentaren, also kommentiere ruhig viel 
und hfetig.

> - tut genau EINE Sache (Single Responsibility Priciple)

Schlecht. Vor allem weil zwischendrin Speicher benutzt wird und Speicher 
auf Microcontrollern oft rar ist. Dynamischen Speicher (in C malloc) 
sollte man gar ganz meiden. Man ist also ganz ganz weit weg von der 
garbage collection von Java.

BeispieL:
Einen String von der seriellen Schnittstell einlesen, im Zeichensatz 
wandeln und Escape Sequenzen entfernen und ein Kommando erkennen.

Wenn man das in 4 Funktionen aufspaltet, braucht man doppelten Speicher 
für den String. Man liest ihn erst in den Puffer, dann wandelt eine 
Funktion aus diesem Puffer in den nächsten, dann rödelt man zur 
Entfernung von Escapes durch, dann analysiert eine Funktion was übrig 
blieb.

Im uC reduziert man am besten, in dem die Funktion, die die Zeichen aus 
der seriellen abholt schon den Zeichensatz wandelt, bei erkannten 
Escapes einen Zustand speichert, und nur noch die echten Zeichen 
abliefert, die gleich von einem state machine Parser analysiert werden, 
dann muß nämlich der String gar nicht gepeichert werden, nur 1 Byte für 
Escape und 1 Wort für den Zustand der state machine des Parsers.

Man kann auch solchen Code so schreiben, daß man ihn noch durchschaut 
und nachträglich ändern kann. Aber Anfänger können das nicht.

> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen

Schlecht weil Funktionsaufrufe/Verlassen Platz und Zeit kostet. Manche 
Funktioen können vom Compiler inline aufgelöst werden, aber besser man 
macht das per Hand. Bei Microcontroller sind Fuktionen von tausenden von 
Zeilen keine Seltenheit, weil man oft state machines mit switch/case 
abbildet. Davon wird natürlich bei jedem Durchlauf nur ein kurzes Stück 
ausgeführt, aber den Funktionsaufruf, den spart man möglichst schon bei 
schreiben des Programms ein.
Auch eine Funktion die z.B. eine LED-Matrix mit einem Video versoprgt 
und in Helligkeitn multiplext, die also strengen zeitlichen Kriterien 
unterliegt, schreibt man am Stück. Allerdings nicht in C sondern gleich 
in Assembler. Jeder Funktonsaufruf bei einem Pixel wäre schon zu viel, 
man veruscht im Gegenteil 8 Pixel zusammen abzuhandeln.

Beliebt ist auch solcher Code:
1
uint8_t tasten,gedrueckt; // globale Variablen
2
while(1)// die Programm-Hauptschleife
3
{
4
  tasten=PIND; // 8 Tasten auf ein mal
5
  gedrueckt=tasten&~gedrueckt; // 8 Tasten auf ein mal
6
  if(gedrueckt&1)
7
  {
8
    // Taster 1 wurde gerade runtergedrückt, mach was
9
  }
10
  if(gedrueckt&2)
11
  {
12
    // Taster 2 wurde gerade runtergedrückt, mach was
13
  }
14
  // mach was sonst in der Programm-Hauptschleife passieren muß
15
  gedrueckt=tasten; // 8 Tasten auf ein mal
16
   _delay_ms(10); // Prellzeit abwarten
17
}
Der entprellt gleich alle Tasten in einem Rutsch ohne daß überhaupt eine 
Funktion dafür nötig ist.

> - möglichst wenige Funktionsargumente

Bei Microcontroller C darf man sogar massenhaft globale Variablen 
verwenden um die Parameterlisten kurz zu halten, denn es gibt keinen 
zweite Instanz (und wenn, dann per Interrupt um den man sich sowieso 
kümmern muß, z.B: mit volatile).

> - alle Logik befindet sich auf dem selben Abstraktionslevel, keine
> Vermischung von high- und lowlevel-Logik.

Nicht klug. Siehe das genannte Beispiel.

> - keine tiefen Verschachtelungen von if- und Schleifenkonstrukten.

Gilt auch, weil so was ja quadratische Rechenzeit kostet.

> - wiederkehrende Muster in Funktionen auslagern - Duplikation vermeiden

Wenn man keine Platz hat sucht man möglichst alles was doppelt ist 
zusam,men. Braucht man Geschindigkeit, macht man loop unrollment und 
dupliziert eben auch. Meistens braucht man Geschwindigkeit und 
dupliziert schon deswegen den Code, weil man ihn an siener 
Verwendungsstelle optimal an das anpasst, was man dort wirklich braucht, 
also Funktionen auf das beim jeweiligen Aufruf wirklich benötigte 
abspeckt - und schon sind es gar keine Duplikate mehr.

> - Zuweisung an eine Variable mit aussagekräftigem Namen statt direkter
> Verwendung in einem if-Ausdruck

Nur für die dümmsten der dummen Programmierer notwendig.

> - Komplexen Ausdruck in mehrere kleine zerlegen, auch wieder jeden mit
> aussagekräftiger Bezeichnung versehen

Kann man machen, der (GCC) Compiler kürzt diese temporären Variablen 
normalerweise weg, aber wenn man einfachere Kompiler verwendet, ist das 
nicht immer der Fall (BASCOM ?).

> - keine mehrfachen Zuweisungen
> - keine kryptischen Abkürzungen, schon gar keine einzelnen Buchstaben

i und j sind super Variablennamen, nur sollte der Scope sich höchstens 
auf eine Funktion beschränken, sondern verliert man damit den Überblick.

> In Java muss man sich um die Effizienz solchen Codes keine Gedanken

Doch, auch, man macht sich nur nicht und deswegen sind viele 
Java-Programme grottenlangsam und riesigfett.

> Der Compiler bzw. die virtuelle Maschine optimieren das alles weg.

Normalerweise optimiert C besser.

> Hier könnte ich mir allerdings vorstellen, dass einige bei einer
> solchen Programmierpraxis die Hände über dem Kopf zusammenschlagen.

Natürlich, weil manches doch sehr auf Anfänger zugeschnitten ist. Der 
normale uC Programmierer läuft sehr schnell an Speichergrenzen 
(Programmspeicher und RAM-Speicher) und dann an Geschwindigkeitslimits. 
Jeder trick der es trotzdem ermöglicht den kleineren billigeren 
Controller zu verwenden ist sinnvoll, denn er spart in der Produktion 
Millionen ein.

Bei Java "nur-Programmieren" werden diese Kosten ja auf den Anwender 
abgewälzt. Was schlechter Java Code schon beweirkt weil Hundertmillionen 
Benutzer z.B. ihr altes Smaprtphone weil zu klein und zu langsam für das 
neue schlecht geshriebene Programm sind in neue Hardware investieren, 
istb dem Programmierer solcher Apps ja egal.

Bei Micircontroller haut ihm sein Chef schon auf's Haupt, denn der muß 
die leistungsfähigeren Recher den Kunden auf edn Kaufpreis draufshclagen 
und verliert dabei gegen die KOnkurrenz.

Die Allianz Microsoft (schreibt fette Programme die nur auf dem neuesten 
Prozessor laufen) und Intel (verkauft gerne diese Prozessoren) ist in 
der Microcontrollerwelt nicht möglich.


Allerdings: Auch Microcontroller werden immer gröser, Megabytes an 
Speicher sind keine Seltenheit mehr, man programmiert nicht mehr in 
Assembler, nicht unbedingt in C, sondern auch in Java, tweilweise laufen 
WebServer darauf und eben ganze Smartphones.

von (prx) A. K. (prx)


Lesenswert?

Aber wo wir schon grad so schön am Optimieren sind, eine kleine Schote 
aus der Abteilung "premature optimization...":

Android verwendet eine andere virtuelle Maschinen für seinen 
Zwischencode als Java, nämlich die Dalvik-VM. Hintergrund war, dass sich 
eine registeroptimierte VM effizienter in echten Code umsetzen lässt, 
als die Stackmaschine der Java-VM.

Soweit die Theorie. In der Realität ist es freilich andersrum. Der 
mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM 
führt zu schnellerem Code als der weniger weit entwickelte Compiler der 
Dalvik-VM.

von F. F. (foldi)


Lesenswert?

@MaWin:

Melde dich mal richtig an!
Danke für den Beitrag! Du hilfst mir mit deinem Wissen in vielen 
Überlegungen weiter.

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:
> tweilweise laufen WebServer darauf und eben ganze Smartphones.

Würde ich freilich nicht mehr als Microcontroller bezeichnen. Ein PC 
wird ja nicht dadurch zum Microcontroller, dass man ihn auf die 
Dimension einer Zigarettenschachtel eindampft.

von Fabian O. (xfr)


Lesenswert?

MaWin schrieb:
> Beliebt ist auch solcher Code:
1
uint8_t tasten,gedrueckt; // globale Variablen
2
while(1)// die Programm-Hauptschleife
3
{
4
  tasten=PIND; // 8 Tasten auf ein mal
5
  gedrueckt=tasten&~gedrueckt; // 8 Tasten auf ein mal
6
  if(gedrueckt&1)
7
  {
8
    // Taster 1 wurde gerade runtergedrückt, mach was
9
  }
10
  if(gedrueckt&2)
11
  {
12
    // Taster 2 wurde gerade runtergedrückt, mach was
13
  }
14
  // mach was sonst in der Programm-Hauptschleife passieren muß
15
  gedrueckt=tasten; // 8 Tasten auf ein mal
16
   _delay_ms(10); // Prellzeit abwarten
17
}
> Der entprellt gleich alle Tasten in einem Rutsch ohne daß überhaupt eine
> Funktion dafür nötig ist.

Der Code ist eher ein Anti-Beispiel statt eins für guten Code:
- Schlecht lesbar, da keine Leerzeichen
- Überflüssige Kommentare
- Verschwendet ein Byte Speicher für globale Variable tasten
- Variable gedrueckt programmweit sichtbar, obwohl nicht außerhalb
  gebraucht.
- Vermischt Logik verschiedener Schichten

Nach Optimierung dürfte hier ziemlich das gleiche rauskommen:
1
static uint8_t get_keys(void)
2
{
3
  return PIND;
4
}
5
6
static uint8_t get_pressed_keys(void)
7
{
8
  static uint8_t keys_prev = 0;
9
  uint8_t keys = get_keys();
10
  uint8_t pressed = keys & ~keys_prev;
11
  keys_prev = keys;
12
  return pressed;
13
}
14
15
void main(void)
16
{
17
  while (1) {
18
    uint8_t gedrueckt = get_pressed_keys();
19
    if (gedrueckt & 1) {
20
      // Taster 1 wurde gerade runtergedrückt, mach was
21
    }
22
    if (gedrueckt & 2) {
23
      // Taster 2 wurde gerade runtergedrückt, mach was
24
    }
25
    // Prellzeit abwarten
26
    _delay_ms(10);
27
  }
28
}

von Axel S. (a-za-z0-9)


Lesenswert?

A. K. schrieb:

> Android verwendet eine andere virtuelle Maschinen für seinen
> Zwischencode als Java, nämlich die Dalvik-VM. Hintergrund war, dass sich
> eine registeroptimierte VM effizienter in echten Code umsetzen lässt,
> als die Stackmaschine der Java-VM.

Das mag ein Grund gewesen sein. Es gibt noch etliche mehr.

> Soweit die Theorie. In der Realität ist es freilich andersrum. Der
> mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM
> führt zu schnellerem Code als der weniger weit entwickelte Compiler der
> Dalvik-VM.

Magst du das auch irgendwie belegen?

Mein Eindruck ist ja, daß Java (und Derivate) überall da wo 
Geschwindigkeit essentiell ist (z.B. Video-Decoding) mittlerweile an 
nativen Code delegieren und "echter" Java-Code nur noch als Glue dient. 
Diese Entwicklung war abzusehen, weil Java ja von Anfang an 
performancepessimiert designt war. Ein Schelm wer Arges dabei denkt, 
wenn eine Programmiersprache ausgerechnet von einer Hardware-Bude 
gehyped wird.

Ich würde die Aussage des TE

nik janusch schrieb:
> In Java muss man sich um die Effizienz solchen Codes keine Gedanken
> machen. Der Compiler bzw. die virtuelle Maschine optimieren das alles
> weg.

eher so formulieren: In Java muß man sich um die Effizienz des selber 
geschriebenen Codes keine Gedanken machen, weil es von Hause aus schon 
so langsam ist, daß man es gar nicht mehr verschlechtern kann.


XL

von (prx) A. K. (prx)


Lesenswert?

Axel Schwenke schrieb:
> Magst du das auch irgendwie belegen

Behauptet zumindest Oracle, wobei die in dieser Frage vielleicht nicht 
gänzlich neutral sind: 
https://blogs.oracle.com/javaseembedded/entry/how_does_android_22s_performance_stack_up_against_java_se_embedded

> eher so formulieren: In Java muß man sich um die Effizienz des selber
> geschriebenen Codes keine Gedanken machen, weil es von Hause aus schon
> so langsam ist, daß man es gar nicht mehr verschlechtern kann.

;-)

Jedenfalls neigen Java-Webserver erfahrungsgemäss dazu, weit mehr CPU 
und Speicher zu erfordern als PHP-Webserver. Wobei das nicht direkt 
vergleichbar ist, weil stets unterschiedliche Funktion, aber der Trend 
ist erkennbar.

von nik j. (mirkovolt)


Lesenswert?

Es freut mich, ein Thema angestoßen zu haben, das einiges an Kontroverse 
zulässt. Ich gebe zu, unkommentiert ein paar Regeln formuliert zu haben, 
die ich in dieser Radikalität auch nicht stehen lassen würde. Bob Martin 
hat genau solche Regeln in seinem Buch 'Clean Code' aufgestellt und 
damit einiges an Bewegung und Gegenbewegung in der Entwicklerwelt 
augelöst – hin zum Positiven, wie ich finde.

Natürlich würde ich z.B. nicht stumpfsinnig darauf beharren, dass eine 
Funktion nicht mehr als 5 Zeilen Code enthalten darf, sondern eher nach 
der 'richtigen' Länge suchen, wie @casandro empfiehlt.

Vielen Dank an alle, die sich hier konstruktiv mit dem Thema 
auseinandersetzen! Den Vorschlag von @mahwe, das Wiki um die Thematik zu 
bereichern, finde ich sehr interessant. Allerdings fürchte ich, dass 
hier auch leider zuviel Potential für Flamewars besteht.

Nik

von (prx) A. K. (prx)


Lesenswert?

Axel Schwenke schrieb:
>> Soweit die Theorie. In der Realität ist es freilich andersrum. Der
>> mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM
>> führt zu schnellerem Code als der weniger weit entwickelte Compiler der
>> Dalvik-VM.
>
> Magst du das auch irgendwie belegen?

"In the JITC mode, however, Dakvik is slower than HotSpot by more than 
2.9 times": http://dl.acm.org/citation.cfm?id=2388956

von D2D (Gast)


Lesenswert?

Das Primäre ist die gute Lesbarkeit der Quellen. Dieses realisiert man 
durch Aufteilung in Dateien und Funktionen. Eine Funktion ist dann gut 
lesbar, wenn sie eine Bildschirmseite zu 50-150 % ausfüllt. Mann sollte 
ein Problem also nicht zu sehr sezieren denn eine Horde von 
Dreizeilen-Funktionen ist eben so ungünstig wie seitenlanger 
Spagettikode.

von Falk B. (falk)


Lesenswert?


von Läubi .. (laeubi) Benutzerseite


Lesenswert?

A. K. schrieb:
> Jedenfalls neigen Java-Webserver erfahrungsgemäss dazu,
> weit mehr CPU und Speicher zu erfordern als PHP-Webserver.
Wo gibt es den PHP-Webserver? ;-P
Erfahrungsgemäß hängt vieles auch einfach von der Umsetzung ab.

von nik j. (mirkovolt)


Lesenswert?

Falk Brunner schrieb:
> Strukturierte Programmierung auf Mikrocontrollern

Sehr schön. Der Artikel enthält sehr viele gute Hinweise. Nur statt SVN 
würde ich heutzutage eher raten, git zu verwenden – besonders wenn man 
im Team arbeitet oder Open Source macht.

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


Lesenswert?

nik janusch schrieb:
> Nur statt SVN
> würde ich heutzutage eher raten, git zu verwenden – besonders wenn man
> im Team arbeitet oder Open Source macht.

Naja, Geschmackssache.  Um nicht zu sagen: hat einen hohen
Religiösitätsfaktor.  Letztlich ist selbst CVS vor allem dafür
geschaffen worden, damit man (im Gegensatz zu seinem Vorgänger
RCS) im Team an einem Projekt arbeiten kann, ohne sich gegenseitig
zu blockieren.  Und Opensource gab's schon lange vor git, sogar mit
CVS. ;-)

von cppler (Gast)


Lesenswert?

Wie schon erwähnt hängt das von der Architektur und Programmiersprache 
ab.
Bei jedem Funktionsaufruf muß der Rechner den aktuellen Kontekt sichern 
und nach verlassen der Funktion wieder herstellen.
Oder meinst Du nicht die Takte die benötigt werden um eine Funktion zu 
bedienen ?

von Bronco (Gast)


Lesenswert?

nik janusch schrieb:
> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen

Mein Senf dazu:

Ich hab mal eine Weile hauptberuflich C-Code mit einem 
Struktogramm-Editor entwickelt. Bei diesem konnten Blöcke (Schleifen, 
If, Case usw.) ausgeblendet und durch Kommentare ersetzt werden.
Am Ende hatte man einen sehr kompaktes Struktogramm, welches sich Block 
für Block "aufklappen ließ".
Die Funktionen hatten mitunter mehrere 100 Zeilen Code, aber dank des 
Editors war es trotzdem sehr übersichtlich.
Ist Geschmackssache, aber mir hat das richtig gut gefallen, da die 
SW-Doku direkt im Struktogramm bzw. C-Code verhanden war.
Man durfte nur nie auf die verwegene Idee kommen, und so eine Datei in 
einem normalen Texteditor öffnen...

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.