Feld initialisieren



  • 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);
    


  • 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.



  • @net hübscher Versuch Natürlich war gemeint unser "tolles" Array mit 1000 Elementen so möglichst effektiv zu füllen.

    @pointercrash() Danke für die Information das du mit einem µController Typ 8051 arbeitest.

    Leider ist es so, das solche Implementierung von Routinen sehr stark Architektur und Hersteller abhängig sind.
    Wie könnte es sonst sein das der Intel 80x86 oder gnu Compiler immer noch performanter ist als der M$.

    ⚠ 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.

    Diese Aussage ist nur richtig wenn du dich auf deine Architektur bezeihst, Hast du diesilben Versuche mal mit IAR Compiler und Target z80 gemacht?

    Und so schlecht ist eine 8051 als 8 Bit Microcontroller nun auch wieder nicht, die Architektur gibts seit vor 1994.
    Und obs die "modernen" Pic´s wirklich besser machen?

    Andere Frage Welchen IAR und welchen Keil Compiler benutzt du, und wie bist du damit zufrieden, Ich tendiere mehr zum IAR-Compiler



  • @pointercrash()
    Kannst du mal den Assemblercode von

    int a[100] = {};
    

    für deine Plattform posten. Würde mich echt mal interessieren. Zum einen, da ich Nullinitialisierung bisher immer mit

    int a[100] = {0};
    

    gemacht habe. Aber so, wie ich den Standard verstehe, dies auch ohne die 0 funktionieren sollte. Zum anderen interssiert mich auch der Vergleich zur Schleife.

    pointercrash() schrieb:

    Am schnellsten geht es, wenn ein Array beim Deklarieren auch initialisiert wird

    Jaja, RAII ist doch 'ne schöne Sache. 🙂



  • MSVC meldet sich wie folgt bei
    int array[1000] = {};

    E:\aufl\aufl.cpp(19) : error C2059: Syntaxfehler : '}'
    E:\aufl\aufl.cpp(19) : error C2143: Syntaxfehler : Fehlendes ';' vor '}'
    E:\aufl\aufl.cpp(19) : error C2143: Syntaxfehler : Fehlendes ';' vor '}'

    Meiner Ansicht nach ist das eine Unsauberkeit in der Implemtierung von deinem C-Compiler

    Beim IARC Compiler 3.0 haben die es auch fertig gebracht toupper nicht mit einem Bitweise Oder zu implementieren sondern
    duch abziehen von 0x20 und zwar sowohl beim Z80 als auch beim 8051. Sah toll aus wenn man toupper zweimal nacheinander anwendete. Ist dann auf unseren " 😃 Wunsch" ab Version 4 korrigiert worden.



  • Hi groovemaster und Konsorten!

    also,

    int belegt[1000] = {0};
    

    und

    int belegt[1000] = {};
    

    sind codeidentisch. Und wenn man sich das ansieht, wird auch klar, warum der Code so schnell ist:

    0F0120   7CF2C8         _belegen  ENTER     #C8H
    0F0123   7BF5                     STC       FB,A1
    0F0125   77554800                 SUB.W     #0048H,A1
    0F0129   7AF5                     LDC       A1,FB
    0F012B   D90B80                   MOV.W     #0H,-80H[FB]
    0F012E   D90B82                   MOV.W     #0H,-7EH[FB]
    0F0131   D90B84                   MOV.W     #0H,-7CH[FB]
    0F0134   D90B86                   MOV.W     #0H,-7AH[FB]
    0F0137   D90B88                   MOV.W     #0H,-78H[FB]
    0F013A   D90B8A                   MOV.W     #0H,-76H[FB]
    0F013D   D90B8C                   MOV.W     #0H,-74H[FB]
    0F0140   D90B8E                   MOV.W     #0H,-72H[FB]
    0F0143   D90B90                   MOV.W     #0H,-70H[FB]
    ... usw, usw und so fort ...
    

    Da war ich selber platt - Speed zu Lasten Codekompaktheit, aber gleich derb 🕶 .

    @PAD
    Der Assemblercode wurde mit dem NC30 für M16C erzeugt, der IAR für M16C verhält sich bis auf ein paar Taktzyklen Unterschied identisch. Den Gegentest habe ich mit einem Keil C für 8051 älteren Baujahrs gemacht, wobei ich nur 30 Elemente angefordert habe. Aber die Ergebnisse bleiben übertragbar.
    Den IAR hab' ich nur als Demoversion, und das wird auch so bleiben - hab' mit denen fast nur schlechte Erfahrungen gemacht, aus ähnlichen Gründen wartet der Keil C seit 1994 auf ein Update 👎



  • pointercrash() schrieb:

    0F0120   7CF2C8         _belegen  ENTER     #C8H
    0F0123   7BF5                     STC       FB,A1
    0F0125   77554800                 SUB.W     #0048H,A1
    0F0129   7AF5                     LDC       A1,FB
    0F012B   D90B80                   MOV.W     #0H,-80H[FB]
    0F012E   D90B82                   MOV.W     #0H,-7EH[FB]
    0F0131   D90B84                   MOV.W     #0H,-7CH[FB]
    0F0134   D90B86                   MOV.W     #0H,-7AH[FB]
    0F0137   D90B88                   MOV.W     #0H,-78H[FB]
    0F013A   D90B8A                   MOV.W     #0H,-76H[FB]
    0F013D   D90B8C                   MOV.W     #0H,-74H[FB]
    0F0140   D90B8E                   MOV.W     #0H,-72H[FB]
    0F0143   D90B90                   MOV.W     #0H,-70H[FB]
    ... usw, usw und so fort ...
    

    ne kleine schleife hätt's aber auch nicht viel langsamer gemacht. ausserdem, wenn man bedenkt dass das array 1000 elemente hat wird der code auch nicht greade klein. und sowas macht ein embedded-compiler tztztz...


Anmelden zum Antworten