후로링의 프로그래밍 이야기

#2 OpenCL 튜토리얼. OpenCL 인터페이스의 생성. 본문

OpenCL

#2 OpenCL 튜토리얼. OpenCL 인터페이스의 생성.

후로링 2016. 10. 26. 00:45




 OpenCL은 인터페이스입니다. 다시 말해 CPU와 GPU사이를 오가며 작업을 처리 해 줄 수 있게 도와주는 역활을 해줍니다. 아래 그림의 빨간색 선에 해당하는 것이 바로 OpenCL 인터페이스 입니다. 




GPU를 사용하기위해서는 platform, device 포인터와 device를 이용해서 만드는 context 포인터 정보를 가지고 있어야 합니다.  그리고 device와 context를 이용해 실제 CL코드를 실행하는 queue에 대한 포인터를 를 생성해야 합니다. 


각각이 무엇인지 확실히 이해를 하고 넘어가야 OpenCL이 실행되는 방식을 이해 할 수 있습니다. 


Platform

 만약 컴퓨터에 Interl CPU와  AMD GPU가 설치되어 있다면 선택 가능한 platform은 intel, amd입니다. 다시말해 platform이란 opencl서비스를 제공할 수 있는 환경을 말합니다. 플랫폼이 여러개 존재한다면 플랫폼을 한가지 선택해서 동작 시킬 수 있습니다. platform은 여러개 받을 수 있으며, 이렇게 처리한다면 이기종 컴퓨팅환경에서 다른 플랫폼의 디바이스 여러개를 동시에 사용 하는 것이 가능합니다. 


 OpenCL에서 Platform을 가져오는 과정은 다음과 같습니다. 

 현재 컴퓨터에 얼마나 많은 플랫폼이 있는지를 먼저 알아와야 합니다. 이후 플랫폼의 개수만큼 메모리를 할당하고 플랫폼을 얻어오게 됩니다. 


1
2
3
4
// get all platforms
clGetPlatformIDs(0NULL&platformCount);
platforms = (cl_platform_id*)malloc(sizeof(cl_platform_id) * platformCount);
clGetPlatformIDs(platformCount, platforms, NULL);
cs


clGetPlatformIDs()를 이용해 플랫폼의 개수를 먼저 받아온 후에 platforms공간을 할당하고, 다시 clGetPlatformIDs()를 통해 플랫폼을 받아오게 됩니다. 


Device

 디바이스란 계산을 수행할수있는 유닛의 집합입니다. GPU로 설명하면 GPU내의 수많은 코어들의 집합이라고 이해하시면 될 것 같습니다. 플랫폼 내의 디바이스를 찾아 실제로 연산을 진행하게 됩니다. 

 

1
2
3
4
// get all devices
clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0NULL&deviceCount);
devices = (cl_device_id*)malloc(sizeof(cl_device_id) * deviceCount);
clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, deviceCount, devices, NULL);
cs

platform을 받아오는 과정과 동일합니다. 

Context
 CL 커널이 실행되는 환경으로, 동기화와 메모리 관리가 정의됩니다. OpenCL디바이스에서 실행할 OpenCL함수들을 포함하고 있습니다. clCreateContext()함수를 이용해 생성합니다. 
1
2
//create context
context = clCreateContext(NULL1&device, NULLNULL,    NULL);
cs

Queue
 호스트에서 디바이스 별로 생성되는 것으로 하나의 디바이스에서 여러개의 커맨드 큐가 연결 가능합니다. 커맨드 큐를 이용헤 커널을 실행하고 매모리의 매핑과 언매핑, 동기화 등을 할 수 있습니다. 

1
2
//create command queue
queue = clCreateCommandQueue(context, device, 0NULL);
cs

Program

 program이란 kernel의 집합이라고 보시면 됩니다. 간단하게 말해 프로그램 소스 혹은 빌드된 바이너리.

1
2
3
4
5
// compile program
program = clCreateProgramWithSource(context, 1, (const char**)&source, NULLNULL);
cl_int build_status;
build_status = clBuildProgram(program, 1&device, NULLNULL,
    NULL);
cs

kernel
 아래 코드는 kernel의 집합인 program 내에서 특정 커널을 선택해 커널을 실행 할 수있도록 커널을 만들고 커널의 포인터를 반환합니다. 
1
2
// 프로그램으로부터 커널 생성
simpleKernel = clCreateKernel(program, "simpleKernel"NULL);
cs

CLInit() 함수 전체 코드
 CLInit()함수는 위의 과정 순차적으로 실행하여 GPU를 사용할 준비를 하는 함수입니다. 17, 25라인의 for문은 플랫폼이 여러개이고 각 플랫폼에 있는 디바이스가 여러개일때 알맞은 플랫폼과 디바이스를 선택 할 수 있도록 한 부분입니다. 
 아래 Visual Studio 프로젝트를 다운로드 받아 실행 해 보면서 보시면 됩니다. 

