2014년 3월 1일 토요일

OpenCL 교육 정리

OpenCL 교육 정리

이 문서는 2014/02/24, 25 양일간 3D융합기술지원센터에서 실시한 OpenCL 교육을 받고 내용을 정리한 문서입니다.

OpenCL 이란?

l  OpenCL(Open Computing Language)은 개방형 범용 병렬 컴퓨팅 프레임워크이다.
l  CPU, GPU, DSP 등의 프로세서로 이루어진 이종 플랫폼에서 실행되는 프로그램을 작성할 수 있게 해 준다.
l  OpenCL은 커널 코드를 작성하기 위한 C99 기반의 언어인 OpenCL C와 플랫폼을 정의하고 제어하기 위한 API를 포함하고 있다.
l  OpenCL은 작업 기반(task-based) 및 데이터 기반(data-based) 병렬 컴퓨팅을 제공한다.
l  최신 버전 : 2.0
l  OpenCL platform model
l  Execution Model
n  kernel : device에서 실행되는 하나의 함수
n  work-item : 가장 작은 단위의 실행 객체. work-item 단위로 나뉘어 각각 병렬처리.
n  work-group : work-item 그룹.
n  Global : 전체 work 영역

OpenCL 개발 환경 구축

Windows OS 에서 NVIDIA가 제공하는 CUDA Toolkit을 사용하여 개발하는 환경을 구축하는 것을 기준으로 설명함.
CUDA GPU에서 수행하는 병렬처리 알고리즘을 C 프로그래밍 언어 등의 언어를 사용하여 작성할 수 있도록 하는 GPGPU 기술로 NVIDIA의 지포스 8시리즈급 이상에서 지원함.

1.      Microsoft Visual Studio 준비
2.      NVIDIA CUDA 5.5 다운로드 및 설치
B.      반듯이 OS 버전 및 BIT(32/64) 에 맞는 설치 파일을 설치해야 함.
3.      참고
A.     OpenCL.dll 빌드 오류
                         i.         64비트 윈도우의 경우 디폴트로 로딩되는 OpenCL.dll 32비트 용으로 로딩되어 빌드가 정상적으로 되지 않는 경우 발생.
                       iii.         위 링크로 다운로드한 파일을 설치하여 intel OpenCL x64 SDK 가 설치되도록 함.

OpenCL 프로젝트 설정

가장 기본적인 Win32 콘솔 응용 프로젝트를 생성하여 OpenCL 프로그래밍이 가능하도록 설정하는 방법을 설명함.
1.      Win32 콘솔 응용 프로그램 생성
2.      속성 > C/C++
A.     '일반'의 추가 포함 디렉터리 항목에 아래 경로 추가
                           i.         CUDA Toolkit 설치 경로/include
3.      속성 > 링커
A.     '일반'의 추가 라이브러리 디렉터리 항목에 아래 경로 추가
                           i.         CUDA Toolkit 설치 경로/lib/Win32
                          ii.         64bit OS 라고 x64 폴더를 지정해도 빌드가 제대로 되지 않음.
B.      '입력'의 추가 종속성 항목에 OpenCL.lib 추가.

OpenCL 프로그래밍

기본적으로 병렬 덧셈 프로그램을 작성해 본다.
1.      소스 파일에 sample.cl 파일 추가 후 아래 코드 추가
__kernel void vecAdd(__global int *a, __global int *b, __global int *c) {
int id = get_global_id(0);
c[id] = a[id] + b[id];
}

2.      main cpp 파일에 아래 코드 추가
#include "stdafx.h"
#include
#include

#include

#define MEM_SIZE 128
#define MAX_SOURCE_SIZE 0x100000

