Этот доклад продолжает тему моего выступления с прошлого DotNext про сложную науку о микрооптимизациях. Вас ждут новые увлекательные истории о том, что же происходит под капотом .NET-программ. Будем обсуждать различия разных C# и JIT компиляторов (Roslyn и RyuJIT в том числе), медитировать на IL и ASM листинги, а также разбираться с особенностями современных CPU.
3. Про что будем разговаривать?
Не будет:
• Универсальных способов оптимизации
• Скучной теории
• Подробного устройства .NET, GC, JIT, CPU
2/32
4. Про что будем разговаривать?
Не будет:
• Универсальных способов оптимизации
• Скучной теории
• Подробного устройства .NET, GC, JIT, CPU
Будет:
• Много весёлых историй1
1
Все истории основаны на реальных событиях
2/32
12. Примеры
struct Bar {}
struct Bar<T> {}
Foo<int, int>(1);
Foo<long, long>(2);
Foo<Bar, Bar>(new Bar());
Foo<Bar<int>, Bar<int>>(new Bar<int>());
Foo<Bar<IList>, Bar<IList>>(new Bar<IList>());
Но не сможет ли JIT нам помочь?
7/32 Boxing
13. Иногда сможет
Foo<int, int>(1);
Foo<long, long>(2);
Foo<Bar, Bar>(new Bar());
Foo<Bar<int>, Bar<int>>(new Bar<int>());
⇓
mov eax,ecx ; (С точностью до регистра)
ret
8/32 Boxing
16. GC
class Foo
{
public object Bar { get; set; }
}
var foo = new Foo() { Bar = new object() };
// Long live the Bar!
GC.KeepAlive(foo);
11/32 GC
17. GC
class Foo
{
public object Bar { get; set; }
}
var foo = new Foo() { Bar = new object() };
// Long live the Bar!
GC.KeepAlive(foo);
Уважаемые знатоки, внимание, вопрос:
Может ли string удерживать ссылку на object?
11/32 GC
18. GC
class Foo
{
public object Bar { get; set; }
}
var foo = new Foo() { Bar = new object() };
// Long live the Bar!
GC.KeepAlive(foo);
Уважаемые знатоки, внимание, вопрос:
Может ли string удерживать ссылку на object?
var foo = "DotNext";
var bar = new object();
BlackBox(); // Что же находится в чёрном ящике?
// Long live the Bar!
GC.KeepAlive(foo);
11/32 GC
20. GC
Удивительный BCL:
ConditionalWeakTable<TKey, TValue> Class
Enables compilers to dynamically attach object fields to managed objects.
c MSDN
А давайте немножко пошалим:
var foo = "DotNext";
var bar = new object();
var cwt = new ConditionalWeakTable<string, object>();
cwt.Add(foo, bar);
String.Intern(foo);
// foo держит ссылку на bar
GC.KeepAlive(cwt);
12/32 GC
26. Benchmarks
Что там может быть сложного?
// Нужно замерить время?
public double WithoutStopwatch()
{
double a = 1, b = 1;
for (int i = 0; i < 100; i++)
a = a + b;
return a;
}
17/32 Benchmarks
27. Benchmarks
Что там может быть сложного?
// Нужно замерить время?
public double WithoutStopwatch()
{
double a = 1, b = 1;
for (int i = 0; i < 100; i++)
a = a + b;
return a;
}
// У нас же есть Stopwatch!
public double WithStopwatch()
{
double a = 1, b = 1;
var sw = Stopwatch.Start();
for (int i = 0; i < 100; i++)
a = a + b;
Print(sw.ElapsedMilliseconds);
return a;
}
17/32 Benchmarks
28. Benchmarks
Что там может быть сложного?
// Нужно замерить время?
public double WithoutStopwatch()
{
double a = 1, b = 1;
for (int i = 0; i < 100; i++)
a = a + b;
return a;
}
// У нас же есть Stopwatch!
public double WithStopwatch()
{
double a = 1, b = 1;
var sw = Stopwatch.Start();
for (int i = 0; i < 100; i++)
a = a + b;
Print(sw.ElapsedMilliseconds);
return a;
}
WithoutStopwatch WithStopwatch
∼100ns ∼350ns
17/32 Benchmarks
29. Benchmarks
Что там может быть сложного?
// Нужно замерить время?
public double WithoutStopwatch()
{
double a = 1, b = 1;
for (int i = 0; i < 100; i++)
a = a + b;
return a;
}
// У нас же есть Stopwatch!
public double WithStopwatch()
{
double a = 1, b = 1;
var sw = Stopwatch.Start();
for (int i = 0; i < 100; i++)
a = a + b;
Print(sw.ElapsedMilliseconds);
return a;
}
WithoutStopwatch WithStopwatch
∼100ns ∼350ns
; a + b
fld1
faddp st(1),st
; a + b
fld1
fadd qword ptr [ebp-0Ch]
fstp qword ptr [ebp-0Ch]
17/32 Benchmarks
31. RyuJIT
Если бы вы были JIT-компилятором,
то как бы вы скомпилировали следующий код?
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong RotateRight64(ulong value)
{
return (value >> 1) | (value << 63);
}
19/32 RyuJIT
32. RyuJIT
Если бы вы были JIT-компилятором,
то как бы вы скомпилировали следующий код?
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong RotateRight64(ulong value)
{
return (value >> 1) | (value << 63);
}
Может быть так?
mov rdx,rax ; value
shr rdx,1 ; value >> 1
shl rax,3Fh ; value << 63
or eax,edx ; (value >> 1) | (value << 63)
19/32 RyuJIT
33. RyuJIT
Если бы вы были JIT-компилятором,
то как бы вы скомпилировали следующий код?
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong RotateRight64(ulong value)
{
return (value >> 1) | (value << 63);
}
Может быть так?
mov rdx,rax ; value
shr rdx,1 ; value >> 1
shl rax,3Fh ; value << 63
or eax,edx ; (value >> 1) | (value << 63)
Или так?
ror rax,1
19/32 RyuJIT
37. Blittable
Загадка для любителей помаршалить:
[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
[FieldOffset(0)] public ulong Value1;
[FieldOffset(8)] public ulong Value2;
}
22/32 Blittable
38. Blittable
Загадка для любителей помаршалить:
[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
[FieldOffset(0)] public ulong Value1;
[FieldOffset(8)] public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
public UInt128 UInt128;
public char Char;
}
22/32 Blittable
39. Blittable
Загадка для любителей помаршалить:
[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
[FieldOffset(0)] public ulong Value1;
[FieldOffset(8)] public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
public UInt128 UInt128;
public char Char;
}
var myStruct = new MyStruct();
var baseAddress = (int)&myStruct;
var uInt128Address = (int)&myStruct.UInt128;
22/32 Blittable
40. Blittable
Загадка для любителей помаршалить:
[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
[FieldOffset(0)] public ulong Value1;
[FieldOffset(8)] public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
public UInt128 UInt128;
public char Char;
}
var myStruct = new MyStruct();
var baseAddress = (int)&myStruct;
var uInt128Address = (int)&myStruct.UInt128;
Console.WriteLine(uInt128Address - baseAddress); // ???
Console.WriteLine(Marshal.OffsetOf(typeof(MyStruct), "UInt128")); // ???
22/32 Blittable
41. Blittable
Загадка для любителей помаршалить:
[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
[FieldOffset(0)] public ulong Value1;
[FieldOffset(8)] public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
public UInt128 UInt128;
public char Char;
}
var myStruct = new MyStruct();
var baseAddress = (int)&myStruct;
var uInt128Address = (int)&myStruct.UInt128;
Console.WriteLine(uInt128Address - baseAddress); // ???
Console.WriteLine(Marshal.OffsetOf(typeof(MyStruct), "UInt128")); // ???
MS.NET-x86 MS.NET-x64 Mono
Address 4 8 0
Marshal 0 0 0
22/32 Blittable
42. Memory
Event Latency Scaled
1 CPU cycle 0.3 ns 1 s
Level 1 cache access 0.9 ns 3 s
Level 2 cache access 2.8 ns 9 s
Level 3 cache access 12.9 ns 43 s
Main memory access 120 ns 6 min
Solid-state disk I/O 50-150 µs 2-6 days
Rotational disk I/O 1-10 ms 1-12 months
Internet: SF to NYC 40 ms 4 years
Internet: SF to UK 81 ms 8 years
Internet: SF to Australia 183 ms 19 years
OS virtualization reboot 4 s 423 years
SCSI command time-out 30 s 3000 years
Hardware virtualization reboot 40 s 4000 years
Physical system reboot 5 m 32 millenia
c Systems Performance: Enterprise and the Cloud
23/32 Memory
44. Memory
Задача: подсчитать сумму элементов массива
const int N = 1024;
int[,] a = new int[N, N];
[Benchmark]
public double Sum_ij()
{
var sum = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += a[i, j];
return sum;
}
24/32 Memory
45. Memory
Задача: подсчитать сумму элементов массива
const int N = 1024;
int[,] a = new int[N, N];
[Benchmark]
public double Sum_ij()
{
var sum = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += a[i, j];
return sum;
}
[Benchmark]
public double Sum_ji()
{
var sum = 0;
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += a[i, j];
return sum;
}
24/32 Memory
46. Memory
Задача: подсчитать сумму элементов массива
const int N = 1024;
int[,] a = new int[N, N];
[Benchmark]
public double Sum_ij()
{
var sum = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += a[i, j];
return sum;
}
[Benchmark]
public double Sum_ji()
{
var sum = 0;
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += a[i, j];
return sum;
}
Sum_ij() Sum_ji()
∼1.5ms ∼9ms
∗
LegacyJIT-x86, i7-4702MQ CPU @ 2.20GHz
24/32 Memory
49. stackalloc
for (int i = 0; i < 10000000; i++)
{
// Alloc class on heap
var hello = new HelloClassOnStack(random);
result += hello.Compute(i);
}
vs
for (int i = 0; i < 10000000; i++)
{
// Alloc class on stack
var hello = stackalloc HelloClassOnStack(random);
result += hello.Compute(i);
}
27/32 stackalloc
50. stackalloc
for (int i = 0; i < 10000000; i++)
{
// Alloc class on heap
var hello = new HelloClassOnStack(random);
result += hello.Compute(i);
}
vs
for (int i = 0; i < 10000000; i++)
{
// Alloc class on stack
var hello = stackalloc HelloClassOnStack(random);
result += hello.Compute(i);
}
Time GC Collect
stack ∼400ms 0
heap ∼5000ms 100+
27/32 stackalloc
52. Branch prediction
const int N = 32767;
int[] sorted, unsorted; // random numbers [0..255]
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}
29/32 Branch prediction
53. Branch prediction
const int N = 32767;
int[] sorted, unsorted; // random numbers [0..255]
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}
[Benchmark]
public int Sorted()
{
return Sum(sorted);
}
[Benchmark]
public int Unsorted()
{
return Sum(unsorted);
}
29/32 Branch prediction
54. Branch prediction
const int N = 32767;
int[] sorted, unsorted; // random numbers [0..255]
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
if (data[i] >= 128)
sum += data[i];
return sum;
}
[Benchmark]
public int Sorted()
{
return Sum(sorted);
}
[Benchmark]
public int Unsorted()
{
return Sum(unsorted);
}
Sorted Unsorted
∼20µs ∼150µs
∗
LegacyJIT-x86, i7-4702MQ CPU @ 2.20GHz
29/32 Branch prediction
55. Branch prediction
Branchless version:
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
{
// if (data[i] >= 128)
// sum += data[i];
int t = (data[i] - 128) >> 31;
sum += ~t & data[i];
}
return sum;
}
30/32 Branch prediction
56. Branch prediction
Branchless version:
private static int Sum(int[] data)
{
int sum = 0;
for (int i = 0; i < N; i++)
{
// if (data[i] >= 128)
// sum += data[i];
int t = (data[i] - 128) >> 31;
sum += ~t & data[i];
}
return sum;
}
Sorted Unsorted
Branch ∼20µs ∼150µs
Branchless ∼30µs ∼30µs
∗
LegacyJIT-x86, i7-4702MQ CPU @ 2.20GHz
30/32 Branch prediction