.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進行邊界檢查。
另外還有一個概念是迴圈提升,這個就另說了。
作者:江湖評談
參照:[微軟官方部落格]
文章首發於公眾號【江湖評談】,歡迎大家關注。