суббота, 31 августа 2013 г.

Сравнение производительности массивов в .NET, часть 2

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/arrays-access-performance/.


В первой части статьи я задался задачей сравнить производительность многомерных массивов. Рассматривались данные в виде двумерного массива N*M, тестировалась скорость доступа к элементу [i,j] при итерировании по всему массиву двумя циклами. Для сравнения было выбрано три варианта: одномерный массив single[i * N + j] и двумерные массивы jagged[i][j], rectangular[i, j]. Изначально у меня получилось, что jagged-версия работает быстрее single версии, но более детальное изучение проблемы показало, что дело может измениться в зависимости от используемых JIT-оптимизаций. Разберёмся с проблемой более подробно.

Рассмотрим методы из бенчмарка с наборами параметров «100»(N=M=100, IterationCount=100000) и «101»(N=M=101, IterationCount=100001) под x86 и x64. На моей машине (Intel Core i7-3632QM CPU 2.20GHz) получились следующие результаты:

  100-x86
Single      : 1012ms
Jagged      :  772ms
Rectangular : 1460ms

  101-x86
Single      : 1036ms
Jagged      :  785ms
Rectangular : 1485ms  

  100-x64
Single      : 544ms
Jagged      : 346ms
Rectangular : 741ms

  101-x64
Single      :  785ms
Jagged      :  793ms
Rectangular : 1050ms

четверг, 29 августа 2013 г.

Об итерировании статичных массивов в .NET, часть 2

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/static-array-iteration/.


В первой части я встретился с весьма интересной ситуацией. Были измерены скорости работы двух методов, в первом из которых считалась сумма элементов массива, ссылка на который хранилась в обычном поле объекта, а во втором — массива, ссылка на который хранилась в статичном поле. Результаты меня удивили: массивы были одинаковые, но второй метод работал ощутимо дольше. Сперва я подумал, что дело в организации скорости доступа к статичным полям, но более детальный анализ ситуации и разговоры с коллегами помогли мне понять, что истинная причина такого поведения намного интересней: для массивов, длина которых кратна 4, JIT использует различные оптимизации в случае обычных и статичных массивов. Давайте разберёмся с ситуацией более детально.

среда, 28 августа 2013 г.

Совершенный код и реальные проекты

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dev/perfect-code-and-real-projects/.


У меня есть проблема — я перфекционист. Я люблю совершенный код. Ведь это не только правильный подход к написанию программ, но и настоящее искусство. От чтения хорошего листинга я получаю не меньше удовольствия, чем от чтения хорошей книги. Проектировать архитектуру большого проекта ничуть не легче, чем проектировать архитектуру большого здания, а в случае хорошей работы — результат не менее прекрасен. Порой меня завораживает то, как изящно переплелись паттерны проектирования в создании совершенной программной системы. Меня восхищает внимание к деталям, когда абсолютно каждый метод настолько прост и понятен, что претендует на место классического примера совершенного кода.

Но, увы, всё это великолепие разбивается о суровую действительность и реальные проекты. Если мы говорим о продакшн-проекте, то пользователей не волнует, насколько красив ваш код и насколько хороша архитектура, их волнует, чтобы проект хорошо работал. Но я всё равно считаю, что в любом случае нужно стремиться писать правильно, просто при этом фанатизма быть не должно. После чтения различных холиваров на тему правильных подходов к написанию кода мне в глаза бросилась одна тенденция: каждый пытается применить означенные подходы не в целом к программированию, а только к своему опыту разработки, к своим проектам. Многие не осознают, что хорошие практики — это не абсолютные правила, которые должны строго соблюдаться в 100% сценариев, это лишь советы о том, как следовало бы поступать в большинстве ситуаций. На каждую хорошую практику всегда можно придумать несколько дюжин примеров, в которых она работать не будет. Но это вовсе не означает, что хорошая практика не такая уж и хорошая, просто её применили не к месту.

