Memset parallel zu Threads, die an jeden physischen Kern gebunden sind

Ich habe den Code unter getestetWäre es in einem OpenMP-Parallelcode von Vorteil, wenn memset parallel ausgeführt würde? und ich beobachte etwas Unerwartetes.

Mein System ist ein Single-Socket-Xeon E5-1620, ein Ivy-Bridge-Prozessor mit 4 physischen Kernen und acht Hyper-Threads. Ich verwende Ubuntu 14.04 LTS, Linux Kernel 3.13, GCC 4.9.0 und EGLIBC 2.19. Ich kompiliere mitgcc -fopenmp -O3 mem.c

Wenn ich den Code im Link ausführe, sind standardmäßig acht Threads vorhanden

Touch:   11830.448 MB/s
Rewrite: 18133.428 MB/s

Wenn ich jedoch die Threads binde und die Anzahl der Threads auf die Anzahl der physischen Kerne wie folgt einstelle

export OMP_NUM_THREADS=4 
export OMP_PROC_BIND=true

Ich bekomme

Touch:   22167.854 MB/s
Rewrite: 18291.134 MB/s

Die Berührungsrate hat sich verdoppelt! Das mehrmalige Ausführen nach dem Binden hat immer eine schnellere Berührung als das Neuschreiben. Ich verstehe das nicht.Warum ist Touch schneller als das Umschreiben, nachdem die Threads gebunden und auf die Anzahl der physischen Kerne festgelegt wurden? Warum hat sich die Berührungsrate verdoppelt?

Hier ist der Code, den ich ohne Änderung von Hristo Iliev Antwort genommen habe.

#include <stdio.h>
#include <string.h>
#include <omp.h>

void zero(char *buf, size_t size)
{
    size_t my_start, my_size;

    if (omp_in_parallel())
    {
        int id = omp_get_thread_num();
        int num = omp_get_num_threads();

        my_start = (id*size)/num;
        my_size = ((id+1)*size)/num - my_start;
    }
    else
    {
        my_start = 0;
        my_size = size;
    }

    memset(buf + my_start, 0, my_size);
}

int main (void)
{
    char *buf;
    size_t size = 1L << 31; // 2 GiB
    double tmr;

    buf = malloc(size);

    // Touch
    tmr = -omp_get_wtime();
    #pragma omp parallel
    {
        zero(buf, size);
    }
    tmr += omp_get_wtime();
    printf("Touch:   %.3f MB/s\n", size/(1.e+6*tmr));

    // Rewrite
    tmr = -omp_get_wtime();
    #pragma omp parallel
    {
        zero(buf, size);
    }
    tmr += omp_get_wtime();
    printf("Rewrite: %.3f MB/s\n", size/(1.e+6*tmr));

    free(buf);

    return 0;
}

Bearbeiten: Ohne Profilbindung, aber mit vier Threads werden die Ergebnisse achtmal ausgeführt.

Touch:   14723.115 MB/s, Rewrite: 16382.292 MB/s
Touch:   14433.322 MB/s, Rewrite: 16475.091 MB/s 
Touch:   14354.741 MB/s, Rewrite: 16451.255 MB/s  
Touch:   21681.973 MB/s, Rewrite: 18212.101 MB/s 
Touch:   21004.233 MB/s, Rewrite: 17819.072 MB/s 
Touch:   20889.179 MB/s, Rewrite: 18111.317 MB/s 
Touch:   14528.656 MB/s, Rewrite: 16495.861 MB/s
Touch:   20958.696 MB/s, Rewrite: 18153.072 MB/s

Bearbeiten:

Ich habe diesen Code auf zwei anderen Systemen getestet und kann das Problem auf diesen nicht reproduzieren

i5-4250U (Haswell) - 2 physische Kerne, 4 Hyper-Threads

4 threads unbound
    Touch:   5959.721 MB/s, Rewrite: 9524.160 MB/s
2 threads bound to each physical core
    Touch:   7263.175 MB/s, Rewrite: 9246.911 MB/s

Vier Sockel E7-4850 - 10 physische Kerne, 20 Hyper-Threads pro Sockel

80 threads unbound
    Touch:   10177.932 MB/s, Rewrite: 25883.520 MB/s
40 threads bound
    Touch:   10254.678 MB/s, Rewrite: 30665.935 MB/s

Dies zeigt, dass das Binden der Threads an die physischen Kerne sowohl das Berühren als auch das Neuschreiben verbessert, das Berühren jedoch auf diesen beiden Systemen langsamer ist als das Neuschreiben.

Ich habe auch drei verschiedene Variationen von memset getestet:my_memset, my_memset_stream, undA_memset. Die Funktionenmy_memset undmy_memset_stream sind unten definiert. Die FunktionA_memset kommt von Agner Fog'sasmlib.

my_memset Ergebnisse:

Touch:   22463.186 MB/s
Rewrite: 18797.297 MB/s

Ich denke, dies zeigt, dass das Problem nicht in der Memset-Funktion von EGLIBC liegt.

A_memset Ergebnisse:

Touch:   18235.732 MB/s
Rewrite: 44848.717 MB/s

my_memset_stream:

Touch:   18678.841 MB/s
Rewrite: 44627.270 MB/s

Als ich mir den Quellcode der asmlib ansah, sah ich, dass zum Schreiben großer Speicherbereiche nicht temporäre Speicher verwendet werden. Deshalbmy_memset_stream Holen Sie sich ungefähr die gleiche Bandbreite wie Agner Fog's asmlib. DasDer maximale Durchsatz dieses Systems beträgt 51,2 GB / s. Also das zeigt dasA_memset undmy_memset_stream Erhalten Sie ungefähr 85% dieses maximalen Durchsatzes.

void my_memset(int *s, int c, size_t n) {
    int i;
    for(i=0; i<n/4; i++) {
        s[i] = c;
    }
}

void my_memset_stream(int *s, int c, size_t n) {
    int i;
    __m128i v = _mm_set1_epi32(c);

    for(i=0; i<n/4; i+=4) {
        _mm_stream_si128((__m128i*)&s[i], v);
    }
}

Antworten auf die Frage(1)

Ihre Antwort auf die Frage