나무모에 미러 (일반/어두운 화면)
최근 수정 시각 : 2026-08-25 07:01:28

OpenCL/문법


#!if top = 문서명1 != null ? 문서명1 : calleeTitle != null ? (i = calleeTitle.lastIndexOf("/")) != -1 ? calleeTitle.substr(0, i) : '상위 문서' : '상위 문서'
[[파일:상위 문서 아이콘.svg|align=left&width=21.6px]] {{{#!html  }}} 상위 문서: [[]]

1. 개요2. 코드 도메인
2.1. Platform 코드
2.1.1. cl.h와 opencl.hpp의 차이
2.2. 커널
2.2.1. 자료형2.2.2. 벡터 접근자2.2.3. 지정자 (qualifiers)2.2.4. 연산자2.2.5. 내장 함수
2.3. 메모리 모델

1. 개요

OpenCL은 C99를 바탕으로 한 확장 언어로, 문법은 C와 같으나, 병렬처리 시스템에 맞는 확장된 문법을 가지고 있다.

OpenCL 코드는 런타임을 초기화하는 Platform/Runtime 코드와 오프로드 장치에서 동작하는 커널로 이뤄져 있으며, 커널 코드를 동작시키기 위해서는 Platform 코드가 필요하다.

2. 코드 도메인

2.1. Platform 코드

Platform 코드는 장치를 초기화하거나 장치와 통신해 커널을 실행하기 위해 필수적인 런타임 기능을 가진다. 이 플랫폼은 ICD를 실행시키도록 되어 있으며 이를 통해 각 벤더가 자사의 디바이스에 맞는 장치를 관리할 수 있게 해 준다.

2.1.1. cl.h와 opencl.hpp의 차이

C++ 래핑 버전인 OpenCL.hpp 헤더 (구 cl2.hpp) 의 경우 C++ STL 컨테이너에 맞춰 일일히 수동으로 처리해 줘야 하는 플랫폼 관련 코드들의 배열을 자동으로 처리해 주므로 필요한 소스코드의 길이가 적다.

특히 CUDA나 oneAPI에 비해 OpenCL은 장치 초기화 단계가 꽤 긴 편 인데다, 기본인 C를 사용하면 이 처리를 온전히 직접 처리해야 하지만 C++ 레퍼 헤더 (OpenCL.hpp)를 사용하는 경우 보일러플레이트 코드가 획기적으로 줄어드므로 C가 꼭 필요한게 아니라면 C++ 쪽이 훨씬 간결하다.

다음 예시는 임의로 0번 플랫폼과 0번 디바이스를 사용하고 일부 메모리 할당과 예외 처리는 무시하였다.

#!syntax cpp
#include <CL/CL.h>
#include <stdlib.h>

int main()
{
  cl_int clError = CL_SUCCESS;

  // Platform Query
  cl_uint         numPlatforms = 0;
  cl_platform_id *platforms    = NULL;

  clError = clGetPlatformIDs(0, NULL, &numPlatforms);
  cl_platform_id platforms =
      (cl_platform_id *)malloc(sizeof(cl_platform_id) * numPlatforms);
  clGetPlatformIDs(numPlatforms, platforms, NULL);

  cl_platform_id platform = platforms[0];

  // Device Query
  cl_device_id *devices    = NULL;
  cl_uint       numDevices = 0;

  clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 0, NULL, &numDevices);
  cl_device_id = (cl_device_id *)malloc(sizeof(cl_device_id) * numDevices);

  clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, numDevices, devices, NULL);

  cl_device_id device = devices[0];

  char deviceName[128];

  clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(deviceName)-1, deviceName, NULL);

  deviceName;
}




#!syntax cpp
#include <CL/OpenCL.hpp>
#include <vector>
#include <string>

int main()
{
    // Platform Query
    std::vector<cl::Platform> platforms;
    cl::Platform::get(&platforms);
    
    cl::Platform platform = platforms[0];

    // Device Query
    std::vector<cl::Device> devices;

    platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);
    cl::Device device = devices[0];

    std::string deviceName = device.getInfo<CL_DEVICE_NAME>();
    deviceName;
  // ...
}


2.2. 커널

커널은 하드웨어에서 실제로 수행되는 코드이다. C99를 바탕으로 된 문법을 가지며 __kernel 또는 kernel attribute 선언을 통해 호스트에서 진입할 수 있는 함수를 만들 수 있다. 그 외 코드는 디바이스 내부에서만 접근이 가능하다.

#!syntax cpp
__kernel void
arrAdd(global const int *src1, global const int *src2, global int *dest)
{
  const uint index = get_global_id(0);
  dest[index]      = src1[index] + src2[index];
  return;
}

2.2.1. 자료형

기본 자료형은 다음을 지원한다.
스칼라
bool, char, unsigned char, uchar, short, unsigned short, ushort, int, unsigned int, uint, long, unsigned long, ulong, float, double, half, size_t, ptrdiff_t, intptr_t, uintptr_t, void
벡터
char<>, uchar<>, short<>, ushort<>, int<>, uint<>, long<>, ulong<>, half<>, float<>, double<>
이미지형
image2d_t, image3d_t, image2d_array_t, image1d_t, image1d_buffer_t, image1d_array_t, image2d_depth_t, image2d_array_depth_t, sampler_t, queue_t, ndrange_t, clk_event_t, reserve_id_t, event_t, cl_mem_fence_flags, image2d_msaa_t, image2d_array_msaa_t, image2d_msaa_depth_t, image2d_array_msaa_depth_t
예약
bool<>, quad, quad<>, complex/imaginary (half/float/double/quad)(<>), float<>x<>, double<>x<>, long double(<>), long long(<>), (unsigned long) (long/ulong)(<>)