Комментировать или не комментировать?

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dev/comments/.


По-настоящему хороший комментарий — тот,
без которого вам удалось обойтись.
© Дядюшка Боб


В последнее время меня стали очень утомлять оживлённые дебаты о том, нужно ли комментировать код. Как правило, по одну сторону баррикад — самоуверенные джуниоры, имеющие непререкаемую позицию вида «А как же его не комментировать, ведь без комментариев непонятно будет!». По другую — умудрённые опытом сеньоры. Они понимают, что если возможно обойтись без комментариев, то «Лучше бы, чёрт возьми, так и сделать!». Наверное, у многих жажда комментировать идёт со студенческой скамьи, когда товарищи преподаватели заставляли комментировать каждую строчку, «чтобы студент лучше разобрался». В реальном проекте не должно быть кучи комментариев, которые только и делают, что засоряют код. Впрочем, я не агитирую вообще не писать комментарии, но если вам удалось написать такой код, который не требует пояснений, то расценивайте это, как свою маленькую победу. Сразу хотелось бы сослаться на нескольких очень умных книжек, на основе которых формировалась моя позиция. Я люблю и уважаю авторов этих работ, полностью разделяя их мнение.

понедельник, 26 августа 2013 г.

Недокументированные ключевые слова C# или превращаем объект в тыкву

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/undocumented-keywords-in-cs/.


Стандартный компилятор C# поддерживает 4 недокументированных ключевых слова: __makeref, __reftype, __refvalue, __arglist. Эти слова даже успешно распознаются в Visual Studio (хотя, ReSharper на них ругается). Они не даром исключены из стандарта — их использование может повлечь серьёзные проблемы с безопасностью. Поэтому не нужно их использовать везде подряд, но в отдельных исключительных случаях они могут пригодится. В этом посте я обсужу предназначение недокументированных команд, рассмотрю вопросы их производительности и научусь превращать объект в тыкву.

воскресенье, 18 августа 2013 г.

Об итерировании статичных массивов в .NET, часть 1

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/static-array-iteration/.


Содержание поста было обновлено: результаты были уточнены, появилась вторая часть с подробным объяснением сложившейся ситуации.

Управляемый подход платформы .NET делает жизнь разработчиков достаточно простой, беря на себя многие рутинные операции. Большую часть времени программист может вообще не вспоминать о технической реализации платформы, сосредоточившись исключительно на логике своего приложения. Но иногда попадаются задачи, критичные по производительности. Существует множество различных подходов к оптимизации кода в таких ситуациях вплоть до переписывания наиболее важных частей кода через неуправляемый код. Однако, зачастую для увеличения скорости приложения достаточно понимать, сколько времени тратится на ту или иную операцию. Знание подобных вещей позволит оптимизировать некоторые методы с помощью достаточно простых модификаций исходного кода.

В этой статье мне хотелось бы поговорить о скорости доступа к массивам, ссылки на которые хранятся в статичных переменных. Дело в том, что в скорость итерирования по ним в зависимости от условий запуска может быть ниже, чем для массива, ссылка на который хранится в обычном поле экземпляра класса или локальной переменной. Рассмотрим пример.

Сравнение производительности массивов в .NET, часть 1

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/arrays-access-performance/.


