вторник, 21 февраля 2017 г.

Оптимизация типового пути исполнения

Роясь недавно в исходниках TPL Dataflow я заметил некоторый паттерн: многие функции разбиты на две. Основная называется обычным образом, AddElement или что-то в этом роде, а вторая – AddElement_Slow.

При этом мне очень понравилась причина, по которой это дело делалось: эта оптимизация позволяет заинлайнить метод в типовом кейсе. Казалось бы, а есть ли в этом толк? И, как оказалось, что есть (я бы удивился, если бы камрад Тауб решил бы использовать подобную оптимизацию не проверив, что она имеет смысл).

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

Ну, и подробности, у меня в англоязычном посте: “A common execution path optimization”.

вторник, 7 февраля 2017 г.

О «маркировке» объекта во время сборки мусора или Рихтер был не прав

Сегодня речь пойдет о бесполезной, с практической точки зрения, информации о внутренностях CLR и сборщика мусора.

Многие из нас изучали внутренности .NET и CLR по книге Джеффри Рихтера “CLR via C#”. Книга просто замечательной, глубокая, детальная и очень точная. Но, как это обычно бывает, даже Рихтер иногда ошибается (пусть и в деталях).

Вот цитата:

When the CLR starts a GC, the CLR first suspends all threads in the process. This prevents threads from accessing objects and changing their state while the CLR examines them. Then, the CLR performs what is called the marking phase of the GC. First, it walks through all the objects in the heap setting a bit (contained in the sync block index field) to 0. This indicates that all objects should be deleted. Then, the CLR looks at all active roots to see which objects they refer to. This is what makes the CLR’s GC a reference tracking GC. If a root contains null, the CLR ignores the root and moves on to examine the next root.

Any root referring to an object on the heap causes the CLR to mark that object. Marking an object means that the CLR sets the bit in the object’s sync block index to 1. When an object is marked, the CLR examines the roots inside that object and marks the objects they refer to. If the CLR is about to mark an already-marked object, then it does not examine the object’s fields again. This prevents an infinite loop from occurring in the case where you have a circular reference.

И что же здесь не так?

пятница, 3 февраля 2017 г.

О “вреде” книг: напутствие любому программисту

Недавно наткнулся на любопытную статью под названием «О вреде книг: напутствие начинающему программисту». Идея в статье простая: книги – это скорее опасно, и лучше практика с пополнением теории по ходу дела, да и образование современное – ни к черту.

Мне сложно судить о современном программистском образовании в России/Украине (эта тема также поднимается в статье). У меня самого нет специализированного образования (я по образованию «специалист» в области систем автоматизированного управления), да и с момента получения оного прошло уже довольно много времени (19 лет с момента поступления в университет). Но мне явно есть что сказать по поводу самообразования и использования книг в этом процессе.

Как и любым инструментом, книгами нужно пользоваться правильно. И дело тут не столько в книгах, сколько способностях мозга усваивать новую информацию и в подходах, которые помогут сделать этот процесс наиболее эффективным.

среда, 1 февраля 2017 г.

Исследуем new() ограничение в C#

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

Поэтому я решил, что вместо перевода или простых ссылок, я буду делать здесь довольно развернутые анонсы, с превьюхой англоязычного поста. Так что эти публикации можно будет рассматривать в виде таких себе «трейлеров», да и обсуждать здесь на великом и могучем.

Ну а теперь, к теме сегодняшней публикации.

Я уже несколько раз затрагивал вопрос реализации одной довольно простой возможности языка C# - ограничения обобщений new(), что она, дескать, реализована через Activator.CreateInstance (да и то, не всегда;), подробности – в оригинале!).

Проблем у этого аспекта аж две (что и делает new() ограничение выдающейся дырявой абстракцией): низкая производительность и хитрость с обработкой исключений.

Так вот, у нас на проекте, активное использование new T() весьма быстро вылезло в профилировщике, было починено с весьма заметным приростом end-to-end времени исполнения. Там мы прикрутили простое решение на основе деревьев выражения и про это забыли.

