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äufen

    Methode 1
    int array[1000];
    # Number = 100
    # Mean = 0.000327 ≡ 0,327 leere Schleife mit einer Variablen Dekalaration
    # sdev.(n) = 0.000026

    Methode 2
    int array[1000] = {0};
    # Number = 100
    # Mean = 0.027193 ≡ 27,193 nSec pro Initialisierung
    # sdev.(n) = 0.000351

    Methode 3
    memset( array, 0, 1000 );
    # Number = 100
    # Mean = 0.008603 ≡ 8,603 nSec pro Initialisierung
    # sdev.(n) = 0.000265

    Methode 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 mit

    array[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);
    

Anmelden zum Antworten