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


87

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

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에서는 또한 구조체를 생성합니다 (모든 이점이 있습니다 ..). 따라서 이것은 편집과 계속을 모두 지원하고 프로덕션에서 더 나은 성능을 지원하는 매우 영리한 솔루션 인 것 같습니다. 참여 해주신 모든 분들께 감사드립니다!


2
나는 그들이 복잡성 (가변 구조체)이 가치가 없다고 결정했다고 생각합니다. async메서드는 거의 항상 진정한 비동기 포인트를 가지고 있습니다. await제어를 생성하는 struct는 어쨌든 boxing되어야합니다. 나는 생각 구조체는 메모리 압력 완화 할 async동 기적으로 실행하는 일이 방법을.
Stephen Cleary

답변:


112

나는 이것에 대한 예지가 없었지만 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는 분명히 더 나은 옵션으로 선택되었습니다.


18
아주 잘 잡았습니다! 그리고 이것을 기반으로 여기에 내가 또한 발견 한 것입니다. 이것은 디버그에서 빌드 할 때만 발생합니다 (이해할 때 EnC를 수행 할 때입니다.). Release에서는 구조체를 만듭니다 (이 경우 EnableEditAndContinue는 false입니다 .. .). Btw. 나는 또한 코드를 조사했지만 이것을 찾지 못했습니다. 감사합니다!
gregkalapos

3

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

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

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

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

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

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

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

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

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


당신은 항상하지 않습니다 필요 당신이 가서 소스를 읽을 수 있습니다 때 컴파일러 팀의 일원을, 그들은 :-) 도움이되는 설명을 떠 났어요
Damien_The_Unbeliever

3
@Damien_The_Unbeliever 예, 확실히 훌륭한 발견이었습니다. 이미 귀하의 답변을 upvoted했습니다. : P
Luaan

1
구조체는 코드가 비동기로 실행되지 않는 경우에 많은 도움이됩니다. 예를 들어 데이터가 이미 버퍼에 있습니다.
Ian Ringrose 2016-04-11
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.