>  기사  >  백엔드 개발  >  고성능 .NET 작성에 대한 실용적인 튜토리얼

고성능 .NET 작성에 대한 실용적인 튜토리얼

零下一度
零下一度원래의
2017-06-25 09:08:431466검색

할당률 감소

이는 거의 설명이 필요하지 않습니다. 이는 메모리 사용량을 줄여 GC 재활용 중 부담을 자연스럽게 줄이는 동시에 메모리 조각화 및 CPU 사용량을 줄여줍니다. 이를 달성할 수 있는 방법이 있지만 다른 디자인과 충돌할 수 있습니다.

디자인할 때 각 개체를 주의 깊게 검토하고 스스로에게 질문해야 합니다.

  1. 이 개체가 정말 필요한가요?

  2. 이 분야가 나에게 필요한 분야인가요?

  3. 배열의 크기를 줄일 수 있나요?

  4. 기본 요소의 크기를 줄일 수 있나요(Int64를 Int32로 대체하는 등)?

  5. 이러한 개체는 드문 경우에만 사용됩니까, 아니면 초기화 중에만 사용됩니까?

  6. 일부 클래스를 스택에 할당하거나 객체의 일부가 될 수 있도록 구조로 변환할 수 있나요?

  7. 많은 메모리를 할당하지만 그 중 아주 작은 부분만 사용하고 있나요?

  8. 다른 곳에서 관련 데이터를 얻을 수 있나요?

작은 이야기: 서버측 요청에 응답하는 함수에서 메모리 세그먼트보다 큰 일부 메모리가 요청에 할당된다는 사실을 발견했습니다. 이로 인해 모든 요청에 ​​대해 전체 GC가 트리거됩니다. 이는 CLR에서 모든 0세대 개체가 하나의 메모리 세그먼트에 있어야 하기 때문입니다. 현재 할당된 메모리 세그먼트가 가득 차면 새 메모리 세그먼트가 열리고 원래 메모리가 열립니다. 세그먼트는 동시에 열릴 것입니다. 메모리 세그먼트는 2세대 동안 재활용됩니다. 메모리 할당을 줄이는 것 외에는 다른 방법이 없기 때문에 이는 좋은 구현이 아닙니다.

가장 중요한 규칙

가비지 컬렉션을 사용한 고성능 프로그래밍에는 실제로 코드 설계의 지침이 되는 기본 규칙이 있습니다.

수집할 객체는 gen 0에 있거나 전혀 없습니다.
차이점은 GC 중에 객체의 수명 주기가 매우 짧기를 원한다는 것입니다. 절대 만지지 않거나, 할 수 없는 경우 즉, 가능한 한 빨리 2세대로 이동하여 영원히 거기에 있어야 하며 재활용되지 않아야 합니다. 이는 수명이 긴 개체에 대한 참조를 영원히 유지한다는 의미입니다. 일반적으로 이는 개체, 특히 대형 개체 힙의 개체를 재사용할 수 있음을 의미하기도 합니다. 각 상위 세대의 GC 재활용은 이전 세대보다 더 많은 시간이 소요됩니다. 0,1세대 개체를 많이 유지하고 2세대 개체를 거의 유지하려는 경우. 2세대 재활용을 위해 백그라운드 GC를 켜더라도 CPU 작업을 많이 소모하게 되므로 GC 대신 애플리케이션에 CPU의 이 부분을 사용하는 것이 더 나을 수도 있습니다.


참고 0세대 재활용 10번마다 1세대 재활용이 생성되고, 1세대 재활용 10번마다 2세대 재활용이 생성된다는 말을 들어보셨을 것입니다. 이는 실제로 잘못된 것이지만 빠른 0세대 컬렉션은 최대한 많이 생성하고 2세대 컬렉션은 적게 생성하려고 한다는 점을 이해해야 합니다.

이때 0세대에서 1세대로 승격된 객체가 2세대로 이전되기 때문에 1세대 재활용을 피하는 것이 좋습니다. 1세대는 2세대에 진입하는 개체에 대한 버퍼입니다.

