No Such Blog or Diary

«Prev || 1 | 2 | 3 |...| 12 | 13 | 14 || Next»

やっぱりわからん

C++で,与えられたクラスがとあるメソッドを実装しているか否かを判定したかったのだけど結局適切な方法が分からず.http://stackoverflow.com/questions/257288/possible-for-c-template-to-check-for-a-functions-existence とか見てテンプレート化されてないメソッドの存在判定はできるようになったのだけど,テンプレート化されたメソッドの存在判定がうまくできないというオチ.

まだまだC++力が足りない今日この頃.

メモリバンド幅を使い切りたかったのだが…

メモリバンド幅って完全に使い切ることができるのかなぁとか思ったのでプログラム書いて試してみた.でもバンド幅を使い切るのはなかなか難しい.

並列プログラムを共有メモリ環境で作っているとメモリバンド幅が足りなくて並列化の効果が頭打ちになってるっぽいことがある.特に,処理するデータは沢山あるけれど個々の要素に適用する操作が単純だという場合に顕著に見られる.でも,並列プログラムの台数効果の頭打ちがメモリバンド幅が狭いせいだと主張するにはメモリバンド幅をちゃんと把握しておかねばならない.別にカタログスペック以上の幅があるわけではないのだから無駄だけど.

実験に使った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スレッド数読み書き
PC18 on 2 CPUs40.4GB/s27.3GB/s
PC14 on 1 CPU22.0GB/s13.7GB/s
PC28 on 2 CPUs6.16GB/s7.11GB/s
PC24 on 1 CPU6.29GB/s7.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);
  }
}

λ式を返す関数を作りたかったのだけど

あー,New wording for C++0x Lambdas (rev. 2) (N2927) にλ式はdecltypeの引数にはなれないと書いてあった.残念.

In addition, this rewrite adds the restriction that lambda expressions cannot be used in the operand of a sizeof operator, alignof operator, or decltype specifier.That restriction—suggested by Doug Gregor and John Spicer—avoids severe implementation difficulties with template argument deduction.

ということは,λ式を返す関数は戻り値の型にautoと書けないのか.λ式の正確な型の書き方を知る必要があるな.できるかどうかすら分からないけど.

うーむ,decltypeに直接λ式を入れるだけでなく,部分式にすらλ引きが許されないのか….しかもN2927とかN2550とか読む限りではλ式が変換されるクロージャオブジェクトにはユニークだけど名無しの型がつくそうで….やっぱλ式を返すにはstd::functionに突っ込むしかないのかな(何の問題もないのだけど何か負けた気分).

gcc 4.5.0 (experimental) でラムダ式を返す関数を作ろうとしたが…

下のようにdecltypeの中にλ式を書いたらコンパイル通らなかった.うーん,expressionなら何でもよいと思ったのだが何か間違っただろうか?

#include <iostream>
 
template <typename T>
auto func(T i) -> decltype(i) { return i; }
 
template <typename T>
auto func1(T i) -> decltype([=](int j){ return i+j; })
 { return [=](int j){ return i+j; }; }
 
int main(int argc, char *argv[])
{
        func(1);
        func1(1);
        return 0;
}

上側のfuncの定義は通る.でも下のfunc1の定義が通らない.expected primary-expression before ')' token (λ式の後の)の手前に式が必要)とか言われる.

そしてλ式とテンプレートの同時使用の仕方が分からない.ポリモーフィックなλ式は作れるのか?

gcc 4.5.0 (experimental) でラムダ式をデマングルしてみた

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
 
int main(int argc, char *argv[])
{
  auto f = [](int a) { return (a + 1) ^ 7;};
  int status;
  std::cout << abi::__cxa_demangle((typeid (f)).name(), 0, 0, &status) << std::endl;
  auto g = [](int a, float b) { return ((a + 1) ^ 7) + b;};
  std::cout << abi::__cxa_demangle((typeid (g)).name(), 0, 0, &status) << std::endl;
  auto h = [](int a, float b) { return ((a + 1) ^ 7) + b;};
  std::cout << abi::__cxa_demangle((typeid (h)).name(), 0, 0, &status) << std::endl;
  std::cout << abi::__cxa_demangle((typeid (h(1,1.0f))).name(), 0, 0, &status) << std::endl;
  return 0;
}

を実行して

main::{lambda(int)#1}
main::{lambda(int, float)#2}
main::{lambda(int, float)#3}
float

が出力された.

とりあえず,宣言された場所+lambdaに引数が連なって,ついでに何番目に宣言されたかがくっつくと.なるほど.

C++で忘れがちなこと

__LINE__ で行番号が取れる.これ使えば行ごとに一意な静的定数を作れる.便利かもしれない.

typeid と demangle による型情報の出力.Expression Templates で頑張っている時には必須.操作対象が型なので型を出力できないと色々困る.

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
 
template<typename T>
void showtype() {
  int status;
  std::cout << abi::__cxa_demangle(typeid(T).name(),0,0,&status) << std::endl;
}
template<typename T>
void showtypeof(const T&) {
  showtype<T>();
}

std::inner_product は色々計算できる.

«Prev || 1 | 2 | 3 |...| 12 | 13 | 14 || Next»
Search
Feeds

Page Top