int _tmain(int argc, _TCHAR* argv[])
{
          // 변수 선언
          cl_platform_id                     platform_id;
          cl_uint                               ret_num_platforms;
         
          cl_device_id                        device_id;
          cl_uint                               ret_num_devices;

          cl_context                          context;

          cl_command_queue             command_queue;

          cl_mem                             memA;
          cl_mem                             memB;
          cl_mem                             memC;

          char                                  *source_str;
          size_t                                 source_size;

          FILE                                   *fp;
          char                                  filename[]="./sample.cl";

          cl_program                         program;
          cl_kernel                            kernel;

          cl_int                                 ret;
          cl_int                                 err;
          size_t                                 size;

          int a[MEM_SIZE];
          int b[MEM_SIZE];
          int c[MEM_SIZE];
         

          // Platform ID 얻기
          ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);

          // Divice ID 얻기
          ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id, &ret_num_devices);

          // Context 생성. context Device 리소스를 관리함.
          context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);

          // Command Queue 생성. Command Queue를 통해 Device에 작업 시킴.
          command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
         
          // 계산할 변수에 값 할당
          for (int i = 0; i < MEM_SIZE; i++) {
                       a[i] = i;
                       b[i] = 4 * i;
          }

          // 계산에 사용되는 각 변수 메모리 할당
          memA = clCreateBuffer(context, CL_MEM_READ_WRITE, MEM_SIZE*sizeof(int), NULL, &ret);
          memB = clCreateBuffer(context, CL_MEM_READ_WRITE, MEM_SIZE*sizeof(int), NULL, &ret);
          memC = clCreateBuffer(context, CL_MEM_READ_WRITE, MEM_SIZE*sizeof(int), NULL, &ret);

          // kernel에 입력해야하는 변수를 command queue에 할당
          ret = clEnqueueWriteBuffer(command_queue, memA, CL_TRUE, 0, MEM_SIZE*sizeof(int), a, 0, NULL, NULL);
          ret = clEnqueueWriteBuffer(command_queue, memB, CL_TRUE, 0, MEM_SIZE*sizeof(int), b, 0, NULL, NULL);

          // kernel 함수 파일 읽어오기
          fp = fopen(filename, "r");
          if (!fp) {
                       printf("Not Kernel File. \n");
                       exit(1);
          }
          source_str = (char*)malloc(MAX_SOURCE_SIZE * sizeof(char)) ;
          source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp);
          fclose(fp);

          // kernel 함수 string 으로 부터 program 생성
          program = clCreateProgramWithSource(context, 1, (const char**)&source_str, (const size_t*)&source_size, &ret);

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

          // kernel 생성. 두번째 param kernel 함수명을 정확기 기입하여야함.
          kernel = clCreateKernel(program, "sample", &ret);

          // kernel 함수 param 지정
          ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&memA);
          ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&memB);
          ret = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*)&memC);

          // global item 수 설정. 128번의 연산을 수행할 것이므로 128 설정.
          size_t global_item_size = 128;
          // work_group 수 설정. global item 수에따른 work_group 분류로 global item 수에 나누어 떨어지는 수가 되어야함.
          size_t local_item_size = 1;

          // kernel queue 할당. 실제 계산 수행.
          ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, &global_item_size, &local_item_size, 0, NULL, NULL);

          // 결과값 읽어오기
          ret = clEnqueueReadBuffer(command_queue, memC, CL_TRUE, 0, MEM_SIZE*sizeof(int), c, 0, NULL, NULL);
         
          // 결과값 출력
          for (int i = 0; i < MEM_SIZE; i++) {
                       if (i%4 == 0 ) printf("\n");
                       printf("%d\t", c[i]);
          }

          // 메모리 해제
          ret = clFlush(command_queue);
          ret = clFinish(command_queue);
          ret = clReleaseKernel(kernel);
          ret = clReleaseProgram(program);
          ret = clReleaseMemObject(memA);
          ret = clReleaseMemObject(memB);
          ret = clReleaseMemObject(memC);
          ret = clReleaseCommandQueue(command_queue);
          ret = clReleaseContext(context);

          return 0;
}

3.      빌드
4.      디버깅하지 않고 시작
5.      결과 확인
128번의 덧셈 계산 병렬 계산.

참고

l  OpenCL 홈페이지
l  OpenCL 2.0 Spec
l  NVIDIA 예제 파일 다운로드
l  추천 도서
n  OpenCL Programing Guide - AAFTAB MUNSHI / Addison Wesley
n  OpenCL 프로그래밍 - 주식회사 Fixstars / 한빛미디어


댓글 없음:

댓글 쓰기