Soru Neden belleğe yazmak onu okumaktan çok daha yavaş?


İşte basit memset bant genişliği kıyaslaması:

#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("%4d/%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);
}

Sistemimde (aşağıdaki ayrıntılar) tek bir DDR3-1600 bellek modülü ile çıktılar:

Bant genişliği = 4.751 GB / s (Giga = 10 ^ 9)

Bu, teorik RAM hızının% 37'sidir: 1.6 GHz * 8 bytes = 12.8 GB/s

Öte yandan, 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);
}

Bu çıktılar:

Bant genişliği = 11.516 GB / s (Giga = 10 ^ 9)

XORing gibi büyük bir dizi gibi okuma performansı teorik sınırına yaklaşabilirim, ancak yazma çok daha yavaş görünüyor. Niye ya?

işletim sistemi Ubuntu 14.04 AMD64 (Ben derledim gcc -O3. kullanma -O3 -march=native okuma performansını biraz daha kötü yapar, ancak etkilemez memset)

İşlemci Xeon E5-2630 v2

Veri deposu Tek bir "16GB PC3-12800 Parite REG CL11 240-Pin DIMM" (Kutu üzerinde ne yazıyor) Tek bir DIMM'e sahip olmanın performansı daha öngörülebilir hale getirdiğini düşünüyorum. Bunu 4 DIMM ile kabul ediyorum. memset olacak kadar 4 kat daha hızlı.

Anakart Supermicro X9DRG-QF (4 kanallı belleği destekler)

Ek sistem: 2x4GB DDR3-1067 RAM'lik bir dizüstü bilgisayar: okuma ve yazma işlemi yaklaşık 5,5 GB / sn'dir, ancak 2 DIMM kullanır.

Not; yerine memset Bu sürümde tam olarak aynı performansla sonuçlanır

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;
}

46
2017-09-13 20:32


Menşei


printf("%4d/%4ld\r", p[0], r); Kıyaslama ölçütünüzde, her şeyden çok, zamanlama anlamına gelir. G / Ç yavaş. - Retired Ninja
@RetiredNinja Hayır! printf 20 saniyeliğine çalışan bir programda 101 kez çağrılır - MaxB
Gönderdiğiniz kodda 100 kez çağrılmalıdır. Kıyaslama yaptığınız kodun parçası olmasının bir nedeni yok. - Retired Ninja
Sistemimde loop ile birlikte ve printf olmadan denedim. Fark beklediğimden daha küçüktü (3 kez çalıştır). 9.740, 9.614 ve 9.653 olmadan 9.644, 9.667 ve 9.629 aldım. - some
Benim 2010 eski MacBook optimizasyon olmadan 1.937 GB / s, ve değiştirilmemiş kod ile optimizasyon ile 173010.381 GB / s raporları :-) Muhtemelen memset değiştirilmek üzere ilk olarak RAM'den önbellek okumak için bir önbellek satırına yazar ve daha sonra da temizlendi, böylece her önbellek satırı salt okunur yerine okunur. Kalan fark muhtemelen bitişik olmayan yerlerde okuma / yazma nedeniyle olacaktır. PowerPC, önbellek çizgilerini temizlemeye yönelik talimatlara sahipti. - gnasher729


Cevaplar:


Programlarınla ​​birlikte

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

bir masaüstünde (Core i7, x86-64, GCC 4.9, GNU libc 2.19) altı adet 2GB DIMM'li makine. (Elimden daha fazla detayım yok, üzgünüm.)

Ancak, bu program raporları bant genişliği yazmak 12.209 GB/s:

#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 */

Sihir hepsi _mm_stream_si128, makine eğitimi movntdqsistem RAM'e 16 baytlık bir miktar yazıyor, önbelleği atlamak (bunun için resmi jargon "zamansız mağaza") Bence bu oldukça sonuçta performans farkının olduğunu gösteriyor. olduğu tüm önbellek davranışı hakkında.

