LegacyJIT-x86 и первый вызов метода
Сегодня я расскажу вам об одном из моих любимых бенчмарков (данный метод не возвращает ничего полезного, он нам нужен только в качестве примера):
[Benchmark]
public string Sum()
{
double a = 1, b = 1;
var sw = new Stopwatch();
for (int i = 0; i < 10001; i++)
a = a + b;
return string.Format("{0}{1}", a, sw.ElapsedMilliseconds);
}
Интересный факт: если вы вызовете Stopwatch.GetTimestamp()
перед первым вызовом метода Sum
, то это увеличит скорость работы метода в несколько раз (фокус работает только для LegacyJIT-x86).
Читать дальше
Visual Studio и ProjectTypeGuids.cs
Это история о том, как я несколько часов пытался открыть проект в Visual Studio. Как-то раз я решил немножко поработать: стянул себе последние коммиты из репозитория, открыл Visual Studio и собрался программировать. Увы, один из моих проектов не открылся, а в окошке Output я увидел странное сообщение:
error : The operation could not be completed.
В Solution Explorer, рядом с названием проекта была надпись “load failed”, а вместо файлов было написано следующее: “The project requires user input. Reload the project for more information.” Хмм, ну ок, я попробовал перегрузить проект. Увы, не помогло, я получил ещё два уже знакомых сообщения об ошибке:
error : The operation could not be completed.
error : The operation could not be completed.
Читать дальше
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;
}
class Program
{
public static unsafe void Main()
{
var myStruct = new MyStruct();
var baseAddress = (int)&myStruct;
var uInt128Adress = (int)&myStruct.UInt128;
Console.WriteLine(uInt128Adress - baseAddress);
Console.WriteLine(Marshal.OffsetOf(typeof(MyStruct), "UInt128"));
}
}
Если вы подумали, что в консоли напечатается два нуля (или просто два одинаковых значения), то вам нужно узнать больше про внутреннее устройство структур в .NET. Ниже представлены результаты выполнения кода в зависимости от рантайма:
MS.NET-x86 | MS.NET-x64 | Mono | |
---|---|---|---|
uInt128Adress - baseAddress | 4 | 8 | 0 |
Marshal.OffsetOf(typeof(MyStruct), "UInt128") | 0 | 0 | 0 |
Чтобы разобраться с ситуацией, нам необходимо узнать больше про blittable-типы.
Читать дальше
RyuJIT RC и свёртка констант
Update: Нижеприведённый материал справедлив для релизной версии RyuJIT (часть .NET Framework 4.6).
Задачка дня: какой из методов быстрее?
public double Sqrt13()
{
return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) +
Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) +
Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13);
}
public double Sqrt14()
{
return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) +
Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) +
Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13) + Math.Sqrt(14);
}
Я померил скорость работы с помощью BenchmarkDotNet для RyuJIT RC (часть .NET Framework 4.6 RC) получил следующие результаты:
// BenchmarkDotNet=v0.7.4.0
// OS=Microsoft Windows NT 6.2.9200.0
// Processor=Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz, ProcessorCount=8
// CLR=MS.NET 4.0.30319.0, Arch=64-bit [RyuJIT]
Common: Type=Math_DoubleSqrtAvx Mode=Throughput Platform=X64 Jit=RyuJit .NET=Current
Method | AvrTime | StdDev | op/s |
------- |--------- |---------- |------------- |
Sqrt13 | 55.40 ns | 0.571 ns | 18050993.06 |
Sqrt14 | 1.43 ns | 0.0224 ns | 697125029.18 |
Как же так? Добавление в выражение одно дополнительного Math.Sqrt
ускорило метод в 40 раз! Давайте разберёмся.
Читать дальше
Размотка маленьких циклов в разных версиях JIT
Вопрос дня: что выведет нижеприведённый код?
struct Point
{
public int X;
public int Y;
}
static void Print(Point p)
{
Console.WriteLine(p.X + " " + p.Y);
}
static void Main()
{
var p = new Point();
for (p.X = 0; p.X < 2; p.X++)
Print(p);
}
Правильный ответ: зависит. В JIT-x86 под CLR2 был баг, который портил эту замечательную программу. А проблема кроется в оптимизации, которая назвается раскрутка маленького цикла. Тема интересная, давайте обсудим её подробно.
Читать дальше
RyuJIT CTP5 и размотка циклов
Уже скоро нам будет доступен RyuJIT, JIT-компилятор следующего поколения для .NET-приложений. Microsoft любит рассказывать нам о преимуществах использования SIMD и сокращением времени JIT-компиляции. Но что можно сказать о базовых оптимизациях кода, за которые обычно отвечает компилятор? Сегодня мы поговорим о такой оптимизации как размотка (раскрутка) цикла. Если кратко, то это оптимизации кода вида
for (int i = 0; i < 1024; i++)
Foo(i);
превращается в
for (int i = 0; i < 1024; i += 4)
{
Foo(i);
Foo(i + 1);
Foo(i + 2);
Foo(i + 3);
}
Подобный подход может заметно увеличить производительность вашего кода. Итак, как же обстоят дела с раскруткой цикла в .NET?
Читать дальше
Определение версии JIT в рантайме
Иногда мне в моих маленьких C#-экспериментах нужно определять версию используемого JIT-компилятора. Понятно, что её можно определить заранее исходя из окружения. Но порой мне хочется знать её в рантайме, чтобы выполнять специфичный код для текущего JIT-компилятора. Строго говоря, я хочу получать значение из следующего перечисления:
public enum JitVersion
{
Mono, MsX86, MsX64, RyuJit
}
Я могу легко определить, что работаю под Mono, по наличию класса Mono.Runtime
. Если это не так, то можно считать, что мы работаем с JIT от Microsoft. JIT-x86 легко узнать с помощью IntPtr.Size == 4
. А вот чтобы отличить старый JIT-x64 от нового RyuJIT необходимо немного призадуматься. Далее я покажу, как это можно сделать с помощью бага JIT-x64, который я описывал в предыдущем посте.
Читать дальше
История про баг в JIT-x64
Можете ли вы сказать, что выведет следующий код для step=1
?
public void Foo(int step)
{
for (int i = 0; i < step; i++)
{
bar = i + 10;
for (int j = 0; j < 2 * step; j += step)
Console.WriteLine(j + 10);
}
}
Если вы назвали конкретные числа, то ошиблись. Правильный ответ: зависит. Заголовок подсказывает нам, что под x64 программа может вести себя не так, как мы от неё ожидаем.
Читать дальше
История про инлайнинг под JIT-x86 и starg
Порой можно узнать много интересного во время чтения исходников .NET. Взглянем на конструктор типа Decimal
из .NET Reference Source (mscorlib/system/decimal.cs,158):
// Constructs a Decimal from an integer value.
//
public Decimal(int value) {
// JIT today can't inline methods that contains "starg" opcode.
// For more details, see DevDiv Bugs 81184: x86 JIT CQ: Removing the inline striction of "starg".
int value_copy = value;
if (value_copy >= 0) {
flags = 0;
}
else {
flags = SignMask;
value_copy = -value_copy;
}
lo = value_copy;
mid = 0;
hi = 0;
}
В комментарии сказано, что если метод содержит IL-опкод starg, то он не может быть заинлайнен под x86. Любопытно, не правда ли?
Читать дальше
Как я блог на кренделёк переводил
Наконец-то у меня дошли руки, и я сделал себе статический блог. Раньше я писал посты в aakinshin.blogspot.ru на Google Blogger aka blogspot, но много чего мне в нём не нравилось, отбивалось всё желание писать. Теперь всё работает через GitHub, писать посты можно в MarkDown, шаблоны делать через Razor, кастомизировать всё по своему вкусу. И мультиленг нормальный =). Под катом можно найти техническую информацию.
Читать дальше