[C#] 디버그 빌드와 릴리스 빌드의 성능 차이

나는 보통 내 프로그램에서 디버그 구성 과 릴리스 구성 간 전환을 방해하지 않았 으며 프로그램이 실제로 고객 위치에 배포 된 경우에도 디버그 구성 을 사용하기로 결정했습니다 .

내가 아는 한, 수동으로 변경하지 않으면 이러한 구성 간의 유일한 차이점은 디버그DEBUG상수가 정의되어 있고 릴리스최적화 코드가 선택되어 있다는 것입니다.

그래서 내 질문은 실제로 두 가지입니다.

  1. 이 두 구성간에 성능 차이가 많이 있습니까? 여기에 성능에 큰 차이를 일으키는 특정 유형의 코드가 있습니까? 아니면 실제로 그렇게 중요하지 않습니까?

  2. 릴리스 구성에서 실패 할 수 있는 디버그 구성에서 제대로 실행되는 모든 유형의 코드가 있습니까 , 또는 디버그 구성 에서 테스트되고 올바르게 작동하는 코드가 릴리스 구성에서도 제대로 작동 하는지 확신 할 수 있습니다 .



답변

C # 컴파일러 자체는 릴리스 빌드에서 방출 된 IL을 크게 변경하지 않습니다. 주목할 점은 더 이상 중괄호에 중단 점을 설정할 수있는 NOP opcode를 방출하지 않는다는 것입니다. 가장 큰 것은 JIT 컴파일러에 내장 된 최적화 프로그램입니다. 나는 그것이 다음과 같은 최적화를한다는 것을 알고있다.

  • 인라인 방법. 메소드 호출은 메소드의 코드 삽입으로 대체됩니다. 이것은 큰 것으로, 속성 접근자를 본질적으로 무료로 만듭니다.

  • CPU 레지스터 할당. 지역 변수와 메소드 인수는 스택 프레임에 다시 저장되지 않고 CPU 레지스터에 저장된 상태로 유지 될 수 있습니다. 이것은 최적화 된 코드 디버깅을 어렵게 만드는 데 주목할만한 큰 것입니다. 그리고 휘발성 키워드에 의미를 부여합니다 .

  • 배열 인덱스 검사 제거. 배열 작업시 중요한 최적화 (모든 .NET 컬렉션 클래스는 배열을 내부적으로 사용) JIT 컴파일러가 루프가 범위를 벗어난 배열을 색인화하지 않는지 확인할 수 있으면 색인 검사가 제거됩니다. 큰 것.

  • 언 롤링 루프. 본문에서 코드를 최대 4 번 반복하고 반복 횟수를 줄임으로써 작은 몸체가있는 루프가 개선됩니다. 지점 비용을 줄이고 프로세서의 슈퍼 스칼라 실행 옵션을 향상시킵니다.

  • 데드 코드 제거. if (false) {/ /} 와 같은 문장 은 완전히 제거됩니다. 이것은 일정한 접힘과 인라인으로 인해 발생할 수 있습니다. 다른 경우는 JIT 컴파일러가 코드에 부작용이 없음을 확인할 수있는 경우입니다. 이 최적화는 프로파일 링 코드를 매우 까다롭게 만듭니다.

  • 코드 게양. 루프의 영향을받지 않는 루프 내부의 코드는 루프 밖으로 이동할 수 있습니다. C 컴파일러의 최적화 프로그램은 호이스트 기회를 찾는 데 더 많은 시간을 할애합니다. 그러나 필요한 데이터 흐름 분석으로 인해 비용이 많이 드는 최적화이며 지터가 시간을 할애 할 수 없으므로 명백한 경우 만 호이스트합니다. .NET 프로그래머가 더 나은 소스 코드를 작성하고 호이스트하도록 강요합니다.

  • 일반적인 하위 표현 제거. x = y + 4; z = y + 4; z = x가된다; dest [ix + 1] = src [ix + 1]과 같은 문장에서 매우 흔합니다. 도우미 변수를 도입하지 않고 가독성을 위해 작성되었습니다. 가독성을 손상시킬 필요가 없습니다.

  • 일정한 접힘. x = 1 + 2; x = 3이되고; 이 간단한 예제는 컴파일러에 의해 일찍 발견되지만 다른 최적화가이를 가능하게하는 JIT 시간에 발생합니다.

  • 전파를 복사하십시오. x = a; y = x; y = a가된다; 이것은 레지스터 할당자가 더 나은 결정을 내리는 데 도움이됩니다. 작업 할 레지스터가 거의 없기 때문에 x86 지터에서 큰 문제입니다. 올바른 것을 선택하는 것은 성능에 중요합니다.

예를 들어 앱의 디버그 빌드를 프로파일 링하고 릴리스 빌드와 비교할 때 차이를 만들 수있는 매우 중요한 최적화입니다 . 코드가 중요한 경로에있을 때 실제로 중요합니다. 실제로 작성한 코드의 5 ~ 10 % 프로그램 성능에 영향을 미칩니다. JIT 옵티마이 저는 중요한 것이 무엇인지 미리 알기에 충분히 똑똑하지 않으며 모든 코드에 대해 “11로 돌리기”다이얼 만 적용 할 수 있습니다.

프로그램 실행 시간에 대한 이러한 최적화의 효과적인 결과는 종종 다른 곳에서 실행되는 코드의 영향을받습니다. 파일 읽기, dbase 쿼리 실행 등. JIT 최적화 프로그램이 완전히 보이지 않게합니다. 그래도 상관 없습니다 🙂

JIT 최적화 프로그램은 수백만 번 테스트를 거쳤기 때문에 매우 안정적인 코드입니다. 릴리스 빌드 버전의 프로그램에 문제가있는 경우는 매우 드 rare니다. 그러나 발생합니다. x64와 x86 지터 모두 구조체에 문제가있었습니다. x86 지터는 부동 소수점 일관성에 문제가있어 부동 소수점 계산의 중간체가 메모리로 플러시 될 때 잘리지 않고 80 비트 정밀도로 FPU 레지스터에 유지 될 때 미묘하게 다른 결과를 생성합니다.


답변

  1. 예, 많은 성능 차이가 있으며 이는 실제로 코드 전체에 적용됩니다. 디버그는 성능 최적화를 거의하지 않으며 모드를 많이 해제합니다.

  2. DEBUG상수 에 의존하는 코드 만 릴리스 빌드에서 다르게 수행 될 수 있습니다. 그 외에도 아무런 문제가 없어야합니다.

DEBUG상수에 의존하는 프레임 워크 코드의 예 Debug.Assert()는 속성이 [Conditional("DEBUG)"]정의 된 메소드 입니다. 이는 DEBUG상수에 따라 달라지며 릴리스 빌드에는 포함되지 않습니다.


답변

이것은 응용 프로그램의 특성에 크게 좌우됩니다. 응용 프로그램이 UI가 많은 경우 최신 컴퓨터에 연결된 가장 느린 구성 요소가 사용자이므로 차이가 없을 것입니다. 일부 UI 애니메이션을 사용하는 경우 DEBUG 빌드에서 실행할 때 눈에 띄는 지연을 감지 할 수 있는지 테스트 할 수 있습니다.

그러나 계산이 많은 계산이 많은 경우에는 차이가 있음을 알 수 있습니다 (계산의 특성에 따라 @Pieter가 언급 한 것보다 40 % 높을 수 있음).

기본적으로 디자인 트레이드 오프입니다. DEBUG 빌드에서 릴리스하는 경우 사용자에게 문제가 발생하면 더 의미있는 역 추적을 얻을 수 있으며 훨씬 유연한 진단을 수행 할 수 있습니다. DEBUG 빌드를 릴리스하면 최적화 프로그램이 희미한 Heisenbugs를 생성하지 않아도 됩니다.


답변

  • 필자의 경험은 중간 규모 이상의 응용 프로그램이 릴리스 빌드에서 눈에 띄게 반응한다는 것입니다. 응용 프로그램을 사용 해보고 느낌을 확인하십시오.

  • Release 빌드로 물릴 수있는 한 가지는 디버그 빌드 코드가 경쟁 조건 및 기타 스레딩 관련 버그를 억제 할 수 있다는 것입니다. 최적화 된 코드는 명령 순서가 변경 될 수 있으며 실행 속도가 빠르면 특정 경쟁 조건이 악화 될 수 있습니다.


답변

프로덕션 환경에 .NET 디버그 빌드를 출시해서는 안됩니다. Edit-and-Continue를 지원하기위한 못생긴 코드 나 다른 것을 아는 사람이있을 수 있습니다. 내가 아는 한 이것은 C #이 아닌 VB에서만 발생하지만 (참고 : 원본 게시물에는 C # 태그가 지정되어 있음) 여전히 Microsoft가 디버그 빌드로 할 수 있다고 생각하는 것에 대해 일시 중지 할 이유가 있어야합니다. 실제로 .NET 4.0 이전에는 VB 코드가 Edit-and-Continue를 지원하기 위해 생성 한 이벤트가있는 개체의 인스턴스 수에 비례하여 메모리가 누출됩니다. (이것은 https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging 에 따라 수정 된 것으로보고되었지만 생성 된 코드 불쾌한 것처럼 보이고 WeakReference객체를 만들고 정적 목록에 추가하는 동안 자물쇠 들고) 확실히 프로덕션 환경에서 이런 종류의 디버깅 지원을 원하지 않습니다!


답변

내 경험에 따르면 릴리스 모드에서 나온 최악의 상황은 모호한 “릴리스 버그”입니다. IL (중급 언어)은 릴리스 모드에서 최적화되므로 디버그 모드에서 나타나지 않을 버그가있을 수 있습니다. 이 문제를 다루는 다른 SO 질문이 있습니다.
릴리스 버전의 버그가 디버그 모드에없는 일반적인 이유

이것은 간단한 콘솔 앱이 디버그 모드에서 완벽하게 잘 실행되지만 정확히 동일한 입력이 주어지면 릴리스 모드에서 오류가 발생하는 한두 번 나에게 일어났습니다. 이러한 버그는 릴리스 모드의 정의에 따라 아이러니하게 디버깅하기가 매우 어렵습니다.


답변

나는 1) 주로 구현에 달려 있다고 말한다. 일반적으로 그 차이는 그리 크지 않습니다. 나는 많은 측정을했고 종종 차이를 볼 수 없었습니다. 관리되지 않는 코드, 많은 거대한 배열 및 이와 유사한 것들을 사용하면 성능 차이가 약간 커지지 만 다른 세계 (C ++과 같은)는 아닙니다. 2) 일반적으로 릴리스 코드에서 적은 오류가 표시되고 (높은 공차) 스위치가 제대로 작동합니다.