C - halbwegs große, konstante Structures

#2778134
Lesenswert?

Hallo zusammen,
ich habe momentan das Problem, daß ich in C Structures mit zur 
Kompilierzeit bekannen Werten füllen muß. Momentan sieht das so aus:
1
#include "werte.h"
2

3
typedef enum {reich,
4
              stamm,
5
              klasse,
6
              ordnung,
7
              familie,
8
              gattung,
9
              art } viecher_t
10

11

12
typedef struct {
13
        const char*    name;       
14
        const char*    zeugs;      
15
        boolean        tot;
16
        unsigned int   beine;
17
        viecher_t      viech;
18
        double         masse;
19
     } Getier_t;
20

21
int main() {
22
   Getier_t Getier = GETIER;
23

24
   ...
25
}

und in der Headerdatei werte.h
1
#define GETIER {\
2
  {"Helmut","Huhn",false,2,gattung,1.000000e-001},\
3
  {"Mark","Hund",true,4,art,,1.500000e+001},\
4
  {"Margret","Paarhufer",false,3,familie,8.200000e+002},\
5
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
6
}
sodaß die Daten in die ausführbare Datei einkompiliert werden, was auch 
Ziel der Sache ist, da ich nicht auf externe Daten zugreifen kann und 
will.

Mache ich das Ganze allerdings mit ein paar Daten mehr (so 10000 
structure-Einträge) schießt die Kompilierzeit (hier mit dem MS 
VC-Compiler) ins unermeßliche (die .h-Datei ist so ca. 2MB groß und er 
kompiliert jetzt seit gut 40 Minuten).

Kennt jemand eine effiziente Möglichkeit, diese konstanten Structures 
mit Werten zu befüllen, daß am Ende trotzdem alles in einer ausführbaren 
Datei bleibt?

Viele Grüße
Nicolas
#2778148
Lesenswert?

Peter II schrieb:
> lagere doch die Daten in eine Datei aus und laden sie zur laufzeit. Es
> ist doch sinnlos jedesmal eine neue Programm zu bauen, nur weil sich
> daten ändern.

Das ist genau das Gegenteil von dem, was ich will. Ich habe keinen 
Dateilademechanismus zur Verfügung (kein Dateisystem). Auch kein EEPROM 
oder sonstwie addressierbaren externen Speicher. Dafür bleiben die Daten 
dauerhaft konstant. Die Structures sind nur ein Vehikel, weil das 
Auslesen dann sehr komfortabel und übersichtlich geht.
Gast #2778152
Lesenswert?

Nicolas S. schrieb:
> Das ist genau das Gegenteil von dem, was ich will. Ich habe keinen
> Dateilademechanismus zur Verfügung (kein Dateisystem).

du bist hier aber in bereich "PC-Programmierung" und dort gibes 
Dateisystem und ladefunktionen.

Man könnte als es obj-Datei direkt in den linker vorgang übernehmen, 
dann musst aber es aber selber noch in die Structur überführen.
Gast #2778155
Lesenswert?

Bei 10'000 Codezeilen sollte die Compilierzeit auf einer halbwegs 
modernen Maschine längst nicht so gross sein. Deine Variante ist also 
schon ok. Ich vermute mal, dass der Präprozessor oder der Compiler sich 
aus irgend einem Grund verrennt. Mehr kann ich dir aber beim besten 
Willen nicht helfen.

Peter II schrieb:
> lagere doch die Daten in eine Datei aus und laden sie zur laufzeit. Es
> ist doch sinnlos jedesmal eine neue Programm zu bauen, nur weil sich
> daten ändern.

Man kann auch umgekehrt argumentieren. Warum eine Laderoutine bauen, 
wenn sich die Daten sowieso nur selten ändern.
#2778226
Lesenswert?

Zum Testen habe ich in der Headerdatei die gleiche Zeile immer 
wiederholt, also quasi so:
1
#define GETIER {\
2
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
3
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
4
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
5
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
6
       ....
7
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
8
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
9
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
10
  {"Dirk","1/2 Haehnchen",true,1,familie,3.200000e-001},\
