Tải bản đầy đủ (.doc) (24 trang)

Cu da ngôn ngữ lập trình song song

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (340.36 KB, 24 trang )

Mụ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
4 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
1 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ề 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.
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ằng
CUDA đượ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ột
thờ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:
Tố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ệt
vớ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 render
củ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
GPU 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ể được
thể hiện trong tính toán dữ liệu song song – các chương trình giống nhau được
thự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ình
giố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 cho
việ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ính
thay 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 song
song để 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 song
của GPU. Phát triển cuối cùng về GPU của NVIDIA dựa trên sơ sở kiến trúc
Tesla. 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ết
trên CUDA.
2 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 thread
CUDA khác nhau.
Một kernel được định nghĩa bằng việc dùng _global_ để khai báo và số lượng
thread cho mỗi lời gọi được chỉ ra bằng việc sử dụng cú pháp <<<…>>>
// Kernel definition
__global__ void vecAdd(float* A, float* B, float* C)
{
}
int main()
{
// Kernel invocation
vecAdd<<<1, N>>>(A, B, C);
}
Mỗi thread thực hiện một kernel mang một threadID riêng nó chứa bên trong

thread và được tao bởi biến threadIdx . Giống như lý thuyết đưa ra, ví dụ dưới đây
là một đoạn code thực hiện cộng hai vector A và B của N và lưu kết quả vào
vector C:
__global__ void vecAdd(float* A, float* B, float* C)
{
int i = threadIdx.x;
C[i] = A[i] + B[i];
}
int main()
{
// Kernel invocation
vecAdd<<<1, N>>>(A, B, C);
}
Phân cấp Thread
Để thuận tiện, ThreadIdx có 3vector , nhờ đó những thread có thể được định nghĩa
bằng việc dùng 1, 2 hay 3 chiều chỉ số. Đoạn code dưới đây thể hiện việc cộng 2
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;
int j = threadIdx.y;
C[i][j] = A[i][j] + B[i][j];
}
int main()
{
// Kernel invocation
dim3 dimBlock(N, N);
matAdd<<<1, dimBlock>>>(A, B, C);
}

Chỉ số của thread và threadID của nó liên quan tới nhau theo một cách dễ hiểu:
khối 1 chiều , chúng giống nhau; với khối 2 chiều (Dx, Dy) , threadID của chỉ số
(x, y) là (x+y Dx) ; với khối 3 chiều (Dx, Dy, Dz) threadID của chỉi số (x,y,z) là
(x+y Dx+z Dx Dy).
Thread bên trong block có thể kết hợp với nhau bằng việc chia sẻ dữ liệu thông
qua vài vùng nhớ chia sẻ và đồng bộ hoá việc thực hiện chúng theo điều kiện truy
nhậ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ằng
nhau, do đó tổng số thread bằng với số thread trên một khối nhân với số lượng
khố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 định
bởi tham số cú pháp <<<…>>> . Mỗi khối trong lưới được chỉ ra là 1 chiều 2
chiề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;
if (i < N && j < N)
C[i][j] = A[i][j] + B[i][j];
}
int main()
{
// Kernel invocation
dim3 dimBlock(16, 16);
dim3 dimGrid((N + dimBlock.x – 1) / dimBlock.x,
(N + dimBlock.y – 1) / dimBlock.y);
matAdd<<<dimGrid, dimBlock>>>(A, B, C);
}

Khối thread kích thước 16x16 =256 thread được chọn và lưới được tạo với số
khối đủ để có một thread tren một thành phần matrận như trước.
Lướ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ử
dụ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 đặc
biệ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, khi
kernel 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ên
CPU.
2.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ọc
mức cao CUFFT và CUBLAS cả hai đều được mô tả trong văn bản chung.
Khối lượng tính toán
3 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 cho
người đã sử dụng quen thuộc ngôn ngữ C và dễ dàng viết các chương trình chạy
trê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ực
hiệ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 con
trong 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ủ hay
chạy trên thiết bị tính toán và các hàm đó có thể được gọi từ máy chủ hay
gọ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ên
thiế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ồng
tiế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ếp
theo. 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ạn
chế này, song tồn tại một số lỗi không thể phát hiện ra.
3.3 Các loại hạn định của hàm
3.3.1 Chỉ thị __device__
Chỉ thị __device__ định nghĩa một hàm:
− Chạy trên thiết bị tính toán
− 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
− Chỉ có thể gọi từ máy chủ
3.3.3 Chỉ thị __host__
Chỉ thị __host__ định nghĩa hàm có thể:
− Thực thi trên máy chủ
− Chỉ có thể gọi từ máy chủ
Việc đặt chỉ thị này tương đương với việc không đặt chỉ thị nào trong số các chỉ
thị __host__, __device__, __global__, và hàm mặc định được biên dịch để chạy
trên máy chủ.
Tuy nhiên, chỉ thị __host__ có thể được khai báo kèm với __device__ trong
trườ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ĩnh
trong 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ước
khi 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.
Phần lớn các biến được khai báo với chỉ thị __device__ để xác định rõ ràng hơn
vù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ột
biế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ác
hà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ảo
biế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ự do
thì 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:

