首页 > 解决方案 > 使用内在函数时如何避免“out”参数错误?

问题描述

我正在尝试添加到 .NET Core 3.0 中的新硬件内在函数,特别是为了加速矩阵上的操作。对于矩阵加法,我有一个函数,它将两个 4x4float矩阵作为in参数,第三个out矩阵用于存储结果。它使用 SSE 128 位向量内在函数将结果相加并存储在输出中:

public unsafe static void Add(in Matrix l, in Matrix r, out Matrix o)
{
    fixed (float* lp = &l.m00, rp = &r.m00, op = &o.m00)
    {
        var c1 = Sse.Add(Sse.LoadVector128(lp + 0),  Sse.LoadVector128(rp + 0));
        var c2 = Sse.Add(Sse.LoadVector128(lp + 4),  Sse.LoadVector128(rp + 4));
        var c3 = Sse.Add(Sse.LoadVector128(lp + 8),  Sse.LoadVector128(rp + 8));
        var c4 = Sse.Add(Sse.LoadVector128(lp + 12), Sse.LoadVector128(rp + 12));
        Sse.Store(op + 0,  c1);
        Sse.Store(op + 4,  c2);
        Sse.Store(op + 8,  c3);
        Sse.Store(op + 12, c4);
    }
}

现在显然 C# 编译器对此存在问题,因为它无法判断输出矩阵是否被写入,因此它会生成函数无法返回的错误,直到o变量被分配。我的问题是是否有任何方法可以解决这个问题,而不必在执行内在操作之前分配给变量,例如o = default;函数中的第一行。

我最初考虑的内容是:

var op = stackalloc float[16];
fixed (float* lp = &l.m00, rp = &r.m00)
{
...
}
o = *(Matrix*)op;

但意识到这并不能避免复制结构,这消除了将矩阵作为out.

我意识到,如果我将输出 Matrix 作为替代,或者如果我只是从ref函数返回一个矩阵实例,这将起作用,但是保留有用的内联语法(Matrix.Add(l, r, out Matrix o)参考。

标签: c#.net-coreintrinsicsout

解决方案


我在这里假设您使用的Matrix类型是struct. 显然,如果它是引用类型,那么您的方法实际上必须先初始化参数值,然后才能使用它,因此您的代码并没有向我表明它是值类型。

不能使 C# 编译器忽略编译时错误。out在方法返回之前不初始化参数是编译时错误。所以你被卡住了。

也就是说,我认为这不应该是一个重大的困难。你可以这样写你的方法:

public unsafe static void Add(in Matrix l, in Matrix r, out Matrix o)
{
    o = default(Matrix);

    fixed (float* lp = &l.m00, rp = &r.m00, op = &o.m00)
    {
        var c1 = Sse.Add(Sse.LoadVector128(lp + 0),  Sse.LoadVector128(rp + 0));
        var c2 = Sse.Add(Sse.LoadVector128(lp + 4),  Sse.LoadVector128(rp + 4));
        var c3 = Sse.Add(Sse.LoadVector128(lp + 8),  Sse.LoadVector128(rp + 8));
        var c4 = Sse.Add(Sse.LoadVector128(lp + 12), Sse.LoadVector128(rp + 12));
        Sse.Store(op + 0,  c1);
        Sse.Store(op + 4,  c2);
        Sse.Store(op + 8,  c3);
        Sse.Store(op + 12, c4);
    }
}

这将编译成这样的东西(Matrix为了示例,我选择了一种任意类型......它显然不是你正在使用的类型,但基本前提是相同的):

IL_0000:  ldarg.0
IL_0001:  initobj    System.Windows.Media.Matrix

这反过来将简单地将内存块初始化为0

initobj指令将由推送地址(类型 、 或 )指定的值类型的每个字段初始化native int&*引用或相应原始类型的 0。调用此方法后,实例已准备好调用构造方法。如果是引用类型,则该指令与后跟的typeTok效果相同。ldnullstind.ref

不像Newobjinitobj不调用构造方法。Initobj用于初始化值类型,而newobj用于分配和初始化对象。

换句话说,initobj当您使用 时,您会得到 ,这default(Matrix)是一个非常简单的初始化,只是将内存位置归零。它应该足够快,并且在任何情况下显然都比分配对象的全新副本然后将结果复制回原始变量(无论是在本地完成还是通过返回值完成)的开销要小。

综上所述,这在很大程度上取决于您将如何调用该方法的上下文。虽然您说您希望保留内联声明的便利性,但我不清楚为什么您会想要一个明显对性能至关重要以使用 SSE 功能和不安全代码的方法。使用内联声明,您必须在每次调用时重新初始化变量。

如果这个方法实际上是以性能关键的方式被调用的,那么对我来说这意味着它在一个循环中被调用了很多次,可能是数百万次或更多。在这种情况下,您可能更喜欢该ref选项,您可以在循环外初始化变量,然后为每次调用重用该变量,而不是为每次调用重新声明一个新变量。


推荐阅读