76 Stimmen

Schnellerer Weg zum Nullen des Speichers als mit memset?

Ich habe gelernt, dass memset(ptr, 0, nbytes) ist wirklich schnell, aber gibt es einen schnelleren Weg (zumindest auf x86)?

Ich nehme an, dass memset die mov Beim Nullsetzen des Speichers verwenden die meisten Compiler jedoch xor da es schneller ist, richtig? edit1: Falsch, wie GregS sagte, funktioniert das nur mit Registern. Was habe ich mir dabei gedacht?

Außerdem habe ich eine Person, die sich mit Assembler besser auskennt als ich, gebeten, sich die stdlib anzuschauen, und er sagte mir, dass memset auf x86 die 32 Bit breiten Register nicht voll ausnutzt. Allerdings war ich zu diesem Zeitpunkt sehr müde, so dass ich nicht sicher bin, ob ich es richtig verstanden habe.

bearbeiten2 : Ich habe dieses Thema erneut aufgegriffen und ein paar Tests durchgeführt. Hier ist, was ich getestet habe:

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    #include <sys/time.h>

    #define TIME(body) do {                                                     \
        struct timeval t1, t2; double elapsed;                                  \
        gettimeofday(&t1, NULL);                                                \
        body                                                                    \
        gettimeofday(&t2, NULL);                                                \
        elapsed = (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_usec - t1.tv_usec) / 1000.0; \
        printf("%s\n --- %f ---\n", #body, elapsed); } while(0)                 \

    #define SIZE 0x1000000

    void zero_1(void* buff, size_t size)
    {
        size_t i;
        char* foo = buff;
        for (i = 0; i < size; i++)
            foo[i] = 0;

    }

    /* I foolishly assume size_t has register width */
    void zero_sizet(void* buff, size_t size)
    {
        size_t i;
        char* bar;
        size_t* foo = buff;
        for (i = 0; i < size / sizeof(size_t); i++)
            foo[i] = 0;

        // fixes bug pointed out by tristopia
        bar = (char*)buff + size - size % sizeof(size_t);
        for (i = 0; i < size % sizeof(size_t); i++)
            bar[i] = 0;
    }

    int main()
    {
        char* buffer = malloc(SIZE);
        TIME(
            memset(buffer, 0, SIZE);
        );
        TIME(
            zero_1(buffer, SIZE);
        );
        TIME(
            zero_sizet(buffer, SIZE);
        );
        return 0;
    }

Ergebnisse:

zero_1 ist das langsamste, außer bei -O3. zero_sizet ist das schnellste mit ungefähr gleicher Leistung bei -O1, -O2 und -O3. memset war immer langsamer als zero_sizet. (doppelt so langsam bei -O3). Interessant ist, dass zero_1 bei -O3 genauso schnell war wie zero_sizet. Allerdings hatte die disassemblierte Funktion etwa viermal so viele Anweisungen (ich denke, das liegt an der Schleifenabwicklung). Ich habe auch versucht, zero_sizet weiter zu optimieren, aber der Compiler hat mich immer überholt, aber das ist keine Überraschung.

Im Moment gewinnt memset, frühere Ergebnisse wurden durch den CPU-Cache verzerrt. (alle Tests wurden unter Linux durchgeführt) Weitere Tests sind erforderlich. Ich werde als nächstes Assembler ausprobieren :)

edit3: Fehler im Testcode behoben, Testergebnisse sind nicht betroffen

edit4: Beim Stochern rund um die disassemblierte VS2010 C-Laufzeit, bemerkte ich, dass memset hat eine SSE-optimierte Routine für Null. Es wird schwer sein, dies zu übertreffen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X