使用内在函数时如何避免`out`参数错误?

问题描述 投票:2回答:1

我正在尝试添加到.NET Core 3.0的新硬件内在函数,特别是为了加快对矩阵的操作。对于矩阵加法,我有一个函数,该函数将两个4x4 float矩阵作为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传递的整个要点。

我意识到,如果我将输出矩阵传递为ref,或者我刚从函数返回了一个矩阵实例,这将起作用,但是最好保留有用的内联语法(Matrix.Add(l, r, out Matrix o))和性能通过引用传递大值类型而受益。

c# .net-core intrinsics out
1个回答
2
投票

我在这里假设您使用的是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

反过来将只是initialize the block of memory to 0 values

0指令将由压入地址(类型initobjnative int&)指定的值类型的每个字段初始化为空引用或适当原始类型的0。调用此方法后,实例就可以调用构造方法了。如果*是引用类型,则此指令与typeTok后跟ldnull的效果相同。

stind.ref不同,Newobj不调用构造方法。 initobj用于初始化值类型,而Initobj用于分配和初始化对象。

换句话说,newobj是使用initobj时得到的,它是一个非常简单的初始化,只是将存储位置清零。它应该足够快,无论如何在本地或通过返回值完成分配,然后再将结果复制回原始变量,都比分配对象的整个新副本所需的开销明显更少。

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

如果实际上是以性能要求严格的方式调用此方法,那么对我而言,这意味着它在一个循环中被调用很多次,甚至可能数百万次或更多。在这种情况下,您可能更喜欢default(Matrix)选项,您可以在循环外初始化变量,然后只为每个调用重用该变量,而不是为每个调用重新声明新变量。

© www.soinside.com 2019 - 2024. All rights reserved.