Feld initialisieren
-
Hi,
hätte mal ne Frage zum initialisieren von Feldern. Ich hab gelesen, dass wenn ich ein Feld nicht explizit initialisiere und es einen arithmetischen Typ hat, es implizit mit Nullen besetzt wird. Warum klappt dass bei unten folgendem Programm nicht?Gruß vom Muschelsucher
#include <stdio.h> #include <math.h> int main (void) { int l,i,n,k,Zahl=0,Anzahl=0; char Entsch; int Feld[100]; ... ... ...
-
Muschelsucher schrieb:
Ich hab gelesen, dass wenn ich ein Feld nicht explizit initialisiere und es einen arithmetischen Typ hat, es implizit mit Nullen besetzt wird.
Weils falsch ist. Ist afaik bei Java so. Ein Feld ist implizit mit dem gefuellt was vorher dort im Speicher stand.
memset(buffer, 0, bufferlength)
hilft, dann ist alles mit Nullern gefuellt.
-
Hi pli,
dann hat da wohl jemand beim schreiben nen schlechten Tag gemacht( Hab nen c-Nachschlagewerk von der Uni Hannover)
Habs jetzt so gemacht:int Feld[100]={ 0 };
ist finde ich, weniger umständlich als die einzelnen Bits auf Null zu setzen (das macht memset doch ,oder?) aber natürlich Geschmackssache.
Vielen Dank für die Hilfe!Gruß vom Muschelsucher
-
Muschelsucher schrieb:
int Feld[100]={ 0 };
ist finde ich, weniger umständlich als die einzelnen Bits auf Null zu setzen (das macht memset doch ,oder?) aber natürlich Geschmackssache.
Jupp, aber von der Performance her sollte es kein Unterschied sein.
Edit1/2: Kannst die 0 auch weglassen und nur {} machen, dann wirds auch mit 0ern initialisiert.
-
Ich hab das ganze ml getestet:
int main() { time_t start, end; int i = 0; start = time( NULL ); for( ; i < 100000000; ++i ) { int array[1000]; } end = time( NULL ); printf( "%lf\n", difftime( end, start ) ); fflush( stdout ); i = 0; start = time( NULL ); for( ; i < 100000000; ++i ) { int array[1000] = {}; } end = time( NULL ); printf( "%lf\n", difftime( end, start ) ); fflush( stdout ); i = 0; start = time( NULL ); for( ; i < 100000000; ++i ) { int array[1000]; memset( array, 0, 1000 ); } end = time( NULL ); printf( "%lf\n", difftime( end, start ) ); fflush( stdout ); return 0; }
Ausgabe:
0.000000 44.000000 16.000000
Also ist memset() doch deutlich schneller.
-
Schau dir nochmal genau an, wie memset funktioniert.
Kleiner Tipp:memset( array, 0, 1000 * sizeof(*array));
-
pli schrieb:
int array[1000] = {}; 44.000000
schlechter compiler
-
440 nanoSekunden für eine Initialisierung sind kein schlechter Wert.
Allerdings ist die Messmethodik äußerst zweifelhaft
time(NULL) hat eine geringe Auflösung.
Er arbeitet auf einem Multitasking System.Wenn der diese Messungen 100 mal Wiederholt hätte Mittelwert und Standar4dabweichung nagegeben hätte, könnte man anfangen
damit zu arbeiten
-
bauer schrieb:
pli schrieb:
int array[1000] = {}; 44.000000
schlechter compiler
Kennst du eine schnellere Möglichkeit einen Speicherblock auf 0 zu setzen?
-
probier memset?
-
Messwerte: Athlon 64 3000, 1GB Memory, Windows XP 32 Bit SP2, MSVC 6.0 default Debug Mode, Insgesamt 50 Prozesse
Messung im Release Modus mit diesem Code nicht möglich wird komplett wegoptimiert
Schleife über 100 Messungen von jeweils 1 000 000 DurchläufenMethode 1
int array[1000];
# Number = 100
# Mean = 0.000327 ≡ 0,327 leere Schleife mit einer Variablen Dekalaration
# sdev.(n) = 0.000026Methode 2
int array[1000] = {0};
# Number = 100
# Mean = 0.027193 ≡ 27,193 nSec pro Initialisierung
# sdev.(n) = 0.000351Methode 3
memset( array, 0, 1000 );
# Number = 100
# Mean = 0.008603 ≡ 8,603 nSec pro Initialisierung
# sdev.(n) = 0.000265Methode 4
correktes memset( array, 0, 1000 * sizeof(array));
# Number = 100
# Mean = 0.027193 ≡ 27,193 nSec pro Initialisierung
# sdev.(n) = 0.000351
**
Interessanterweise (wie zu erwarten war) sind beide Methoden 2 und 4 gleichschnell*
-
Hallo ihr Super- Benchmarker
,
seid ihr überhaupt sicher, das zu messen, was ihr zu messen glaubt
?
Mit 'nem Integer auf 'ne Million hochzählen und angeblich macht das der MSVC mit?
Einen Array in einer Schleife eine Million mal anlegen und an das Ergebnis glauben, das kann doch gar nicht wahr sein - macht Windows wirklich doof?
Also, wer's wissen will:
Methode 1
int array[1000];
wird statisch vom Compiler erledigt. Kein ausführbarer Code - eh logisch.
Methode 2
int array[1000] = {0};
Tja, auch das wird für main vom Compiler statisch erledigt und in die Startup- Routine gepackt. Im Frame einer Funktion liegend kommen etwa 6000 Zyklen bei meinem Beispielsprozessor heraus.
Methode 3
memset( array, 0, 1000 );
Gehört zur <strings.h> und muß daher zuviele Umwege nehmen. Die Strafe: 31000 Zyklen.
Methode 4
Na, das war ja nicht so toll, dann löschen wir von Hand:for (i = 0; i< 1000; i++) { array[i] = 0; }
Freudige Überraschung: 20000 Zyklen.
Fazit:
Wenn möglich mitarray[1000] = {0};
belegen, falls zwischendurch gelöscht werden muß, bitte die gute alte Schleife nehmen, aber Finger weg von memset!
-
@pointecrash Bekanntlicherweise sind in MSVC int 32 Bit lang somit kann man bis 2.147.483.648 zählen
bzw bis 4.294.967.296 wenn man unsigned int nimmt.- Das Thema hier ist nicht die absolute Cylenzahl es geht hier nicht darum eine Routine zu verbessern sondern eher darum welche der bestehenden Methode im Verhältnis zueinander die schnellste ist. Deswegen ist es ja auch zulässig im Debug Mode zu "messen". da dort keine Optimierungen angewendet werden zumindest nicht bei MSVC. Wechselt man in den Release Mode mit Optimierungen an liefern alle Schliefen nahezu 0 Zeit.
Einzelmessung M1 0.000000838 M2 0.000000559 M3 0.000000559 M4 0.000000838 Sekunden- Womit hast du bitte die CompilerCyclen gezählt
- Könnste es vielleicht sein das Methode 1 dazu gedacht ist einen Überblick über die Länge einer "leeren Schleife" zu bekommen.
- Deine Aussage bezüglich
Gehört zur <strings.h> und muß daher zuviele Umwege nehmen
ist eigenartig
Der compiler erzeugt eine dword mit dem zusetzenden Byte in allen 4 Bytes und der Rest ist rep stosd. Das einzige was hinken kann sind strings die nicht alligned und nicht durch vier teilbar sind, da dann jeweils eine Vorabeit zum alignment und eine Nacharbeit für den Rest nötig ist. Der technisch schlechteste String ist durch hat eine Länge von 10 Bytes und beginnt ein Byte nach einer Dword Grenze (1*Alignment +3 Byte füllen, 1* Hauptschleife, 1*3 Bytes füllen für den Rest)Dieser Ausriss ist aus MSVC 6.0. d.h memset ist direkt in Assembler codiert und wird unmittelbar angespringen.
dwords: ; set all 4 bytes of eax to [value] mov ecx,eax ; ecx=0/0/0/value shl eax,8 ; eax=0/0/value/0 add eax,ecx ; eax=0/0val/val mov ecx,eax ; ecx=0/0/val/val shl eax,10h ; eax=val/val/0/0 add eax,ecx ; eax = all 4 bytes = [value] ; Set dword-sized blocks mov ecx,edx ; move original count to ecx and edx,3 ; prepare in edx byte count (for tail loop) shr ecx,2 ; adjust ecx to be dword count jz tail ; jump if it was less then 4 bytes rep stosd main_loop_tail: test edx,edx ; if there is no tail bytes, jz finish ; we finish, and it's time to leave
Deine geniale Schleife führt zu folgedem Assembler, un das soll schneller sein ale memset???
1000 ein Byte schreiben statt 2500 eine DWORD
67: for (i1=0 ; i1< 1000; i1++) 0040116E mov dword ptr [ebp-0C4h],0 00401178 jmp main+69h (00401189) 0040117A mov eax,dword ptr [ebp-0C4h] 00401180 add eax,1 00401183 mov dword ptr [ebp-0C4h],eax 00401189 cmp dword ptr [ebp-0C4h],3E8h 00401193 jge main+88h (004011a8) 68: { 69: barray[i1] = 0; 00401195 mov ecx,dword ptr [ebp-0C4h] 0040119B mov dword ptr [ebp+ecx*4-1274h],0 70: } 004011A6 jmp main+5Ah (0040117a)
-
PAD schrieb:
Deswegen ist es ja auch zulässig im Debug Mode zu "messen".
Nein. Aussagekräftige Benchmarks werden niemals mit Debug Builds gemacht. Warum? Siehe nachfolgend.
PAD schrieb:
Womit hast du bitte die CompilerCyclen gezählt
Pentium+ CPUs kennen zB rdtsc, damit kannst du die CPU Cycles "messen".
pointercrash() schrieb:
Tja, auch das wird für main vom Compiler statisch erledigt und in die Startup- Routine gepackt.
Hallo pointercrash, lang nicht mehr gesehen.
Was meinst du mit statisch? Sofern es sich um automatischen Speicher handelt, wird bei jedem Durchlauf auch immer wieder das Array neu angelegt und somit auch neu initialisiert.pointercrash() schrieb:
Gehört zur <strings.h> und muß daher zuviele Umwege nehmen.
Nicht zwangsläufig. memset ist in vielen Compilern intrinsic implementiert. Deshalb machen Debug Builds keinen Sinn, weil oftmals nur bei eingeschalteter Optimierung die Funktion auch intrinsic in den Code eingebunden wird. Wobei sich das auch mit Compiler Einstellungen regeln lässt. Es ist daher sehr wahrscheinlich, dass der Code für
// int a[1000] = {}; // int b[1000]; memset(b, 0, 1000 * sizeof(*b));
vom Compiler gleichwertig erzeugt wird (natürlich sofern wir von automatischem Speicher reden).
pointercrash() schrieb:
aber Finger weg von memset
Sehe ich ähnlich. memset ist für Byte Arrays ausgelegt, deshalb sollte man es, wenn möglich, auch nur dafür verwenden. Ansonsten kann dies schnell zu Fehlern führen (wie man ja hier auch wieder gesehen hat). Ansonsten ist die Schleife die bessere Wahl. Wer sowas häufig verwendet, kann auch über eine inline Funktion nachdenken.
-
groovemaster schrieb:
pointercrash() schrieb:
aber Finger weg von memset
Sehe ich ähnlich. memset ist für Byte Arrays ausgelegt, deshalb sollte man es, wenn möglich, auch nur dafür verwenden. Ansonsten kann dies schnell zu Fehlern führen
warum? memset kann richtig schnell sein. das macht bestimmt keine einzelnen byte-zugriffe wie 'ne blöde selbstgeschriebene schleife
-
net schrieb:
warum? memset kann richtig schnell sein. das macht bestimmt keine einzelnen byte-zugriffe wie 'ne blöde selbstgeschriebene schleife
Gute Compiler können auch solch triviale Schleifen optimieren. Zudem, hast du schon mal versucht, ein int Array mit memset auf 1 zu setzen?
-
groovemaster schrieb:
Zudem, hast du schon mal versucht, ein int Array mit memset auf 1 zu setzen?
nö, echt nicht, kann schwer werden
-
Soweit ich weis heißt die routine memset und nicht arrayfill. Die aufgabe ist einfach einen vom volumen vorgegebnen Bereich mit einem Bytemuster zu füllen.
Viel interessanter wäre is anstelle von int mit memset den wert 1234.5678 als double zu schreiben. Aber dafür ist sie nicht gedacht.
-
@groovemaster
was heißt das rdtsc, damit kannst du die CPU Cycles "messen"?
Welche CPU-cyclen, die Cyclen die eine Routine verbraucht, die Cyclen die seit dem letzten ausfruf des rdtsc vergangen sind oder was sonst.
Der Befehl wäre dann interresant wenn er die cyclen zählt die in einem Process/ noch besser thread verbraucht werden.Könntest du dazu noch ein paar informationen/quellen liefern
Fehlern führen (wie man ja hier auch wieder gesehen hat).
Zu welchen Fehlern hat es geführt, der Speicherbereich ist wie gewünscht mit 0 gefüllt worden. Da hier
zufällig
auch das Muster für int 0 passt ist kein fehler aufgetreten wenn man ds Memset richtig aufruft.
-
PAD schrieb:
Viel interessanter wäre is anstelle von int mit memset den wert 1234.5678 als double zu schreiben. Aber dafür ist sie nicht gedacht.
double a = 1234.5678; double b; int s; for (s=0; s<sizeof(double); s++) memset (&((char*)&b)[s], ((char*)&a)[s], 1);
-
Hi groovemaster,
ja stimmt, hatte in letzter Zeit eher mit Netzwerken zu tun.
In der Sache selbst:
Ich hab' den MSVC selbst nicht, wundere mich aber schon sehr, daß er WIRKLICH ein Array eine Million mal neu anlegen soll. Einerseits ist das völliger Blödsinn, zweitens habe ich keinen Compiler, der sich nicht weigert, das Deklarieren und Initialisieren innerhalb einer Schleife auszuführen.Um das dennoch ausmessen zu können, habe ich das im Frame einer aufgerufenen Funktion erledigen lassen und kam beim M16C so auf die gut 6000 Zyklen.
Messen tue ich das übrigens mit einem Simulator und auch nur für einen Durchlauf, weil uns ja die Parallelisierungs- und Cache- Tricksereien der i386- Familie nicht interessieren sollen. Wir sind ja schließlich im ANSI C- Forum.
Und wenn man die Sourcen vergleicht ...
a) memset aus der strings.h:void *memset( void *s, int c, size_t n ) { char *ss=s; while ( n-- ) *ss++ = ( char )c; return ( (char *)s ); /* */ }
Resultierender Assemblercode:
0F0758 7CF204 $memset ENTER #04H 0F075B 73B009 MOV.W 9H[FB],R0 0F075E 73BB05FC MOV.W 5H[FB],-4H[FB] 0F0762 73BB07FE MOV.W 7H[FB],-2H[FB] 0F0766 7301 MOV.W R0,R1 0F0768 C9F0 ADD.W #-1H,R0 0F076A D101 CMP.W #0H,R1 0F076C 6A17 JEQ F0784H 0F076E 7324 MOV.W R2,A0 0F0770 7242 MOV.B A0,R1L 0F0772 7342 MOV.W A0,R2 0F0774 73B4FC MOV.W -4H[FB],A0 0F0777 73B5FE MOV.W -2H[FB],A1 0F077A 7422 STE.B R1L,[A1A0] 0F077C C91BFC ADD.W #1H,-4H[FB] 0F077F 77EBFE ADCF.W -2H[FB] 0F0782 FEE3 JMP.B F0766H 0F0784 73B005 MOV.W 5H[FB],R0 0F0787 73B207 MOV.W 7H[FB],R2 0F078A 7DF2 EXITD
Der richtigerweise mit sizeof korrigierte Aufruf benötigt übrigens tatsächlich um die 60'000 Zyklen.
b) for- Schleife
for (i = 0; i< 1000; i++) { array[i] = 0; }
resultierender Assemblercode:
0F013A D90BFE MOV.W #0H,-2H[FB] 0F013D 778BFEE803 CMP.W #03E8H,-2H[FB] 0F0142 7DCA0F JGE F0153H 0F0145 73B4FE MOV.W -2H[FB],A0 0F0148 E904 SHL.W #1H,A0 0F014A D90C1004 MOV.W #0H,0410H[A0] 0F014E C91BFE ADD.W #1H,-2H[FB] 0F0151 FEEB JMP.B F013DH
... dürfte klar sein, daß memset mehr zu erledigen hat - oder
?
Am Bild ändert sich auch nicht viel, wenn man die paar Zeilen durch den IAR- Compiler schickt. Bei Keil C für 8051 sieht es ähnlich aus, nur daß der 8051er ein lahmer Hund ist.
Also, es bleibt dabei: Am schnellsten geht es, wenn ein Array beim Deklarieren auch initialisiert wird, dann kommt die Scheife und memset bitte nur nehmen, wenn's nicht anders geht.