Neden hafıza okumaktan daha çok daha yavaş yazıyor? | Netgez.com
SORU
13 EYLÃœL 2014, CUMARTESÄ°


Neden hafıza okumaktan daha çok daha yavaş yazıyor?

Ä°ÅŸte memset bant geniÅŸliÄŸi basit bir kriter:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main()
{
    unsigned long n, r, i;
    unsigned char *p;
    clock_t c0, c1;
    double elapsed;

    n = 1000 * 1000 * 1000; /* GB */
    r = 100; /* repeat */

    p = calloc(n, 1);

    c0 = clock();

    for(i = 0; i < r;   i) {
        memset(p, (int)i, n);
        printf("M/%4ld\r", p[0], r); /* "use" the result */
        fflush(stdout);
    }

    c1 = clock();

    elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;

    printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);

    free(p);
}

DDR3-1600 tek bir bellek modülü ile sistemi (detaylar aşağıda), çıkışlar:

Bant geniÅŸliÄŸi = 4.751/s (Giga = 10^9) GB

Bu teorik RAM hızı 7: 1.6 GHz * 8 bytes = 12.8 GB/s

DiÄŸer taraftan, iÅŸte benzer bir "okuma" testi:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

unsigned long do_xor(const unsigned long* p, unsigned long n)
{
    unsigned long i, x = 0;

    for(i = 0; i < n;   i)
        x ^= p[i];
    return x;
}

int main()
{
    unsigned long n, r, i;
    unsigned long *p;
    clock_t c0, c1;
    double elapsed;

    n = 1000 * 1000 * 1000; /* GB */
    r = 100; /* repeat */

    p = calloc(n/sizeof(unsigned long), sizeof(unsigned long));

    c0 = clock();

    for(i = 0; i < r;   i) {
        p[0] = do_xor(p, n / sizeof(unsigned long)); /* "use" the result */
        printf("%4ld/%4ld\r", i, r);
        fflush(stdout);
    }

    c1 = clock();

    elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;

    printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);

    free(p);
}

Çıktılar:

Bant geniÅŸliÄŸi = 11.516/s (Giga = 10^9) GB

Büyük bir dizi yazılmış bir oyun programı gibi okuma performansı için teorik sınırı, yaklaşamıyorum, ama yazarken çok daha yavaş görünüyor. Neden?

OSUbuntu 14.04 AMD64 (I gcc -O3 ile derleyin. -O3 -march=native kullanarak biraz daha kötü okuma performansı yapar, ama* *13) etkilemez

CPU"Çok çekirdekli E5-2630 v2

RAMBir" kutunun üzerinde Ne yazıyor () ben tek dediğimiz olması, performansı tahmin edilebilir. yapar diye düşünüyorum "16 GİGABAYT PC3-12800 Eşlik REG CL11 240-Pin DIMM tek 4 Taneye, memset olacağını varsayıyorumkadar4 kat daha hızlı.

AnakartX9DRG-ön ELEME Destekler 4 kanal bellek () Supermicro

Ek sistem:- 1067 DDR3 RAM 2x 4GB ile bir dizüstü bilgisayar ve yaklaşık 5.5 GB/s, ama 2 Taneye kullanır unutmayın. okuma yazma

S. S.bu sürüm ile memset yerine tam olarak aynı performans elde edilir

void *my_memset(void *s, int c, size_t n)
{
    unsigned long i = 0;
    for(i = 0; i < n;   i)
        ((char*)s)[i] = (char)c;
    return s;
}

CEVAP
14 EYLÃœL 2014, Pazar


Programlarınızı, anlıyorum

(write) Bandwidth =  6.076 GB/s
(read)  Bandwidth = 10.916 GB/s

altı 2 GB bellek modülleri ile (Core i7, 86-64, 4.9, GNU C kütüphanesi 2.19 GCC) masaüstü bir makine. (El için daha fazla detay, üzgünüm yok.)

Ancakbuprogram raporları 12.209 GB/s bant genişliği yazmak:

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <emmintrin.h>