11
}
um auszuschließen, daß der Compiler sich an einer fehlerhafen Zeile 
verrennt. Außerdem besteht mein Testcode nur noch aus dem Deklarieren 
und Instanzieren dieses Structures, um Nebeneffekte auszuschließen.

Bei ca. 500 Zeilen geht das Ganze auch noch recht zügig vonstatten, bei 
mehr als 10000 Zeilen (Headerdatei über 1,2 MB) ist die Kompilierzeit 
immer noch bei mehr als 20 Minuten (danach abgebrochen). Der Rechner ist 
"schnell genug mit ausreichend Hauptspeicher (TM)". Also scheint die 
Methode nicht so toll geeignet zu sein.

Viele Grüße
Nicolas

P.S.: Im "Orignal" steht natürlich
1
const Getier_t Getier[] = GETIER;
ich habe mich nur beim Codekürzen vertippt.
#2778246
Lesenswert?

Nicolas S. schrieb:
> Das ist genau das Gegenteil von dem, was ich will. Ich habe keinen
> Dateilademechanismus zur Verfügung (kein Dateisystem). Auch kein EEPROM
> oder sonstwie addressierbaren externen Speicher.

Ähm blöde Frage, aber was ist das für ein seltenes Zielsystem das genug 
Speicher hat für Deine Aktion, für das mit MS VC compiliert wird, aber 
das keinerlei Zugang zu einem Dateisystem haben soll? Nicht mal zu ner 
SD-Card? Das kann doch jeder poplige 4-Bit-Mikrocontroller, wenn man ihm 
eine dranlötet ;-)
Gast #2778268
Lesenswert?

Das sieht so aus, als ob sich der Compiler beim Optimieren einen 
abbricht. Vielleicht versucht er, konstante Werte direkt in den 
Assemblercode zu übernehmen, wenn er beim Compilen schon weiss, welches 
struct-Element benutzt wird.

Falls dem so ist, lagere die Variablendefinition in eine eigene Datei 
aus, die als separate Objektdatei gelinkt wird:

getier.h
1
extern const getier_t Getier[];

getier.c
1
const getier_t Getier[] = {
2
 ...
3
};
#2778336
Lesenswert?

Ich hab eher den Präprozessor in Verdacht, der sich mit der 
Makro-Textersetzung einen abbricht.

Probiers mal so

Werte.inc
1
 {"Helmut",  "Huhn",          false, 2, gattung, 1.000000e-001},
2
 {"Mark",    "Hund",          true,  4, art,     1.500000e+001},
3
 {"Margret", "Paarhufer",     false, 3, familie, 8.200000e+002},
4
 {"Dirk",    "1/2 Haehnchen", true,  1, familie, 3.200000e-001},

1
...
2

3
int main() {
4
   Getier_t Getier = {
5
#include "werte.inc"
6
   };
7

8
   ...
9
}

Wozu erst mal ein 2GB Makro anlegen, das kein Mensch braucht?

#include ist #include: Der Inhalt des Files wird an der Stelle 
reingezogen, an der der #include steht. Und erst dieses Endergebnis muss 
gültigen C Code ergeben. Kein Mensch schreibt vor, was in einem 
#includiertem File stehen muss.
Man kann auch so programmieren
1
#include "int.inc"
2
#include "main.inc"
3
#include "LeftParen.inc"
4
#include "RightParen.inc"
5
#include "LeftBrace.inc"
6
#include "RightBrace.inc"
und zusammen mit den entsprechenden zu includierenden Files

int.inc
1
int

main.inc
1
main

