Performance struct versus char-Array



  • Mal wieder eine Performancefrage an Euch Experten:

    Momentan benutze ich 4 parallele char-Arrays für eine Anwendung, die alle etwa so aufgebaut werden:

    char *a1;
    a1 = new char[groesse];
    

    Der Zugriff erfolgt dann einzeln indiziert a la

    a1[i] = 'd';
    

    Wie ist performancemäßig dazu diese Variante:

    typedef struct {
       char a1;
       char a2;
       char a3;
       char a4;
    } foo;
    
    foo *s;
    
    s = new foo[groesse];
    

    mit dem Zugriff

    s[i].a3 = 'x';
    

    Will sagen, welche der beiden Zugriffskonstruktionen - Einzelarrays oder ein struct-Array - kann ein Compiler in schnelleren Code umsetzen, wenn man davon ausgeht, dass die a1, a2, a3 und a4 für einen Index i immer im gleichen sehr engen Codezusammenhang benutzt werden?



  • intern wird soweit ich weis bei fast allen compilern bei structs ganz normales displacement gemacht, du gewinnst also keinen vorteil. im gegenteil, wenn du ein normales array nimmst, kannst du deutlcih flotter zuweisen.

    char a[50];
    char *a_ptr = a;
    for (int i = 0; i < 50; ++i)
        (*a_ptr++) = some_value;
    

    da haste dann kein displacement



  • Bei einer Struktur kann der Compiler Paddingbytes hinzufügen und durch das bessere Memoryalignmend, ist der Zugriff ggf. schneller.

    Aber ich denke, dass es sich nicht lohnt mit so einer Frage zu befassen. Nimm einfach den weg, der am leserlichsten und schnellsten geschrieben ist. Das ist viel rentabler, als sich wegen so ein paar Takten einen haufen unverständlichen Codes zu basteln.



  • char ist die kleinste adressierbare einheit - insofern spielt memoryalignment hier kein rolle. im normalfall wird auch das layout zwischen array und struct identisch sein, solange man nicht mit irgendwelchen compilerdirectiven herumpfuscht.



  • du hast also entweder 4 arrays of char oder ein array mit je 4chars pro eintrag?

    oder hab ich da was missverstanden?

    rapso->greets();



  • rapso schrieb:

    du hast also entweder 4 arrays of char oder ein array mit je 4chars pro eintrag?

    oder hab ich da was missverstanden?

    rapso->greets();

    Nee, hast Du nicht, so ist es.

    Ich habe auch schon damit geliebäugelt, da eine union mit einem unsigned int zu verwenden, da auf den Prozessoren, auf denen mein Kram läuft, sizeof(int)=4*sizeof(char) ist, und damit z.B. ein memset() auf die ints schneller sein dürfte als 4 memset() auf die 4 char-Arrays. Und diese Initialisierungen kommen im Code etwa 30.000mal pro Sekunde vor, da hilft jede Nanosekunde 🙂



  • memset auf ein chararray läuft doch genau wie memset auf ein int oder sonstwasarray ab?! Da optimiert wieder wer an der falschen Stelle.

    Bye, TGGC (Wähle deine Helden)



  • TGGC schrieb:

    memset auf ein chararray läuft doch genau wie memset auf ein int oder sonstwasarray ab?! Da optimiert wieder wer an der falschen Stelle.

    Bye, TGGC

    ...abgesehen vom vierfachen Schleifenoverhead, weil die 4 Arrays an unterschiedlichen Adressen liegen.
    Ich hab's eben mal einfach ausprobiert: die union-Variante ist insgesamt einen Hauch (ca. 5%) schneller als die 4 char-Arrays. Kann man sicher nicht generalisieren, aber bei meiner Anwendung lohnt es doch.



  • Toll. 5%. Kauf dir'n schnelleren Rechner, bringt mehr.

    Bye, TGGC (Wähle deine Helden)



  • TGGC schrieb:

    Toll. 5%. Kauf dir'n schnelleren Rechner, bringt mehr.

    Bye, TGGC

    Toller Kommentar... 🙄
    Gib Geld, kauf' ich Rechner - kannst Du Dir außerdem vorstellen, dass es Spaß machen kann, das letzte aus einem Code 'rauszuholen, statt einfach nicht zu denken und Hardware zu kaufen?



  • Miq schrieb:

    rapso schrieb:

    du hast also entweder 4 arrays of char oder ein array mit je 4chars pro eintrag?

    oder hab ich da was missverstanden?

    rapso->greets();

    Nee, hast Du nicht, so ist es.

    dann kommt es drauf an wie du auf die daten zugreifst, kommt es öfter vor, dass du

    s[i].a1 = '...'; s[i].a2 = '...'; s[i].a3 = '...'; s[i].a4 = '...';
    

    hast, oder öfter

    s[i].a1 = '...'; s[i+1].a1 = '...'; s[i+2].a1 = '...'; s[i+3].a1 = '...';
    

    ?

    je nachdem wie die zugriffe sind, kann man die speicheranlegung versuchen so zu optimieren, dass sie die cacheausnutzung optimiert, damit kann man sehr viel gewinnen.

    rapso->greets();



  • Variante 1 ist bei mir häufiger, also struktur interne Zugriffe bei konstantem i. Und die memset(...,0,...) gehen auch eindeutig schneller.
    Was mir jetzt noch fehlt, ist eine schnelle, aber portable Zuweisung der ganzen struct. Ich denke momentan an sowas hier:

    typedef union
    {
       unsigned int i;
       struct {
          unsigned char a1;
          unsigned char a2;
          unsigned char a3;
          unsigned char a4;
       } s;
    } U;
    U bar[groesse];
    
    // Standardpaket 6,0,44,7 bauen
    U baz;
    baz.s.a1 = 6;
    baz.s.a2 = 0;
    baz.s.a3 = 44;
    baz.s.a4 = 7;
    unsigned int bazi = baz.i;
    ...
    bar[index].i = bazi; // Standardpaket zuweisen
    ...
    

    Das sollte mir noch einige Zuweisungen sparen helfen.


Anmelden zum Antworten