Почему асинхронные классы государственных машин (а не структуры) в Roslyn?
давайте рассмотрим этот простой метод async:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
когда я компилирую это с VS2013 (pre Roslyn compiler), сгенерированный state-machine является структурой.
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
когда я компилирую его с VS2015 (Roslyn) сгенерированный код таков:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
как вы можете видеть, Рослин генерирует класс (а не структуру). Если я правильно помню, первые реализации поддержки async/await в старом компиляторе (CTP2012, я думаю) также сгенерированы классы, а затем он был изменен на структуру из соображений производительности. (в некоторых случаях вы можете полностью избежать бокса и выделения кучи...) (см. этой)
кто-нибудь знает, почему это было изменено снова в Рослине? (У меня нет никаких проблем по этому поводу, я знаю, что это изменение прозрачно и не меняет поведение любого кода, мне просто любопытно)
Edit:
ответ от @Damien_The_Unbeliever (и источник код :) ) имхо все объясняет. Описанное поведение Roslyn применяется только для отладки сборки (и это необходимо из-за ограничения среды CLR, упомянутого в комментарии). В релизе он также генерирует структуру (со всеми преимуществами этого..). Таким образом, это кажется очень умным решением для поддержки как редактирования, так и продолжения и повышения производительности в производстве. Интересный материал, спасибо всем, кто участвовал!
2 ответа:
у меня не было никакого предвидения этого, но так как Рослин является открытым исходным кодом в эти дни, мы можем пойти на охоту через код для объяснения.
и здесь строка 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
методы, очевидно, был выбран в качестве лучшего варианта.
трудно дать окончательный ответ на что-то вроде этого (если кто-то из команды компилятора капель :)), но есть несколько моментов, которые вы можете рассмотреть:
производительность "бонус" структур всегда является компромиссом. В принципе, вы получаете следующее:
- значение семантики
- возможный стек (может быть, даже регистрация?) распределение
- избегать косвенности
что это означает в случае ожидания? Ну, вообще-то... ничего. Есть только очень короткий период времени, в течение которого государственная машина находится в стеке - помните,
await
эффективно лиreturn
, поэтому стек метода умирает; конечный автомат должен быть сохранен где-то, и это "где-то" определенно находится в куче. Время жизни стека не соответствует асинхронному коду хорошо :)кроме того, государственная машина нарушает некоторые хорошие рекомендации по определению структур:
struct
s должно быть на самый большой 16-байтовый автомат содержит два указателя, которые самостоятельно заполняют 16-байтовый лимит аккуратно на 64-битном. Кроме того, есть само государство, поэтому оно переходит "предел". Это не большой сделка, так как это, скорее всего, только когда - либо передавалось по ссылке, но обратите внимание, как это не совсем соответствует прецеденту для структур-структура, которая в основном является ссылочным типом.struct
s должен быть неизменным - Ну, это, наверное, не нужен комментировать. Это же государственную машину. Опять же, это не имеет большого значения, так как структура является автоматически сгенерированным кодом и частным, но...struct
s должно логически представлять одно значение. Определенно не так здесь, но это уже отчасти следует из наличия изменчивого состояния в первую очередь.- это не должно быть в коробке часто - не проблема здесь, так как мы используем дженерики везде. Государство в конечном счете находится где-то на куча, но по крайней мере она не упаковывается (автоматически). Опять же, тот факт, что он используется только внутри, делает это довольно пустым.
и конечно, все это в случае, когда нет оцепления. Когда у вас есть местные жители (или поля), которые пересекают
await
s, состояние дополнительно раздувается, ограничивая полезность использования структуры.учитывая все это, подход к классу определенно чище, и я бы не ожидал заметного увеличения производительности от использования
struct
вместо. Все задействованные объекты имеют одинаковое время жизни, поэтому единственный способ улучшить производительность памяти - сделать все из нихstruct
s (хранить в каком - то буфере, например) - что в общем случае, конечно, невозможно. И в большинстве случаев, когда вы будете использоватьawait
во - первых (то есть некоторые асинхронные операции ввода-вывода работают) уже вовлекают другие классы-например, буферы данных, строки... Это довольно маловероятно, что вы быawait
что просто возвращает42
не делая каких-либо выделений.в конце концов, я бы сказал, что единственным местом, где вы действительно увидите реальную разницу в производительности, будут бенчмарки. И оптимизация для бенчмарков-это, мягко говоря, глупая идея...