SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
Продолжаем говорить о микрооптимизациях .NET-приложений
Этот доклад продолжает тему моего выступления с прошлого DotNext про сложную науку о микрооптимизациях. Вас ждут новые увлекательные истории о том, что же происходит под капотом .NET-программ. Будем обсуждать различия разных C# и JIT компиляторов (Roslyn и RyuJIT в том числе), медитировать на IL и ASM листинги, а также разбираться с особенностями современных CPU.
Этот доклад продолжает тему моего выступления с прошлого 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
19.
GC
Удивительный BCL:
ConditionalWeakTable<TKey, TValue> Class
Enables compilers to dynamically attach object fields to managed objects.
c MSDN
12/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
25.
Benchmarks
Что там может быть сложного?
17/32 Benchmarks
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
43.
Memory
Задача: подсчитать сумму элементов массива
const int N = 1024;
int[,] a = new int[N, N];
24/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