私はOpenCLの学習を始めました。現在、単純な骨格アニメーションアルゴリズムのパフォーマンスをどれだけ向上させることができるかを試しています。これを行うために、無作為に生成された頂点と変換行列から骨格アニメーションを2回、普通のC++でSSE最適化線形代数ライブラリで1回、GPUで自分のOpenCLカーネルを使って1回実行するプログラムを作成しました。 Nvidia GTX 460)。OpenCLパフォーマンスの最適化
私は、各作業項目が正確に1つの頂点を変換し、すべての値がグローバルメモリから読み込まれる単純なカーネルを使い始めました。私はこのカーネルの性能に満足していなかったので、私は少し最適化しようとしました。私の現在のカーネルは、次のようになります。
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;
}
私が最適化される可能性がより多くのものがあると思います、多分他のグローバルの一部をバッチ処理すると、一緒に読みますが、最初、私は本当に好きですこの最初の最適化がうまくいかなかった理由を知りたい
: その後
OpenCL
ライブラリはGPU
内の一定の領域にメモリを割り当てるでしょうし、その配列へのアクセスが高速になります。 async_copy操作の後にバリアーを使用します。バリアは、非同期コピーが終了するのを待たず、すべての作業項目がその時点に達するとすぐに継続します。仕様によると、async_copyの後にカーネルでwait_group_events関数を使用するか、結果が未定義です。 async_copyはカーネルの残りの部分が実行されている間に起こっているので、wait_group_eventsはカーネルにメモリコピーが確実に行われるように強制します。 –