short array0[128]
float array1[14]
int array2[256]
thì trong khi cấp phát bộ nhớ động, ta phải khai báo và khởi tạo các mảng như sau:
extern __shared__ char array[];
__device__ 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];
}
3.4.4 Những hạn chế
Những hạn định trên không được sử dụng trên các cấu trúc dữ liệu struct và
union, trong các tham số chính tắc và với các biến số trong hàm được thực thi tại
máy chủ.
__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ại
cá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ường
hợ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ủ.
3.5 Cấu hình thực thi
Một lời gọi tới một hàm __global__ phải được xác định với cấu hình thực thi cho
lời gọi đó.
Cấu hình thực thi định nghĩa ra kích thước của lưới hoặc của các khối có thể sử
dụng để chạy các hàm trên thiết bị, giống như các luồng liên kết. Trên lý thuyết ta
chèn vào một biểu thức theo dạng <<< Dg, Db, Ns, S >>> giữa tên của hàm và
dấu ngặc đơn danh sách tham số. Trong đó:
− 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ẻ được
cấ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ác
mả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ép
củ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.
3.6 Những biến số định nghĩa sẵn
3.6.1 gridDim
Biến số này có kiểu là dim3 và chứa chiều của lưới.
3.7 blockIdx
Biến số này có kiểu là uint3 chứa chỉ số của khối trong lưới.
3.7.1 blockDim
Biến số nà có kiểm là dim3 và chứa kích thước của khối.
3.7.2 threadIdx
Biến số này có kiểu uint3 và chứa chỉ số luồng trong khối.
3.7.3 warpSize
Biến số này có kiểu int và chứa kích thước warp trong các luồng
3.7.4 Các hạn chế
Không thể lấy địa chỉ của bất kỳ biến định nghĩa sẵn nào.
Không thể gán giá trị cho các biến định nghĩa sẵn.
3.8 Biên dịch với NVCC
Nvcc là trình biên dịch đơn giản hóa quá trình dịch các đoạn mã CUDA. Nó cung
cấp những dòng lệnh đơn giản, thân thiện và thực thi chúng sau khi gọi đến các
công cụ được sử dụng trong những kịch bản biên dịch khác nhau.
Luồng công việc cơ bản của nvcc bao gồm việc chia các đoạn mã trên thiết bị tính

