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
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
...
.)
Neden bir döngü iki döngü daha yavaş g...
Neden bazı yüzer < karşılaştırmalar...
Neden sıralanmış bir dizi sıralanmamış...
Neden 512x512 matrix 513x513 bir matri...
Neden küçük bir liste daha küçük bir d...