.Net7基础类型的优化和循环克隆优化
前言
.Net7里面对于基础类型的优化,是必不可少的。因为这些基础类型基本上都会经常用到,本篇除了基础类型的优化介绍之外,还有一个循环克隆的优化特性,也一并看下。
概括
1.基础类型优化
基础类型的优化不会有些不会涉及ASM,主要是记忆。
一:double.Parse和float.Parse,把某数值转换成double或者float类型,这两个Parse进行了优化。
二:bool.TryParse和bool.TryFormat也进行了性能优化。
假如说有以下代码:
destination[0] = 'T';
destination[1] = 'r';
destination[2] = 'u';
destination[3] = 'e';
四次写操作,写入到destination数组。这个可以一次性写入(单个ulong)来进行性能优化,基准不测:
BinaryPrimitives.WriteUInt64LittleEndian(MemoryMarshal.AsBytes(destination), 0x65007500720054); // "True"
0x65007500720054是内存地址,里面存放了四个char的值。
private bool _value = true;
private char[] _chars = new char[] { 'T', 'r', 'u', 'e' };
[Benchmark] public bool ParseTrue() => bool.TryParse(_chars, out _);
[Benchmark] public bool FormatTrue() => _value.TryFormat(_chars, out _);
三:Enum枚举也进行了性能优化
这里主要是二进制算法和线性算法的综合应用,因为当我们执行枚举的一些方法,比如Enum.IsDefined、Enum.GetName或Enum.ToString的时候。它会搜索一些值,这些值也是存储在数组中的,会使用Array.BinarySearch二进制来搜索。涉及到复杂的算法的时候Array.BinarySearch二进制搜索是可以的,但是如果比较简单的算法则用它相当于杀鸡用牛刀,这里就引入了线性搜索:SpanHelpers.IndexOf。那么何时用线性何时用二进制搜索呢?对于小于或等于32个定义值的枚举用线性,大于的用二进制。
代码如下,benchmark这里就不打印出来了
private DayOfWeek[] _days = Enum.GetValues<DayOfWeek>();
[Benchmark]
public bool AllDefined()
{
foreach (DayOfWeek day in _days)
{
if (!Enum.IsDefined(day))
{
return false;
}
}
return true;
}
Enums在与Nullable和EqualityComparer.Default的配合下也得到了性能提升,因为EqualityComparer.Default缓存了一个从所有对Default的访问中返回的EqualityComparer实例,EqualityComparer
代码如下,Benchmark不测
private DayOfWeek?[] _enums = Enum.GetValues<DayOfWeek>().Select(e => (DayOfWeek?)e).ToArray();
[Benchmark]
[Arguments(DayOfWeek.Saturday)]
public int FindEnum(DayOfWeek value) => IndexOf(_enums, value);
private static int IndexOf<T>(T[] values, T value)
{
for (int i = 0; i < values.Length; i++)
{
if (EqualityComparer<T>.Default.Equals(values[i], value))//这里的T值是IndexOf传过来的,进行的一个性能优化
{
return i;
}
}
return -1;
}
四:Guid的优化
Guid实现将数据分成4个32位的值,并进行4个int的比较。如果当前的硬件支持128位SIMD,实现就会将两个Guid的数据加载为两个向量,并简单地进行一次比较。benchmark不测
private Guid _guid1 = Guid.Parse("0aa2511d-251a-4764-b374-4b5e259b6d9a");
private Guid _guid2 = Guid.Parse("0aa2511d-251a-4764-b374-4b5e259b6d9a");
[Benchmark]
public bool GuidEquals() => _guid1 == _guid2;
五:DateTime.Equals的优化
DateTime.Equals,DateTime是用一个单一的ulong _dateData字段实现的。其中大部分位存储了从1/1/0001 12:00am开始的ticks偏移量,每个tick是100纳秒,并且前两个位描述了DateTimeKind。因此,公共的Ticks属性返回_dateData的值,但前两位被屏蔽掉了,例如:_dateData & 0x3FFFFFFFFFFFFFFF。然后,平等运算符只是将一个DateTime的Ticks与其他DateTime的Ticks进行比较,这样我们就可以有效地得到(dt1._dateData & 0x3FFFFFFFFFFF)==(dt2._dateData & 0x3FFFFFFFFFFF)。然而,作为一个微观的优化,可以更有效地表达为((dt1._dateData ^ dt2._dateData) << 2) == 0。
这里其实是一个细微的优化,但是依然可见优化力度。
.Net6
; Program.DateTimeEquals()
mov rax,[rcx+8]
mov rdx,[rcx+10]
mov rcx,0FFFFFFFFFFFF
and rax,rcx
and rdx,rcx
cmp rax,rdx
sete al
movzx eax,al
ret
; Total bytes of code 34
而在.NET 7上则产生。
; Program.DateTimeEquals()
mov rax,[rcx+8]
mov rdx,[rcx+10]
xor rax,rdx
shl rax,2
sete al
movzx eax,al
ret
; Total bytes of code 22
所以我们得到的不是mov、and、and、cmp,而是xor和shl。
其它还有一些DateTime.Day、DateTime.DayOfYear、DateTime.DayOfYear改进性能。
六:数学API的优化
七:System.Formats.Tar压缩文件库的优化
2.循环克隆优化
循环克隆实际上是通过提前判断是否超出数组边界来进行的一个优化,如果没哟超过数组边界,则快速路就,超过了就慢速路径进行数组边界检查。
private int[] _values = Enumerable.Range(0, 1000).ToArray();
[Benchmark]
[Arguments(0, 0, 1000)]
public int LastIndexOf(int arg, int offset, int count)
{
int[] values = _values;
for (int i = offset + count - 1; i >= offset; i--)
if (values[i] == arg)
return i;
return 0;
}
.Net7 ASM
; Program.LastIndexOf(Int32, Int32, Int32)
sub rsp,28
mov rax,[rcx+8]
lea ecx,[r8+r9+0FFFF]
cmp ecx,r8d
jl short M00_L02
test rax,rax
je short M00_L01
test ecx,ecx
jl short M00_L01
test r8d,r8d
jl short M00_L01
cmp [rax+8],ecx
jle short M00_L01
M00_L00:
mov r9d,ecx
cmp [rax+r9*4+10],edx
je short M00_L03
dec ecx
cmp ecx,r8d
jge short M00_L00
jmp short M00_L02
M00_L01:
cmp ecx,[rax+8]
jae short M00_L04
mov r9d,ecx
cmp [rax+r9*4+10],edx
je short M00_L03
dec ecx
cmp ecx,r8d
jge short M00_L01
M00_L02:
xor eax,eax
add rsp,28
ret
M00_L03:
mov eax,ecx
add rsp,28
ret
M00_L04:
call CORINFO_HELP_RNGCHKFAIL
int 3
; Total bytes of code 98
M00_L00快速路径,M00_L01慢速路径,在M00_L00前面进行了一个判断,如果没有超出数组边界以及其它判断,那么就M00_L01不进行,否则M00_L02进行边界检查。
另外还有一个概念是循环提升,这个就另说了。
结尾
作者:江湖评谈
参照:[微软官方博客]
文章首发于公众号【江湖评谈】,欢迎大家关注。