среда, 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.

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

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

11 комментариев:

  1. Согласен, что читать такое превью гораздо интереснее, чем голая ссылка. Но мне кажется, что эту ссылку как раз лучше обозначить в начале статьи.

    ОтветитьУдалить
    Ответы
    1. ненене, пусть ссылка будет, как босс - в конце

      Удалить
    2. Ну вот, как всегда, мнения разделились:)

      Удалить
    3. Так почему бы не добавить ссылку и в начало и в конец :-)

      Удалить
  2. Скажите Сергей, а на сколько статей у вас будет такая обзорная статья. Не боитесь, что ваш блог будет похож на агрегатор новостей и из-за этого он потеряет свою ценность?

    ОтветитьУдалить
    Ответы
    1. Боюсь, поэтому буду стараться писать и полноценные посты только сюда.

      Удалить
    2. Сергей, для технических постов и превью достаточно, один фиг половину литературы на английском приходится читать. А около-технические (про книги, обучение, заметки по архитектуре) - можно и полностью на русском. Полагаю, их перевод на английский займет гораздо больше времени и сил.

      Удалить
  3. Никогда не задумывался через какую ж... могут быть реализованы дженерики.
    Сказать что я выпал в осадок - не сказать ничего.
    Базовый механизм языка - спецификатор дженерика.
    Да блин, это же должно преобразовываться в простой вызов конструктора по умолчанию.
    Но нет....
    Где же эта картинка про лазерную пушку верхом на ослике....

    ОтветитьУдалить
    Ответы
    1. На самом деле, это сложнее, чем кажется:)
      Ведь дженерики реализованы так, что в момент компиляции еще нет информации о типе, который будет создан. Это значит, что для того, чтобы компилятор мог сгенерировать код вызова конструктора, информация о конструкторе должна быть first class citizen в CLR. А это не так.

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

      Удалить
    2. Это понятно, но плюсист во мне негодует! :)
      Как бы ожидалось, что спецификатор new() дает возможность среде понять, что у типа этот нью должен быть - и по идее исключение должно быть не в тот момент когда каким угодно образом вызывали функцию с несуществующим конструктором, а в момент когда этот код поступил на джит или эмит или куда угодно но до вызова.
      С другой... все как всегда сложно.
      Но что-то эта новость меня задела прям вот реально.
      Это не просто пробой абстракции, а прям чорная дыра я бы сказал. Мина.
      Не ожидал я такого...

      Удалить