static void
nt_memset(char *buf, unsigned char val, size_t n)
{
    /* this will only work with aligned address and size */
    assert((uintptr_t)buf % sizeof(__m128i) == 0);
    assert(n % sizeof(__m128i) == 0);

    __m128i xval = _mm_set_epi8(val, val, val, val,
                                val, val, val, val,
                                val, val, val, val,
                                val, val, val, val);

    for (__m128i *p = (__m128i*)buf; p < (__m128i*)(buf   n); p  )
        _mm_stream_si128(p, xval);
    _mm_sfence();
}

/* same main() as your write test, except calling nt_memset instead of memset */

Sihirli _mm_stream_si128, nam-ı diğer makine talimat tüm sistem RAM için 16 bayt miktarı bir yazar olan movntdq,,önbellek atlayarak(bu resmi jargon "non-temporal store"). Bu oldukça kesin performans farkı gösteriyor benceönbellek davranış hakkında.

N. B. abone olarak giriş 2.19yokvar özenle bir vektör yönergeleri yapan el-optimize edilmiş memset. Ancak, öyledeğilnon-temporal mağazaları kullanın. Muhtemelen memset; genel olarak, kısa bir süre, bu yüzden kullanmadan önce bellek temizlemek için yapılacak en iyi Şeyistiyorumönbellekte sıcak olacak. (memset non-temporal mağazaları için anahtar olabilir zeki bir bile sanırımgerçekten çok büyükblok net, muhtemelen önbelleği sadece büyük değil, çünkü önbellekte hepsini istiyorsun, değil teorik olarak.)

