5. 곱셈을 하기 위해 여러 단계가 필요하고,
곱해야 하는 수가 많은 경우
ex) 1000개의 곱셈, 곱셈당 5개의 단계가 필요
Non-파이프라인: 5000 Cycle, 파이프라인: 1005 Cycle
a7*b7 a6*b6 a5*b5 a4*b4 a3*b3
a1
*b1
a2
*b2b8b7b6 b9
a8a7a6 a9
So multiplying a thousand pairs of numbers takes a whisker over a thousand
clock cycles, not the five thousand we might expect from the fact that multi-
plying a single pair takes five clock cycles.
Multiple ALUs
The component within a CPU that performs operations such as multiplication
is commonly known as the arithmetic logic unit, or ALU:
Operand 1 Operand 2
Day 1: GPGPU Programming • 191
파이프라이닝 예제:
두수의 곱
6. 여러개의 ALU와 넓은 메모리 버스를 연결하여,
거대한 데이터에 가하는 연산이 병렬화 됨
다중 ALU 예제:
두수의 곱
a1 a2 a3 a4
a5 a6 a7 a8
a9
a10
a11
a12
a13 a14 a15 a16
b1 b2 b3 b4
b5
b6
b7
b8
b9 b10 b11 b12
b13 b14 b15 b16
a9
a10
a11
a12
b9
b10
b11
b12
c1 c2 c3 c4
c5 c6 c7 c8
c9 c10 c11 c12
c13 c14 c15 c16
c9
c10
c11
c12
Figure 12—Large Amounts of Data Parallelized with Multiple ALUs
manufacturer then provides its own compilers and drivers that allow that
program to be compiled and run on its hardware.
Our First OpenCL Program
To parallelize our array multiplication task with OpenCL, we need to divide
it up into work-items that will then be executed in parallel.
Work-Items
If you’re used to writing parallel code, you will be used to worrying about the
Chapter 7. Data Parallelism • 192
8. Open CL 프로그램 작성 순서
1. 커널(작업 항목이 어떻게 처리되어야 하는지) 작성
2. 커널이 명령어 큐와 함께 실행되는 문맥 생성
3. 커널 컴파일
4. 입력과 출력 데이터를 위한 버퍼 생성
5. 커널을 한번씩 실행하는 명령어를 큐에 넣음
6. 결과를 로드
9. inputA inputB output
work-item 0
work-item 1
work-item 2
work-item 1023
Figure 13—Work Items for Pairwise Multiplication
Day 1: GPGPU Programmin
첫 번째 Open CL프로그램
(배열의 곱셈)
10. __kernel void multiply_arrays(__global const float* inputA,
__global const float* inputB,
__global float* output) {
int i = get_global_id(0);
output[i] = inputA[i] * inputB[i];
}
병렬 처리를 위한 커널
14. // 비교 샘플
for (int i = 0; i < NUM_ELEMENTS; ++i)
results[i] = a[i] * b[i];
CPU 코드와 성능 비교
저자의 맥북 프로에서 실행한 결과(100,000번의 곱셈)
GPU 프로그램: 43,000 ns
CPU 프로그램: 400,000 ns
16. 다차원 예제: 행렬 곱셈
// 순차적인 방식
#define WIDTH_OUTPUT WIDTH_B
#define HEIGHT_OUTPUT HEIGHT_A
float a[HEIGHT_A][WIDTH_A] = «initialize a»;
float b[HEIGHT_B][WIDTH_B] = «initialize b»;
float r[HEIGHT_OUTPUT][WIDTH_OUTPUT];
for (int j = 0; j < HEIGHT_OUTPUT; ++j) {
for (int i = 0; i < WIDTH_OUTPUT; ++i) {
float sum = 0.0;
for (int k = 0; k < WIDTH_A; ++k) {
sum += a[j][k] * b[k][i];
}
r[j][i] = sum;
}
}
17. __kernel void matrix_multiplication(uint widthA, __global const float* inputA,
__global const float* inputB, __global float* output) {
int i = get_global_id(0);
int j = get_global_id(1);
int outputWidth = get_global_size(0);
int outputHeight = get_global_size(1);
int widthB = outputWidth;
float total = 0.0;
for(int k=0; k < widthA; ++k){
total += inputA[j * widthA + k] * inputB[k * widthB + i];
}
output[j * outputWidth + i] = total;
}
병렬 행렬 처리를 위한 커널
18. // 작업항목 실행하기
size_t work_units[] = {WIDTH_OUTPUT, HEIGHT_OUTPUT};
clEnqueueNDRangeKernel(queue, kernel, 2/* 차원 */, NULL, work_units,
NULL, 0, NULL, NULL);
행렬 연산의 성능 비교
저자의 맥북 프로에서 실행한 결과(200x400 * 300x200)
GPU 프로그램이 순차 프로그램에 비해 20배 향상됨.
19. 플랫폼 모델
Host
Devices Compute Unit
Processing Element
Figure 14—The OpenCL Platform Model
Memory Model
A work-item executing a kernel has access to four different memory regions:
Chapter 7. Data Parallelism • 206
작업항목
- 프로세싱 요소에서 실행됨
작업그룹
- 작업 항목의 컬렉션
- 하나의 계산 유닛에서 실행됨
- 작업 항목들은 지역 메모리 공유
20. 메모리 모델
1. 전역 메모리: 장치내의 모든 작업 항목에서 접근 가능
2. 상수 메모리: 전역 메모리중 커널을 실행하는 동안 남아 있는 부분
3. 지역 메모리: 작업 그룹에 할당한 메모리
4. 사적 메모리: 하나의 작업 항목만 접근할수 있는 메모리
21. 데이터 병렬 축소 예제: min
// 순차적인 방식
cl_float acc = FLT_MAX;
for (int i = 0; i < NUM_VALUES; ++i)
acc = fmin(acc, values[i]);
22. // 단일 작업 그룹 축소
__kernel void find_minimum(__global const float* values, __global float* result,
__local float* scratch) {
int i = get_global_id(0);
int n = get_global_size(0);
scratch[i] = values[i];
barrier(CLK_LOCAL_MEM_FENCE);
for(int j=n/2; j>0; j/=2){ // 축소 실행
if (i < j)
scratch[i] = min(scratch[i], scratch[i + j]);
barrier(CLK_LOCAL_MEM_FENCE);
}
if(i==0)
*result = scratch[0];
}
// 실행
clSetKernelArg(kernel, 2, sizeof(cl_float) * NUM_VALUES, NULL);
1. It copies the array from global to local (scratch) memory
2. It performs the reduce (lines 8–12).
3. It copies the result to global memory (line 14).
The reduce operation proceeds by creating a reduce tree ver
one we saw when looking at Clojure’s reducers (see Divide a
page 67):
After each loop iteration, half the work-items become inact
items for which i < j is true perform any work (this is why we’r
the number of elements in the array is a power of two—so we
halve the array). The loop exits when only a single work-ite
Each active work-item performs a min() between its value and th
23. // 여러개의 작업 그룹 축소
__kernel void find_minimum(__global const float* values, __global float* results,
__local float* scratch) {
int i = get_local_id(0);
int n = get_local_size(0);
scratch[i] = values[get_global_id(0)];
barrier(CLK_LOCAL_MEM_FENCE);
for (int j = n / 2; j > 0; j /= 2) {
if (i < j)
scratch[i] = min(scratch[i], scratch[i + j]);
barrier(CLK_LOCAL_MEM_FENCE);
}
if (i == 0)
results[get_group_id(0)] = scratch[0];
}
// 실행
size_t work_units[] = {NUM_VALUES};
size_t workgroup_size[] = {WORKGROUP_SIZE};
clEnqueueNDRangeKernel(queue, kernel, 1, NULL, work_units, workgroup_size,
0, NULL, NULL));
Figure 15—Extending the Reduce across Multiple Work-Groups
group id 0 group id 1 group id 2 group id n
local id 0
global id 0
local size
global size
Figure 16—The Local ID with a Work-Group
Here’s a kernel that makes use of local IDs:
Chapter 7. Data Parallelism • 210
24. 정리
• 장점
• 커다란 용량의 데이터를 대상으로 수치 계산이 필요한 경우 이상적
• GPU는 강력한 병렬 프로세서인 동시에 전력 효율이 좋음
• 단점
• 수치가 아닌 문제에 적용이 가능하지만, 여러움
• OpenCL의 커널 최적화는 매우 어려움
• 호스트 프로그램과 장치 사이에 데이터 복사로 인해 시간을 많이 소요