忍者ブログ
  • 2024.08
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.10
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/09/19 11:55 】 |
並列処理について
並列処理を行う方法について書きます。
グラフィックカードやマルチコアのCPUなど、並列処理を行える
ハードウェアには複数のプロセッサが搭載されています。
(4コアCPUなら4つのコアがこのプロセッサにあたる)

誤解を生む言い方をすれば、これらのプロセッサには番号が
ふられています。したがってこれらの番号ごとに違う処理を
記述すれば複数のプロセッサが並列処理をします。

例えばこんな感じ

id = get_id();
a[id] = b[id]+c[id];

プロセッサの番号を取得し、番号ごとに違う要素をいじる。
例えば番号として0がふられているプロセッサが実行する処理は

id = 0;
a[id] = b[id]+c[id];

つまりa[0] = b[0]+c[0];を実行します。
同じように1番のプロセッサはa[1] = b[1]+c[1];を、
2番のプロセッサはa[2] = b[2]+c[2];を、
3番のプロセッサはa[3] = b[3]+c[3];を、
それぞれ実行することになります。
これにより同時に実行させる処理が同じでも、
番号の違いにより並列処理を実現することができます。

実際には嘘八百だったりしますが原理は同じです。
とりあえずOpenCLでのサンプルを続きに書きます。
(バグ修正に疲れたので解説は次回)
ホストコードadd.cが無駄に長い・・・
あとコンパイルするときに
cl add.c OpenCL.lib
みたいな感じでOpenCL.libをコマンドライン引数に追加するとうまくいった。

add.cl

__kernel void add(__global float *result, __global float *ope1, __global float *ope2){
  int id = get_global_id(0);
  result[id] = ope1[id] + ope2[id];
}


add.c

#include<stdio.h>
#include<stdlib.h>
#ifdef __APPLE__
#include<OpenCL/opencl.h>
#else
#include<CL/cl.h>
#endif


#define MAX_SOURCE_SIZE (0x100000)

#define ARRAY_SIZE (4)

int main(){

  float *result=NULL, *ope1=NULL, *ope2=NULL;
  int i;


  cl_platform_id platform_id = NULL;
  cl_uint ret_num_platforms;

  cl_device_id device_id = NULL;
  cl_uint ret_num_devices;

  cl_context context = NULL;

  cl_command_queue command_queue = NULL;


  cl_mem resmem = NULL;
  cl_mem ope1mem = NULL;
  cl_mem ope2mem = NULL;

  cl_program program = NULL;

  cl_kernel kernel = NULL;

  size_t global_item_size = 4;
  size_t local_item_size = 1;

  cl_int ret;

  FILE *fp;
  char fileName[] = "./add.cl";
  char *source_str;
  size_t source_size;


  fp = fopen(fileName, "r");
  if (!fp){
    fprintf(stderr, "Failed to load kernel.\n");
    exit(1);
  }
  source_str = (char*)malloc(MAX_SOURCE_SIZE);
  source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp);
  fclose(fp);



  result = (float *)malloc(ARRAY_SIZE*sizeof(float));
  ope1   = (float *)malloc(ARRAY_SIZE*sizeof(float));
  ope2   = (float *)malloc(ARRAY_SIZE*sizeof(float));
  if((result==NULL) || (ope1==NULL) || (ope2==NULL)){
    printf("Memory alloc failed.\n");
    exit(EXIT_FAILURE);
  }

  for(i=0;i<ARRAY_SIZE;i++){
    ope1[i]   = i;
    ope2[i]   = i+ARRAY_SIZE;
  }

  ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);

  ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id, &ret_num_devices);

  context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);

  command_queue = clCreateCommandQueue(context, device_id, 0, &ret);


  resmem  = clCreateBuffer(context, CL_MEM_READ_WRITE, ARRAY_SIZE*sizeof(float), NULL, &ret);
  ope1mem = clCreateBuffer(context, CL_MEM_READ_WRITE, ARRAY_SIZE*sizeof(float), NULL, &ret);
  ope2mem = clCreateBuffer(context, CL_MEM_READ_WRITE, ARRAY_SIZE*sizeof(float), NULL, &ret);

  ret = clEnqueueWriteBuffer(command_queue, ope1mem, CL_TRUE, 0, ARRAY_SIZE*sizeof(float), ope1, 0, NULL, NULL);
  ret = clEnqueueWriteBuffer(command_queue, ope2mem, CL_TRUE, 0, ARRAY_SIZE*sizeof(float), ope2, 0, NULL, NULL);


  program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret);

  ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);

  kernel = clCreateKernel(program, "add", &ret);


  ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *) &resmem);
  ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *) &ope1mem);
  ret = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *) &ope2mem);

  ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &global_item_size, &local_item_size, 0, NULL, NULL);

  ret = clEnqueueReadBuffer(command_queue, resmem, CL_TRUE, 0, ARRAY_SIZE*sizeof(float), result, 0, NULL, NULL);

  for(i=0;i<ARRAY_SIZE;i++){
    printf("result[%d]:%5.2f   =   ope1[%d]:%5.2f   +   ope2[%d]:%5.2f\n", i, result[i], i, ope1[i], i, ope2[i]);
  }

  ret = clFlush(command_queue);
  ret = clFinish(command_queue);
  ret = clReleaseKernel(kernel);
  ret = clReleaseProgram(program);
  ret = clReleaseMemObject(resmem);
  ret = clReleaseMemObject(ope1mem);
  ret = clReleaseMemObject(ope2mem);
  ret = clReleaseCommandQueue(command_queue);
  ret = clReleaseContext(context);


  free(source_str);

  free(result);
  free(ope1);
  free(ope2);

  return 0;
}


実行結果

result[0]: 4.00   =   ope1[0]: 0.00   +   ope2[0]: 4.00
result[1]: 6.00   =   ope1[1]: 1.00   +   ope2[1]: 5.00
result[2]: 8.00   =   ope1[2]: 2.00   +   ope2[2]: 6.00
result[3]:10.00   =   ope1[3]: 3.00   +   ope2[3]: 7.00


青文字を適当にコピペして赤文字の部分をちょっと書き換えることに
なるのかな?他は普通に書く方が早そうな?理解が進めば青いところでも
引数をちょこっと書き換える必要あるかも。とにかく無駄に長い。

カーネルコードadd.clがエラーを吐かないうえに実行までできてしまう件
引数が__global float *ope1ではなく__ global float *ope1と余計なスペースが
入っていてもresult[0]~result[3]が0.00とか謎の数値とかになるだけだった。
エラーを吐かせる方法も調べないと・・・orz


3/14追記


switch(get_id()){
case 0:
  信号処理
  break;
case 1:
  情報処理
  break;
case 2:
  データ処理
  break;
case 3:
  信号処理
  break;
}

というような並列処理は調査が足りないのでよく分かりません。
グラフィックカードの場合は分岐予測やアウトオブオーダーが苦手
(グラフィックカード上では複雑な機構を実装するのが困難
なので避けた方が無難だと思いますが、マルチコアCPUの場合
などは十分な有用性があると思います。
※上のような処理はCUDAだとウォープダイバージェントが起こる
※OpenCLではswitch文ではなく実際にはタスク並列で処理を記述します

PR
【2013/02/17 18:39 】 | OpenCL習得 | 有り難いご意見(0)
<<サンプルソースコード解説 | ホーム | OpenCL>>
有り難いご意見
貴重なご意見の投稿














<<前ページ | ホーム | 次ページ>>