N.B. glibc 2.19 yapar özenle el ile optimize edilmiş memset Bu vektör talimatlarını kullanır. Ancak, o yapar değil zamansız mağazaları kullan. Muhtemelen bu Doğru Şey memset; Genel olarak, kullanmadan önce hafızayı temizlerseniz, istemek önbellekte sıcak olması. (Bence hatta bir zeki memset geçici olmayan mağazalara geçebilir çok büyük Bloku temizleyin, teoride, önbelleğin tamamını istemediğinizi, çünkü önbellek sadece bu kadar büyük değil.

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    %ecx,(%rdi)
   0x00007ffff7ab9509 <+233>:   mov    %ecx,-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, programın kendisi değil - meclisi terk etmeye çalışan diğer kişi memset sadece PLT girişini bulmuş gibi görünüyor. Derlemeyi gerçek için almanın en kolay yolu memset Unixy sisteminde

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

.)


42
2017-09-14 16:00



Mükemmel cevap! JarkkoL'un cevabını tarayıcımda görünce kabul etmiştim. Cevabını doğru göründüğünden, bu karara sadık kalacağımı düşünüyorum. - MaxB
Ah, yanlış olduğumu düşünmüştüm. memsetDoğru demontajı gönderdiğiniz için teşekkürler. Ve gdb'de bu hileyi bilmek harika! - Patrick Collins
Asıl sebep movnt Mağazalar büyük memsetler için daha iyi yazma bant genişliği verebilirler, zayıf bir şekilde sipariş edilmişlerdir. Yeni bir önbellek hattına yazarken sahiplik için okumaya devam etme adımını atlayabilirler, çünkü birbirleriyle veya normal mağazalarla ilgili olarak global olarak görünmeleri garanti edilmez. "Hızlı dize işlemleri" olan CPU'larda (Intel IvB ve sonrası), rep stos Aynı hıza ulaşmak için biraz zayıf sıralı mağazalar kullanır, ancak önbelleği atlamaz. Dokümanları anladığım gibi, operasyonun sonunda bir mağaza çiti var, bu yüzden bayrağı memset / cpy'nin bir parçası olarak saklamayın. - Peter Cordes


Performanstaki ana fark, PC / bellek bölgenizin önbellekleme politikasından kaynaklanmaktadır. Bir bellekten okunduğunuzda ve veriler önbellekte olmadığında, verilerle herhangi bir hesaplama yapabilmeniz için önce belleğin önce bellek veri yolu aracılığıyla önbellekte getirilmesi gerekir. Ancak, belleğe yazdığınızda farklı yazma politikaları vardır. Muhtemelen sisteminiz geri yazma önbelleğini (ya da daha kesin olarak "tahsisat") kullanmaktadır, bu da önbellekte olmayan bir bellek konumuna yazdığınızda, verilerin belleğe önbellekte getirilmesi ve sonuçta yazılı olarak yazılması anlamına gelir. Veriler önbellekten çıkarıldığında belleğe geri dönülür, bu da veri için gidiş-dönüş ve yazmalar üzerine 2x veriyolu bant genişliği kullanımı anlamına gelir. Yazma önbelleğe alma politikası (ya da "no-write allocate") de vardır; bu, genellikle, önbellek-miss'de, verilerin önbellekte getirilmediği ve her iki okuma için de aynı performansa daha yakın olması gerektiği anlamına gelir. yazıyor.


27
2017-09-14 01:22



Daha önceki tahminimi teyit ettiğiniz için teşekkür ederim (30 dakika önce gönderdim)! Onu kabul etmeyeceğim, aksi halde biri beni yanlış olduğu konusunda ikna etmediği sürece. - MaxB
Bazı platformlarda, her bir dağıtım için önbellekleme politikasını gerçekten kontrol edebilir ve performans yazma, nedenlerden biridir. - JarkkoL
Geleneksel mimariler, tüm kirli verileri bir süreliğine belleğe geri yazar. Günümüzde birçok platform, ek önbellek kontrolü özellikleriyle performansı artırmaya çalışıyor. Örneğin, Cavium Octeon gibi platformlar, L2 önbellek verilerini geri yazmamak için DWB (Geri Yazma) seçenekleri gibi özel önbellek kontrol politikaları sağlar. Bu gereksiz L2 verisinden dolayı belleğe geri yazılabilir. - Karthik Balaguru


Fark - en azından makinemde, bir AMD işlemcisiyle - okuma programının vectorized işlemleri kullanıyor olması. Yazma programı için bu iki verimi toparlamak:

