2010年01月
メモリバンド幅を使い切りたかったのだが…
メモリバンド幅って完全に使い切ることができるのかなぁとか思ったのでプログラム書いて試してみた.でもバンド幅を使い切るのはなかなか難しい.
並列プログラムを共有メモリ環境で作っているとメモリバンド幅が足りなくて並列化の効果が頭打ちになってるっぽいことがある.特に,処理するデータは沢山あるけれど個々の要素に適用する操作が単純だという場合に顕著に見られる.でも,並列プログラムの台数効果の頭打ちがメモリバンド幅が狭いせいだと主張するにはメモリバンド幅をちゃんと把握しておかねばならない.別にカタログスペック以上の幅があるわけではないのだから無駄だけど.
実験に使ったPCは次の2台.
PC1: Xeon X5550 (8MB L3 キャッシュ、2.66GHz、トリプルチャネル 32GB/s MC、6.4GT/s QPI) x2 X5520 チップセット 12GB (2GBx6) DDR3 RDIMM メモリ(1333MHz、ECC) Maximum bandwidth: 64GB/s
PC2: Xeon E5430 (2x6MB L2 キャッシュ、2.66GHz, 1333MHz FSB)x2 5400 チップセット 8GB (1GBx8) クワッドチャネル DDR2-SDRAM (667MHz、ECC) Maximum bandwidth: 10.6GB/s
PC1のメモリバンド幅は,単体CPUがトリプルチャネルで32GB/sが可能なところに3枚のDDR3 PC3-10600が付いてるので,単体で32GB/sになって2つで64GB/sになるはず.PC2の方は,FSBのバンド幅が 10.6GB/s で,クアッドチャネルに DDR2 PC2-5300 が8枚なので 21.2GB/sで,結局FSB側で押さえられてバンド幅が10.6GB/sになる.
で,下にあるプログラムで実験した結果:
PC | スレッド数 | 読み | 書き |
---|---|---|---|
PC1 | 8 on 2 CPUs | 40.4GB/s | 27.3GB/s |
PC1 | 4 on 1 CPU | 22.0GB/s | 13.7GB/s |
PC2 | 8 on 2 CPUs | 6.16GB/s | 7.11GB/s |
PC2 | 4 on 1 CPU | 6.29GB/s | 7.16GB/s |
PC1は各CPUにメモリがくっついているので,使うCPU数を倍にするとバンド幅も倍になる.一方,PC2は共通のFSBの上にメモリがあるのでCPUを増やしてもバンドは増えない.
で,今のところハードウェア仕様の最大バンドの60%位しか出せていない.どやったら良くなるのかなぁと.
以下,実験に使ったプログラム.メモリを読むのみ(runreadA)&書くのみ(runwriteNT).SSE命令で16Bごとの読み書き.また,書き込み時にはmovntpdでキャッシュを汚染しないようにした.さらに,スレッドごとにaffinityを指定したかったので,OpenMPでなくpthreadを使った(OpenMPでもできるのかもしれないけど).また,各スレッドでメモリを割り当てているので,スレッドの乗っかっているCPUにつながっている物理メモリにデータが置かれると期待.
#ifndef N #define N "536870912" // バッファサイズ #endif #ifndef I #define I "16" // 繰り返し回数 #endif #include <iostream> #include <vector> #include <string> #include <pthread.h> #include <stdio.h> #include <sched.h> #include <stdlib.h> #include <time.h> #include <sys/time.h> double gettimeofday_sec() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + (double)tv.tv_usec*1e-6; } extern "C" void runreadA(unsigned char *p); asm volatile("#here-readA"); asm volatile("\n" "runreadA: \n" " pushq %rbx \n" " pushq %rcx \n" " pushq %rdx \n" " \n" " leaq "N"(%rdi), %rdx \n" " xorq %rbx, %rbx \n" ".L10runrA: \n" " movq %rdi, %rcx \n" ".L9runrA: \n" " movapd (%rcx), %xmm0 \n" " movapd 16(%rcx), %xmm1 \n" " movapd 32(%rcx), %xmm2 \n" " movapd 48(%rcx), %xmm3 \n" " movapd 64(%rcx), %xmm4 \n" " movapd 80(%rcx), %xmm5 \n" " movapd 96(%rcx), %xmm6 \n" " movapd 112(%rcx), %xmm7 \n" " addq $128, %rcx \n" " cmpq %rdx, %rcx \n" " jne .L9runrA \n" " addq $1, %rbx \n" " cmpq $" I ", %rbx \n" " jne .L10runrA \n" " \n" " popq %rdx \n" " popq %rcx \n" " popq %rbx \n" " ret \n" ""); extern "C" void runwriteNT(unsigned char *p); asm volatile("#here-writeNT"); asm volatile("\n" "runwriteNT: \n" " pushq %rbx \n" " pushq %rcx \n" " pushq %rdx \n" " \n" " leaq "N"(%rdi), %rdx \n" " xorq %rbx, %rbx \n" ".L10runwNT: \n" " movq %rdi, %rcx \n" ".L9runwNT: \n" " movntpd %xmm0, (%rcx) \n" " movntpd %xmm1, 16(%rcx) \n" " movntpd %xmm2, 32(%rcx) \n" " movntpd %xmm3, 48(%rcx) \n" " movntpd %xmm4, 64(%rcx) \n" " movntpd %xmm5, 80(%rcx) \n" " movntpd %xmm6, 96(%rcx) \n" " movntpd %xmm7, 112(%rcx) \n" " addq $128, %rcx \n" " cmpq %rdx, %rcx \n" " jne .L9runwNT \n" " addq $1, %rbx \n" " cmpq $" I ", %rbx \n" " jne .L10runwNT \n" " \n" " popq %rdx \n" " popq %rcx \n" " popq %rbx \n" " ret \n" ""); struct info_t { int rank; int size; int n; int i; int cpu; double gb; void (*f)(unsigned char*); std::string str; pthread_barrier_t *st_barrier; pthread_barrier_t *et_barrier; }; void *task(void *arg) { info_t *info = ((info_t*)arg); int n = info->n; int i = info->i; int rank = info->rank; int cpu = info->cpu; unsigned char *p = (unsigned char*)malloc(n); //printf("%16lx\n", p); for(int j = 0; j < n; j++) { p[j] = 0; } std::cout << "rank " << rank << " at cpu " << cpu << std::endl; </div> <div class="section"> double st = 0; pthread_barrier_wait(info->st_barrier); if(rank==0) { st = gettimeofday_sec(); } info->f(p); pthread_barrier_wait(info->et_barrier); if(rank==0) { double et = gettimeofday_sec(); std::cout << info->size << " " << (info->str) << " " << (et-st) << " " << (info->gb/(et-st)) << "GB/s" << std::endl; } free(p); return NULL; } int main(int argc, char *argv[]) { if(argc<=1) { std::cout << argv[0] << " p" << std::endl; return 0; } int n = atoi(N); int i = atoi(I); int p = atoi(argv[1]); double gb = (1./1024.0/1024.0/1024.0*i*n*p); std::cout << "# run (buffer) size: "<< (1./1024.0/1024.0*n) <<"MB." << std::endl; std::cout << "# iteration: "<< i <<" times" << std::endl; std::cout << "# "<< (1./1024.0/1024.0/1024.0*i*n)<<"GB/core will be transfered." << std::endl; std::cout << "# in total "<< gb <<"GB will be transfered." << std::endl; pthread_barrier_t st_barrier, et_barrier; pthread_t ps[p]; info_t is[p]; cpu_set_t cs[p]; for(int k = 0; k < p; k++) { is[k].rank = k; is[k].size = k; is[k].n = n; is[k].i = i; is[k].gb = gb; is[k].st_barrier = &st_barrier; is[k].et_barrier = &et_barrier; is[k].cpu = argc > 2 ? k : ((k&1)<<2) | ((k&0x6)>>1); CPU_ZERO(&cs[k]); CPU_SET(is[k].cpu, &cs[k]); } std::vector<std::string> ss; std::vector<void (*) (unsigned char*)> fs; fs.push_back(runreadA); ss.push_back("ra"); fs.push_back(runwriteNT); ss.push_back("wnt"); for(int l = 0; l < fs.size(); l++) { pthread_barrier_init(&st_barrier, NULL, p); pthread_barrier_init(&et_barrier, NULL, p); for(int k = 0; k < p; k++) { is[k].f = fs[l]; is[k].str = ss[l]; } </div> <div class="section"> for(int k = 0; k < p; k++) { pthread_create(&ps[k], NULL, task, &is[k]); pthread_setaffinity_np(ps[k], sizeof(cpu_set_t), &cs[k]); } for(int j = 0; j < p; j++){ int *tmp; pthread_join(ps[j], (void**)&tmp); } pthread_barrier_destroy(&st_barrier); pthread_barrier_destroy(&et_barrier); } }
- Comments: 0
- TrackBack (Close): -