OpenCL_Tutorial1_SumOfArray.zip


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void CLInit()
{
    int i, j;
    char* value;
    size_t valueSize;
    cl_uint platformCount;
    cl_platform_id* platforms;
    cl_uint deviceCount;
    cl_device_id* devices;
    cl_uint maxComputeUnits;
 
    // get all platforms
    clGetPlatformIDs(0NULL&platformCount);
    platforms = (cl_platform_id*)malloc(sizeof(cl_platform_id) * platformCount);
    clGetPlatformIDs(platformCount, platforms, NULL);
 
    for (i = 0; i < platformCount; i++) {
 
        // get all devices
        clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0NULL&deviceCount);
        devices = (cl_device_id*)malloc(sizeof(cl_device_id) * deviceCount);
        clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, deviceCount, devices, NULL);
 
        // for each device print critical attributes
        for (j = 0; j < deviceCount; j++) {
 
            // print device name
            clGetDeviceInfo(devices[j], CL_DEVICE_NAME, 0NULL&valueSize);
            value = (char*)malloc(valueSize);
            clGetDeviceInfo(devices[j], CL_DEVICE_NAME, valueSize, value, NULL);
            printf("platform %d. Device %d: %s\n", i + 1, j + 1, value);
            free(value);
 
            // print hardware device version
            clGetDeviceInfo(devices[j], CL_DEVICE_VERSION, 0NULL&valueSize);
            value = (char*)malloc(valueSize);
            clGetDeviceInfo(devices[j], CL_DEVICE_VERSION, valueSize, value, NULL);
            printf(" %d.%d Hardware version: %s\n", i + 11, value);
            free(value);
 
            // print software driver version
            clGetDeviceInfo(devices[j], CL_DRIVER_VERSION, 0NULL&valueSize);
            value = (char*)malloc(valueSize);
            clGetDeviceInfo(devices[j], CL_DRIVER_VERSION, valueSize, value, NULL);
            printf(" %d.%d Software version: %s\n", i + 12, value);
            free(value);
 
            // print c version supported by compiler for device
            clGetDeviceInfo(devices[j], CL_DEVICE_OPENCL_C_VERSION, 0NULL&valueSize);
            value = (char*)malloc(valueSize);
            clGetDeviceInfo(devices[j], CL_DEVICE_OPENCL_C_VERSION, valueSize, value, NULL);
            printf(" %d.%d OpenCL C version: %s\n", i + 13, value);
            free(value);
 
            // print parallel compute units
            clGetDeviceInfo(devices[j], CL_DEVICE_MAX_COMPUTE_UNITS,
                sizeof(maxComputeUnits), &maxComputeUnits, NULL);
            printf(" %d.%d Parallel compute units: %d\n", i + 14, maxComputeUnits);
        }
    }
 
    int platformNum;
    int deviceNum;
 
    printf("\n\n SELECT PLATFORM('1' ~ '%d') : ", platformCount);
    scanf("%d"&platformNum);
    printf("\n");
 
    printf("SELECT DEVICE('1' ~ '%d') : ", deviceCount);
    scanf("%d"&deviceNum);
    printf("\n");
 
    clGetDeviceIDs(platforms[platformNum - 1], CL_DEVICE_TYPE_ALL, deviceCount, devices, NULL);
 
    device = devices[deviceNum - 1];
 
    // context 생성
    context = clCreateContext(NULL1&device, NULLNULL,    NULL);
 
    //create command queue
    queue = clCreateCommandQueue(context, device, 0NULL);
 
    // 텍스트파일로부터 프로그램 읽기
    char* source = readSource("convolution.cl");
 
    // 프로그램 컴파일 및 빌드
    program = clCreateProgramWithSource(context, 1,
        (const char**)&source, NULLNULL);
    cl_int build_status;
    build_status = clBuildProgram(program, 1&device, NULLNULL,
        NULL);
 
    //프로그램으로부터 커널 포인터 생성
    simpleKernel = clCreateKernel(program, "simpleKernel"NULL);
 
}
cs


이해를 돕기위해 실행 화면을 캡쳐합니다. 
 제 컴퓨터는 i7 6세대 CPU이며 GPU쪽 플랫폼과 CPU쪽 플랫폼 두개가 잡힙니다. GPU쪽 플랫폼인 플랫폼 1에서 내장 GPU인 디바이스 1을 선택하면 제대로 OpenCL이 실행됩니다. OpenCL은 CPU에서도 CPU코어로 병렬 처리가 가능하기때문에 여러개의 선택 옵션을 가지고 있습니다. 위 코드는 직접 선택이 가능하도록 하였습니다. clGetDeviceIDs()의 두번째 인자에서 GPU 디바이스만 선택 한다던지 하는 옵션의 선택이 가능합니다. 궁금하신 분들은 도큐먼트를 참고하시면 될 것 같습니다.
 


다음 포스팅은 호스트에서 커널을 실행하는 방법과 커널 코드 작성법 입니다. 


Comments