0000000000400610 <main>:
  ...
  400628:       e8 73 ff ff ff          callq  4005a0 <clock@plt>
  40062d:       49 89 c4                mov    %rax,%r12
  400630:       89 de                   mov    %ebx,%esi
  400632:       ba 00 ca 9a 3b          mov    $0x3b9aca00,%edx
  400637:       48 89 ef                mov    %rbp,%rdi
  40063a:       e8 71 ff ff ff          callq  4005b0 <memset@plt>
  40063f:       0f b6 55 00             movzbl 0x0(%rbp),%edx
  400643:       b9 64 00 00 00          mov    $0x64,%ecx
  400648:       be 34 08 40 00          mov    $0x400834,%esi
  40064d:       bf 01 00 00 00          mov    $0x1,%edi
  400652:       31 c0                   xor    %eax,%eax
  400654:       48 83 c3 01             add    $0x1,%rbx
  400658:       e8 a3 ff ff ff          callq  400600 <__printf_chk@plt>

Ama bu okuma programı için:

00000000004005d0 <main>:
  ....
  400609:       e8 62 ff ff ff          callq  400570 <clock@plt>
  40060e:       49 d1 ee                shr    %r14
  400611:       48 89 44 24 18          mov    %rax,0x18(%rsp)
  400616:       4b 8d 04 e7             lea    (%r15,%r12,8),%rax
  40061a:       4b 8d 1c 36             lea    (%r14,%r14,1),%rbx
  40061e:       48 89 44 24 10          mov    %rax,0x10(%rsp)
  400623:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  400628:       4d 85 e4                test   %r12,%r12
  40062b:       0f 84 df 00 00 00       je     400710 <main+0x140>
  400631:       49 8b 17                mov    (%r15),%rdx
  400634:       bf 01 00 00 00          mov    $0x1,%edi
  400639:       48 8b 74 24 10          mov    0x10(%rsp),%rsi
  40063e:       66 0f ef c0             pxor   %xmm0,%xmm0
  400642:       31 c9                   xor    %ecx,%ecx
  400644:       0f 1f 40 00             nopl   0x0(%rax)
  400648:       48 83 c1 01             add    $0x1,%rcx
  40064c:       66 0f ef 06             pxor   (%rsi),%xmm0
  400650:       48 83 c6 10             add    $0x10,%rsi
  400654:       49 39 ce                cmp    %rcx,%r14
  400657:       77 ef                   ja     400648 <main+0x78>
  400659:       66 0f 6f d0             movdqa %xmm0,%xmm2 ;!!!! vectorized magic
  40065d:       48 01 df                add    %rbx,%rdi
  400660:       66 0f 73 da 08          psrldq $0x8,%xmm2
  400665:       66 0f ef c2             pxor   %xmm2,%xmm0
  400669:       66 0f 7f 04 24          movdqa %xmm0,(%rsp)
  40066e:       48 8b 04 24             mov    (%rsp),%rax
  400672:       48 31 d0                xor    %rdx,%rax
  400675:       48 39 dd                cmp    %rbx,%rbp
  400678:       74 04                   je     40067e <main+0xae>
  40067a:       49 33 04 ff             xor    (%r15,%rdi,8),%rax
  40067e:       4c 89 ea                mov    %r13,%rdx
  400681:       49 89 07                mov    %rax,(%r15)
  400684:       b9 64 00 00 00          mov    $0x64,%ecx
  400689:       be 04 0a 40 00          mov    $0x400a04,%esi
  400695:       e8 26 ff ff ff          callq  4005c0 <__printf_chk@plt>
  40068e:       bf 01 00 00 00          mov    $0x1,%edi
  400693:       31 c0                   xor    %eax,%eax

Ayrıca, "homegrown" unuzu da not edin. memset aslında bir çağrıya doğru optimize edildi memset:

00000000004007b0 <my_memset>:
  4007b0:       48 85 d2                test   %rdx,%rdx
  4007b3:       74 1b                   je     4007d0 <my_memset+0x20>
  4007b5:       48 83 ec 08             sub    $0x8,%rsp
  4007b9:       40 0f be f6             movsbl %sil,%esi
  4007bd:       e8 ee fd ff ff          callq  4005b0 <memset@plt>
  4007c2:       48 83 c4 08             add    $0x8,%rsp
  4007c6:       c3                      retq   
  4007c7:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  4007ce:       00 00 
  4007d0:       48 89 f8                mov    %rdi,%rax
  4007d3:       c3                      retq   
  4007d4:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  4007db:       00 00 00 
  4007de:       66 90                   xchg   %ax,%ax

