Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich habe da eine Verständnis Frage. Ich habe da ein umfangreiches und, wie ich finde, etwas unübersichtliches Konstrukt aus lauter if() Blöcken. Das habe ich mal versuchsweise in verschachtelte switces überführt. Ich bin aber noch nicht sicher, ob das jetzt wirklich übersichtlicher ist. Was mich aber verwundert ist die Tatsache, dass das switch() Konstrukt merkbar mehr Speicher belegt (18836/18932). Nicht das mich das wirklich stören würde, aber wundern tut mich das schon und ich wüsste auch gerne, was dahinter steckt. Und da stellt sich dann auch noch die Frage, ob das, zumindest theoretisch, Auswirkungen auf die Laufzeit hat. Hier mal 2 Code Schnipsel zum Vergleich:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | while( state > 0 ) { // not fin && not error
switch( state ) {
case 1: // idle
if( *p == '\0' || *p == '\n' ) {
state = 0;
break;
}
if( *p == ' ' || *p == '\t' ) {
p++;
break;
}
if( *p == '"' ) {
p++;
state = 2;
cnt = 0;
break;
}
if( *p == '#' ) { // should not happen here
state = 0;
break;
}
if( *p == '0' ) { // Null Eintrag
p++;
state = 3;
break;
}
if( *p == ',' ) { // Null Eintrag
state = 3;
break;
}
state = -1;
break;
...
|
Version 2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | while( state > 0 ) { // not fin && not error
switch( state ) {
case 1: // idle
switch( *p ) {
case '\0':
case '\n':
state = 0;
break;
case ' ':
case '\t':
p++;
break;
case '"':
p++;
state = 2; // -> in string
cnt = 0; // reset counter
break;
case '#': // should not happen here
state = 0;
break;
case '0': // marked as empty
p++;
case ',': // IS empty
state = 3;
break;
default:
state = -1; // error
}
break;
...
|
Abgesehen vom Speicherverbrauch, welche Version würdet ihr bevorzugen?
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17552
Wohnort: Berlin
|
Dakuan schrieb: Ich habe da eine Verständnis Frage. Was mich aber verwundert ist die Tatsache, dass das switch() Konstrukt merkbar mehr Speicher belegt (18836/18932). Nicht das mich das wirklich stören würde, aber wundern tut mich das schon und ich wüsste auch gerne, was dahinter steckt. Und da stellt sich dann auch noch die Frage, ob das, zumindest theoretisch, Auswirkungen auf die Laufzeit hat.
Ja, das switch ist theoretisch schneller, und zwar unter den 2 Prämissen, dass C hier ähnlich ist wie C++, zu dem Bjarne S. das ausdrücklich geäußert hat und dass sich daran seit der Äußerung zu Beginn der 90er nichts geändert hat. Wenn die Codes funktional äquivalent sind, dann könnte ein Compiler natürlich auch beide zum gleichen Maschinencode compilieren. Aber die etwas kontraintuitive Sache ist ja die, dass längerer Code schneller ist, so dass der längere Code darauf hindeutet, das die Aussage auch für C heute zutrift. Compileroptionen könnten das Bild wieder ändern.
Abgesehen vom Speicherverbrauch, welche Version würdet ihr bevorzugen?
Speichergebrauch, nicht -verbrauch! Die Sitchanweisung. Allgemein: Die besser verstehbare.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Speichergebrauch, nicht -verbrauch!
Ok, merke ich mir. Wird ja nicht weniger.
Wenn die Codes funktional äquivalent sind, dann könnte ein Compiler natürlich auch beide zum gleichen Maschinencode compilieren.
Das hatte ich eigentlich mehr oder weniger erwartet. Allerdings erinnere ich mich jetzt, das mir mal jemand gesagt hat, dass längere Switche oft auch als Sprungtabellen realisiert werden.
Aber die etwas kontraintuitive Sache ist ja die, dass längerer Code schneller ist, so dass der längere Code darauf hindeutet, das die Aussage auch für C heute zutrift.
Diese Aussage kenne ich seit meiner Z80 Zeit. Allerdings trifft sie nicht unbedingt generell zu. Richtig ist, das schnellerer Code meist länger ist, das ist aber nicht umkehrbar. Also wenn ich in einer Schleife 5 Mal ein Unterprogramm aufrufe kann ich stattdessen auch 5 mal den Code direkt hintereinander schreiben. Da stimmt das dann. Aber ich habe auch schon Code gesehen, der einfach nur umständlich war. Und bei Tabellen bin ich unsicher. Da kommt es sicherlich sehr darauf an, dass die häufigsten Fälle vorne sind.
Die Sitchanweisung. Allgemein: Die besser verstehbare.
OK, dann werde ich den Rest auch noch umstellen. Ich war mir nicht ganz sicher, ob das der richtige Weg ist (55:45).
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17552
Wohnort: Berlin
|
Dakuan schrieb:
Das hatte ich eigentlich mehr oder weniger erwartet. Allerdings erinnere ich mich jetzt, das mir mal jemand gesagt hat, dass längere Switche oft auch als Sprungtabellen realisiert werden.
Nur längere? Eben dadurch soll es ja schneller sein.
Diese Aussage kenne ich seit meiner Z80 Zeit. Allerdings trifft sie nicht unbedingt generell zu. Richtig ist, das schnellerer Code meist länger ist, das ist aber nicht umkehrbar.
Versteht sich.
Also wenn ich in einer Schleife 5 Mal ein Unterprogramm aufrufe kann ich stattdessen auch 5 mal den Code direkt hintereinander schreiben. Da stimmt das dann.
Oder Du lösst die Schleife stehen aber schaltest Loop-unrolling beim compilieren ein.
Und bei Tabellen bin ich unsicher. Da kommt es sicherlich sehr darauf an, dass die häufigsten Fälle vorne sind.
Wenn Du es mit If-Cascaden vergleichst, bei denen die auch nicht vorne sind, nicht.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Nur längere? Eben dadurch soll es ja schneller sein.
Ok, dazu kann ich nicht wirklich etwas sagen. Mein Wissen dazu ist in den 80-er Jahre stehen geblieben, als ich mich noch mit dem Innenleben von Small-C beschäftigt hatte.
Oder Du lösst die Schleife stehen aber schaltest Loop-unrolling beim compilieren ein.
Diese Option kannte ich noch nicht. Die muss ich mir beim nächsten zeitkritischem Problem mal näher ansehen. Beim Einlesen von Parameter- bzw. Konfigurationsdateien hat das ja eher geringe Priorität.
Wenn Du es mit If-Cascaden vergleichst, bei denen die auch nicht vorne sind, nicht.
Aber darauf achte ich schon immer. Auch, oder besonders, bei if() Abfragen mit Mehrfach Bedingungen. Ich denke so eine Vorgehensweise wird man nicht wirklich los, wenn man das Programmieren mit Assembler begonnen hat. Das prägt irgendwie die Denkweise. Aber besonders bei switch() Anweisungen bin ich mir nicht sicher, ob der Compiler die Reihenfolge manchmal ändert oder ändern kann/darf. Wenn ein Programm ca. 300.000 Dateien absuchen soll, ergibt sich da sicher irgendwann eine Möglichkeit, die Laufzeit zu messen.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Das hat mir jetzt keine Ruhe gelassen und ich habe mir den erzeugten Code einmal im Debugger angesehen, trotz der Tatsache dass ich die meisten Assemblerbefehle nicht wirklich kanne. Eines ist mir sofort aufgefallen: Da sind keine Tabellen im Spiel. Bei den if-Cascaden scheint der Assemblercode exakt dem Quelltext zu entsprechen. Bei den Switch Anweisungen ist das teilweise massiv anders. Zuerst ist mir aufgefallen, dass die Reihenfolge der case-Blöcke oft (nicht immer) abweicht. Der zugehörige Code wird immer nach hinten verschoben. Offenbar wird da auch versucht, die Blöcke so anzuordnen, dass Teile davon mehrfach genutzt werden können. Das ist dann wohl als Optimierung gedacht. Und genau das scheint in meinem speziellen Fall schief zu gehen. Was wohl daran liegt, das viele case-Blöcke nur aus einem einzigen Befehl bestehen. Durch den zusätzlichen Sprung in den eigentlichen Programmblock wird dann mehr Code erzeugt als wenn man die einzige Anweisung direkt an Ort und Stelle gelassen hätte. Hier mal der Aufbau des äußeren Switches (entspricht der obigen Version 2):
Dump of assembler code from 0x804af12 to 0x804b012:
0x0804af12 <getqstr+98>: mov -0x1c(%ebp),%edx ; switch( state ) {
0x0804af15 <getqstr+101>: mov %edx,-0x58(%ebp)
0x0804af18 <getqstr+104>: cmpl $0x2,-0x58(%ebp) ; 2
0x0804af1c <getqstr+108>: je 0x804afa9 <getqstr+249>
0x0804af22 <getqstr+114>: cmpl $0x3,-0x58(%ebp) ; 3
0x0804af26 <getqstr+118>: je 0x804b0ed <getqstr+573>
0x0804af2c <getqstr+124>: cmpl $0x1,-0x58(%ebp) ; 1
0x0804af30 <getqstr+128>: je 0x804af37 <getqstr+135>
0x0804af32 <getqstr+130>: jmp 0x804b137 <getqstr+647> ; default:
0x0804af37 <getqstr+135>: mov -0x14(%ebp),%eax
0x0804af3a <getqstr+138>: movzbl (%eax),%eax
0x0804af3d <getqstr+141>: movsbl %al,%eax
0x0804af40 <getqstr+144>: mov %eax,-0x50(%ebp)
Den Aufbau der if-Cascaden lasse ich jetzt mal weg, kann ich aber bei Bedarf nachliefern. Ich gehe mal davon aus, das diese Art der Optimierung normalerweise gute Ergebnisse ergibt. Aber gibt es eine Möglichkeit nur für einen bestimmten Codeblock die Optimierung auszuschalten?
|
Dee
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
Die Sitchanweisung. Allgemein: Die besser verstehbare.
OK, dann werde ich den Rest auch noch umstellen. Ich war mir nicht ganz sicher, ob das der richtige Weg ist.
Ich bin unsicher, ob der Code, den Du dann hast, wirklich verständlicher ist. Es liest sich wie C-Code, obwohl Du doch C++ programmieren möchtest. Wenn Du eine Variable state nennst, ist die Wahrscheinlichkeit hoch, dass das State-Pattern sinnvoll angewendet werden kann: https://de.wikipedia.org/wiki/Zustand_%28Entwurfsmuster%29 Gruß Dee
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Es liest sich wie C-Code, obwohl Du doch C++ programmieren möchtest.
Es ist tatsächlich ein reines C Programm. Ich wollte das auch erwähnen, ist aber wohl beim editieren untergegangen. C++ benutze ich momentan nur für größere Projekte mit GUI. In diesem Fall kommt noch dazu, das ich ein früheres Projekt recycled habe. Ich habe hier praktisch nur den Zellkern ausgewechselt und erweitert.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Dakuan schrieb:
Es ist tatsächlich ein reines C Programm. Ich wollte das auch erwähnen, ist aber wohl beim editieren untergegangen.
Du kannst das State-Pattern auch in C anwenden - genau so, wie Du auch in C objektorientiert programmieren kannst. Im einfachsten Fall brauchst Du dafür einen Funktionszeiger, wenn es etwas komplizierter wird, dann vermutlich eine Struktur mit mehreren davon.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Du kannst das State-Pattern auch in C anwenden - genau so, wie Du auch in C objektorientiert programmieren kannst.
Ansatzweise mache ich das auch schon. Ich merke das daran, das ich aus neueren C Programmen einzelne Teile als Methoden fast unverändert übernehmen kann. Und die globalen Veriablen werden weniger. Übrigens, der von Dee Verlinkte Artikel war einer der wenigen, die ich nicht mehrfach lesen musste auch wenn die Beispiele wieder mal in Java sind (was mir nicht so liegt). Allerdings steht da auch: Auf der anderen Seite rechtfertigt der Nutzen bei sehr einfachem zustandsbehaftetem Verhalten unter Umständen nicht den teils beträchtlichen Implementierungsaufwand.
Ich habe in diesem Fall eigentlich nur 3 Zustände: vor, in und hinter einem gequotetem String.
|
Dee
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
Ich habe in diesem Fall eigentlich nur 3 Zustände: vor, in und hinter einem gequotetem String.
Ggf. reicht es dann schon aus, etwas Refactoring zu betreiben und die drei cases in eigene Methoden auszulagern. Grund ist einfach, dass es dann in der Regel einfacher zu lesen ist als ein doppeltes switch-case. Gruß Dee
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Was die Lesbarkeit betrifft hast Du sicher Recht. Was mich bisher davon abgehalten hat, ist die Tatsache, das die Funktion 3 Zeiger übergeben bekommt und 2 davon an diese Methoden weitergereicht werden müssen. Wenn da dann nur ein oder zwei Zeilen ausgeführt werden, ist das schon ziemlich viel Overhead, was in diesem Fall allerdings nicht wichtig ist. Bei dieser Anwendung geht es mehr um eine paranoide Fehlerprüfung, bevor die eingelesenen Werte auf tausende Dateien angewendet werden. Mich hatte nur irritiert, das ein Switch Konstrukt merkbar mehr Speicher benötigen kann, als eine funktional gleichwertige if-Kaskade. Ich denke das ein Programmierer das wissen sollte, wenn das in bestimmten Anwendungsfällen relevant werden könnte. Also wenn es einmal wirklich um Zeitkritische Probleme geht, wo dann auch die Reihenfolge wichtig ist, oder wenn man z.B. mit begrenztem Speicherplatz umgehen muss, wie z.B. beim Arduino. Wie viel "beautifying" man sich leisten will, hängt auch immer von den Umständen ab. Programmierer sollten dazu aber wissen, was die Compiler da machen. Ich betrachte das Thema daher als gelöst.
|
TheDarkRose
Anmeldungsdatum: 28. Juli 2010
Beiträge: 3459
|
Kommt auf den Compiler an. Die meisten inlinen die Funktionen dann sowieso wieder, besonders wenn diese nur einmalig dort verwendet werden. Also verschönern schadet da nicht unbedingt.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Die meisten inlinen die Funktionen dann sowieso wieder,...
Gilt das auch für ANSI-C? Ich dachte das gilt nur für C++. Ich wollte das heute Morgen gleich ausprobieren, habe aber nach einem Blick auf den Quelltext sofort wieder aufgegeben. Was ich zuvor geschrieben hatte, war ungenau. Ich müsste da immer ein bis zwei Adressen von Zeigern, und in zwei Fällen auch die Adresse eines Zählers übergeben. Das war mir dann doch zu umständlich. Ich denke, dass der Vorschlag von Dee die beste Lösung ist. Man könnte natürlich noch weiter gehen, aber wenn man keine globalen Variablen einsetzten will, bleiben nur noch Makros. Allerdings sind Makros mit Parametern immer mal für Überraschungen gut, weshalb ich die ungern einsetze. Interessant scheint mir, das bei C++ sich dieses Problem automatisch auflöst. Denn da alle Variablen zum Objekt gehören, kann jede Methode darauf zugreifen und man braucht auch keine Parameter zu übergeben.
|
ExcitedSpoon
Anmeldungsdatum: 17. Juli 2010
Beiträge: 226
Wohnort: /home/berlin
|
Dakuan schrieb: Die meisten inlinen die Funktionen dann sowieso wieder,...
Gilt das auch für ANSI-C? Ich dachte das gilt nur für C++.
Die meisten Compiler inlinen Funktionen automatisch wenn das der Performance zuträglich ist, das ist nichts C++-spezifisches. Unter ANSI-C kannst du Funktionen zusätzlich speziell mit dem inline -Keyword auszeichnen, wenn du inlining enforcen willst, sofern du mit -std=c99 kompilierst. Grüße
|