Sự ra đời của bộ xử lý đa lõi CPU và đa nhân GPU được hiểu rằng nhiều chíp xử lý cùng thực hiện trong các hệ thống song song. Xa hơn nữa lý thuyết về song song của chúng ta là luật của Moore. Những thách thức đặt ra để dẫn tới việc phát triển các phần mềm ứng dụng là trong suốt sự song song trong tiến trình chạy lệnh ảnh hưởng bởi sự gia tăng số lượng lõi của bộ xử lý, rất cần thiết và quan trọng trong việc xử lý các lệnh của ứng dụng đồ hoạ 3D. CUDA là một ngôn ngữ lập trình và môi trường phần mềm thiết kế khắc phục được những khó khăn trong việc duy trì thời lượng học ngắn của lập trình viên và thân thiện với các ngôn ngữ lập trình chuẩn như C. Về bản chất , nó có 3 khái niệm quan trọng Phân cấp của các nhóm thread Phia sẻ bộ nhớ Đồng bộ hoá phân cách Nó thật đơn giản để người lập trình tìm hiểu cũng như cài đặt tối thiểu của sự mở rộng sang C.
Trang 1Mục lục
1 PHẦN 1 - CUDA : NGÔN NGỮ LẬP TRÌNH SONG SONG 3
1.1 Giới thiệu chung 3
1.2 GPU: Bộ xử lý nhiều lõi độ song song cao đa thread 3
2 CHƯƠNG 2 - MÔ HÌNH LẬP TRÌNH 6
2.1 Mô hình lập trình CUDA 6
2.2 Phân cấp bộ nhớ 8
2.3 Ngăn xếp phần mềm 10
3 CHƯƠNG 3 - GIAO DIỆN LẬP TRÌNH 12
3.1 Một mở rộng của ngôn ngữ C 12
3.2 Những mở rộng của ngôn ngữ 12
3.3 Các loại hạn định của hàm 12
3.3.1 Chỉ thị device 12
3.3.2 Chỉ thị global 13
3.3.3 Chỉ thị host 13
3.3.4 Những hạn chế 13
3.4 Các loại hạn định của biến 13
3.4.1 Chỉ thị device 13
3.4.2 Chỉ thị constant 14
3.4.3 Chỉ thị share 14
3.4.4 Những hạn chế 15
3.5 Cấu hình thực thi 16
3.6 Những biến số định nghĩa sẵn 16
3.6.1 gridDim 16
3.7 blockIdx 16
3.7.1 blockDim 17
3.7.2 threadIdx 17
3.7.3 warpSize 17
3.7.4 Các hạn chế 17
3.8 Biên dịch với NVCC 17
3.8.1 noinline 18
3.8.2 #pragma unroll 18
Trang 24 Các thành phần thực thi chung 18
4.1 Các kiểu Vector được dựng sẵn 18
4.1.1 char1, uchar1, char2, uchar2, char3, uchar3, char4, uchar4, short1, ushort1, short2, ushort2, short3, ushort3, short4, ushort4, int1, uint1, int2, uint2, int3, uint3, int4, uint4, long1, ulong1, long2, ulong2, long3, ulong3, long4, ulong4, float1, float2, float3, float4, double2 18
4.1.2 Kiểu dữ liệu dim3 19
4.2 Các hàm toán học 19
4.3 Hàm thời gian 19
4.4 Kiểu Texture 19
4.4.1 Khai báo Tham chiếu texture 20
4.4.2 Tính chất của của Tham chiếu texture 20
4.4.3 Texure từ bộ nhớ tuyến tính qua mảng của CUDA 20
5 Các thành phần chạy trên thiết bị tính toán 20
5.1 Các hàm toán học 21
5.2 Hàm đồng bộ 21
5.3 Hàm Texture 21
5.3.1 Texture từ bộ nhớ tuyến tính 21
5.3.2 Texture từ các mảng CUDA 22
5.4 Các hàm phần tử 23
6 Các thành phần trên máy chủ 23
Tài liệu tham khảo 24
Trang 31 PHẦN 1 - CUDA : NGÔN NGỮ LẬP TRÌNH SONG
SONG
1.1 Giới thiệu chung
Sự ra đời của bộ xử lý đa lõi CPU và đa nhân GPU được hiểu rằng nhiều chíp xử
lý cùng thực hiện trong các hệ thống song song Xa hơn nữa lý thuyết về songsong của chúng ta là luật của Moore Những thách thức đặt ra để dẫn tới việc pháttriển các phần mềm ứng dụng là trong suốt sự song song trong tiến trình chạy lệnhảnh hưởng bởi sự gia tăng số lượng lõi của bộ xử lý, rất cần thiết và quan trọngtrong việc xử lý các lệnh của ứng dụng đồ hoạ 3D
CUDA là một ngôn ngữ lập trình và môi trường phần mềm thiết kế khắc phụcđược những khó khăn trong việc duy trì thời lượng học ngắn của lập trình viên vàthân thiện với các ngôn ngữ lập trình chuẩn như C
Về bản chất , nó có 3 khái niệm quan trọng
- Phân cấp của các nhóm thread
- Phia sẻ bộ nhớ
- Đồng bộ hoá phân cách
Nó thật đơn giản để người lập trình tìm hiểu cũng như cài đặt tối thiểu của sự mởrộng sang C
Những khái niệm ở đây cung cấp về lý thuyết lập trình song song dữ liệu nhỏ sạch
và lý thuyết lập trình song song thread, cùng với lý thuyết lập trình song song trên
dữ liệu nhỏ thô và lập trình song song nhiệm vụ một chương trình viết bằngCUDA được dịch có thể thực hiện trên nhiều lõi xử lý và chỉ chạy trong cùng mộtthời gian
1.2 GPU: Bộ xử lý nhiều lõi độ song song cao đa thread
Được xây dựng do yêu cầu của thị trường, đồ hoạ 3D phân giải cao, GPU có thểlập trình phát triển thành bộ xử lý nhiều lõi song song cao và đa thread với tốc độtính toán cực cao và băng thông nhớ rất rộng , được minh hoạ trong hình vẽ dướiđây:
Trang 4Tốc độ tính toán trên giây và băng thông nhớ rộng của CPU và GPU
Nguyên nhân của sự xung đột giữa CPU và GPU là GPU được thiết kế đặc biệtvới khả năng tính toán mạnh, tính toán song song cao - cụ thể là do yêu cầu rendercủa đồ hoạ - và do đó nó được thiết kế nhiều transistor hơn để sử dụng cho việc xử
lý dữ liệu trên cache và điều khiển dòng
Trang 5GPU cần nhiều Transistor cho quá trình xử lý dữ liệu hơn
Đặc biệt hơn, GPU rất thích hợp với các vấn đề liên quan tới địa chỉ có thể đượcthể hiện trong tính toán dữ liệu song song – các chương trình giống nhau đượcthực hiện một cách song song trên nhiều bộ dữ liệu.- với độ tính toán mạnh - tỉ lệgiữa độ phức tạp tính toán và bộ nhớ cho các phép tính Bởi những chương trìnhgiống nhau được thực hiện cho mỗi bộ dữ liệu, có những yêu cầu thấp hơn choviệc điều khiển dòng phức tạp; và còn bởi vì nó được thực hiện trên nhiều dữ liệu
và độ mạnh tính toán cao , bộ nhớ truy nhập ẩn có thể bị che bởi các phép tínhthay vì một cache dữ liệu thật lớn
Xử lý song song dữ liệu nối các phần tử dữ liệu voíư các thread xử lý song song Nhiều ứng dụng xử lý lương lớn tập dữ liệu có thể dùng mô hình lập trình songsong để tăng tốc độ tính toán Trong việc render đồ hoạ 3D, một tập các điểm vàđường thẳng đứng được nối với các thread song song
CUDA là một ngôn ngữ lập trình rất thích hợp với khả năng tính toán song songcủa GPU Phát triển cuối cùng về GPU của NVIDIA dựa trên sơ sở kiến trúcTesla Nó hỗ trợ mô hình lập trình CUDA và khả năng tăng tốc các ứng dụng viếttrên CUDA
Trang 62 CHƯƠNG 2 - MÔ HÌNH LẬP TRÌNH
2.1 Mô hình lập trình CUDA
CUDA mở rộng C bằng việc cho phép người lập trình định nghĩa các hàm của C,được gọi Kernels, khi được gọi nó được thực hiện N lần song song bởi N threadCUDA khác nhau
Một kernel được định nghĩa bằng việc dùng _global_ để khai báo và số lượngthread cho mỗi lời gọi được chỉ ra bằng việc sử dụng cú pháp <<<…>>>
ma trận A và B kích thước NxN và kết quả được lưu và ma trận C :
global void matAdd(float A[N][N], float B[N][N],float C[N][N])
{
int i = threadIdx.x;
Trang 7Thread bên trong block có thể kết hợp với nhau bằng việc chia sẻ dữ liệu thôngqua vài vùng nhớ chia sẻ và đồng bộ hoá việc thực hiện chúng theo điều kiện truynhập vùng nhớ Rõ ràng hơn , có thể chỉ ra điểm đồng bộ trên kernel bằng việc gọi_syncthreads() trong hàm.
Tuy nhiên kernel có thể được thực hiện bởi nhiều khối thread được phân chia bằngnhau, do đó tổng số thread bằng với số thread trên một khối nhân với số lượngkhối Các khối đa chiều thể hiện ở hình dưới đây Chiều của lưới được xác địnhbởi tham số cú pháp <<<…>>> Mỗi khối trong lưới được chỉ ra là 1 chiều 2chiều có thể truy cập trong kernel thông qua biến blockIdx chiều của khối threadđược truy cập trong kernel qua biến blockDim
global void matAdd(float A[N][N], float B[N][N],float C[N][N])
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
Trang 8Lưới khối thread
2.2 Phân cấp bộ nhớ
Thread CUDA có thể truy cập dữ liệu từ nhiều không gian nhớ trong suốt quátrình thực hiện của nó như ví dụ minh hoạ trên Mỗi thread có một vùng nhớ nội
bộ riêng Mỗi khối thread có vùng nhớ chia sẻ chúng với tất cả thread thuộc block
đó và với các block trong cùng thời gian đó Tóm lại đó là tất cả các thread có thểtruy cập tới một vùng nhớ chung gọi là vùng nhớ cục bộ
Có những không gian nhớ chỉ có thể đọc bởi tất cả các thread: vùng nhớ không đổi
và vùng nhớ kết cấu Cả hai loại này được tối ưu hoá từ các vùng nhớ được sử
Trang 9dụng khác nhau (xem trong các chương sau) vùng nhớ kết cấu cho phép các modeđịa chỉ khác nhau cũng như các cách lọc dữ liệu cho một số định dạng dữ liệu đặcbiệt.
Phân cấp bộ nhớ
Host và thiết bị
Các thread của CUDA được thực hiện trên các thiết bị vật lý cái mà thực thi nhưmột bộ xử lý trên host chạy bằng chương trình C Ví dụ một trường hợp, khikernel thực hiện trên GPU và phần còn lại chương trình C được thực hiện trênCPU
Trang 102.3 Ngăn xếp phần mềm
Ngăn xếp phần mềm CUDA gồm các lớp như hình dưới đây Driver của thiết bị,giao diện ứng dụng của chương trình(API) thời gian chạy và hai thư viện toán họcmức cao CUFFT và CUBLAS cả hai đều được mô tả trong văn bản chung
Trang 11Khối lượng tính toán
Trang 123 CHƯƠNG 3 - GIAO DIỆN LẬP TRÌNH
3.1 Một mở rộng của ngôn ngữ C
Mục tiêu của giao diện lập trình CUDA là cũng cấp một mối liên hệ đơn giản chongười đã sử dụng quen thuộc ngôn ngữ C và dễ dàng viết các chương trình chạytrên các thiết bị Giao diện lập trình bào gồm:
− Một số các mở rộng từ ngôn ngữ lập trình C cho phép lập trình viên thựchiện một phần của chương trình trên thiết bị tính toán
− Một thư viện được chia ra thành các thành phần:
− Một thành phần chủ: chạy trên máy chủ và cung cấp các chức năng đểtruy cập tới một hoặc nhiều thiết bị trên máy chủ
− Một thành phần thiết bị, chạy trên thiết bị và cũng cấp các chức năngđặc trưng của thiết bị
− Một thành phần chung cung cấp các kiểu dữ liệu vector và các tập controng thư viện C chuẩn được hỗ trợ trên máy chủ và thiết bị
Ta phải nhấn mạnh ở đây rằng các hàm trong thư viện chuẩn C được hỗ trợ vàchạy trên thiết bị phải được cung cấp bởi thành phần chung
3.2 Những mở rộng của ngôn ngữ
Những mở rộng từ ngôn ngữ C được thể hiện tại 4 điểm sau:
− Các hàm được xác định rõ rằng hàm đó sẽ được chạy trên máy chủ haychạy trên thiết bị tính toán và các hàm đó có thể được gọi từ máy chủ haygọi từ thiết bị tính toán
− Giới hạn các biến được xác định trong bộ nhớ của thiết bị hay của máy chủ
− Một chỉ thị mới cho biết phần nhân được thực thi trên máy chủ hay trênthiết bị tính toán
− Bốn biến xây dựng sẵn chỉ rõ kích thước lưới và khối, chỉ thị khối và luồngtiến trình
Mỗi mở rộng này đều có những hạn chế riêng và sẽ được trình bày trong phần tiếptheo trình biên dịch ncvv có thể đưa ra các lỗi hoặc các cảnh báo về những hạnchế này, song tồn tại một số lỗi không thể phát hiện ra
Trang 13− Chỉ có thể gọi từ thiết bị tính toán
3.3.2 Chỉ thị global
Chỉ thị global định nghĩa rằng hàm là một nhân thực thi Hàm có thể
− Chạy trên thiết bị tính toán
Tuy nhiên, chỉ thị host có thể được khai báo kèm với device trongtrường hợp hàm được dịch để chạy cả trên máy chủ và thiết bị tính toán
3.3.4 Những hạn chế
Các hàm với chỉ thị device và global không hỗ trợ đệ quy
Các hàm với chỉ thị device và global không thể định nghĩa các biến tĩnhtrong hàm đó
Các hàm với chỉ thị device và global không thể có số các tham số thayđổi trong khai báo hàm
Các hàm với chỉ thị device không thể lấy được địa chỉ của hàm này Con trỏhàm trỏ tới các hàm global
Chỉ thị global và host không thể sử dụng đồng thời
Chỉ thị global phải có kiểu trả về là void
Mọi lời gọi đến hàm global phải có các cài đặt để thực thi riêng
Một lời gọi đến hàm global phải là không đồng bộ, có nghĩa phải trả về trướckhi thiết bị tính toán hoàn tất công việc
Các tham số của hàm global được truyền qua bộ nhớ chia sẻ (share memory)
tớ các thiết bị tính toán và giới hạn trong 256 bytes
3.4 Các loại hạn định của biến
3.4.1 Chỉ thị device
Chỉ thị device _ định nghĩa một biến số có thể thay đổi kích thước trên thiết bịtính toán
Trang 14Phần lớn các biến được khai báo với chỉ thị device để xác định rõ ràng hơnvùng bộ nhớ mà biến được cấp phát Biến đó có thể:
− Chứa trong trong vùng nhớ tổng thể
− Có thời gian sống xác định trong một ứng dụng
− Có thể truy cập được từ tất cả các luồng thực thi trong lưới và từ máy chủqua các hàm thư viện
3.4.2 Chỉ thị constant
Chỉ thị constant , có thể sử dụng chung với chỉ thị device , định nghĩa mộtbiến số có thể:
− Chứa trong vùng nhớ hằng số
− Có thời gian sống xác định trong một ứng dụng
− Có thể truy cập được từ tất cả các luồng thực thi trong lưới và từ máy chủqua các hàm thư viện
3.4.3 Chỉ thị share
Chỉ thị share , có thể sử dụng cùng với device , định nghĩa một biến số:
− Chứa trong vùng nhớ chia sẻ (share memory) của khối luồng thực thi
− Có thời gian sống trong khối
− Chỉ có thể truy cập từ tất cả các luồng trong lưới và từ máy chủ qua cáchàm thư viện
Chỉ sau khi gọi lệnh syscthreads() được viết tới vùng nhớ chia sẻ mới đảm bảobiến số được nhìn thấy bởi các luồng khác Khi biến số được định nghĩa là tự dothì trình biên dịch mới có thể tối ưu được việc đọc và ghi lên vùng nhớ chia sẻ vớiđiều kiện các trạng thái trước đó đúng
Khi định nghĩa một biến số trong vùng nhớ chia sẻ bằng một mảng ngoài như sau:extern shared float shared[];
thì kích thước của mảng được xác định vào thời điểm chạy Tất cả các biến sốđược khai báo theo cách này, bắt đầu bằng một địa chỉ bộ nhớ giống nhau, vì thếviệc bố trí các biến trong mảng phải được quản lý một cách rõ ràng thông qua độlệch Ví dụ nếu muốn khai báo tương đương như sau:
Trang 15device void func() // hàm có thể là device hoặc global
{
short* array0 = (short *)array;
float* array1 = (float *)&array0[128];
int* array2 = (int*)&array1[64];
shared và constant mặc nhiên được lưu trữ tĩnh
device , share và constant không thể khai báo là biến ngoài với từkhóa extern
device và constant chỉ cho phép trong phạm vi tệp
Các biến constant không thể gán tới thiết bị tính toán mà chỉ tại máy chủ tạicác hàm thực thi
Các biến shared không thể khởi tạo trong khi khai báo
Một biến số được khai báo trên thiết bị tính toán mà không có các chỉ thị trên thìthông thường sẽ được lưu trữ tại các thành ghi Tuy nhiên trong một số trườnghợp, trình biên dịch có thể chọn nới đặt biến này trong bộ nhớ Đó là khi cấu trúc
dữ liệu quá lớn hoặc cá mảng chiếm quá nhiều không gian thanh ghi và các mảng
mà trình biên dịch không thể xác định được kích thước khi biên dịch Kiểm trađoạn mã assembly (nhận được khi biên dịch với tham số -ptx hoặc -keep) ta có thểthấy các biến được đặt trong bộ nhớ cục bộ trong lần biên dịch đầu tiên khi các
biến này sử dụng local và khi truy cập sử dụng ld.local và st.local Nếu không
xác định, bước biên dịch kế tiếp vẫn phải quyết định nếu nó thấy rằng cấu trúc dữliệu đó tốn quá nhiều bộ nhớ thanh ghi trên kiến trúc của thiết bị tính toán Điều
này có thể kiểm tra bằng tham số –ptxas-options=-v để lấy báo cáo về tình trạng
sử dụng bộ nhớ cục bộ (lmem).
Con trỏ trong mã thực thi trên thiết bị tính toán được hỗ trợ khi trình biên dịch cóthể xác định được con trỏ trỏ tới vùng nhớ chia sẻ (shared memory) hay vùng nhớchung (global memory) hoặc đôi khi con trỏ bị hạn chế chỉ trỏ tới vùng nhớ trên
bộ nhớ chung
Địa chỉ được lấy từ các chỉ thị device , share hoặc constant chỉ cóthể sử dụng được trong các đoạn mã trên thiết bị lưu trữ Địa chỉ lấy từ các chỉ thị
device hoặc constant có thể lấy bằng cudaGetSysmbolAddress() và chỉ
có thể thực thi trên mã máy chủ
Trang 16− Dg là dữ liệu dạng dim3 để được xác định kích thước và chiều của lưới, ví
dụ Dg.x * Dg.y tương đương với số các khối được chạy; Dg.z không được
sử dụng
− Db là dữ liệu dạng dim3 để xác định kích thước và chiều của mỗi khối, ví
dụ như Db.x * Db.y * Db.z tương đương với số luồng trên mỗi khối
− Ns là dữ liệu dạng site_t, xác định số các bytes trong bộ nhớ chia sẻ đượccấp phát động cho mỗi khối cho lời gọi hàm bên cạnh các cấp phát tĩnh.Cấp phát bộ nhớ động được sử dụng khi các biến số được khai báo như cácmảng ngoài Ns là một tham số tùy chọn mà mặc định được đặt bằng 0
− S là dữ liệu dạng cudaStream_t và chỉ định cho các dòng được gán; S làmột tham số tùy chọn mà mặc định được đặt bằng 0
Lấy một ví dụ, ta có một hàm khai báo như sau:
global void Func(float* parameter);
phải được gọi như sau:
Func<<<Dg, Db, Ns >>>(parameter);
Các tham số cho cấu hình thực thi được đánh giá trước các tham số thực sự vàgiống như tham số các hàm, hiện tại được truyền qua bộ nhớ chia sẻ tới thiết bịtính toán
Hàm được gọi sẽ thất bại nếu Dg hoặc Db lớn hơn kích thước lớn nhất cho phépcủa thiết bị tính toán, hoặc nếu Ns lớn hơn dung lượng còn trống của bộ nhớ chia
sẻ trừ đi lượng bộ nhớ chia sẻ cần cho việc cấp phát tĩnh, các tham số của hàm vàcấu hình thực thi