Rozważmy tę bardzo prostą metodę asynchroniczną:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
Kiedy kompiluję to za pomocą VS2013 (kompilator sprzed Roslyn), wygenerowana maszyna stanu jest strukturą.
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Kiedy kompiluję go z VS2015 (Roslyn), wygenerowany kod wygląda tak:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Jak widać Roslyn generuje klasę (a nie strukturę). Jeśli dobrze pamiętam, pierwsze implementacje async / await w starym kompilatorze (chyba CTP2012) również generowały klasy, a następnie ze względów wydajnościowych zostało zmienione na struct. (w niektórych przypadkach można całkowicie uniknąć boksowania i alokacji sterty…) (Zobacz to )
Czy ktoś wie, dlaczego zmieniono to ponownie w Roslyn? (Nie mam z tym problemu, wiem że ta zmiana jest przejrzysta i nie zmienia zachowania żadnego kodu, jestem po prostu ciekawy)
Edytować:
Odpowiedź od @Damien_The_Unbeliever (i kod źródłowy :)) imho wyjaśnia wszystko. Opisane zachowanie Roslyn dotyczy tylko kompilacji debugowania (i jest to potrzebne ze względu na ograniczenie środowiska CLR wspomniane w komentarzu). W wersji Release generuje również strukturę (ze wszystkimi tego korzyściami ...). Wydaje się więc, że jest to bardzo sprytne rozwiązanie obsługujące zarówno Edycję, jak i Kontynuuj oraz lepszą wydajność w produkcji. Ciekawe rzeczy, dziękujemy wszystkim, którzy wzięli udział!
źródło
async
metody prawie zawsze mają prawdziwy punkt asynchroniczny -await
który zapewnia kontrolę, która i tak wymagałaby zapakowania struktury. Uważam, że struktury zmniejszyłyby obciążenie pamięci tylko w przypadkuasync
metod, które działały synchronicznie.Odpowiedzi:
Nie miałem żadnej wiedzy na ten temat, ale ponieważ Roslyn jest obecnie open-source, możemy poszukać wyjaśnienia w kodzie.
A tutaj, w linii 60 AsyncRewriter , znajdujemy:
// 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;
Tak więc, chociaż użycie
struct
s jest atrakcyjne , to duża korzyść, jaką daje zezwolenie Edycji i Kontynuuj na pracę w ramachasync
metod, została oczywiście wybrana jako lepsza opcja.źródło
Trudno udzielić ostatecznej odpowiedzi na coś takiego (chyba, że wpadnie ktoś z zespołu kompilatorów :)), ale jest kilka punktów, które możesz wziąć pod uwagę:
Premia za wydajność struktur jest zawsze kompromisem. Zasadniczo otrzymujesz:
Co to oznacza w przypadku czekania? Właściwie ... nic. Jest tylko bardzo krótki okres czasu, w którym maszyna stanu znajduje się na stosie - pamiętaj,
await
efektywnie wykonuje areturn
, więc stos metody umiera; maszyna stanu musi gdzieś zostać zachowana, a to „gdzieś” jest zdecydowanie na kupie. Okres istnienia stosu nie pasuje dobrze do kodu asynchronicznego :)Poza tym maszyna stanowa narusza kilka dobrych wytycznych dotyczących definiowania struktur:
struct
s powinien mieć co najwyżej 16 bajtów - automat stanowy zawiera dwa wskaźniki, które samodzielnie wypełniają 16-bajtowe ograniczenie na 64-bitowym. Poza tym jest sam stan, więc przekracza „granicę”. To nie jest wielka sprawa, ponieważ jest całkiem prawdopodobne, że jest ona przekazywana tylko przez odniesienie, ale zauważ, że nie pasuje to do przypadku użycia dla struktur - struktury, która jest w zasadzie typem referencyjnym.struct
s powinny być niezmienne - cóż, prawdopodobnie nie wymaga to większego komentarza. To maszyna stanowa . Ponownie, nie jest to wielka sprawa, ponieważ struktura jest automatycznie generowanym kodem i prywatnym, ale ...struct
s powinny logicznie reprezentować pojedynczą wartość. Zdecydowanie nie w tym przypadku, ale to już w pewnym sensie wynika z posiadania zmiennego stanu w pierwszej kolejności.I oczywiście wszystko to dzieje się w przypadku, gdy nie ma zamknięć. Kiedy masz lokale (lub pola), które przechodzą przez
await
s, stan jest dalej zawyżany, co ogranicza użyteczność użycia struktury.Biorąc pod uwagę to wszystko, podejście klasowe jest zdecydowanie czystsze i nie spodziewałbym się zauważalnego wzrostu wydajności przy użyciu
struct
zamiast tego. Wszystkich obiektów biorących udział mają podobną żywotność, więc jedynym sposobem, aby poprawić wydajność pamięci byłoby, aby wszystkie z nichstruct
s (sklep w jakiś bufor, na przykład) - co jest niemożliwe w przypadku ogólnym, oczywiście. A większość przypadków, w których używałbyśawait
w pierwszej kolejności (to znaczy pewnej asynchronicznej pracy we / wy) obejmuje już inne klasy - na przykład bufory danych, ciągi znaków ... Jest raczej mało prawdopodobne, abyś zrobiławait
coś, co po prostu powróci42
bez wykonywania żadnych alokacje sterty.Ostatecznie powiedziałbym, że jedynym miejscem, w którym naprawdę widać prawdziwą różnicę w wydajności, są testy porównawcze. A optymalizacja pod kątem testów porównawczych to co najmniej głupi pomysł ...
źródło