Почему асинхронные классы государственных машин (а не структуры) в 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 82

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, поэтому стек метода умирает; конечный автомат должен быть сохранен где-то, и это "где-то" определенно находится в куче. Время жизни стека не соответствует асинхронному коду хорошо :)

кроме того, государственная машина нарушает некоторые хорошие рекомендации по определению структур:

  • structs должно быть на самый большой 16-байтовый автомат содержит два указателя, которые самостоятельно заполняют 16-байтовый лимит аккуратно на 64-битном. Кроме того, есть само государство, поэтому оно переходит "предел". Это не большой сделка, так как это, скорее всего, только когда - либо передавалось по ссылке, но обратите внимание, как это не совсем соответствует прецеденту для структур-структура, которая в основном является ссылочным типом.
  • structs должен быть неизменным - Ну, это, наверное, не нужен комментировать. Это же государственную машину. Опять же, это не имеет большого значения, так как структура является автоматически сгенерированным кодом и частным, но...
  • structs должно логически представлять одно значение. Определенно не так здесь, но это уже отчасти следует из наличия изменчивого состояния в первую очередь.
  • это не должно быть в коробке часто - не проблема здесь, так как мы используем дженерики везде. Государство в конечном счете находится где-то на куча, но по крайней мере она не упаковывается (автоматически). Опять же, тот факт, что он используется только внутри, делает это довольно пустым.

и конечно, все это в случае, когда нет оцепления. Когда у вас есть местные жители (или поля), которые пересекают awaits, состояние дополнительно раздувается, ограничивая полезность использования структуры.

учитывая все это, подход к классу определенно чище, и я бы не ожидал заметного увеличения производительности от использования struct вместо. Все задействованные объекты имеют одинаковое время жизни, поэтому единственный способ улучшить производительность памяти - сделать все из них structs (хранить в каком - то буфере, например) - что в общем случае, конечно, невозможно. И в большинстве случаев, когда вы будете использовать await во - первых (то есть некоторые асинхронные операции ввода-вывода работают) уже вовлекают другие классы-например, буферы данных, строки... Это довольно маловероятно, что вы бы await что просто возвращает 42 не делая каких-либо выделений.

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