Почему в Roslyn асинхронные классы состояний (а не структуры)?
Давайте рассмотрим этот очень простой асинхронный метод:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
Когда я компилирую это с помощью VS2013 (предварительный компилятор Roslyn), сгенерированная государственная машина является структурой.
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Когда я скомпилирую его с VS2015 (Roslyn), сгенерированный код таков:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Как вы можете видеть, Roslyn генерирует класс (а не структуру). Если я правильно помню первые реализации поддержки async/await в старом компиляторе (CTP2012, я думаю), также сгенерировали классы, а затем он был изменен на структуру из соображений производительности. (в некоторых случаях вы можете полностью избежать бокса и распределения кучи...) (см. this)
Кто-нибудь знает, почему это было снова изменено в Roslyn? (У меня нет никаких проблем с этим, я знаю, что это изменение прозрачно и не меняет поведения какого-либо кода, мне просто интересно)
Edit:
Ответ от @Damien_The_Unbeliever (и исходного кода:)) imho объясняет все. Описанное поведение Roslyn применяется только для сборки отладки (и это необходимо из-за ограничения CLR, упомянутого в комментарии). В Release также генерирует структуру (со всеми преимуществами этого..). Таким образом, это, кажется, очень умное решение для поддержки как редактирования, так и продолжения и повышения производительности в производстве. Интересный материал, спасибо всем, кто участвовал!
Ответы
Ответ 1
У меня не было никаких предвидений этого, но поскольку Roslyn является открытым исходным кодом в эти дни, мы можем отправиться на поиски через код для объяснения.
И здесь, на строке 60 AsyncRewriter, мы находим:
// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;
Итак, хотя есть некоторые призывы к использованию struct
s, большая победа в разрешении "Редактировать и продолжить" для работы в методах async
был выбран в качестве лучшего варианта.
Ответ 2
Трудно дать окончательный ответ на что-то подобное (если только кто-то из команды компилятора не упал:)), но есть несколько моментов, которые вы можете рассмотреть:
Эффективность "бонуса" структур всегда является компромиссом. В основном вы получаете следующее:
- Сеантика значений
- Возможное распределение стека (возможно, даже зарегистрироваться?)
- Избегать косвенности
Что это значит в случае ожидания? Ну, на самом деле... ничего. Там только очень короткий период времени, в течение которого конечный автомат находится в стеке - помните, await
эффективно выполняет return
, поэтому стек метода умирает; государственный автомат должен быть где-то сохранен, и что "где-то" определенно находится в куче. Время жизни стека не подходит для асинхронного кода:)
Кроме того, государственный механизм нарушает некоторые хорошие рекомендации для определения структур:
-
struct
должно быть не более 16 байт - машина состояний содержит два указателя, которые сами по себе заполняют 16-байтовое ограничение аккуратно на 64-разрядном. Помимо этого, есть само государство, поэтому оно переходит через "предел". Это не имеет большого значения, так как это, скорее всего, только когда-либо передавалось по ссылке, но обратите внимание, что это не совсем подходит для использования для structs - структуры, которая в основном является ссылочным типом.
-
struct
должен быть неизменным - хорошо, это, вероятно, не требует большого количества комментариев. Это государственная машина. Опять же, это не имеет большого значения, поскольку структура является автоматически сгенерированным кодом и частным, но...
-
struct
должно логически представлять одно значение. Определенно, не здесь, но это уже происходит из-за наличия изменчивого состояния.
- Он не должен быть помещен в коробку часто - здесь не проблема, так как мы используем дженерики повсюду. Государство в конечном счете находится где-то в куче, но по крайней мере оно не помещается в коробку (автоматически). Опять же, тот факт, что он используется только внутренне, делает это в значительной степени недействительным.
И, конечно, все это в случае, когда нет закрытий. Когда у вас есть locals (или поля), которые пересекают await
s, состояние еще раздувается, ограничивая полезность использования структуры.
Учитывая все это, подход класса определенно более чист, и я бы не ожидал, что заметное увеличение производительности будет связано с использованием struct
. Все задействованные объекты имеют одинаковое время жизни, поэтому единственный способ улучшить производительность памяти - сделать все из них struct
(например, хранить в некотором буфере) - что невозможно в общем случае, конечно. И в большинстве случаев, когда вы использовали бы await
в первую очередь (то есть, некоторые асинхронные операции ввода-вывода), уже задействованы другие классы - например, буферы данных, строки... Скорее всего, вы бы await
что-то который просто возвращает 42
без каких-либо распределений кучи.
В конце концов, я бы сказал, что единственное место, где вы действительно увидите реальную разницу в производительности, было бы эталоном. И оптимизация для тестов - глупая идея, если не сказать...