[c#] Roslyn에서 비동기 상태 머신 클래스 (구조체가 아님)가있는 이유는 무엇입니까?

이 매우 간단한 비동기 메서드를 고려해 봅시다.

static async Task myMethodAsync()
{
    await Task.Delay(500);
}

VS2013 (Pre Roslyn 컴파일러)으로 이것을 컴파일하면 생성 된 상태 머신이 구조체입니다.

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

VS2015 (Roslyn)로 컴파일 할 때 생성 된 코드는 다음과 같습니다.

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

보시다시피 Roslyn은 클래스를 생성합니다 (구조체가 아님). 이전 컴파일러에서 비동기 / 대기 지원의 첫 번째 구현 (CTP2012 추측)도 클래스를 생성 한 다음 성능상의 이유로 struct로 변경되었습니다. (어떤 경우에는 권투와 힙 할당을 완전히 피할 수 있습니다…) ( 이것을보십시오 )

Roslyn에서 이것이 왜 다시 변경되었는지 아는 사람이 있습니까? (나는 이것에 대해 아무런 문제가 없습니다.이 변경이 투명하고 코드의 동작을 변경하지 않는다는 것을 알고 있습니다. 저는 궁금합니다)

편집하다:

@Damien_The_Unbeliever (및 소스 코드 :)의 대답 imho는 모든 것을 설명합니다. 설명 된 Roslyn 동작은 디버그 빌드에만 적용됩니다 (주석에 언급 된 CLR 제한 때문에 필요함). Release에서는 또한 구조체를 생성합니다 (모든 이점이 있습니다 ..). 따라서 이것은 편집과 계속을 모두 지원하고 프로덕션에서 더 나은 성능을 지원하는 매우 영리한 솔루션 인 것 같습니다. 참여 해주신 모든 분들께 감사드립니다!



답변

나는 이것에 대한 예지가 없었지만 Roslyn은 요즘 오픈 소스이기 때문에 설명을 위해 코드를 탐색 할 수 있습니다.

그리고 여기 AsyncRewriter의 60 행 에서 다음을 찾을 수 있습니다.

// 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;

따라서 structs 를 사용하는 데 약간의 호소력이 있지만 메서드 내에서 편집 및 계속 작업을 수행 할 수있는 큰 승리 async는 분명히 더 나은 옵션으로 선택되었습니다.


답변

(컴파일러 팀의 누군가가 :)에 빠지지 않는 한 이와 같은 것에 대해 확실한 답을 제공하기는 어렵지만 고려할 수있는 몇 가지 사항이 있습니다.

구조체의 성능 “보너스”는 항상 절충안입니다. 기본적으로 다음을 얻습니다.

  • 가치 의미론
  • 가능한 스택 (아마도 등록?) 할당
  • 간접적 회피

이것은 await 케이스에서 무엇을 의미합니까? 글쎄, 사실 … 아무것도. 상태 머신이 스택에있는 매우 짧은 시간 동안 만 있습니다. 기억하세요, await효과적으로 a return를 수행하므로 메서드 스택이 죽습니다. 상태 머신은 어딘가에 보존되어야하며 그 “어딘가”는 확실히 힙에 있습니다. 스택 수명은 비동기 코드에 적합하지 않습니다. 🙂

이 외에도 상태 머신은 구조체 정의에 대한 몇 가지 좋은 지침을 위반합니다.

  • structs는 최대 16 바이트 크기 여야합니다. 상태 머신에는 두 개의 포인터가 포함되어 있으며 64 비트에서 자체적으로 16 바이트 제한을 깔끔하게 채 웁니다. 그 외에도 상태 자체가 있으므로 “한계”를 초과합니다. 이것은 참조로만 전달 될 가능성이 높기 때문에 문제 는 아니지만, 기본적으로 참조 유형 인 구조체 인 구조체의 사용 사례에 얼마나 적합하지 않은지 주목하십시오.
  • structs는 불변이어야합니다-글쎄, 이것은 아마도 많은 주석이 필요하지 않을 것입니다. 그것은 상태 머신입니다 . 다시 말하지만 구조체는 자동 생성 코드이고 비공개이기 때문에 큰 문제는 아니지만 …
  • structs는 논리적으로 단일 값을 나타내야합니다. 여기에서는 확실히 그렇지는 않지만, 처음에 변경 가능한 상태를 갖는 것에서 이미 일종의 다음과 같습니다.
  • 우리는 어디에서나 제네릭을 사용하고 있기 때문에 여기에서 문제가되지 않습니다 . 상태는 궁극적으로 힙의 어딘가에 있지만 적어도 (자동으로) boxing되지는 않습니다. 다시 말하지만, 내부적으로 만 사용된다는 사실은이를 거의 무효화합니다.

물론이 모든 것은 폐쇄가없는 경우입니다. awaits 를 횡단하는 지역 (또는 필드)이 있으면 상태가 더욱 부풀려져 구조체 사용의 유용성이 제한됩니다.

이 모든 것을 감안할 때 클래스 접근 방식은 확실히 깨끗하며 struct대신 a를 사용하여 눈에 띄는 성능 향상을 기대하지 않습니다 . 관련된 모든 개체의 수명이 비슷하므로 메모리 성능을 향상시키는 유일한 방법은 모든 개체를 만드는 입니다 struct(예 : 일부 버퍼에 저장). 물론 일반적인 경우에는 불가능합니다. 그리고 await처음에 사용하는 대부분의 경우 (즉, 비동기 I / O 작업)에는 이미 다른 클래스 (예 : 데이터 버퍼, 문자열)가 포함되어 있습니다. 아무 작업도 수행하지 않고 await단순히 반환 42하는 작업을 수행 할 가능성은 거의 없습니다. 힙 할당.

결국 실제 성능 차이를 실제로 볼 수있는 유일한 곳은 벤치 마크라고 말하고 싶습니다. 벤치 마크를 최적화하는 것은 어리석은 생각입니다.


답변