我进行了一些优化。
public unsafe uint ParseUint2(string text)
{
fixed (char* c = text)
{
Vector128<ushort> raw = Sse3.LoadDquVector128((ushort*)c);
raw = Sse2.ShiftLeftLogical128BitLane(raw, (byte)(8 - text.Length << 1));
Vector128<ushort> digit0 = Vector128.Create('0');
raw = Sse2.SubtractSaturate(raw, digit0);
Vector128<short> mul0 = Vector128.Create(10, 1, 10, 1, 10, 1, 10, 1);
Vector128<int> res = Sse2.MultiplyAddAdjacent(raw.AsInt16(), mul0);
Vector128<int> mul1 = Vector128.Create(1000000, 10000, 100, 1);
res = Sse41.MultiplyLow(res, mul1);
res = Ssse3.HorizontalAdd(res, res);
res = Ssse3.HorizontalAdd(res, res);
return (uint)res.GetElement(0);
}
}
使用vphaddd
可以减少类型转换和最终计算的数量,因此速度提高了约10%。
但是...imm8
必须是编译时常量。这意味着您不能在imm8
作为参数的地方使用变量。否则,JIT编译器不会为该操作生成内部指令。它将在此处进行外部方法调用
(也许有一些解决方法)。感谢@PeterCordes的帮助。
与上面的代码相比,无论text.Length
如何,这个怪兽都没有显著提高速度。
public unsafe uint ParseUint3(string text)
{
fixed (char* c = text)
{
Vector128<ushort> raw = Sse3.LoadDquVector128((ushort*)c);
switch (text.Length)
{
case 0: raw = Vector128<ushort>.Zero; break;
case 1: raw = Sse2.ShiftLeftLogical128BitLane(raw, 14); break;
case 2: raw = Sse2.ShiftLeftLogical128BitLane(raw, 12); break;
case 3: raw = Sse2.ShiftLeftLogical128BitLane(raw, 10); break;
case 4: raw = Sse2.ShiftLeftLogical128BitLane(raw, 8); break;
case 5: raw = Sse2.ShiftLeftLogical128BitLane(raw, 6); break;
case 6: raw = Sse2.ShiftLeftLogical128BitLane(raw, 4); break;
case 7: raw = Sse2.ShiftLeftLogical128BitLane(raw, 2); break;
};
Vector128<ushort> digit0 = Vector128.Create('0');
raw = Sse2.SubtractSaturate(raw, digit0);
Vector128<short> mul0 = Vector128.Create(10, 1, 10, 1, 10, 1, 10, 1);
Vector128<int> res = Sse2.MultiplyAddAdjacent(raw.AsInt16(), mul0);
Vector128<int> mul1 = Vector128.Create(1000000, 10000, 100, 1);
res = Sse41.MultiplyLow(res, mul1);
res = Ssse3.HorizontalAdd(res, res);
res = Ssse3.HorizontalAdd(res, res);
return (uint)res.GetElement(0);
}
}
再次提到,@PeterCordes不让我写慢代码。下面的版本有两个改进。现在字符串已经加载并移位,然后通过相同的偏移减去移位掩码。这避免了使用变量计数时
ShiftLeftLogical128BitLane
的缓慢回退。
第二个改进是用
pshufd
+
paddd
替换
vphaddd
。
public unsafe uint ParseUint4(string text)
{
const string mask = "\xffff\xffff\xffff\xffff\xffff\xffff\xffff\xffff00000000";
fixed (char* c = text, m = mask)
{
Vector128<ushort> raw = Sse3.LoadDquVector128((ushort*)c - 8 + text.Length);
Vector128<ushort> mask0 = Sse3.LoadDquVector128((ushort*)m + text.Length);
raw = Sse2.SubtractSaturate(raw, mask0);
Vector128<short> mul0 = Vector128.Create(10, 1, 10, 1, 10, 1, 10, 1);
Vector128<int> res = Sse2.MultiplyAddAdjacent(raw.AsInt16(), mul0);
Vector128<int> mul1 = Vector128.Create(1000000, 10000, 100, 1);
res = Sse41.MultiplyLow(res, mul1);
Vector128<int> shuf = Sse2.Shuffle(res, 0x1b);
res = Sse2.Add(shuf, res);
shuf = Sse2.Shuffle(res, 0x41);
res = Sse2.Add(shuf, res);
return (uint)res.GetElement(0);
}
}
比初始方案快两倍,至少在我的Haswell i7上是这样。
int.Parse()
吗? - JAlexpmaddubsw
将字节相乘并水平加入到单词(shorts)中。https://www.felixcloutier.com/x86/pmaddubsw。但我认为你的数据是以UTF-16开头的,所以不用考虑了。相关(针对UTF-8 / ASCII):如何使用SIMD实现atoi? 检测数字的结尾并相应地进行洗牌,因此与仅有几个数字的标量相比,速度可能会慢得多。 - Peter Cordes