LeftParen.inc
1
(

....

ergibt sich nach den ganzen includes
1
int main(){}
welches ein gültiges C-Programm ist
Moderator #2778348
Lesenswert?

Der GCC braucht bei mir auf einem schon etwas betagteren Laptop für die
10000 Einträge 0,6 s. Die Headerdatei nach deinem Muster ist aber nicht
2 MB, sondern nur 580 kB groß. Trotzdem ist das ein riesiges Makro, was
dem MS-Compiler (bzw.Präprozessor) durchaus Schwierigkeiten machen
könnte. Deswegen würde ich auch den Weg von Karl Heinz vorschlagen.
#2778387
Lesenswert?

Hallo zusammen,
am Präprozessor liegt es wahrscheinlich nicht, da die Kompilierdauer für 
ein direktes Einsetzen im C-Quelltext ziemlich gleich ist.

Ich habe jetzt auf folgenden (lauffähigen) Quelltext gekürzt:
1
// MEX_loadgetier.c 
2
/* Test fuer das Kompilieren grosser Structures: Liegt hier der Flaschenhals? */ 
3

4
#include "mex.h"
5
#include "string.h"
6
#include "getier.h"
7

8
typedef char boolean;
9
typedef enum {reich,
10
              stamm,
11
              klasse,
12
              ordnung,
13
              familie,
14
              gattung,
15
              art } viecher_t;
16

17
typedef struct {
18
        const char*    name;       
19
        const char*    zeugs;      
20
        boolean        tot;
21
        unsigned int   beine;
22
        viecher_t      viech;
23
        double         masse;
24
     } Getier_t;
25

26

27
/* *********************       main       ****************************** */
28
void mexFunction(int nlhs, mxArray *plhs[],
29
        int nrhs, const mxArray *prhs[]) {
30

31
    const Getier_t Getier[] = GETIER;
32
    return;
33
}
die Includedatei "getier." wird automatisch erstellt und sieht so aus:
1
/* Configuration of struct Variables                        */
2
#define GETIER {\
3
     {"Helmut","Huhn",0,2,gattung,1.000000e-001},\
4
     {"Mark","Hund",1,4,art,1.500000e+001},\
5
     {"Margret","Paarhufer",0,3,familie,8.200000e+002},\
6
     {"Dirk","1/2 Haehnchen",1,1,familie,3.200000e-001},\
7
     {"Helmut","Huhn",0,2,gattung,1.000000e-001},\
8
     {"Mark","Hund",1,4,art,1.500000e+001},\
9
     {"Margret","Paarhufer",0,3,familie,8.200000e+002},\
10
     {"Dirk","1/2 Haehnchen",1,1,familie,3.200000e-001},\
11
     {"Helmut","Huhn",0,2,gattung,1.000000e-001},\
12
     {"Mark","Hund",1,4,art,1.500000e+001},\
13
       ...
14
     {"Margret","Paarhufer",0,3,familie,8.200000e+002},\
15
     {"Dirk","1/2 Haehnchen",1,1,familie,3.200000e-001},\
16
     {"Helmut","Huhn",0,2,gattung,1.000000e-001},\
17
     {"Mark","Hund",1,4,art,1.500000e+001}}
und das Ganze habe ich unter Matlab wie folgt kompilieren lassen:
1
%% Check handling of large structured arrays
2
% Design test for handling of large structured arrays
3
clear all
4

5
hName = 'getier.h';
6
inkrement = 1.5;
7

8
nRows = 10;
9
for testNo = 1:500
10

11
nRows = floor(nRows*inkrement);
12

13
% Structure content dummies
14
names = {'Helmut','Mark','Margret','Dirk'};
15
zeugs = {'Huhn','Hund','Paarhufer','1/2 Haehnchen'};
16
tot   = {false,true,false,true};
17
beine = {2,4,3,1};
18
viech = {'gattung','art','familie','familie'};
19
masse = {0.1,15,820,0.32};
20

21
% Generate header file
22
FID = fopen(hName,'w');
23
fprintf(FID,'\n\n/* Configuration of struct Variables                        */\n');
24
fprintf(FID,'#define GETIER {\\\n');
25
nVals = numel(names);
26
j = 1;
27
for i = 1:nRows
28
    str = '     {\"%s\",\"%s\",%i,%i,%s,%e},\\\n';
29
    fprintf(FID,str,names{j},zeugs{j},tot{j},beine{j},viech{j},masse{j});
30
    j = j+ 1;
31
    if j > nVals; j = 1; end
32
end
33
fseek(FID,-3,'cof');
34
fprintf(FID,'}\n');
35
fclose(FID);
36

