相同的代码,与C#,AleaGPU和设备内存的不同行为

时间:2017-10-23 18:17:55

标签: c# .net wpf visual-studio aleagpu

我使用AleaGPU库执行矩阵乘法和类似操作,我似乎无法理解为什么我的代码没有按预期工作。

  

不按预期工作"我的意思是结果矩阵的第一行(或前几行)具有正确的值,其余的行都用0填充,在下面的其他代码示例中使用了相同的代码

功能#1(不起作用):这个由于某种原因不起作用,并且它具有上述行为。这听起来像是我对索引感到困惑,但我没有看到下面三个例子的代码有什么不同,而且我没有得到任何类型的错误(AleaGPU通常会抛出异常时试图访问无效的数组位置。)

public static double[,] Multiply([NotNull] this double[,] m1, [NotNull] double[,] m2)
{
    // Checks
    if (m1.GetLength(1) != m2.GetLength(0)) throw new ArgumentOutOfRangeException("Invalid matrices sizes");

    // Initialize the parameters and the result matrix
    int h = m1.GetLength(0);
    int w = m2.GetLength(1);
    int l = m1.GetLength(1);

    // Execute the multiplication in parallel
    using (DeviceMemory2D<double> m1_device = Gpu.Default.AllocateDevice(m1))
    using (DeviceMemory2D<double> m2_device = Gpu.Default.AllocateDevice(m2))
    using (DeviceMemory2D<double> mresult_device = Gpu.Default.AllocateDevice<double>(h, w))
    {
        // Pointers setup
        deviceptr<double>
            pm1 = m1_device.Ptr,
            pm2 = m2_device.Ptr,
            pmresult = mresult_device.Ptr;

        // Local wrapper function
        void Kernel(int ki)
        {
            // Calculate the current indexes
            int
                i = ki / w,
                j = ki % w;

            // Perform the multiplication
            double sum = 0;
            int im1 = i * l;
            for (int k = 0; k < l; k++)
            {
                // m1[i, k] * m2[k, j]
                sum += pm1[im1 + k] * pm2[k * w + j];
            }
            pmresult[i * w + j] = sum; // result[i, j]
        }

        // Get the pointers and iterate fo each row
        Gpu.Default.For(0, h * w, Kernel);

        // Return the result
        return Gpu.Copy2DToHost(mresult_device);
    }
}

我查看这段代码几个小时试图检查每一行,但我真的不知道它有什么问题。

这项工作很精细,但我看不出第一个与

的差异

public static double[,] MultiplyGpuManaged([NotNull] this double[,] m1, [NotNull] double[,] m2)
{
    // Checks
    if (m1.GetLength(1) != m2.GetLength(0)) throw new ArgumentOutOfRangeException("Invalid matrices sizes");

    // Initialize the parameters and the result matrix
    int h = m1.GetLength(0);
    int w = m2.GetLength(1);
    int l = m1.GetLength(1);
    double[,]
        m1_gpu = Gpu.Default.Allocate(m1),
        m2_gpu = Gpu.Default.Allocate(m2),
        mresult_gpu = Gpu.Default.Allocate<double>(h, w);

    // Execute the multiplication in parallel
    Gpu.Default.For(0, h * w, index =>
    {
        // Calculate the current indexes
        int
            i = index / w,
            j = index % w;

        // Perform the multiplication
        double sum = 0;
        for (int k = 0; k < l; k++)
        {
            sum += m1_gpu[i, k] * m2_gpu[k, j];
        }
        mresult_gpu[i, j] = sum;
    });

    // Free memory and copy the result back
    Gpu.Free(m1_gpu);
    Gpu.Free(m2_gpu);
    double[,] result = Gpu.CopyToHost(mresult_gpu);
    Gpu.Free(mresult_gpu);
    return result;
}

这个工作太精细,我做了这个额外的测试来检查我是否搞砸了第一个函数中的索引(显然它们很好)

public static double[,] MultiplyOnCPU([NotNull] this double[,] m1, [NotNull] double[,] m2)
{
    // Checks
    if (m1.GetLength(1) != m2.GetLength(0)) throw new ArgumentOutOfRangeException("Invalid matrices sizes");

    // Initialize the parameters and the result matrix
    int h = m1.GetLength(0);
    int w = m2.GetLength(1);
    int l = m1.GetLength(1);
    double[,] result = new double[h, w];
    Parallel.For(0, h * w, index =>
    {
        unsafe
        {
            fixed (double* presult = result, pm1 = m1, pm2 = m2)
            {
                // Calculate the current indexes
                int
                    i = index / w,
                    j = index % w;

                // Perform the multiplication
                double sum = 0;
                int im1 = i * l;
                for (int k = 0; k < l; k++)
                {
                    sum += pm1[im1 + k] * pm2[k * w + j];
                }
                presult[i * w + j] = sum;
            }
        }
    });
    return result;
}

我真的不明白我在第一种方法中缺少的东西而且我不明白为什么它不起作用。

提前感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

事实证明,问题是由gpu用于分配2D数组的方法引起的 - 而不是像标准.NET数组那样使用单个连续内存块,但出于性能原因,它在每行的末尾添加了一些填充。

寻址2D gpu阵列的正确方法是使用间距,它表示每行的有效宽度(列+填充)。

这是一个工作代码示例,它只是填充2D gpu数组并将其复制回主机:

const int size = 10;
double[,] matrix_gpu;
using (DeviceMemory2D<double> m_gpu = Gpu.Default.AllocateDevice<double>(size, size))
{
    deviceptr<double> ptr = m_gpu.Ptr;
    int pitch = m_gpu.PitchInElements.ToInt32();
    Gpu.Default.For(0, size, i =>
    {
        for (int j = 0; j < size; j++)
        {
            ptr[i * pitch + j] = i * size + j;
        }
    });
    matrix_gpu = Gpu.Copy2DToHost(m_gpu);
}