장치와 호스트간 엄격한 자료 교환이 필요한 경우 추가 자료형을 지원한다. 예를 들어 커널 코드에서 half 자료형을 사용하는 경우, 호스트 코드 (API)에서는 cl_half로 앞에 cl_ prefix를 붙이면 된다. 예외로 bool, size_t는 동치형이 없으며 ptrdiff_t와 같은 포인터 주소형도 지원하지 않는다.

half 반정밀도의 경우 IEEE 754 표준을 준수하므로 1S5E10M 구조를 가진다.

벡터형의 경우 연속된 레인에 값이 패킹되어 저장된다. 스칼라형 뒤의 <>에는 길이를 붙이면 되며 예를 들어 char4는 4개의 char를 하나로 다룰 수 있다.
벡터 크기는 2,3,4,8,16 이 지원되며, 그 외는 지원하지 않는다.

이미지형의 경우 메모리 어레이를 일종의 추상화를 통해 접근하는 방식으로 호스트에서는 전부 cl_mem으로 접근한다.

스칼라형은 벡터형으로 캐스팅할 수 있다.
#!syntax cpp
(float4)(float, float, float, float); // 4개의 float을 1개의 float4 형으로
(float4)(float2, float, float); // 한개의 float2 벡터와 두개의 float 스칼라를 float 4 형으로
(float4)(float); // 한개의 float을 float4 로 브로드캐스팅


단 벡터는 자동으로 브로드캐스팅 되지 않으므로 (float4)(float2)는 오류다.

OpenCL C 는 대체로 묵시적 형변환을 인정하므로 예를 들어 float에 int를 대입하는 동작은 허용된다.

벡터와 스칼라간 연산 또한 지원하며 이에 따라
#!syntax cpp
float4 v = (float4)u + (float)f
는 다음과 같다.
#!latex
\begin{bmatrix} v_x \\ v_y \\ v_z \\ v_w \end{bmatrix} = \begin{bmatrix} u_x \\ u_y \\ u_z \\ u_w \end{bmatrix} + \begin{bmatrix} f \\ f \\ f \\ f \end{bmatrix}


당연하지만 벡터와 벡터 연산도 가능하다. 이에 따라 #!syntax cpp (float4)w = (float4)v + (float4)u는 다음과 같다.
#!latex
\begin{bmatrix} w.x \\ w.y \\ w.z \\ w.w \end{bmatrix} = \begin{bmatrix} v.x \\ v.y \\ v.z \\ v.w \end{bmatrix} + \begin{bmatrix} u.x \\ u.y \\ u.z \\ u.w \end{bmatrix} = \begin{bmatrix} v.x + u.x \\ v.y + u.y \\ v.z + u.z \\ v.w + u.w \end{bmatrix}

2.2.2. 벡터 접근자

벡터로 선언된 변수의 원소에 접근하는 경우 다음의 인덱스를 통해 접근하며 Swizzle과 shuffle 을 사용할 수 있다.

xyzw, rgba(OpenCL C 3.0+), 0-9,a-f,A-F, lo, hi, odd, even

#!syntax cpp
float4 myFloat4 = (float4)(0.1f, 0.2f, 0.3f, 0.4f);
myFloat4.x; // 0.1f
myFloat4.y; // 0.2f

myFloat4.xy; // (0.1f, 0.2f) -> float2;
myFloat4.zwz; // (0.3f, 0.4f, 0.3f) -> float3;

myFloat4.lo; // (0.1f, 0.2f) -> float2;
myFloat4.hi; // (0.3f, 0.4f) -> float2;

myFloat4.even; // myFloat4.xz - > float2;
myFloat4.odd; // myFloat4.yw -> float2;

myFloat4.0123; // myFloat4.xyzw -> float4;

단 이렇게 접근하는 경우 주소(레퍼런스)는 오류다.
#!syntax cpp &myFloat4.x;

2.2.3. 지정자 (qualifiers)

주소
global, __global, __local, local, __constant, constant, __private, private, __generic, generic [1]
함수
__kernel, kernel
접근
__read_only read_only, __write_only, write_only, __read_write, read_write
기타
uniform, pipe

2.2.4. 연산자

+ - * & / << >> <<= >>= ? : < > = -- ++ , .

2.2.5. 내장 함수

2.3. 메모리 모델

호스트는 호스트, 디바이스는 디바이스로 구분되어 있으며 이 장치 경계간 값에 접근하려 하는 경우 복사가 필요하다. 보통 DMA를 통해 값을 바로 읽어오지만 내부적으로 복사가 일어나므로 오버헤드가 있으며 이에 따라 메모리 핀을 통한 접근이 가능하다. 이 경우 드라이버 계층 없이 DMA로 바로 접근하므로 더 빠르지만 당연하게도 Evicit이 불가능하다.
[1] generic은 예약되었으나 표준상 아무런 정의가 되어 있지 않다[2] 본래 이 유닛들은 그래픽스를 처리할 때 그래픽카드에 내장된 전용 특수함수 유닛 (SFU)를 사용하여 계산한다. 그래픽의 특성상 정밀도가 크게 중요하지 않기 때문에 이런 함수들을 쓰게 되는데 OpenCL과 일반 셰이더 기반 프로그램의 차이로 OpenCL은 이런 연산의 정확함을 중시하는 반면, 셰이더는 대충 맞고 빠르게 돌아가는걸 중점으로 두다 보니 이런 차이가 생기게 됐다.