Что делать, если все, что можно уже закэшировано, а код всё ещё тормозит? В этом докладе мы обсудим, как работают некоторые низкоуровневые механизмы .NET и как мы с их помощью можем выиграть драгоценные секунды, когда счет идет на отдельные такты процессора.
17. public static unsafe int IndexOf(ref char searchSpace, char value, int length)
{
if (Sse2.IsSupported)
Determine how many to iterate to get data 16 bytes aligned
SequentialScan:
Iterate byte by byte
if (Avx2.IsSupported)
{
if (not 32 bytes aligned)
Check 16 bytes using SSE2
Iterate by 32 bytes using AVX2
if (more than 16 bytes left)
Check 16 bytes using SSE2
if (not all data iterated)
goto SequentialScan;
}
else if (Sse2.IsSupported)
{
Iterate by 16 bytes using SSE2
if (not all data iterated)
goto SequentialScan:
}
Found:
return offset;
}
Benchmark:
Length | Time |
----- |--------:|-----------:|
Old | 15 | 8.817 ns |
New | 15 | 4.577 ns |
Old | 1024 | 68.530 ns |
New | 1024 | 49.741 ns |
62. Devirtualization
Benchmark:
| Method | Mean | Error | StdDev |
|----------- |----------:|---------:|---------:|
| NonVirtual | 163.38 us | 1.286 us | 1.203 us |
| Virtual | 163.77 us | 1.848 us | 1.729 us |
| Interface | 191.27 us | 1.754 us | 1.641 us |
public class MyList<T> : IReadOnlyList<T>
{
private readonly T[] _data;
public virtual int Count => _data.Length;
public virtual T this[int index] => _data[index];
}
public int Test()
{
var result = 0;
var data = ...
var length = data.Count;
for (var i = 0; i < length; i++)
{
result += data[i];
}
return result;
}
64. Inlining
Noun
in-line expansion (plural in-line expansions)
(software compilation)
The replacement by a compiler of a function call with a copy of
the entire function body.
65. Inlining
void DoStuff()
{
var user = GetUser();
Console.WriteLine(user.Name);
}
class User
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
DotStuff method:
push rbp
mov rbp, rsp
call 0x11849bea0 (GetUser)
mov rdi, rax
call 0x11849f650 (User.get_Name)
mov rdi, rax
call 0x1184a0b70 (WriteLine)
pop rbp
ret
User.get_Name method:
mov rax, qword ptr [rdi + 0x8]
ret
66. Inlining
void DoStuff()
{
var user = GetUser();
Console.WriteLine(user.Name);
}
class User
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
DotStuff method:
push rbp
mov rbp, rsp
call 0x11849bea0 (GetUser)
mov rdi, rax
call 0x11849f650 (User.get_Name)
mov rdi, rax
call 0x1184a0b70 (WriteLine)
pop rbp
ret
DotStuff method (with inlining):
push rbp
mov rbp, rsp
call 0x12541bea0 (GetUser)
mov rdi, qword ptr [rax + 0x8]
call 0x125420b70 (WriteLine)
pop rbp
ret
User.get_Name method:
mov rax, qword ptr [rdi + 0x8]
ret
void DoStuff()
{
Console.WriteLine(GetUser()._name);
}
67. Inlining
Benchmark:
| Method | Mean | Error | StdDev |
|----------- |----------:|---------:|---------:|
| Inlining | 58.27 us | 1.140 us | 1.120 us |
| NonVirtual | 163.38 us | 1.286 us | 1.203 us |
| Virtual | 163.77 us | 1.848 us | 1.729 us |
| Interface | 191.27 us | 1.754 us | 1.641 us |
public class MyList<T> : IReadOnlyList<T>
{
private readonly T[] _data;
public virtual int Count => _data.Length;
public virtual T this[int index] => _data[index];
}
public int Test()
{
var result = 0;
var data = ...
var length = data.Count;
for (var i = 0; i < length; i++)
{
result += data[i];
}
return result;
}
68. Inlining requirements
• Devirtualization / non-virtual call
• No recursion
• Heuristic:
• Inlining is profitable
• Stack size is less than 16 bytes
• IL code is smaller than 16 bytes
• …
69. Inlining requirements
• Devirtualization / non-virtual call
• No recursion
• Heuristic:
• Inlining is profitable
• Stack size is less than 16 bytes
• IL code is smaller than 16 bytes
• …
namespace System
{
public static class Math
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal Min(decimal val1, decimal val2)
{
return decimal.Min(val1, val2);
}
}
}
70. foreach optimization
Benchmark:
| Method | Mean | Error | StdDev |
|-------- |---------:|----------:|----------:|
| List | 2.311 us | 0.0233 us | 0.0218 us |
| IList | 4.392 us | 0.0601 us | 0.0562 us |
public int Test()
{
var result = 0;
IList<int> data = GetData();
foreach (var item in data)
{
result += item;
}
return result;
}