37
% compile and measure time
38
tic
39
mex -v 'MEX_loadgetier01.c'
40
et = toc
41

42
   r(testNo) = nRows;
43
   t(testNo) = et;
44
   
45
   semilogy(r,t);
46
   grid on
47
   drawnow
48

49
end
und das Ergebnis ist die Kompilierzeit im Verhältnis zur Anzahl der 
Zeilen im includefile (siehe Bild).

Ich schaue mal, ob der Verlauf mit dem GCC reproduzierbar ist.

Viele Grüße
Nicolas
Angehängte Dateien:
#2779268
Lesenswert?

Hallo zusammen.
Tatsache: Der GCC (hier Version 4.6.2) verschluckt sich nicht so leicht 
an größeren Headerdateien (siehe auch angehängtes Diagramm). Die 
entstandene DLL ist auch etwas kleiner als der ursprüngliche Datensatz.

Jetzt bleibt natürlich die Frage offen, wie dem MSVC Vernunft 
beizubringen ist - die Option, die Konstanten in einer fremdkompilierten 
DLL unterzubringen will ich erst gar nicht in Erwägung ziehen - auch 
genau aus dem Grund, den Rolf Magnus erwähnt: Die Konstanten werden nur 
von main() des Hauptprogramms und nur beim Start benötigt, deswegen soll 
das wenn möglich erst gar nicht im Speicher angelegt werden.

Vielen Dank für die interessante Diskussion
Nicolas
Angehängte Dateien:
Persönliche Seite #2780256
Lesenswert?

1
Getier_t g1 = GETIER;
2
const Getier_t g2 = GETIER;
3

4
int main (void)
5
{
6
   Getier_t g3 = GETIER;
7
}
Welcher Code wird denn da für g1, g2, g3 erzeugt — vorausgesetzt, wie 
werden nicht wegoptimiert?

Für g1 gibt's 2 Möglichkeiten der Initialisierung:

1) Die Daten werden aus einem Literal ins RAM kopiert, z.B. im
   Standard CRT-Code.

2) Der Compiler erzeugt einen "künstlichen" Constructor, der zur
   init-Zeit aufgerufen wird, d.h. vor main.  Der Code in einem solchen
   Konstruktor funktioniert dann gemäß 4) oder 5)

Ditto für g2. Zusätzlich gibt's dafür noch die Möglichkeit

3) Ein Literal wird zur Load-Zeit vom Loader des OS' geladen,
   ebenso wie der Programmcode auch. Ein Kopieren zur Init-Zeit
   ist nicht nötig.

Für g3 gibt's folgende Möglichkeiten:

4) Der Kompiler erzeugt ein implizites Literal im read-only Bereich
   und kopiert diese Daten, indem er in Implizites memcpy erzeugt.

5) Der Compiler erzeugt einzelne Instruktionen; jede Instruktion
   initialisiert zB ein 8-, 16-, 32- oder 64-Bit Happen von g3.

Falls 5) verwendet wird, ist das natürlich sehr teuer, sowohl zur 
Compilerzeit als auch zu Laufzeit, denn der Compiler muss zig 
Instruktionen verwalten, compilieren, optimieren, ausgeben; evtl. 
Constant-Pools anlegen und verwalten, in den Pools suchen, etc. 
Ausserdem steigt die Codegröße linear mit der Größe des Objekts an.

GCC verwendet auch Mischformen zB 4) mit memset(0) und danach 5)

Bei GCC kann man all diese Strategiern beobachten; 5) allerdings nur 
dann, wenn das zu initialisierende Objekt klein ist.  Für Objekte, die 
größer als ein paar Worte sind, nimmt er dann 4)

Weiterer Grund für die Langsamkeit kann wie gesagt PCH sein.

GCC kennt den Schalter -Q, der Auskunft über den Resourcenverbrauch auf 
dem Host macht, ausgabe siehe unten.

Vielleicht gibt's sowas ja auch für den MSCV so daß man besser versteht, 
wo die Zeit draufgeht.
1
Execution times (seconds)
2
 phase setup             :   0.02 ( 5%) usr     506 kB (54%) ggc