이상적으로는 할당하는 모든 개체는 다음 세대 0 컬렉션 이전에 수명 주기를 종료해야 합니다. GC 사이의 시간을 측정하고 이를 애플리케이션에 있는 개체의 수명과 비교할 수 있습니다. 수명주기를 측정하기 위한 도구를 사용하는 방법에 대한 정보는 이 장의 끝 부분에서 찾을 수 있습니다.
이런 생각이 익숙하지 않을 수도 있지만 이 규칙은 응용 프로그램의 모든 측면에 적용됩니다. 가장 중요한 규칙을 구현하려면 자주 생각하고 사고 방식을 근본적으로 바꿔야 합니다.


객체의 수명주기 단축

객체의 범위가 짧을수록 다음 GC가 발생할 때 객체가 다음 세대로 승격될 가능성이 줄어듭니다. 일반적으로 필요할 때까지는 객체를 생성하지 마십시오.

동시에 객체 생성 비용이 너무 높으면 다른 처리 로직을 방해하지 않도록 예외를 더 일찍 생성할 수 있습니다.

또한 개체가 가능한 한 빨리 범위를 벗어나도록 해야 합니다. 지역 변수의 경우 마지막 사용 후 또는 메서드가 끝나기 전에 수명을 종료할 수 있습니다. {}를 사용하여 코드를 묶을 수 있습니다. 이는 실행에 영향을 미치지 않지만 컴파일러는 이 범위의 객체가 수명 주기를 완료하여 더 이상 사용되지 않는 것으로 간주합니다. 객체의 메서드를 호출해야 하는 경우 GC가 가능한 한 빨리 객체를 재활용할 수 있도록 첫 번째와 마지막 사이의 시간 간격을 줄이십시오.

객체가 장기간 유지 관리될 일부 객체와 연결(참조)된 경우 해당 참조 관계를 해제해야 합니다. 더 많은 null 검사가 있을 수 있으며 이로 인해 코드가 더 복잡해질 수 있습니다. 또한 특히 디버깅할 때 개체의 사용 가능한 상태(항상 전체 상태를 사용할 수 있음)와 효율성 사이에 긴장감을 조성할 수 있습니다.
한 가지 해결 방법은 삭제하려는 개체를 로그 메시지와 같은 다른 방식으로 변환하여 후속 디버깅 중에 관련 정보를 쿼리할 수 있도록 하는 것입니다.
또 다른 방법은 구성 가능한 옵션을 코드에 추가하는 것입니다(객체 간의 관계를 해제하지 않고). 프로그램 실행(또는 특정 요청과 같은 프로그램의 특정 부분 실행), 이 모드에서는 객체 참조가 해제되지 않습니다. 관계를 유지하지만 디버깅을 위해 개체를 최대한 편리하게 유지합니다.

객체 계층 구조의 깊이를 줄이세요

이 장의 시작 부분에서 언급했듯이 GC는 재활용 시 객체의 참조 관계를 따라 순회합니다. 서버 GC 모드에서 GC는 다중 스레드 방식으로 실행되지만 스레드가 깊은 수준에서 개체를 처리해야 하는 경우 이를 처리한 모든 스레드는 종료하기 전에 이 스레드가 처리를 완료할 때까지 기다려야 합니다. . 향후 CLR 버전에서는 이 문제에 너무 많은 주의를 기울일 필요가 없습니다. GC는 다중 스레드 실행 중 로드 균형 조정을 위해 더 나은 표시 알고리즘을 사용합니다. 그러나 객체 수준이 매우 깊다면 여전히 이 문제에 주의를 기울여야 합니다.

객체 간 참조 줄이기

