OpenCL을 배우기 시작했으며 현재 간단한 뼈대 애니메이션 알고리즘의 성능을 얼마나 향상시킬 수 있는지 테스트하려고합니다. 이를 위해 무작위로 생성 된 버텍스와 변환 행렬을 일반 C++로 SSE 최적화 선형 대수 라이브러리에서 한 번, GPU에서 내 자신의 OpenCL 커널을 사용하여 한 번씩 두 번 골격 애니메이션을 수행하는 프로그램을 작성했습니다. 엔비디아 GTX 460).OpenCL 성능 최적화
각 작업 항목이 모든 값을 전역 메모리에서 읽은 정확히 하나의 꼭지점을 변환하는 간단한 커널로 시작했습니다. 이 커널의 성능에 만족하지 않았기 때문에 조금 최적화하려고했습니다.
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));
}
}
가 지금은 작업 항목 당 정점의 상수를 처리하고 난 이어질 것이라고 믿었다 각 작업 항목, 한 번만 로컬 메모리에 모든 뼈 매트릭스를 프리 페치 : 내 현재 커널은 다음과 같습니다 이후 여러 개의 정점에 대한 행렬을 더 빠른 로컬 메모리에서 읽을 수 있기 때문에 성능이 향상됩니다. 불행히도,이 커널은 첫 번째 시도보다 성능이 떨어지며 CPU 전용 구현보다 더 나쁩니다.
성능이 그렇게 좋지 않은 이유는 무엇입니까?
는 여기에 도움이 나는 커널 실행 방법 인 경우 : 나는 어쩌면 다른 세계가 함께 읽는 중 일부를 일괄 처리, 최적화 할 수있는 많은 것들이 추측
#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;
}
을하지만, 처음에는 정말 싶습니다 이 첫 번째 최적화가 작동하지 않는 이유를 알아야합니다.
나는 성능에 대해 알고 있지만, 무슨 일을하는 것은 정의되지 않은 결과를 갖고있는 것 같아요 없습니다 . 장벽 뒤에 async_copy 작업을 사용합니다. 장벽은 비동기 복사가 완료 될 때까지 기다리지 않고 모든 작업 항목이 그 지점에 도달하자마자 계속됩니다. 사양에 따르면 async_copy 다음에 커널에서 wait_group_events 함수를 사용해야합니다. 그렇지 않으면 결과가 정의되지 않습니다. 이것은 async_copy가 나머지 커널이 실행되는 동안 일어나기 때문에 의미가 있습니다. 따라서 wait_group_events는 커널이 메모리 복사가 완료되도록합니다. –