3
 phase parsing           :   0.05 (13%) usr     191 kB (21%) ggc
4
 phase cgraph            :   0.31 (82%) usr     223 kB (24%) ggc
5
 phase generate          :   0.31 (82%) usr     223 kB (24%) ggc
6
 callgraph optimization  :   0.01 ( 3%) usr       3 kB ( 0%) ggc
7
 varpool construction    :   0.01 ( 3%) usr       0 kB ( 0%) ggc
8
 ipa SRA                 :   0.01 ( 3%) usr       2 kB ( 0%) ggc
9
 df scan insns           :   0.01 ( 3%) usr       0 kB ( 0%) ggc
10
 register scan           :   0.01 ( 3%) usr       0 kB ( 0%) ggc
11
 preprocessing           :   0.01 ( 3%) usr      19 kB ( 2%) ggc
12
 parser (global)         :   0.02 ( 5%) usr     144 kB (16%) ggc
13
 parser function body    :   0.02 ( 5%) usr      10 kB ( 1%) ggc
14
 tree gimplify           :   0.03 ( 8%) usr      10 kB ( 1%) ggc
15
 tree CFG cleanup        :   0.01 ( 3%) usr       0 kB ( 0%) ggc
16
 tree VRP                :   0.02 ( 5%) usr      12 kB ( 1%) ggc
17
 tree PTA                :   0.01 ( 3%) usr       0 kB ( 0%) ggc
18
 tree CCP                :   0.01 ( 3%) usr       1 kB ( 0%) ggc
19
 tree FRE                :   0.03 ( 8%) usr       5 kB ( 1%) ggc
20
 tree aggressive DCE     :   0.01 ( 3%) usr       6 kB ( 1%) ggc
21
 tree iv optimization    :   0.01 ( 3%) usr       5 kB ( 1%) ggc
22
 expand                  :   0.01 ( 3%) usr      24 kB ( 3%) ggc
23
 lower subreg            :   0.01 ( 3%) usr       4 kB ( 1%) ggc
24
 CSE 2                   :   0.01 ( 3%) usr       0 kB ( 0%) ggc
25
 combiner                :   0.01 ( 3%) usr       2 kB ( 0%) ggc
26
 integrated RA           :   0.02 ( 5%) usr      25 kB ( 3%) ggc
27
 reload                  :   0.01 ( 3%) usr       4 kB ( 1%) ggc
28
 hard reg cprop          :   0.01 ( 3%) usr       0 kB ( 0%) ggc
29
 uninit var analysis     :   0.02 ( 5%) usr       0 kB ( 0%) ggc
30
 rest of compilation     :   0.02 ( 5%) usr       4 kB ( 0%) ggc
31
 unaccounted todo        :   0.01 ( 3%) usr       0 kB ( 0%) ggc
32
 TOTAL                 :   0.38               929 kB
Persönliche Seite #2780599
Lesenswert?

Eventuell hilft dir dieser Ansatz weiter, hatte ich vor nicht 
allzulanger zeit mal probiert und hat eigentlich ganz gut funktioniert:
http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967

Ziel da war ein Firmewareupdater welcher seine Firmware eingebettet 
mitbringt, sodass der Kunde nicht zwei Files laden muss.
Gast #2780648
Lesenswert?

So nun wollte ich es auch selber mal wissen.

Ich kann es bei mir nachvollziehen das es langsam ist. Aber wenn man die 
Variable globale macht, dann geht das ganze bei 100.000 einträgen in 
2sekunden.

Das ganze lokal und damit auf dem Stack zu machen, ist für diese menge 
an daten sowieso unpassend.
1
#include "test.h"
2
#include <stdio.h>
3

4
typedef enum {reich,
5
              stamm,
6
              klasse,
7
              ordnung,
8
              familie,
9
              gattung,
10
              art } viecher_t;
11

12

13
typedef struct {
14
        const char*    name;       
15
        const char*    zeugs;      
16
        bool           tot;
17
        unsigned int   beine;
18
        viecher_t      viech;
19
        double         masse;
20
     } Getier_t;
21

