Pull to refresh
235
0
Сергей Тепляков @SergeyT

Пользователь

Send message
Проблема в том, что нет никакого смысла в том, чтобы «взять развернутый код и получить из него IL». Есть IL-код, генерируемый компилятором в котором упаоквки нет, а есть 'constrained' вызов метода Dispose.

«Развернутый код» же — это лишь пример (псевдокод, по сути) того, что делает компилятор. На самом же деле, поведение несколько иное — более эффективное.

Комментарий Эрика датирован 2011-м, и после этого, скорее всего, спека была изменена, чтобы убрать эту неоднозначность. Я же привел кусок спеки:

An implementation is permitted to implement a given using-statement differently, e.g. for performance reasons, as long as the behavior is consistent with the above expansion.


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

Так что *сегодня* никакого нарушения спеки нет. Не стоит вводить себя и читателей в заблуждение;)
Вы правы. Действительно, компилятор разворачивает блок using для значимых типов в другую конструкцию, нарушая спецификацию языка C# ради оптимизации


И снова я с этим не совсем согласен. Эрик, ведь, — жук, да он пишет вот что: «You’d be perfectly justified in thinking that there is a boxing performed in the finally because that’s what the spec says.». Но ведь он пишет лишь то, что «да, в спеке есть пример, который говорит, что using блок выворачивается в каст», но спека не говорит, что каст там должен быть.

Спека (вот здесь, например) вообще хитро написана, там есть вот что:

An implementation is permitted to implement a given using-statement differently, e.g. for performance reasons, as long as the behavior is consistent with the above expansion.


Так что никто никого не нарушает;), не стоит спешить с выводами.
Нет, все сложнее. Полное описание можно найти здесь: The ‘in’-modifier and the readonly structs in C#.

Если коротко, то in-модификатор очень похож на readonly поля: такие параметры нельзя переприсвоить и для структур компилятор генерирует защитные копии для обращений к свофствам и методам, чтобы обеспечить неизменяемость. А вот эти защитные копии и будут ломать нам ноги, поскольку они не явные.
Да, и хочется добавить.

Вообще, меня очень смущает первый вопрос. Он показывает, что автор этого вопроса сам не совсем понимает, что происходит у дот-нетика под капотом.

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

Защитные же копии — это другой зверь. Их и было много, а с появлением 'in' модфикаторов и ref-return-ов стало еще больше. И я бы рассчитывал, что в голове у автора подобных вопросов эти два кейса лежат на разных полках, поскольку они приводят к разным проблемам, по разному проявляются, по разному ищутся и по разному решаются.

Объяснение к первому примеру — неверное. Дело не в том, что в блоке finally происходит упаковка, а в том, что компилятор создает "защитную" копию переменной, поскольку переменные в блоке 'using' очень похожи на readonly-поля (ее нельзя переприсвоить) и компилятор пытается гарантировать "неизменяемость" состояния.


Посмотрите во что компилятор разворачивает код:


SDummy sDummy = default(SDummy);
        SDummy sDummy2 = sDummy;
        try
        {
            Console.WriteLine(sDummy.GetDispose());
        }
        finally
        {
            ((IDisposable)sDummy2).Dispose();
        }
        Console.WriteLine(sDummy.GetDispose());

И упаковки — не просиходит в этом коде вообще! И это очень, повторюсь, очень важно. Если бы она была, то енумераторы в коллекциях не были бы структурами (какой смысл делать их структурами, если блок 'using', который генерирует компилятор для foreach все равно приведет к упаковке?


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


Потом нужно объяснять семантическую разницу между классами и структурами и говорить о том, что структуры должны быть неизменяемыми (должны быть readonly struct). Если структура изменяемая, то компилятор генерирует огромную кучу защитных компий, которые совершенно не очевидны никому в этом мире, кроме пары десятков человек. А эти копии могут а) негативно повлиять на производительность (тут идет напоминание, что структуры — это оптимизация) и б) вы можете очень легко отстрелить себе ногу, поскольку изменение состояние произойдет на компии.


Ну и использовать подобные вещи в качестве загадок — это очень, имхо, плохая практика. Число профиссиональных разработчиков (а не задротов) знают подобные ньюансы единицы. В буквальном смысле.


Это очень advanced знания, которые нужны либо а) энтузиастам-задротам или б) людям, которые разрабатываеют очень высоконагруженные приложения на .net.

1. Эта возможность есть и сейчас и для этого IEquatable не нужен.
2. Потомка не существует. Для этого нужно, чтобы каждый enum был унаследован от System.Enum.

Это все я к тому, что для того, чтобы избежать виртуального вызова и упаковке совсем не обязательно, чтобы перечисления реализовывали интерфейс. Для этого необходимо и достаточно, чтобы JIT-компилятор знал о них, аналогично тому, как он сейчас уже знает о Enum.HasFlags.
Простите, но я не понимаю:).

1. Наличие IEquatable никак не влияет на то, будет ли вызов Equals/GetHashCode виртуальным или нет.
2. Вызов метода, определенного в System.ValueType или System.Enum приводит к упаковке:
int n = 42; var t = n.GetType(); — невиртуальный, но приводит к упаковке.