Платформа .NET поддерживает два способа задания многомерных массивов: прямоугольные (rectangular) и изломанные (jagged). Второй способ по сути представляет собой массив массивов. Это обстоятельство создаёт у многих программистов иллюзию того, что jagged-массивы должны работать медленнее, т.к. обращение к их элементам реализуется через многократные переходы по ссылкам в управляемой куче. Но на самом деле jagged-массивы могут работают быстрее (если речь идёт непосредственно о работе с массивами, а не о их инициализации), ведь они представляют собой комбинацию одномерных (single) массивов, работа с которыми в CLR весьма оптимизирована (за счёт IL-команд newarr, ldelem, ldelema, ldlen, stelem). Другим подходом к представлению многомерных данных является использование одномерного массива с ручным преобразованием координат (в массиве размерности N*M для обращения к элементу [i,j] будем писать [i*M+j]). Если производительности не хватает, то можно использовать неуправляемый код, но этот случай мы сейчас рассматривать не будем, остановимся на трёх вышеозначенных способах. Для замеров времени используется BenchmarkDotNet. Рассмотрим C# код, который замеряет время работы каждого варианта (полный вариант кода: MultidimensionalArrayProgram.cs, тестировать следует в Release mode without debugging). Данные результаты получены в сборке под x64 для процессора Intel Core i7-3632QM CPU 2.20GHz и параметров N=M=100, IterationCount=100000. Исследование вопроса о влиянии используемой архитектуры и параметров запуска на результат бенчмарка можно найти во второй части статьи.

вторник, 13 августа 2013 г.

четверг, 8 августа 2013 г.

Неожиданное место для сборки мусора в .NET

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/gc-native/.


Платформа .NET обеспечивает нас высокоинтеллектуальным сборщиком мусора, который избавляет от рутины ручного управления памятью. И в 95% случаев можно действительно забыть про память и связанные с ней нюансы. Но вот оставшиеся 5% обладают своей спецификой, связанной с неуправляемыми ресурсами, слишком большими объектами и т.д. И тут лучше бы хорошо разбираться в том, как производится сборка мусора. В противном случае вас могут ждать очень неприятные сюрпризы.

Как вы думаете, может ли GC собрать объект до того, как выполнится последний из его методов? Оказывается, может. Правда, для этого необходимо запустить приложение в Release mode и отдельно от студии (without debugging). В этом случае JIT-компилятор сделает определённые оптимизации, в результате которых такая ситуация возможна. Разумеется, делает он это только тогда, когда в оставшемся теле метода нет ссылок на сам объект или его поля. Казалось бы, достаточно невинная оптимизация. Но она может привести к проблемам, если мы имеем дело с неуправляемыми ресурсами: сборка объекта может произойти до того, как закончится операция над неуправляемым объектом, что вполне вероятно повлечёт падение приложения.

среда, 7 августа 2013 г.

Неочевидности в использовании C#-замыканий

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/closures/.


Язык C# даёт нам возможность пользоваться замыканиями — мощным механизмом, который позволяет анонимным методам и лямбдам захватывать свободные переменные в своём лексическом контексте. И в .NET-мире многие программисты очень любят использовать замыкания, но немногие понимают, как они действительно работают. Начнём с простого примера:

public void Run()
{
  int e = 1;
  Foo(x => x + e);
}

Ничего сложного тут не происходит: мы просто «захватили» локальную переменную e в лямбду, которая передаётся в некоторый метод Foo. Посмотрим, во что компилятор развернёт такую конструкцию*:

public void Run()
{
  DisplayClass c = new DisplayClass();
  c.e = 1;  
  Foo(c.Action);
}

private sealed class DisplayClass
{
  public int e;

  public int Action(int x)
  {
    return x + e;
  }
}

вторник, 6 августа 2013 г.

Проблема с FPU при вызове .NET-логики из Delphi

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/delphi-fpu-issue/.


Ситуация: мы пишем основную логику приложения на C#, но есть необходимость использовать её из Delphi. Для этих целей пользуемся COM-обёрткой, которая успешно справляется с поставленной задачей. Целевая функция перед возвращением результата показывает диалоговое WPF-окно, с которым можно сделать что-нибудь полезное. Проверяем на простом примере — всё отлично работает.

Проблема: в некоторых Delphi приложений окно выбрасывает исключение. Но исключение странное: при формировании WPF-окна падает, скажем, выставление ширины некоторого элемента. Но это только в некоторых приложениях. А в остальных — тот же самый код на тех же самых данных отлично работает.

В чём же дело?