А не так давно на ru.stackoverflow.com был задан вопрос по поводу кодогенерации и примеров ее применения, что дало дополнительную почву для размышлений на эту же тему. В результате были перекопаны следующие вещи, чтобы добиться эффективности кастомного активатора равных вызову делегата вида () => new CustomNode():

  • Как именно реализован активатор (с его кэшами, багами в кэше, и довольно необычным способом создания пустого экземпляра).
  • Как именно компилируются деревья выражений и почему получаемый в результате делегат медленнее рукописного.
  • Как скомпилировать выражение в динамический метод руками.
  • Как внутри устроены обобщения и почему для ссылочных типов вызов одного обобщенного метода из другого имеет ненулевые накладные расходы.

В результате работы над постом, была получена обобщенная фабрика, эффективность которой равна эффективности делегата, создающего конкретный экземпляр. Что, как мне кажется, весьма интересный результат;)

Понятное дело, что подробности – по ссылке: Dissecting the new() constraint in C#: a perfect example of a leaky abstraction.

З.Ы. Я надеюсь, что читать такое введение интереснее, чем просто увидеть ссылку.

З.Ы.Ы. Пожелания, предложения и все такое, всячески приветствуется.

четверг, 19 января 2017 г.

Ретроспектива 2016

Ёлки выброшены (нет, моя – в гараже!), и трудовые выходные перешли в не менее трудовые будни нового года, а значит пришло время подвести итоги года прошедшего. Пусть и с опозданием.

Как известно, 2016-й был непростым годом (четный год таковым быть не может) и по блогу это заметно. За прошлый год можно было заметить следующие тренды: я начал заниматься ErrorProne.NET (правда потом забил), в начале года я допилил Code Contracts и с тех пор только принимал пул рекветы (все еще не знаю, что делать с поддержкой VS 2017), начал блог на английском, ну и написал полтора-два десятка статей на разные темы.

Теперь об этом и другом, более подробно (с)J

понедельник, 19 декабря 2016 г.

Реализация синглтонов в .NET: Field-like vs. Lazy

Недавно один из читателей задал вопрос по поводу разницы между двумя реализациями синглтонов: через обычное статическое поле или же через статическое поле, содержащее Lazy<T>:

public class FieldLikeSingleton
{
    // Вариант C# 6
    public static FieldLikeSingleton Instance { get; } = new FieldLikeSingleton();

   
private FieldLikeSingleton() 
{}
}


public class FieldLikeLazySingleton
{
    private static readonly Lazy<FieldLikeLazySingleton> _instance =
 
       
new Lazy<FieldLikeLazySingleton>(() => new FieldLikeLazySingleton());

   
public static FieldLikeLazySingleton Instance => _instance.Value;

   
private FieldLikeLazySingleton() {}
}

Для простоты, первую реализацию я буду называть field-like реализацией (*), а вторую – ленивой.

 

понедельник, 21 ноября 2016 г.

Горизонтальные и вертикальные слои приложения

«У лука есть слои, и у людоедов есть слои! Ты понял?» - Шрек

Эта заметка навеяна выступлением Jimmy Bogard на 0redev под названием “Solid Architecture in Slices not Layers”, которую я всячески рекомендую.

Как и у людоеда, у любого современного приложения есть слои. Классической моделью является мордочка наверху, бизнес-логика по центру и базейка снизу. Такое разделение вполне уместно и определяется множеством причин.

Conway’s Law

Любой сложный проект требует вовлечения людей с разной специализацией и интересами. Кто-то пилит базы данных, кто-то лабает UI, а кто-то отвечает за логику. Такое разделение позволяет людям специализировать, а менеджеру масштабировать разработку: выделяя группы и подгруппы, отвечающие за отдельные куски.

Такой разделение еще сильнее выстраивает барьеры между слоями приложения, поскольку тут начинает во всей красе проявляться Conway’s Law, когда архитектура приложения начинает повторять структуру организации.