이는 이전 섹션의 깊이와 관련이 있지만 몇 가지 다른 요소도 있습니다.
객체가 많은 객체(배열, 목록 등)를 참조하는 경우 객체를 탐색하는 데 많은 시간이 소요됩니다. 복잡한 관계 그래프를 가지고 있어서 오랫동안 문제를 일으키는 것이 바로 GC입니다.
또 다른 문제는 객체가 얼마나 많은 참조 관계를 가지고 있는지 쉽게 알 수 없다면 객체의 수명주기를 정확하게 예측할 수 없다는 것입니다. 이러한 복잡성을 줄이는 것은 코드를 더욱 강력하게 만들 뿐만 아니라 디버깅을 용이하게 하고 더 나은 성능을 달성하기 위해 매우 필요합니다.
또한, 서로 다른 세대의 개체 간 참조, 특히 오래된 개체에서 새 개체로의 참조도 GC 비효율성을 유발한다는 점에 유의하세요. 예를 들어 2세대 개체가 0세대 개체와 참조 관계를 갖고 있는 경우 0세대 GC가 발생할 때마다 2세대 개체 중 일부도 스캔하여 0세대 개체에 대한 참조를 여전히 보유하고 있는지 확인해야 합니다. . 비록 이것이 Full GC는 아니지만 여전히 불필요한 작업이므로 이러한 상황을 피하도록 노력해야 합니다.

객체 고정 방지(Pinning)

객체 고정은 관리 코드에서 로컬 코드로 전송되는 데이터의 안전성을 보장할 수 있습니다. 일반적인 것은 배열과 문자열입니다. 코드가 네이티브 코드와 상호 작용할 필요가 없다면 성능 오버헤드에 대해 걱정하지 마세요.
객체를 고정한다는 것은 가비지 수집(압축 단계) 중에 객체를 이동할 수 없다는 의미입니다. 개체를 고정하면 오버헤드가 많이 발생하지 않지만 GC 재활용 작업이 방해되고 메모리 조각화 가능성이 높아집니다. GC는 재할당 시 객체 사이의 공간을 활용할 수 있도록 재활용 중에 객체의 위치를 ​​기록합니다. 그러나 고정된 객체가 많으면 메모리 조각화가 증가합니다.
Peg는 명시적이거나 암시적일 수 있습니다. 표시된 것은 GCHandle을 사용할 때 GCHandleType.Pinned 매개변수로 설정하거나 안전하지 않은 모드에서 고정 키워드를 사용하는 것입니다. 고정 키워드를 사용할 때와 GCHandle을 사용할 때의 차이점은 Dispose 메서드를 명시적으로 호출할지 여부입니다. 고정을 사용하면 매우 편리하지만 비동기 상황에서는 사용할 수 없습니다. 그러나 여전히 핸들 개체(GCHandle)를 생성하고 이를 다시 전달하여 콜백 중에 처리할 수 있습니다.
암시적 고정 개체가 더 일반적이지만 감지하고 제거하기가 더 어렵습니다. 가장 확실한 예는 플랫폼 호출(P/Invoke)을 통해 비관리 코드에 개체를 전달하는 것입니다. 이는 단지 코드만이 아닙니다. 자주 호출하는 관리되는 API 중 일부는 실제로 네이티브 코드를 호출하고 개체도 고정합니다.
CLR은 자체 데이터 중 일부를 고정하지만 일반적으로 신경 쓸 필요는 없습니다.
이상적으로는 물체를 최대한 고정하지 않도록 노력해야 합니다. 이것이 가능하지 않은 경우 이전의 중요한 규칙을 따르고 가능한 한 빨리 고정된 개체를 해제하도록 노력하십시오. 객체를 단순히 고정했다가 해제하면 수집 작업에 영향을 미칠 가능성이 별로 없습니다. 또한 동시에 여러 개체를 고정하는 것을 피하고 싶을 수도 있습니다. 고정된 개체를 2세대로 교체하거나 LOH에 할당하면 약간 더 좋습니다. 이 규칙에 따르면 대형 개체 힙에 대형 버퍼를 할당하고 실제 필요에 따라 버퍼를 직접 관리할 수 있습니다. 또는 작은 개체 쌍에 버퍼를 할당한 다음 고정하기 전에 2세대로 업그레이드합니다. 이는 개체를 0세대에 직접 고정하는 것보다 낫습니다.

위 내용은 고성능 .NET 작성에 대한 실용적인 튜토리얼의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.