Dump of assembler code for function memset:
=> 0x00007ffff7ab9420 < 0>:     movd   %esi,%xmm8
   0x00007ffff7ab9425 < 5>:     mov    %rdi,%rax
   0x00007ffff7ab9428 < 8>:     punpcklbw %xmm8,%xmm8
   0x00007ffff7ab942d < 13>:    punpcklwd %xmm8,%xmm8
   0x00007ffff7ab9432 < 18>:    pshufd $0x0,%xmm8,%xmm8
   0x00007ffff7ab9438 < 24>:    cmp    $0x40,%rdx
   0x00007ffff7ab943c < 28>:    ja     0x7ffff7ab9470 <memset 80>
   0x00007ffff7ab943e < 30>:    cmp    $0x10,%rdx
   0x00007ffff7ab9442 < 34>:    jbe    0x7ffff7ab94e2 <memset 194>
   0x00007ffff7ab9448 < 40>:    cmp    $0x20,%rdx
   0x00007ffff7ab944c < 44>:    movdqu %xmm8,(%rdi)
   0x00007ffff7ab9451 < 49>:    movdqu %xmm8,-0x10(%rdi,%rdx,1)
   0x00007ffff7ab9458 < 56>:    ja     0x7ffff7ab9460 <memset 64>
   0x00007ffff7ab945a < 58>:    repz retq 
   0x00007ffff7ab945c < 60>:    nopl   0x0(%rax)
   0x00007ffff7ab9460 < 64>:    movdqu %xmm8,0x10(%rdi)
   0x00007ffff7ab9466 < 70>:    movdqu %xmm8,-0x20(%rdi,%rdx,1)
   0x00007ffff7ab946d < 77>:    retq   
   0x00007ffff7ab946e < 78>:    xchg   %ax,%ax
   0x00007ffff7ab9470 < 80>:    lea    0x40(%rdi),%rcx
   0x00007ffff7ab9474 < 84>:    movdqu %xmm8,(%rdi)
   0x00007ffff7ab9479 < 89>:    and    $0xffffffffffffffc0,%rcx
   0x00007ffff7ab947d < 93>:    movdqu %xmm8,-0x10(%rdi,%rdx,1)
   0x00007ffff7ab9484 < 100>:   movdqu %xmm8,0x10(%rdi)
   0x00007ffff7ab948a < 106>:   movdqu %xmm8,-0x20(%rdi,%rdx,1)
   0x00007ffff7ab9491 < 113>:   movdqu %xmm8,0x20(%rdi)
   0x00007ffff7ab9497 < 119>:   movdqu %xmm8,-0x30(%rdi,%rdx,1)
   0x00007ffff7ab949e < 126>:   movdqu %xmm8,0x30(%rdi)
   0x00007ffff7ab94a4 < 132>:   movdqu %xmm8,-0x40(%rdi,%rdx,1)
   0x00007ffff7ab94ab < 139>:   add    %rdi,%rdx
   0x00007ffff7ab94ae < 142>:   and    $0xffffffffffffffc0,%rdx
   0x00007ffff7ab94b2 < 146>:   cmp    %rdx,%rcx
   0x00007ffff7ab94b5 < 149>:   je     0x7ffff7ab945a <memset 58>
   0x00007ffff7ab94b7 < 151>:   nopw   0x0(%rax,%rax,1)
   0x00007ffff7ab94c0 < 160>:   movdqa %xmm8,(%rcx)
   0x00007ffff7ab94c5 < 165>:   movdqa %xmm8,0x10(%rcx)
   0x00007ffff7ab94cb < 171>:   movdqa %xmm8,0x20(%rcx)
   0x00007ffff7ab94d1 < 177>:   movdqa %xmm8,0x30(%rcx)
   0x00007ffff7ab94d7 < 183>:   add    $0x40,%rcx
   0x00007ffff7ab94db < 187>:   cmp    %rcx,%rdx
   0x00007ffff7ab94de < 190>:   jne    0x7ffff7ab94c0 <memset 160>
   0x00007ffff7ab94e0 < 192>:   repz retq 
   0x00007ffff7ab94e2 < 194>:   movq   %xmm8,%rcx
   0x00007ffff7ab94e7 < 199>:   test   $0x18,%dl
   0x00007ffff7ab94ea < 202>:   jne    0x7ffff7ab950e <memset 238>
   0x00007ffff7ab94ec < 204>:   test   $0x4,%dl
   0x00007ffff7ab94ef < 207>:   jne    0x7ffff7ab9507 <memset 231>
   0x00007ffff7ab94f1 < 209>:   test   $0x1,%dl
   0x00007ffff7ab94f4 < 212>:   je     0x7ffff7ab94f8 <memset 216>
   0x00007ffff7ab94f6 < 214>:   mov    %cl,(%rdi)
   0x00007ffff7ab94f8 < 216>:   test   $0x2,%dl
   0x00007ffff7ab94fb < 219>:   je     0x7ffff7ab945a <memset 58>
   0x00007ffff7ab9501 < 225>:   mov    %cx,-0x2(%rax,%rdx,1)
   0x00007ffff7ab9506 < 230>:   retq   
   0x00007ffff7ab9507 < 231>:   mov    ìx,(%rdi)
   0x00007ffff7ab9509 < 233>:   mov    ìx,-0x4(%rdi,%rdx,1)
   0x00007ffff7ab950d < 237>:   retq   
   0x00007ffff7ab950e < 238>:   mov    %rcx,(%rdi)
   0x00007ffff7ab9511 < 241>:   mov    %rcx,-0x8(%rdi,%rdx,1)
   0x00007ffff7ab9516 < 246>:   retq   

(Bu libc.so.6, memset Kurul sadece PLT girmesinden bulmuş görünüyor terk etmeye çalışan kişi kendisi program değil. Meclis Unixy bir sistem üzerinde memset gerçek dökümü almak için en kolay yoludur

$ gdb ./a.out
(gdb) set env LD_BIND_NOW t
(gdb) b main
Breakpoint 1 at [address]
(gdb) r
Breakpoint 1, [address] in main ()
(gdb) disas memset
...

.)

Bunu PaylaÅŸ:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VÄ°DEO

Rastgele Yazarlar

  • Caroline Saquet

    Caroline Saq

    1 EKÄ°M 2011
  • habpsu

    habpsu

    25 Temmuz 2007
  • RawBrahs

    RawBrahs

    28 Aralık 2010