Теперь вопрос: как реализация IEquatable поможет избавиться от виртуального вызова и при этом не будет упаковки (котооорая дороже виртуального вызова).
Enum — это не просто int. Это кастомный тайп, отнаследованный от System.Enum, который отнаследован от System.ValueType, который содержит примитив внутри. Это может быть int, а может быть short или byte. Именно за счет того, что поля могут быть разного типа mscorlib содержит семейство «сравнителей»: SByteEnumEqualityComparer, и простой EnumEqualityComparer.

Некоторая работа по оптимизации кишочков идет. Например, новый JIT знает о существовании Enum.HasFlags. Плюс идет работа по девиртуализации вызовов.

Да, и я не совсем понял, как реализация IEquatable поможет избавиться от виртуального вызова. Можете пояснить этот момент?
Я подозреваю, что сложность в предложенном подходе заключается в том, что JIT-у пришлось бы много работать, чтобы это сделать. Например, реализация IList массивом является достаточно нетривиальной штукой.
Собственно, предудущий комментарий NIKOSV полностью соответствует выводам статьи и совпадает с моим текущим пониманием рынка.

Тут (район Сиэттла) и правда зп сопоставимы с Нью Йорком и долиной, а стоимость жилья — ниже. Да и с комьютом тоже дела несколько лучше. Сейчас здесь все еще можно на программерскую зп купить нормальный дом в пределах 40 минут езды и не переходить на доширак при этом.

И да, район активно развивается, что сказывается на ценах, как на съем, так и на покупку. Например, вменяемая аренда — ~2K, что, насколько я знаю, соизмеримо со многими районами Калифорнии и Нью Йорка.
Ну, можно почитать, что такое ActionBlock и понять, что подобная задача является выражденным случаем датафлоу.

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

Или вы хотите сказать, что никогда не выибрали инструмент, который потом оказывался неподходящим? Возможно:), я не такой. И после своей ошибки я решил поделиться своим опытом. Если у вас и с этим какое-то несогласие, то я не совсем понимаю, чего вы хотите добиться своими комментариями;)
Нисколько с этим не спорю. Проблема лишь в том, что проблема ActionBlock-а не очевидна.

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

Дана проблема: проанализировать набор файлов с возможными зависимостями.
Возможные решения: ActionBlock, ConcurrentCollection + TPL, что-то свое.

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

В результате, он является вполне адекватным выбором и лишь люди, знающие хорошо его внутреннее устройство смогут сказать сразу же, что у него будут проблемы при обработке древовидных структур данных.
Ну, это просто не важно:). Это не настоящий парсинг, а фейковый, любой вариант будет ок;)
Кстати, лучший способ — использовать SpinWait.Spin(), он реально будет нагружать процессор, в отличие от Sleep-а, который будет блокировать таску. Но в данном случае блокировка, а не yield управления из таски имеет смысл.
Ну, код не совсем продакшн качества, так что в этом случае — это не сильно важно.
Я могу описать проблему еще проще: причина дедлока в наличии join-а потоков. Но ведь это малоинформативно и не очень-то дает понять, почему ожидаемый поток все еще заблокирован.

И нет, тут все сложнее, чем «своя задача блокирует свое исполнение». SendAsync реализован несколько сложнее: на самом деле, это source dataflow block с буфером в один элемент и TaskCompletionSource-ом, который переводит нежележащую таску в завершенное состояние, когда целевой блок (в данном случае — ActionBlock) решается на обработку элемента от него.

И нет, вычислять рекусривно задачи по месту — это не вариант, поскольку в этом случае Degree of parallelism пойдет лесом. Представьте себе, что зависимости между файлами — это дерево. Так вот, дерево может быть (и, как вы сказали по поводу «плохого») или наверняка будет не сбаллансированным. Это значит, что использование этого подхода приведет к тому, что мы останемся с одной задачей, которая будет долго и упорно парсить огромную ветку дерева. Нехорошо это:)
А можно я подкину пару ссылок на уже готовые размышления по этой теме: Размышления о TDD, TDD: Test-Driven vs. Type-Driven development, Is TDD Dead, Часть 5.

А вообще, я очень рекомендую ознакомиться с оригинальным трудом Кента и эпичной баталией под названием Is TDD Dead, чтобы увидеть, насколько автор этой практики является здравомыслящим и прагматичным человеком. Который, кстати, неоднократно писал и говорил о том, что этот подход с короткими итерациями тест-код подходит именно к нему и что у других разработчиком может и должно быть другое мнение по этому поводу.
Ну, как вам сказать:), что можно ожидать от кода, entry point которого называется MikesArchitecture? Комьюнити в последнее время пытается причесать этого монстра, но поддается он с трудом.

Так что у меня есть фичареквест для любого анализатора: если он встречает метод с именем _Some_Authors_Name_Architecture, то прекращает анализ с сообщением, что он сдается и дальше не готов анализировать код дабы не повредить свою психику:)
Понял. Да, вполне возможно, что так и есть.
1
23 ...

Information

Rating
Does not participate
Location
Washington, США
Registered
Activity