Olup olmadığına dair herhangi bir referans bulamıyorum memset vectorized işlemleri kullanır, demontaj memset@plt burada yararsızdır:

00000000004005b0 <memset@plt>:
  4005b0:       ff 25 72 0a 20 00       jmpq   *0x200a72(%rip)        # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
  4005b6:       68 02 00 00 00          pushq  $0x2
  4005bb:       e9 c0 ff ff ff          jmpq   400580 <_init+0x20>

Bu soru o zamandan beri memset her durumu ele almak için tasarlanmış, bazı optimizasyonlar eksik olabilir.

Bu adam kesinlikle kendi assemblerınızı yuvarlamanız gerektiğine ikna olmuş görünüyor memset SIMD talimatlarından yararlanmak için. Bu soru da var.

Karanlıkta bir çekim yapacağım ve sanırım SIMD işlemlerini kullanmıyor çünkü bir vektör işlemin boyutunun katları olan bir şeyin üzerinde çalışıp çalışmadığını veya bazı hizalamaların olup olmadığını anlayamıyor. -ilgili konu.

Ancak, bunu onaylayabiliriz değil ile kontrol ederek önbellek verimliliği sorunu cachegrind. Yazma programı aşağıdakileri üretir:

==19593== D   refs:       6,312,618,768  (80,386 rd   + 6,312,538,382 wr)
==19593== D1  misses:     1,578,132,439  ( 5,350 rd   + 1,578,127,089 wr)
==19593== LLd misses:     1,578,131,849  ( 4,806 rd   + 1,578,127,043 wr)
==19593== D1  miss rate:           24.9% (   6.6%     +          24.9%  )
==19593== LLd miss rate:           24.9% (   5.9%     +          24.9%  )
==19593== 
==19593== LL refs:        1,578,133,467  ( 6,378 rd   + 1,578,127,089 wr)
==19593== LL misses:      1,578,132,871  ( 5,828 rd   + 1,578,127,043 wr) << 
==19593== LL miss rate:             9.0% (   0.0%     +          24.9%  )

ve okuma programı şunları üretir:

==19682== D   refs:       6,312,618,618  (6,250,080,336 rd   + 62,538,282 wr)
==19682== D1  misses:     1,578,132,331  (1,562,505,046 rd   + 15,627,285 wr)
==19682== LLd misses:     1,578,131,740  (1,562,504,500 rd   + 15,627,240 wr)
==19682== D1  miss rate:           24.9% (         24.9%     +       24.9%  )
==19682== LLd miss rate:           24.9% (         24.9%     +       24.9%  )
==19682== 
==19682== LL refs:        1,578,133,357  (1,562,506,072 rd   + 15,627,285 wr)
==19682== LL misses:      1,578,132,760  (1,562,505,520 rd   + 15,627,240 wr) <<
==19682== LL miss rate:             4.1% (          4.1%     +       24.9%  )

Okuma programı daha düşük bir LL kaçırma oranına sahipken, daha fazla okuma yapar XOR operasyon), toplam kayıp sayısı aynıdır. Yani sorun ne olursa olsun, orada değil.


15
2017-09-14 01:53



Ayrıca bant genişliğinde 2 kat fark görüyor musunuz? Sayılarınızı ve RAM yapılandırmanızı kaydeder misiniz? - MaxB
This guy definitely seems convinced ... Onun tamponu 244000 kat daha küçüktür ve çeşitli önbelleklere uyar. - MaxB


Önbelleğe alma ve konumluluk, gördüğünüz etkilerin çoğunu kesinlikle açıklar.

Deterministik olmayan bir sistem istemediğiniz sürece, yazmalarda herhangi bir önbellek veya yer yoktur. Çoğu yazma süresi, verilerin depolama ortamına (sabit sürücü veya bellek yongası olup olmadığına) tam olarak ulaşması için geçen süre olarak ölçülürken, okumalar, daha hızlı olan herhangi bir sayıda önbellek katmanından gelebilir. depolama ortamı.


