我是否正确地重用了 OpenCL/Cloo(C#) 对象?
Am I reusing OpenCL/Cloo(C#) objects correctly?
我正在试验 OpenCL(通过 Cloo 的 C# 接口)。为此,我正在试验 GPU 上的惯用矩阵乘法。问题是,在我的速度测试中,应用程序崩溃了。我正在努力提高各种 OpenCL 对象的重新分配效率,我想知道我是否在这样做时搞砸了。
我将把代码放在这个问题中,但为了更全面的了解,您可以从 github 此处获取代码:https://github.com/kwende/ClooMatrixMultiply
我的主程序是这样做的:
Stopwatch gpuSw = new Stopwatch();
gpuSw.Start();
for (int c = 0; c < NumberOfIterations; c++)
{
float[] result = gpu.MultiplyMatrices(matrix1, matrix2, MatrixHeight, MatrixHeight, MatrixWidth);
}
gpuSw.Stop();
所以我基本上是在调用 NumberOfIterations 次,并计算平均执行时间。
在 MultiplyMatrices 调用中,我第一次调用 Initialize 来设置我要重用的所有对象:
private void Initialize()
{
// get the intel integrated GPU
_integratedIntelGPUPlatform = ComputePlatform.Platforms.Where(n => n.Name.Contains("Intel")).First();
// create the compute context.
_context = new ComputeContext(
ComputeDeviceTypes.Gpu, // use the gpu
new ComputeContextPropertyList(_integratedIntelGPUPlatform), // use the intel openCL platform
null,
IntPtr.Zero);
// the command queue is the, well, queue of commands sent to the "device" (GPU)
_commandQueue = new ComputeCommandQueue(
_context, // the compute context
_context.Devices[0], // first device matching the context specifications
ComputeCommandQueueFlags.None); // no special flags
string kernelSource = null;
using (StreamReader sr = new StreamReader("kernel.cl"))
{
kernelSource = sr.ReadToEnd();
}
// create the "program"
_program = new ComputeProgram(_context, new string[] { kernelSource });
// compile.
_program.Build(null, null, null, IntPtr.Zero);
_kernel = _program.CreateKernel("ComputeMatrix");
}
然后我进入函数的主体(将执行 NumberOfIterations 次的部分)。
ComputeBuffer<float> matrix1Buffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly| ComputeMemoryFlags.CopyHostPointer,
matrix1);
_kernel.SetMemoryArgument(0, matrix1Buffer);
ComputeBuffer<float> matrix2Buffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer,
matrix2);
_kernel.SetMemoryArgument(1, matrix2Buffer);
float[] ret = new float[matrix1Height * matrix2Width];
ComputeBuffer<float> retBuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.WriteOnly | ComputeMemoryFlags.CopyHostPointer,
ret);
_kernel.SetMemoryArgument(2, retBuffer);
_kernel.SetValueArgument<int>(3, matrix1WidthMatrix2Height);
_kernel.SetValueArgument<int>(4, matrix2Width);
_commandQueue.Execute(_kernel,
new long[] { 0 },
new long[] { matrix2Width ,matrix1Height },
null, null);
unsafe
{
fixed (float* retPtr = ret)
{
_commandQueue.Read(retBuffer,
false, 0,
ret.Length,
new IntPtr(retPtr),
null);
_commandQueue.Finish();
}
}
第三次或第四次(有点随机,暗示内存访问问题),程序崩溃了。这是我的内核(我确信有更快的实现,但现在我的目标只是让一些东西工作而不会爆炸):
kernel void ComputeMatrix(
global read_only float* matrix1,
global read_only float* matrix2,
global write_only float* output,
int matrix1WidthMatrix2Height,
int matrix2Width)
{
int x = get_global_id(0);
int y = get_global_id(1);
int i = y * matrix2Width + x;
float value = 0.0f;
// row y of matrix1 * column x of matrix2
for (int c = 0; c < matrix1WidthMatrix2Height; c++)
{
int m1Index = y * matrix1WidthMatrix2Height + c;
int m2Index = c * matrix2Width + x;
value += matrix1[m1Index] * matrix2[m2Index];
}
output[i] = value;
}
这里的最终目标是更好地理解 OpenCL 的零拷贝特性(因为我使用的是 Intel 的集成 GPU)。我一直在让它工作时遇到问题,所以想退后一步,看看我是否了解更多基本的东西……显然我不了解,因为我什至无法在不爆炸的情况下工作。
我能想到的 唯一的另一件事 是我如何固定指针以将其发送到 .Read() 函数。但我不知道有其他选择。
编辑:
为了它的价值,我将代码的最后一部分(读取的代码)更新为此,但它仍然崩溃:
_commandQueue.ReadFromBuffer(retBuffer, ref ret, false, null);
_commandQueue.Finish();
编辑#2
由 huseyin tugrul buyukisik 找到的解决方案(见下面的评论)。
放置后
matrix1Buffer.Dispose();
matrix2Buffer.Dispose();
retBuffer.Dispose();
最后,一切正常。
缓冲区、内核和命令队列等 OpenCl 资源应在 bound-to 释放其他资源后释放。 Re-creating 不释放会很快耗尽可用插槽。
您在 gpu
的方法中使用了 re-creating 个数组,那是 opencl 缓冲区的范围。完成后,GC 无法跟踪 opencl 的非托管内存区域并导致泄漏,从而导致崩溃。
许多 opencl 实现使用 C++ 绑定,需要 C#、Java 和其他环境的显式释放命令。
当重复的内核执行使用与内核参数完全相同的缓冲区顺序时,也不需要多次 set-argument 部分。
我正在试验 OpenCL(通过 Cloo 的 C# 接口)。为此,我正在试验 GPU 上的惯用矩阵乘法。问题是,在我的速度测试中,应用程序崩溃了。我正在努力提高各种 OpenCL 对象的重新分配效率,我想知道我是否在这样做时搞砸了。
我将把代码放在这个问题中,但为了更全面的了解,您可以从 github 此处获取代码:https://github.com/kwende/ClooMatrixMultiply
我的主程序是这样做的:
Stopwatch gpuSw = new Stopwatch();
gpuSw.Start();
for (int c = 0; c < NumberOfIterations; c++)
{
float[] result = gpu.MultiplyMatrices(matrix1, matrix2, MatrixHeight, MatrixHeight, MatrixWidth);
}
gpuSw.Stop();
所以我基本上是在调用 NumberOfIterations 次,并计算平均执行时间。
在 MultiplyMatrices 调用中,我第一次调用 Initialize 来设置我要重用的所有对象:
private void Initialize()
{
// get the intel integrated GPU
_integratedIntelGPUPlatform = ComputePlatform.Platforms.Where(n => n.Name.Contains("Intel")).First();
// create the compute context.
_context = new ComputeContext(
ComputeDeviceTypes.Gpu, // use the gpu
new ComputeContextPropertyList(_integratedIntelGPUPlatform), // use the intel openCL platform
null,
IntPtr.Zero);
// the command queue is the, well, queue of commands sent to the "device" (GPU)
_commandQueue = new ComputeCommandQueue(
_context, // the compute context
_context.Devices[0], // first device matching the context specifications
ComputeCommandQueueFlags.None); // no special flags
string kernelSource = null;
using (StreamReader sr = new StreamReader("kernel.cl"))
{
kernelSource = sr.ReadToEnd();
}
// create the "program"
_program = new ComputeProgram(_context, new string[] { kernelSource });
// compile.
_program.Build(null, null, null, IntPtr.Zero);
_kernel = _program.CreateKernel("ComputeMatrix");
}
然后我进入函数的主体(将执行 NumberOfIterations 次的部分)。
ComputeBuffer<float> matrix1Buffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly| ComputeMemoryFlags.CopyHostPointer,
matrix1);
_kernel.SetMemoryArgument(0, matrix1Buffer);
ComputeBuffer<float> matrix2Buffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer,
matrix2);
_kernel.SetMemoryArgument(1, matrix2Buffer);
float[] ret = new float[matrix1Height * matrix2Width];
ComputeBuffer<float> retBuffer = new ComputeBuffer<float>(_context,
ComputeMemoryFlags.WriteOnly | ComputeMemoryFlags.CopyHostPointer,
ret);
_kernel.SetMemoryArgument(2, retBuffer);
_kernel.SetValueArgument<int>(3, matrix1WidthMatrix2Height);
_kernel.SetValueArgument<int>(4, matrix2Width);
_commandQueue.Execute(_kernel,
new long[] { 0 },
new long[] { matrix2Width ,matrix1Height },
null, null);
unsafe
{
fixed (float* retPtr = ret)
{
_commandQueue.Read(retBuffer,
false, 0,
ret.Length,
new IntPtr(retPtr),
null);
_commandQueue.Finish();
}
}
第三次或第四次(有点随机,暗示内存访问问题),程序崩溃了。这是我的内核(我确信有更快的实现,但现在我的目标只是让一些东西工作而不会爆炸):
kernel void ComputeMatrix(
global read_only float* matrix1,
global read_only float* matrix2,
global write_only float* output,
int matrix1WidthMatrix2Height,
int matrix2Width)
{
int x = get_global_id(0);
int y = get_global_id(1);
int i = y * matrix2Width + x;
float value = 0.0f;
// row y of matrix1 * column x of matrix2
for (int c = 0; c < matrix1WidthMatrix2Height; c++)
{
int m1Index = y * matrix1WidthMatrix2Height + c;
int m2Index = c * matrix2Width + x;
value += matrix1[m1Index] * matrix2[m2Index];
}
output[i] = value;
}
这里的最终目标是更好地理解 OpenCL 的零拷贝特性(因为我使用的是 Intel 的集成 GPU)。我一直在让它工作时遇到问题,所以想退后一步,看看我是否了解更多基本的东西……显然我不了解,因为我什至无法在不爆炸的情况下工作。
我能想到的 唯一的另一件事 是我如何固定指针以将其发送到 .Read() 函数。但我不知道有其他选择。
编辑:
为了它的价值,我将代码的最后一部分(读取的代码)更新为此,但它仍然崩溃:
_commandQueue.ReadFromBuffer(retBuffer, ref ret, false, null);
_commandQueue.Finish();
编辑#2
由 huseyin tugrul buyukisik 找到的解决方案(见下面的评论)。
放置后
matrix1Buffer.Dispose();
matrix2Buffer.Dispose();
retBuffer.Dispose();
最后,一切正常。
缓冲区、内核和命令队列等 OpenCl 资源应在 bound-to 释放其他资源后释放。 Re-creating 不释放会很快耗尽可用插槽。
您在 gpu
的方法中使用了 re-creating 个数组,那是 opencl 缓冲区的范围。完成后,GC 无法跟踪 opencl 的非托管内存区域并导致泄漏,从而导致崩溃。
许多 opencl 实现使用 C++ 绑定,需要 C#、Java 和其他环境的显式释放命令。
当重复的内核执行使用与内核参数完全相同的缓冲区顺序时,也不需要多次 set-argument 部分。