我正在尝试添加到.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)
)和性能通过引用传递大值类型而受益。
我在这里假设您使用的是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
后跟ldnull
的效果相同。与
stind.ref
不同,Newobj
不调用构造方法。initobj
用于初始化值类型,而Initobj
用于分配和初始化对象。换句话说,
newobj
是使用initobj
时得到的,它是一个非常简单的初始化,只是将存储位置清零。它应该足够快,无论如何在本地或通过返回值完成分配,然后再将结果复制回原始变量,都比分配对象的整个新副本所需的开销明显更少。
话虽如此,这很大程度上取决于您将如何调用该方法的上下文。虽然您说您想保留内联声明的便利性,但对我来说尚不清楚,为什么您希望这样做对于显然对性能至关重要的方法能够使用SSE功能和不安全的代码。使用内联声明,您必须在每次调用时都必须重新初始化变量。
如果实际上是以性能要求严格的方式调用此方法,那么对我而言,这意味着它在一个循环中被调用很多次,甚至可能数百万次或更多。在这种情况下,您可能更喜欢default(Matrix)
选项,您可以在循环外初始化变量,然后只为每个调用重用该变量,而不是为每个调用重新声明新变量。