9
2017-09-13 23:45



1 GB dizi herhangi bir önbellek boyutundan daha büyüktür (bu yüzden onu seçtim). Zamana kadar do_xor ikinci kez çalıştırırsa, önceden önbelleğe alınmış değerler silinmiş olur. Ayrıca, önbellek okuma, DRAM-> Önbellek bağlantısından daha hızlı okunmayı açıklayabilir (eğer durum buysa). Yazmanın yavaş olduğunu açıklamıyor. - MaxB
Önbellek etkilerini görmek için 1GB önbelleğe gerek duymadığınızı umuyorum. - Robert Harvey♦
+1 - Prefetching'in onunla bir ilgisinin olduğuna bahse girerim; Bu yazılara yardım etmeyecek, ancak okumalara yardımcı olacak. Ayrıca, GCC'nin yazıları yeniden yazmaları için okumadan daha az istekli olduğuna bahse girerim. - Patrick Collins
X86'da normal mağazalar (değil movnt) şiddetle sipariş edildi. Soğuk bir önbellek hattına yazmak, sahiplik okumayı tetikler. Anladığım kadarıyla, CPU gerçekten önbellek hattını doldurmak için DRAM (veya daha düşük seviyeli önbellek) okur. Yazılanlar, (x86 gibi) güçlü bir şekilde sipariş edilen belleğe sahip bir sistem için okunmaktan daha zordur, ancak verdiğiniz nedenden dolayı değildir. Mağazaların aynı iş parçacığı tarafından yüklendikten sonra arabelleğe alınmasına ve genel olarak görünür hale gelmesine izin verilir. (MFENCE bir StoreLoad bariyeridir ...) AMD, basitlik için yazma önbelleklerini kullanır, ancak Intel daha iyi performans için geri yazma kullanır. - Peter Cordes
Uygulamada L1'e uyan bir tampon ile yalnızca yazma döngüsünün (memset gibi) tekrarlanmasının daha büyük bir tampondan daha hızlı olduğu doğrudur. Bunun bir kısmı, halihazırda M devletinde (MESI) bulunan hatların tahliye edilmek için başka bir hat gerektirmemesidir (tahliye edilen hat M devletindeyse durdurabilir ve öncelikle L2 yazılmalıdır. L2 daha sonra değiştirilmiş bir çizgi vb. DRAM'ye indirilirse. Ancak bunun bir başka kısmı, önbellek zaten E veya M durumunda olduğunda sahiplik okumadan kaçınmaktır. movntve Hızlı Dize rep movsb zayıf siparişli mağazalar RFO'yu önler. - Peter Cordes


Sadece Nasıl (Bir-Tam-Sistem-Bir) gerçekleştirir olabilir. Oku daha hızlı ortak bir eğilim gibi görünüyor Birlikte geniş menzil Göreli verim performansı. Üzerinde hızlı analiz DDR3 Intel ve listelenen DDR2 listelerinin birkaç seçme (yazma / okuma) durumu olarak;

Bazı yüksek performanslı DDR3 çipleri, okuma işleminin yaklaşık% 60-70'inde yazıyor. Bununla birlikte, bazı bellek modülleri (yani Golden Empire CL11-13-13 D3-2666) sadece% 30'a kadar yazılmıştır.

En yüksek performans gösteren DDR2 yongaları, okumaya kıyasla yazma işlem hacminin yalnızca yaklaşık% 50'sine sahiptir. Ama ayrıca ~% 20'ye kadar olan bazı kötü yarışmacılar (yani OCZ OCZ21066NEW_BT1G) var.

Bu açıklamayabilir iken  karşılaştırma kodu ve kurulumun büyük olasılıkla farklı olduğu için,% 40 yazma / okuma raporunun nedeni notlar belirsiz), bu kesinlikle bir faktörü. (Mevcut bazı kıyaslama programlarını çalıştırırdım ve sayıların söz konusu kodunkilerle aynı olup olmadığına bakarım.)


Güncelleştirme:

