Tôi đã bắt đầu học OpenCL và hiện tại tôi đang thử kiểm tra xem tôi có thể cải thiện hiệu suất cho thuật toán hoạt hình xương đơn giản như thế nào. Để thực hiện điều này, tôi đã viết một chương trình thực hiện hoạt hình xương từ các đỉnh được tạo ngẫu nhiên và các ma trận chuyển đổi hai lần, một lần với thư viện đại số tuyến tính được tối ưu hóa SSE ở đồng bằng C++ và một khi sử dụng hạt nhân OpenCL của riêng tôi trên GPU (tôi đang thử nghiệm trên một Nvidia GTX 460).Tối ưu hóa hiệu suất OpenCL
Tôi bắt đầu với một hạt nhân đơn giản trong đó mỗi mục công việc biến đổi chính xác một đỉnh, với tất cả các giá trị được đọc từ bộ nhớ chung. Bởi vì tôi không hài lòng với hiệu suất của hạt nhân này, tôi đã cố gắng tối ưu hóa một chút. hạt nhân hiện tại của tôi trông như thế này:
inline float4 MultiplyMatrixVector(float16 m, float4 v)
{
return (float4) (
dot(m.s048C, v),
dot(m.s159D, v),
dot(m.s26AE, v),
dot(m.s37BF, v)
);
}
kernel void skelanim(global const float16* boneMats, global const float4* vertices, global const float4* weights, global const uint4* indices, global float4* resVertices)
{
int gid = get_global_id(0);
int lid = get_local_id(0);
local float16 lBoneMats[NUM_BONES];
async_work_group_copy(lBoneMats, boneMats, NUM_BONES, 0);
barrier(CLK_LOCAL_MEM_FENCE);
for (int i = 0 ; i < NUM_VERTICES_PER_WORK_ITEM ; i++) {
int vidx = gid*NUM_VERTICES_PER_WORK_ITEM + i;
float4 vertex = vertices[vidx];
float4 w = weights[vidx];
uint4 idx = indices[vidx];
resVertices[vidx] = (MultiplyMatrixVector(lBoneMats[idx.x], vertex * w.x)
+ MultiplyMatrixVector(lBoneMats[idx.y], vertex * w.y)
+ MultiplyMatrixVector(lBoneMats[idx.z], vertex * w.z)
+ MultiplyMatrixVector(lBoneMats[idx.w], vertex * w.w));
}
}
Bây giờ tôi xử lý một hằng số của đỉnh mỗi công việc mục, và tôi prefetch tất cả các ma trận xương vào bộ nhớ địa phương chỉ một lần cho mỗi công việc mục, mà tôi tin rằng sẽ dẫn để đạt hiệu suất tốt hơn bởi vì các ma trận cho nhiều đỉnh có thể được đọc từ bộ nhớ cục bộ nhanh hơn sau đó. Thật không may, hạt nhân này thực hiện tồi tệ hơn nỗ lực đầu tiên của tôi, và thậm chí tệ hơn so với việc thực hiện chỉ CPU.
Tại sao hiệu suất lại xấu như vậy với tối ưu hóa này?
Nếu nó giúp, đây là cách tôi thực hiện các hạt nhân:
#define NUM_BONES 50
#define NUM_VERTICES 30000
#define NUM_VERTICES_PER_WORK_ITEM 100
#define NUM_ANIM_REPEAT 1000
uint64_t PerformOpenCLSkeletalAnimation(Matrix4* boneMats, Vector4* vertices, float* weights, uint32_t* indices, Vector4* resVertices)
{
File kernelFile("/home/alemariusnexus/test/skelanim.cl");
char opts[256];
sprintf(opts, "-D NUM_VERTICES=%u -D NUM_REPEAT=%u -D NUM_BONES=%u -D NUM_VERTICES_PER_WORK_ITEM=%u", NUM_VERTICES, NUM_ANIM_REPEAT, NUM_BONES, NUM_VERTICES_PER_WORK_ITEM);
cl_program prog = BuildOpenCLProgram(kernelFile, opts);
cl_kernel kernel = clCreateKernel(prog, "skelanim", NULL);
cl_mem boneMatBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_BONES*sizeof(Matrix4), boneMats, NULL);
cl_mem vertexBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*sizeof(Vector4), vertices, NULL);
cl_mem weightBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*4*sizeof(float), weights, NULL);
cl_mem indexBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*4*sizeof(uint32_t), indices, NULL);
cl_mem resVertexBuf = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR, NUM_VERTICES*sizeof(Vector4), NULL, NULL);
uint64_t s, e;
s = GetTickcount();
clSetKernelArg(kernel, 0, sizeof(cl_mem), &boneMatBuf);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &vertexBuf);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &weightBuf);
clSetKernelArg(kernel, 3, sizeof(cl_mem), &indexBuf);
clSetKernelArg(kernel, 4, sizeof(cl_mem), &resVertexBuf);
size_t globalWorkSize[] = { NUM_VERTICES/NUM_VERTICES_PER_WORK_ITEM };
size_t localWorkSize[] = { NUM_BONES };
for (size_t i = 0 ; i < NUM_ANIM_REPEAT ; i++) {
clEnqueueNDRangeKernel(cq, kernel, 1, NULL, globalWorkSize, localWorkSize, 0, NULL, NULL);
}
clEnqueueReadBuffer(cq, resVertexBuf, CL_TRUE, 0, NUM_VERTICES*sizeof(Vector4), resVertices, 0, NULL, NULL);
e = GetTickcount();
return e-s;
}
Tôi đoán có rất nhiều điều mà có thể được tối ưu hóa, có lẽ Trạm trộn một số toàn cầu khác đọc với nhau, nhưng đầu tiên tôi thực sự muốn để biết tại sao tối ưu hóa đầu tiên này không hiệu quả.
Tôi không biết về hiệu suất, nhưng những gì bạn đang làm dường như có kết quả không xác định . Bạn sử dụng thao tác async_copy theo sau là một rào cản. Hàng rào sẽ không đợi cho bản sao không đồng bộ kết thúc - nó sẽ tiếp tục ngay sau khi tất cả các mục công việc đã đạt đến điểm đó. Theo spec, bạn phải sử dụng hàm wait_group_events trong kernel của bạn sau một async_copy, hoặc kết quả là undefined. Điều này có ý nghĩa, bởi vì async_copy đang xảy ra trong khi phần còn lại của hạt nhân đang thực hiện, do đó wait_group_events sẽ buộc hạt nhân đảm bảo rằng bản sao bộ nhớ được thực hiện. –