toán và trên máy chủ để biên dịch các đoạn mã trên thiết bị thành dạng nhị phân
hoặc đối tượng cubin. Việc sinh mã trên máy chủ tương tự với việc xuất ra mã
ngôn ngữ C và được chuyển cho một công cụ biên dịch khác hoặc thành các mã
đối tượng trong C (object code) để gọi trực tiếp đến trình biên dịch trên máy chủ
trong bước biên dịch trước đó.
Các ứng dụng có thể bỏ qua việc sinh mã trên máy chủ và nạp thực thi đối tượng
cubin trên thiết bị sử dụng trình điều khiển CUDA với các API, hoặc có thể liên
kết để sinh mã trên máy chủ bao gồm các đối tượng cubun như một khởi tạo các
mảng dữ liệu toàn cục và chứa một cấu hình thực thi để kích hoạt quá trình chạy
của mã CUDA.
Giao diện của tiến trình biên dịch các tệp mã nguồn của CUDA tương thích với
các cú pháp của ngôn ngữ C++. Mã trên máy chủ hỗ trợ toàn bộ cho mã C++. Tuy
nhiên chỉ một phần ngôn ngữ C của C++ được hỗ trợ trên thiết bị tính toán. C++
có các thành phần như lớp, kế thừa hay định nghĩa các biến bên trong khối
không được hỗ trợ trên thiết bị tính toán. Kiểu dữ liệu con trỏ void (ví dụ như giá
trị trả về bởi hàm malloc()) không thể gán cho con trỏ có kiểu khác mà không
chuyển kiểu. Luồng công việc và các lệnh liên quan đến nvcc có thể tìm thấy trong
nhiều tài liệu khác.
Nvcc giới thiệu hai trình biên dịch chỉ thị sẽ được trình bày ở phần tiếp theo.
3.8.1 __noinline__
Mặc định, một hàm __device__ luôn là inline. Một hàm __noinline__ chỉ định
cho trình biên dịch rằng không dịch hàm đó thành inline nếu có thể. Thân của hàm
vẫn phải ở trong tệp đó khi hàm được gọi.
Trình biên dịch không khuyến khích sử dụng __noinline__ cho các hàm có tham
số là con trỏ hoặc hàm có danh sách tham số dài.
3.8.2 #pragma unroll
Mặc định, trình biên dịch sẽ mở ra một vòng lặp nhỏ với số vòng đã xác định
trước. Chỉ thị #pragma unroll sử dụng để điều khiển việc mở vòng lặp bất kỳ. Nó
phải được đặt ngay trước vòng lặp đó. Chỉ thị có thể đi kèm với một số chỉ số lần
lặp phải mở.

Ví dụ:
#pragma unroll 5
for (int i = 0; i < n; ++i)
Vòng lặp sẽ được lặp lại 5 lần. Tùy thuộc vào lập trình viên để chắc chắn rằng việc
mở vòng lặp không ảnh hưởng tới tính đúng đắn của chương trình (trong ví dụ trên
thì n phải nhỏ hơn 5).
Chỉ thị #pragma unroll 1 sẽ ngăn không lặp lại vòng lặp.
Nếu không có tham số đi sau #pragma unroll thì vòng lặp sẽ hoàn thành việc lặp
nếu số lần lặp là hằng số xác định hoặc không lặp lần nào.
4 Các thành phần thực thi chung
Các thành phần thực thi chung có thể được sử dụng trong các hàm trên máy chủ và
các hàm trên thiết bị tính toán.
4.1 Các kiểu Vector được dựng sẵn
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
Các kiểu vector có nguồn gốc từ các kiểu nguyên cơ bản và các kiểu số thực. Các
kiểu này có cấu trúc và các thành phần 1, 2, 3, 4 được truy xuất qua các trường x,
y, z và w. Tất cả được khởi tạo với hàm khởi tạo có dạng make_<type name>; ví
dụ:
int2 make_int2(int x, int y);
sẽ tạo một vector dạng int2 với giá trị (x, y)
4.1.2 Kiểu dữ liệu dim3
Đây là kiểu dữ liệu dựa trên kiểu uint3 để chỉ ra chiều. Khi khai báo một biến dạng
dim3, tất cả các thành phần không được khởi tạo sẽ được gán giá trị bằng 1.
4.2 Các hàm toán học
Các hàm toán học trong thư viện C/C++ được hỗ trợ.
Khi thực thi trên các đoạn mã ở máy chủ, các hàm này có thể sử dụng thư viện của

C nếu có thể.
4.3 Hàm thời gian
clock_t clock();
Khi thực thi mã trên thiết bị tính toán, hàm thời gian trả về giá trị đếm của một bộ
xử lý được tăng lên sau mỗi nhịp đồng hồ. Mẫu của bộ đếm này từ khi bắt đầu đến
khi kết thúc nhân chương trình.
4.4 Kiểu Texture
CUDA hỗ trợ kiểu dữ liệu dạng texture của phần cứng mà GPU sử dụng để truy
cập vào phần bộ nhớ texture. Đọc dữ liệu từ bộ nhớ texture thay vì đọc từ bộ nhớ
toàn cục sẽ tạo ra những hiệu năng cao hơn.
Bộ nhớ texture có thể đọc được từ nhân chương trình sử dụng các hàm trên thiết bị
tính toán được gọi là Chỉ thị gọi texture.
Tham số đầu tiên của chỉ thị gọi texture được đặc trưng bởi một đối tượng được
gọi là Tham chiếu texture.
Một tham chiếu texture định nghĩa một phần bộ nhớ texture được gọi. Nó phải
được ràng buộc qua các hàm thực thi trên máy chủ trên một số vùng nhớ (gọi là
một texture) trước khi có thể sử dụng bởi nhân. Một số texture phân biệt có thể
ràng buộc bởi chính texture đó hoặc các texture gối lên nhau trong bộ nhớ.
Một Tham chiếu texture có một số tính chất. Mỗi Tham chiếu texture có chiều và
kích thước chỉ định việc đánh địa chỉ texture. Mảng một chiều, hai chiều hoặc ba
chiều sử dụng cho texture một chiều, hai chiều hoặc ba chiều. Các phần tử của
mảng được gọi là các texel (gọi tắt của texture elements).
Một tính chất khác được định nghĩa là các kiểu dữ liệu vào và ra của Chỉ thị gọi
texture, giống như chiều của đầu vào được thông dịch và tiến trình nào được thực
thi.
4.4.1 Khai báo Tham chiếu texture
Một số các thuộc tính của Tham chiếu texture là không thay đổi và phải được biết
đến tại thời điểm biên dịch; chúng được xác định khi khai báo các Tham chiếu
texture. Một Tham chiếu texture khai báo một phạm vi tệp của kiểu texture:
texture<Type, Dim, ReadMode> texRef;

Trong đó:
Type chỉ ra kiểu dữ liệu trả về khi lấy ra một texture. Kiểu dữ liệu bị giới hạn
trong các kiểu số nguyên, số thực với độ chính xác đơn và các kiểu dữ liệu vector
1, 2, 4 thành phần.
Dim chỉ ra chiều của Tham chiếu texture và có thể nhận giá trị 1, 2 hoặc 3. Dim
có thể có hoặc không và mặc định được đặt bằng 1.
ReadMode bằng với giá trị cudaReadModeNormalizedFloat hoặc
cudaReadModeElementType. Nếu cudaReadModeNormalizedFloat và Type
là số nguyên 16 bit hoặc 8 bit, giá trị trả về sẽ có kiểu là số thực và có kiểu dữ liệu
nguyên đầy đủ được ánh xạ đến [0.0, 1.0] cho kiểu số thực không dấu và [-1.0,
1.0] cho kiểu số thực có dấu. Ví dụ, một đối tượng texture không dấu với giá trị
0xff đọc là 1; nếu nó là cudaReadModeElementType, không cần có sự chuyển
đổi nào; ReadMode mặc định là cudaReadModeElementType và không bắt
buộc.
4.4.2 Tính chất của của Tham chiếu texture
Một tính chất khác của Tham chiếu texture là có thể thay đổi được trong quá trình
chạy qua máy chủ. Nó xác định xem các chiều có được chuẩn hóa hay không, chế
độ đánh địa chỉ, lọc các texture
4.4.3 Texure từ bộ nhớ tuyến tính qua mảng của CUDA
Một texture có thể chứa trong bất kỳ vùng nào của bộ nhớ tuyến tính của CUDA.
Texture được xác định trong bộ nhớ tuyến tính thì:
− Chỉ có thể có chiều bằng 1.
− Không hỗ trợ lọc texture.
− Chỉ có thể đánh địa chỉ sử dụng số nguyên không chuẩn hóa.
− Không hỗ trợ một số chế độ đánh địa chỉ: Truy xuất đến ngoài khoảng nhớ
sẽ trả về giá trị 0.
5 Các thành phần chạy trên thiết bị tính toán
Các thành phần này chỉ có thể chạy trên thiết bị tính toán.
5.1 Các hàm toán học
Có các hàm toán học sử dụng chung, nhưng các hàm chạy trên thiết bị tính toán có

thể chạy nhanh hơn song độ chính xác không bằng. Các hàm này có chung tiền tố
__ (ví dụ như __sinf(x)).
Trình biên dịch có một tùy chọn là -user_fast_math để ép các hàm được biên dịch
với độ chính xác ít hơn nếu có thể.
5.2 Hàm đồng bộ
void __syncthreads();
đồng bộ hóa tất cả các luồng trong một khối. Một khi tất cả các luồng đã tới điểm
này, quá trình thực thi sẽ được tiếp tục.
__syncthreads(); được sử dụng trong việc truyền thông giữa các chiều giữa các
luồng trong một khối. Khi một số luồng trong khối truy xuất đến cùng một địa chỉ
bộ nhớ trong bộ nhớ chia sẻ hoặc bộ nhớ toàn cục có thể xảy ra xung đột: ghi
trước khi đọc, đọc trước khi ghi hoặc ghi trước khi ghi. Những xung đột về dữ liệu
như vậy có thể tránh được bằng cách đồng bộ các luồng giữa các lần truy xuất bộ
nhớ.
5.3 Hàm Texture
5.3.1 Texture từ bộ nhớ tuyến tính
Khi texture từ bộ nhớ tuyến tính, texture được truy xuất qua các hàm tex1Dfetch().
Ví dụ:
template<class Type>
Type tex1Dfetch(
texture<Type, 1, cudaReadModeElementType> texRef,
int x);
float tex1Dfetch(
texture<unsigned char, 1,
cudaReadModeNormalizedFloat> texRef,
int x);
float tex1Dfetch(
texture<signed char, 1, cudaReadModeNormalizedFloat>
texRef,
int x);

float tex1Dfetch(
texture<unsigned short, 1,
cudaReadModeNormalizedFloat> texRef,
int x);
float tex1Dfetch(
texture<signed short, 1,
cudaReadModeNormalizedFloat> texRef,
int x);
Các hàm triệu gọi này lấy một vùng trong bộ nhớ tuyến tính giới hạn tham chiếu
texture texRef sử dụng chiều x. Không có bộ lọc texture và hỗ trợ chế độ đánh địa
chỉ nào. Với kiểu dữ liệu nguyên, các hàm này có thể tăng độ chính xác của số
nguyên lên số thực với độ chính xác đơn.
Bên cạnh các hàm trên, hàm 2-, 4 chiều cũng được hỗ trợ, ví dụ:
float4 tex1Dfetch(
texture<uchar4, 1, cudaReadModeNormalizedFloat>
texRef,
int x);
5.3.2 Texture từ các mảng CUDA
Khi texture từ các mảng CUDA, texture được truy xuất qua hàm tex1D(), tex2D()
và tex3D():
template<class Type, enum cudaTextureReadMode readMode>
Type tex1D(texture<Type, 1, readMode> texRef,
float x);
template<class Type, enum cudaTextureReadMode readMode>
Type tex2D(texture<Type, 2, readMode> texRef,
float x, float y);
template<class Type, enum cudaTextureReadMode readMode>
Type tex3D(texture<Type, 3, readMode> texRef,
float x, float y, float z);
Các hàm này ràng buộc tới Tham chiếu texture texRef sử dụng texture x, y và z.

Một kết hợp của Tham chiếu texture không thể thay đổi trong thời điểm biên dịch
và có thể thay đổi trong thời điểm chạy.
5.4 Các hàm phần tử
Một hàm phần tử thực hiện một thao tác đọc ghi trên một giá trị 32 bit hoặc 64 bit
chứa trong bộ nhớ toàn cục hay bộ nhớ chia sẻ. Ví dụ, atomicAdd(). Đọc 32 bit
tại một địa chỉ nào đó của của bộ nhớ toàn cục hoặc bộ nhớ chia sẻ., cộng thêm
một số nguyên vào giá trị đọc được, sau đó ghi lại giá trị vào ô nhớ đó. Các hàm
atomic hoạt động không ảnh hưởng đến các luồng công việc khác. Nói cách khác,
không có luồng nào khác có thể truy xuất đến địa chỉ đó trước khi thao tác hoàn
thành.
6 Các thành phần trên máy chủ
Các thành phần trên máy chủ chỉ có thể được chạy bằng các hàm trên máy chủ.
Các thành phần này cung cấp các hàm cho phép:
− Quản lý các thiết bị tính toán.
− Quản lý ngữ cảnh.
− Quản lý bộ nhớ.
− Quản lý các môđun mã.
− Quản lý thực thi.
− Quản lý tham chiếu texture.
− Hỗ trợ OpenGL và Direct3D.
Các thành phần này kết hợp giữa hai API:
− API cấp thấp được gọi là trình điều khiển CUDA.
− API cấp cao được gọi là API thực thi của CUDA được thiết kế dựa trên các
trình điều khiển CUDA.
Tài liệu tham khảo
[1] NVIDA CUDA Compute Unified Device Architecture - Programming Guide,
Version 2.0, 6/7/2008

×