Bellek arama tablosunu bağlantılı siteden indirdim ve Excel'de işledim. Hala bir geniş menzil değerlerin üstünde, orijinal tepeden çok daha az şiddet vardır, ki bunun üzerinde sadece okunan bellek yongalarına ve grafiklerden seçilen birkaç "ilginç" girdiye bakılmıştır. Özellikle, yukarıda sayılan rakipler arasında, aralarındaki farklılıkların neden ikincil listede bulunmadığından emin değilim.

Bununla birlikte, yeni sayılar altında bile, fark hala okuma performansının% 50-% 100'ü (ortanca 65, ortalama 65) arasında değişmektedir. Yazma / okuma oranında bir çipin "% 100" kadar verimli olmasının sadece genel olarak daha iyi olduğu anlamına gelmediğini unutmayın .. sadece iki işlem arasında daha da eşitlenme olduğunu.


6
2017-09-13 21:33



1 DIMM veya birden fazla DIMM'in yüklü olup olmadığı belirsiz. Bunun çok önemli bir fark yaratabileceğine inanıyorum. Benim testim sadece 1 DIMM'im olması anlamında "saf" dır. - MaxB
@MaxB Hiç net değil, ama geniş bir değerler yelpazesi gösteriyor. Bu yüzden tavsiyem, diğer benchmark programlarının belirli bir makinede benzer değerlerle sonuçlanıp sonuçlanmadığını görmek olurdu; ve eğer öyleyse, eğer gönderilen kriter aynı zamanda farklı donanımlardaki kıyafeti de takip ederse. - user2864740


İşte benim çalışma hipotezim. Doğruysa, yazarların neden okumalardan iki kat daha yavaş olduğunu açıklar:

Buna rağmen memset sadece sanal belleğe yazıyor, önceki içeriğini göz ardı ederek, donanım düzeyinde, bilgisayar DRAM'e saf bir yazı yazamıyor: DRAM'in içeriğini önbellekte okur, orada değiştirir ve sonra DRAM'e geri yazar. Bu nedenle, donanım düzeyinde memset hem okumayı hem yazmayı (eskisi işe yaramaz gibi görünse bile) yapar! Bu yüzden kabaca iki kat hız farkı.


4
2017-09-14 00:35



Bu okur-yazarlığı zayıf düzenlenmiş mağazalarla önleyebilirsiniz.movnt veya Intel IvB ve sonrası rep stos / rep movs "Hızlı Dize İşlemleri"). Önbellek de atlanmadan, zayıf sıralı mağazaların (son Intel CPU'larda memset / memcpy dışında) yapılması için uygun bir yol olmadığından emindi. Diğer bazı cevaplarda da benzer yorumlarda bulundum: normal okumaların tetiklenmesinin ana nedeni, x86'nın güçlü bir şekilde sipariş edilen bellek modeli. Sisteminizi bir DIMM'e sınırlamak ya da değil, bunun bir faktörü olmamalıdır. - Peter Cordes
ARM gibi diğer bazı mimarların fazladan çaba sarf etmeden tam DRAM bant genişliğinde yazmasını beklerim, çünkü mağazaların program sırasındaki diğer mesajlar tarafından görüleceğine dair bir garanti yoktur. Örneğin. Bir sıcak önbellek hattına bir mağaza hemen gerçekleşebilir (ya da en azından, herhangi bir komutun hata yapmamasını ya da yanlış tahmin edilen bir şube olmasını sağladıktan sonra), ancak soğuk önbellek hattına yapılan bir depo, diğer çekirdeklerin görünmesi için herhangi bir yoldan arabelleğe alınabilir Soğuk önbellek çizgisine kadar olan değer tamamen yeniden yazılır ve depo tamponu temizlenir. - Peter Cordes


Çünkü okumak için sadece adres çizgilerini nabız edersiniz ve duyu çizgileri üzerindeki çekirdek durumları okursunuz. Geri yazma döngüsü, veriler CPU'ya teslim edildikten sonra gerçekleşir ve bu nedenle de işleri yavaşlatmaz. Diğer taraftan, yazmak için önce çekirdekleri sıfırlamak için sahte bir okuma yapmalısınız, ardından yazma işlemini gerçekleştirin.

(Açıkça görülmediği takdirde, bu cevap, yazma işleminin eski bir çekirdek bellek kutusundaki okumadan daha yavaş olduğunu açıklayan dildir).


2
2017-09-15 01:24