22
Getier_t Getier[] = GETIER;
23

24
int main() {
25
   //Getier_t Getier[] = GETIER;
26
  
27
   int count = sizeof(Getier)/sizeof(Getier_t);
28
   printf("Anzahl: %d\n", count);
29
   
30
   double ges_masse = 0;
31
   for( int i = 0; i < count; ++i ) {
32
      ges_masse += Getier[i].masse;
33
   }
34

35
   printf("gesamtmasse: %f\n", ges_masse);
36
   return 0;
37
}
#2781599
Lesenswert?

Hallo zusammen,

A. K. schrieb:
> Was soll eigentlich der Spuk mit diesem Monster-Define? Warum nicht ein
> ganz normales statisches Array mit Initialisierung?

ist es das nicht? Sobald das define ausgeführt ist steht doch nichts 
anderes da ein statisches structure array. Die Auslagerung in ein define 
ist nur eine Komfortfunktion, da es etwas kompfortabler ist, eine 
.h-Datei zu erstellen mit define als in einem Quelltext nach einer 
Textstelle zu suchen und ersetzen. Das Ganze struct ist nur eine 
Komfortfunktion, um den Quelltext besser lesen zu können.

Peter II schrieb:
> Ich kann es bei mir nachvollziehen das es langsam ist. Aber wenn man die
> Variable globale macht, dann geht das ganze bei 100.000 einträgen in
> 2sekunden.

Danke für den Tipp, muß ich mal ausprobieren, sobald ich meinen Kaffee 
getrunken habe.

troll42 schrieb:
> Darf man fragen was das werden soll? Klingt irgendwie spannend, so nach
> universeller Tierchendatenbank...

Nein, das ist einfach ein hastig zusammengestricktes lauffähiges 
Minimalbeispiel, um die Problemstellung möglichst anschaulich darstellen 
zu können, sich nicht in Nebenschauplätzen des tatsächen Codes zu 
verlieren und "nebenbei" sicherzustellen, daß das Problem überhaupt an 
der Stelle ist, wo man es vermutet.

Viele Grüße
Nicolas
Gast #2781624
Lesenswert?

Nicolas S. schrieb:
> A. K. schrieb:
>> Was soll eigentlich der Spuk mit diesem Monster-Define? Warum nicht ein
>> ganz normales statisches Array mit Initialisierung?
>
> ist es das nicht? Sobald das define ausgeführt ist steht doch nichts
> anderes da ein statisches structure array. Die Auslagerung in ein define
> ist nur eine Komfortfunktion, da es etwas kompfortabler ist, eine
> .h-Datei zu erstellen mit define als in einem Quelltext nach einer
> Textstelle zu suchen und ersetzen. Das Ganze struct ist nur eine
> Komfortfunktion, um den Quelltext besser lesen zu können.

Du hast Karl-Heinz' Beitrag 
Beitrag "Re: C - halbwegs große, konstante Structures" gelesen (und 
verstanden)?
Nach dem Lesen Deines ersten Postings war auch dies mein erster Gedanke: 
Solch riesige Textersetzungen macht "man" eigentlich nicht mit define's 
(bzw. ist es gut vorstellbar, dass hier der Hase im Pfeffer liegt, da 
die Textersetzung eigentlich nicht für derart große Strings ausgelegt 
ist).
Ist zumindest viel wahrscheinlicher, als die Schuld beim Compiler suchen 
zu wollen.
#2781646
Lesenswert?

Hallo Patrick,
ich habe den Beitrag gelesen, verstanden und ausprobiert. Und keinerlei 
meßbaren Unterschied in der Kompilierzeit in den Varianten mit und ohne 
Defines festgestellt. Siehe hier:

Nicolas S. schrieb:
> am Präprozessor liegt es wahrscheinlich nicht, da die Kompilierdauer für
> ein direktes Einsetzen im C-Quelltext ziemlich gleich ist.

Wenn bei einer derartigen Reihe kein Unterschied feststellbar ist, gehe 
ich davon aus, daß die diese Hypothese erst einmal verworfen werden 
sollte.

Viele Grüße
Nicolas

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren