Hi,
mir ist gerade etwas aufgefallen, was ich nicht ganz verstehe...
Ich bin momentan dabei, den Code für meinen Wecker etwas zu optimieren, weil ich doch langsam an die Speichergrenzen des Nano rücke, und eigentlich schon noch ein paar kleinere Funktionen einbauen möchte (Auch wenn der Wecker inzwischen wunderbar funktioniert und mich brav jeden morgen genauso aufweckt wie ich das möchte )
So, im zuge dessen hab ich auch meine Variablentypen ein bisschen verkleinert. Wenn ich in einer variable nur Werte zwischen 0 und 160 speichere (im Beispiel Koordinaten auf dem Display, das ja nur 128x160 hat), muss ja diese Variable keine 2 Byte belegen. Also mal eben jeweils INT durch BYTE ersetzt.
Beim Kompilieren ist mir jetzt aufgefallen, dass die Änderung des Typs für eine einzelne Variable nicht nur 1-2 Bytes an dynamischen Speicher einspart, sondern auch mehrere Bytes an Programmspeicher.
Auch wenn das ein durchaus positiver Effekt ist, würd mich jetzt doch mal interessieren woran das liegt, und hoffe, dass mir das jemand erklären kann
Die Vorbesetzungen für Variablen befinden sich natürlich auch im Flash.
Da Byte der natürliche DatenType für AVRs ist, wird der Compiler einiges schlanker bauen können.
Übringens (hinter den Kulissen):
INT ist der kleinste Datentype, welcher an Funktionen/Methoden übergeben werden kann.
Es kann also sein, dass deine Methode die Angelegenheit einen Hauch ausbremst, da im Hintergrund die Bytes wieder auf INT aufgeblasen werden müssen. Und umgekehrt.
Was mich jetzt aber ein bisschen stutzen lässt ist dein zweiter Absatz...
Heißt das, bei zeitkritischen Funktionen fahre ich unter Umständen mit INT besser als BYTE, auch wenn ich nur beispielsweise eine Variable hab in der nur 1 oder 0 stehen soll?
Heißt das, bei zeitkritischen Funktionen fahre ich unter Umständen mit INT besser als BYTE, auch wenn ich nur beispielsweise eine Variable hab in der nur 1 oder 0 stehen soll?
Ja, das ist möglich.
Habe ich aber noch nicht wirklich getestet, was das ausmacht...
Und unter welchen Umständen...
Das betrifft nur den Transport rein in die Funktion, über die Parameterliste, und raus über den ReturnValue.
Aber solange man noch solche Monster, wie digitalWrite(), benutzt, muss man sich um sowas keinen Kopf machen.
INT ist der kleinste Datentype, welcher an Funktionen/Methoden übergeben werden kann.
Wie kommst Du darauf? Auf einer 8 Bit Maschine können selbstverständlich 8 Bits effizienter als 16 Bits übergeben werden.
Wenn wir schon dabei sind: wenn man Konstanten auch immer schön (in Ihrem Scope) mit "const" hinschreibt, dann hat der Compiler mehr Potential zum Optimieren. Ob er es ausnutzt kommt drauf an, aber per Default sollte man Variablen als "const" hinschreiben.
Aber wie gesagt, ich weiß nicht wie sich das auf die Performance Byte vs. INT auswirkt.
Ist mir auch im Grunde egal, da es sich sowieso nicht ändern läßt.
Naja, ob der jetzt bei der Übergabe ein paar millionstel Sekunden länger braucht oder nicht, reißts dann wohl auch nicht mehr raus ^^
Ich bin nur grad total fasziniert wieviel solche scheinbar unwichtigen Kleinigkeiten ausmachen können.
Wie z.B. const. Ich hab das bisher nie genutzt, weils mir einfach unnötig schien. Jetzt hab ich aber festgestellt, dass das für jede Variable einige Bytes Speicherplatz spart (Was natürlich auch erst jetzt für mich Sinn macht, wo mein Projekt recht umfangreich wird und an die Grenzen des Speicherplatzes stößt ).
Ich hab jetzt sämtliche Variablen, die im Laufe des Programms nicht geändert werden müssen, mit einem const versehen, außerdem hab ich jeder Variable den kleinstmöglichen Typ gegeben (Die meisten waren der Faulheit halber halt einfach als INT definiert, die meisten davon sind jetzt BYTE, einige WORD sind dabei und auch ein paar BOOLEAN), und nebenbei hab ich noch meine Weckzeiten in ARRAYS gepackt, was mir diverse IFs erspart hat (was aber interessanterweise verhältnismäßig wenig Speicherplatz gebracht hat)
Insgesamt bin ich jetzt von 97% (knapp 30.000 Bytes) Speicherplatznutzung runter auf 82% (momentan 25.196 Bytes).
Und ich gehe jede Wette ein dass da noch deutlich mehr Potential drin ist
Aus dem geht aber trotzdem hervor, dass es einen kleinen Unterschied macht, ob Funktionsparameter als int oder byte übergeben werden.
Ist mir auch im Grunde egal, da es sich sowieso nicht ändern läßt.
Ich wäre dafür, es richtig zu machen, selbst wenn sich im Endeffekt erstmal kein Unterschied ergibt.
Also Parameter, die nur Werte 0 .. 255 haben können, als byte deklarieren.
const ist ein viel gravierenderes Thema, stimmt...
Also Parameter, die nur Werte 0 .. 255 haben können, als byte deklarieren.
Richtig!
Immer den, für das Problem angemessenen, Datentype verwenden.
Das steigert die Lesbarkeit.
Und entspricht voll dem: "Prinzip der geringsten Verwunderung"
Für mich heißt das z.B.:
(habe extra mal ein grenzwertiges Beispiel raus gesucht)
// falsch
const int LedPin = 13;
// richtig
const byte LedPin = 13;
Denn digitalWrite() erwartet den Pin als INT Byte, und genau das bleibt mir bewusst, wenn ich int byte schreibe.
Würde ich Int byte verwenden, könnte mich das, ganz vielleicht mal, in eine Falle tappen lassen.
Nichts ist schwerer zu korrigieren als falsche Überzeugungen!
Nichts!
Obwohl, aus technischer Sicht dürfte es vollkommen egal sein, ob man dort int oder byte nimmt. Der Compiler machts gleiche draus.
Korrigiert
Bin in meine eigene Falle getappt und habe heimlich int mit byte vertauscht.
@Doc_Arduino: vielen Dank für den sehr interessanten Link!
Hier steht z.B. auch
In addition to reducing code size, selecting a proper data type and size will reduce execution time as well. For 8-bit AVR, accessing 8-bit (Byte) value is always the most efficient way.
Das Beispiel mit
const int LedPin = 13;
hab ich direkt mal getestet, vom Speicherplatz her machts hier keinen Unterschied ob INT oder BYTE. Interessant wäre zu sehen, wie sich das auf die Ausführungszeit auswirkt... Aber ich schätze mal dass sich da auch nix ändert, weil vermutlich, wie du sagst, der Compiler dasselbe draus macht.
hab das bei meinem Code auch mal ersetzt mit const. Spart auch 54 Byte Programmcode ein.
Zwischen #define und const habe ich keinen Unterschied gesehen.
Nur manche Pin Definitionen für verwendete Librarys erwarten Datentypen statt #define. Dann meckert der Compiler.
#define ist kein C/C++, sondern ein Präprozessordingen
Im erzeugten Code ist es egal, ob man #define verwendet, const, oder direkt die 13
Solange das Ziel mit einem Integer glücklich ist.
Deklarationen mit #define sind untypisiert.
Das war ein Kreuz im alten C, schwer zu findende Fehler....
Überhaupt sind Präprozessordinge ein Mienenfeld
Schön, wenn man sie verstanden hat, aber meist lebt man besser, ohne sie.
Die modernen C/C++ Konstrukte geben dem Compiler wenigstens die Chance, sich ein paar Meldungen abzuquetschen.
Mein Tipp: #define LedPin 13 // ist doof
Konstante 13 in den Code streuen ist auch doof. Schlecht wartbar.
Bleibt noch die const Geschichte. Die hat keine Nachteile, nur Vorteile. (soweit mir bekannt)
Du deklarierst die "Variable" als const, als Konstante. Das versteht der. Der Compiler optimiert die Variable weg. Und streut das Value an den betreffenden Stellen ein. Kann er auch gefahrlos tun, denn der Wert ändert sich nie nicht.
Dieses:
const byte LedPin = 13;
digitalWrite(LedPin,LOW);
Wird zu:
digitalWrite(13,LOW);
So, und wenn du ganz genau wissen willst, was der Compiler da erzeugt, dann kannst du dir das Produkt in Assembler ansehen.
Damit kann man vielleicht nicht das WARUM beantworten, aber, das WAS der Compiler tut.
Die Veränderbarkeit alleine ist sehr wichtig. Das geht nämlich noch viel komplizierter als einfach nur einen int. Vor allem wenn man mit Zeigern oder Referenzen arbeitet. Dann kann man da angeben, dass sich der Wert durch den Zeiger nicht ändern lassen kann soll (es gibt auch einen Unterschied ob der Zeiger selbst const ist, die Daten oder beides). Das ist vor allem wichtig wenn man Code schreibt den andere Leute verwenden sollen. Selbst weiß man vielleicht, was man mit einer Funktion nicht machen soll. Aber dadurch kann man es automatisch einschränken. Es kommt z.B. sehr oft vor, dass Funktionen eine const Referenz oder Zeiger als Rückgabewert haben. Man kann dann nur lesend darauf zugreifen.
Man kann auch ganze Klassen-Methoden als const deklarieren, um zu kennzeichnen, dass sie keine Änderungen an Variablen der Klasse vornehmen darf. Das ist auch wichtig, weil man über eine const Referenz nur const Methoden aufrufen darf.
Für reine Konstanten zeigt es dem Compiler dazu dass er sie wahrscheinlich weg optimieren kann. Was er dann i.d.R. tut. Es geht nicht wenn man z.B. mal die Adresse einer Konstante nimmt, aber das ist auch etwas unsinnig
Zeitsklave:
Was genau bewirkt denn const eigentlich, außer dass die Variable durch den Sketch nicht änderbar ist?
const zeigt prinzipiell nur an, dass es sich um eine Konstante und eben nicht um eine Variable handelt - wie der Name ja bereits sagt. Für die tieferen Einblicke bedanke ich mich bei Serenifly!
@combie: dieses Tutorial sagt nicht einmal für welchen Compiler (Version!!!) es geschrieben ist. Die Wahrheit bringt "avr-objdump -dS" ans Licht. Alles andere ist Spekulation.
@Zeitsklave: die Theorie, daß const int LedPin = 13; und const byte LedPin = 13; äquivalent sind ist falsch. Grund: die Konstanten werden verschieden getypt. An den Stellen an denen der Compiler das auseinandersteuern kann (überladene Funktionen, Templates) führt das zu verschiedenem Verhalten und auch verschiedenen Codegrößen. Deshalb typt man so wie es am besten passt. Den Typ offen zu lassen ("define") ist genau für diese Fälle am schlechtesten weil man dann das bekommt was der Compiler für richtig hält.
Weiter ist es auch eine gute Idee bei Funktionsparameters so oft wie möglich const zu verwenden.
@Doc_Arduino: wenn Du keinen Unterschied erkennst, dann ist Dein Programm zu einfach. Spiel man mit dem Beispiel unten und den Typen von a und b. Compiliere mal mit UNO und einmal mit Due als Target und ändere die Typgrößen ab und schau dabei auf die Programmgröße. Und dann erhöhe mal die Länge des zweiten Strings um ca. 100 Zeichen und schau Dir dann die Variationen an.
const uint8_t a = 13;
const uint16_t b = 13;
void setup() {
Serial.begin(115200);
if (sizeof(a) == sizeof(b)) {
Serial.println("equal sizes");
} else {
Serial.println("different sizes");
}
}
void loop() {
// put your main code here, to run repeatedly:
}
Wenn wir dabei sind: die Theorie, daß const den Wert nicht ändert stimmt auch nicht. Man darf ihn lediglich nicht ändern. Ich sag nur "const volatile"
Ja, const ist keine feste Garantie wie sich was genau verhält. Es ist ein deutlicher Hinweis an den Compiler, aber muss nicht immer daran halten
Man kann const auch weg-casten, mit dem const_cast. Damit sollte man vorsichtig umgehen, aber vor einem paar Tagen habe ich hier mal einen Code-Schippsel gepostet wo das nötig war, da von einer Funktion ein const-Zeiger als Rückgabe-Wert kam und der Parameter einer anderen Funktion (ein Zeiger auf diesen Zeiger) nicht const war. Das ging in dem Fall weil die Funktion die Variable nicht verändert hat.
Man ist also auch selbst nicht 100%ig gezwungen sich an const zu halten.
Was combiesagt, dass der Compiler die Konstante dann "wegrationalisiert" war mir eigentlich irgendwie klar (wenn auch momentan nocht ganz bewusst).
Ich dachte jetzt halt dass durch const vielleicht noch irgendwas tiefergehendes mit der Variable passiert, wie bei volatile (das ich auch noch nicht wirklich kapiere, aber damit hab ich mich auch noch nicht intensiver beschäftigt...)
Danke an Serenifly für die detaillierten Ausführungen, wenn man es so betrachtet ist es tatsächlich nicht "nur" Schreibschutz.
Ich merks selber inzwischen, wenn ich eine neue Funktion hinzufüge oder eine bestehende ändere, und erstmal überlegen oder sogar nachforschen muss um wieder zu wissen welche Variablen ich denn nun ändern darf und welche lieber nicht, damit das Programm dann noch tut was es soll...
Bei const sagt mir das dann im Zweifelsfall der Compiler
@Udo Klein: Meine Theorie war eher dass sich für mich nichts ändert, nicht dass beide Angaben äquivalent sind Es wäre bei dem Beispiel sehr interessant zu sehen was der Compiler draus macht!
Assembler ist für mich leider ein Buch mit sieben Siegeln. Zwar ein sehr faszinierendes Buch, aber vollkommen unverständlich