我有 2 个函数以 2 种不同的方式实现 uint128 乘法:一种是使用变量,另一种是使用 stackalloc“数组”。
可变版本
public static UInt128 operator *(UInt128 i, UInt128 j) {
ulong I0 = i._uint0; ulong I1 = i._uint1; ulong I2 = i._uint2; ulong I3 = i._uint3;
ulong J0 = j._uint0; ulong J1 = j._uint1; ulong J2 = j._uint2; ulong J3 = j._uint3;
ulong R0 = 0; ulong R1 = 0; ulong R2 = 0; ulong R3 = 0;
if (I0 != 0) {
R0 += I0 * J0;
R1 += I0 * J1;
R2 += I0 * J2;
R3 += I0 * J3;
}
if (I1 != 0) {
R1 += I1 * J0;
R2 += I1 * J1;
R3 += I1 * J2;
}
if (I2 != 0) {
R2 += I2 * J0;
R3 += I2 * J1;
}
R3 += I3 * J0;
R1 += R0 >> 32; R0 &= uint.MaxValue;
R2 += R1 >> 32; R1 &= uint.MaxValue;
R3 += R2 >> 32; R2 &= uint.MaxValue;
R3 &= uint.MaxValue;
return new UInt128((uint)R3, (uint)R2, (uint)R1, (uint)R0);
}
Stackalloc 版本
为了清楚起见[0 + 1]
,[1 + 1]
留下 , 等。无论如何,它们都会被 C# 编译器优化为常量。
public unsafe static UInt128 operator *(UInt128 i, UInt128 j) {
var I = stackalloc ulong[4];
var J = stackalloc ulong[4];
var R = stackalloc ulong[4];
I[0] = i._uint0; I[1] = i._uint1; I[2] = i._uint2; I[3] = i._uint3;
J[0] = j._uint0; J[1] = j._uint1; J[2] = j._uint2; J[3] = j._uint3;
if (I[0] != 0) {
R[0] += I[0] * J[0];
R[0 + 1] += I[0] * J[1];
R[0 + 2] += I[0] * J[2];
R[0 + 3] += I[0] * J[3];
}
if (I[1] != 0) {
R[1] += I[1] * J[0];
R[1 + 1] += I[1] * J[1];
R[1 + 2] += I[1] * J[2];
}
if (I[2] != 0) {
R[2] += I[2] * J[0];
R[2 + 1] += I[2] * J[1];
}
R[3] += I[3] * J[0];
R[1] += R[0] >> 32; R[0] &= uint.MaxValue;
R[2] += R[1] >> 32; R[1] &= uint.MaxValue;
R[3] += R[2] >> 32; R[2] &= uint.MaxValue;
R[3] &= uint.MaxValue;
return new UInt128((uint)R[3], (uint)R[2], (uint)R[1], (uint)R[0]);
}
出于某种原因,使用在 .NET 4.6.1 上运行的 C# 7.2 编译器,在 x86 和 x64(经过优化)上,“变量”版本似乎比“stackalloc”版本快约 20%。尚未检查较新/较旧框架的性能,但怀疑它会相似,所以我的问题不仅仅针对 4.6.1,因为通常情况下stackalloc 速度较慢。
考虑到两个版本分配完全相同的内存量(12 * sizeof(ulong)
)并以相同的顺序执行完全相同的操作,是否有任何理由使 stackalloc 版本变慢?我真的更喜欢通过 stackalloc 而不是变量来处理数组。