我通过 C# Cloo 接口使用 OpenCL,当我试图让它在我们的产品中运行良好时遇到了一些非常令人沮丧的问题。
我们的产品是一种计算机视觉产品,它以每秒 30 次的速度从我们的相机中获取 512x424 的像素值网格。我们希望对这些像素进行计算,以生成相对于场景中某些对象的点云。
我正在尝试计算这些像素的是,当我们得到一个新帧时,以下(每一帧):
1) 创建一个 CommandQueue,2) 创建一个对输入像素值只读的缓冲区,3) 创建一个仅对输出点值写入的零拷贝缓冲区。4) 传入用于在 GPU 上进行计算的矩阵,5) 执行内核并等待响应。
每帧工作的一个例子是:
// the command queue is the, well, queue of commands sent to the "device" (GPU)
ComputeCommandQueue commandQueue = new ComputeCommandQueue(
_context, // the compute context
_context.Devices[0], // first device matching the context specifications
ComputeCommandQueueFlags.None); // no special flags
Point3D[] realWorldPoints = points.Get(Perspective.RealWorld).Points;
ComputeBuffer<Point3D> realPointsBuffer = new ComputeBuffer<Point3D>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.UseHostPointer,
realWorldPoints);
_kernel.SetMemoryArgument(0, realPointsBuffer);
Point3D[] toPopulate = new Point3D[realWorldPoints.Length];
PointSet pointSet = points.Get(perspective);
ComputeBuffer<Point3D> resultBuffer = new ComputeBuffer<Point3D>(_context,
ComputeMemoryFlags.UseHostPointer,
toPopulate);
_kernel.SetMemoryArgument(1, resultBuffer);
float[] M = new float[3 * 3];
ReferenceFrame referenceFrame =
perspectives.ReferenceFrames[(int)Perspective.Floor];
AffineTransformation transform = referenceFrame.ToReferenceFrame;
M[0] = transform.M00;
M[1] = transform.M01;
M[2] = transform.M02;
M[3] = transform.M10;
M[4] = transform.M11;
M[5] = transform.M12;
M[6] = transform.M20;
M[7] = transform.M21;
M[8] = transform.M22;
ComputeBuffer<float> Mbuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.UseHostPointer,
M);
_kernel.SetMemoryArgument(2, Mbuffer);
float[] b = new float[3];
b[0] = transform.b0;
b[1] = transform.b1;
b[2] = transform.b2;
ComputeBuffer<float> Bbuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.UseHostPointer,
b);
_kernel.SetMemoryArgument(3, Bbuffer);
_kernel.SetValueArgument<int>(4, (int)Perspective.Floor);
//sw.Start();
commandQueue.Execute(_kernel,
new long[] { 0 }, new long[] { toPopulate.Length }, null, null);
IntPtr retPtr = commandQueue.Map(
resultBuffer,
true,
ComputeMemoryMappingFlags.Read,
0,
toPopulate.Length, null);
commandQueue.Unmap(resultBuffer, ref retPtr, null);
分析时,时间太长了,90% 的时间用于创建所有 ComputeBuffer 对象等。GPU 上的实际计算时间尽可能快。
我的问题是,我该如何解决这个问题?输入的像素数组对于每一帧都是不同的,所以我必须为此创建一个新的 ComputeBuffer。当我们更新场景时,我们的矩阵也可以定期更改(同样,我无法深入了解所有细节)。有没有办法在 GPU 上更新这些缓冲区?我使用的是 Intel GPGPU,所以我有共享内存,理论上可以做到这一点。
这变得令人沮丧,因为我在 GPU 上发现的速度提升一次又一次被为每一帧设置所有内容的开销所淹没。
编辑1:
我不认为我的原始代码示例真正展示了我做得足够好,所以我创建了一个真实的工作示例并将其发布在 github上。
由于遗留原因和时间原因,我无法更改我们当前产品的太多压倒一切的架构。我试图在某些速度较慢的部分“插入”GPU代码以加快速度。考虑到我所看到的限制,这可能是不可能的。但是,让我更好地解释我在做什么。
我将给出代码,但我将指代“GPUComputePoints”类中的函数“ComputePoints”。
正如您在我的 ComputePoints 函数中看到的那样,每次传入一个 CameraFrame 以及转换矩阵 M 和 b。
public static Point3D[] ComputePoints(CameraFrame frame, float[] M, float[] b)
这些是从我们的管道生成的新数组,而不是我可以闲逛的数组。所以我为每个创建一个新的 ComputeBuffer:
ComputeBuffer<ushort> inputBuffer = new ComputeBuffer<ushort>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer,
frame.RawData);
_kernel.SetMemoryArgument(0, inputBuffer);
Point3D[] ret = new Point3D[frame.Width * frame.Height];
ComputeBuffer<Point3D> outputBuffer = new ComputeBuffer<Point3D>(_context,
ComputeMemoryFlags.WriteOnly | ComputeMemoryFlags.UseHostPointer,
ret);
_kernel.SetMemoryArgument(1, outputBuffer);
ComputeBuffer<float> mBuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer,
M);
_kernel.SetMemoryArgument(2, mBuffer);
ComputeBuffer<float> bBuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer,
b);
_kernel.SetMemoryArgument(3, bBuffer);
...我相信,这就是性能的消耗。有人提到要解决这个问题,请使用 map/unmap 功能。但是我看不出这会有什么帮助,因为我仍然需要每次都创建缓冲区来封装传入的新数组,对吧?