머리말
.NET8의 JIT에는 Non-GC Heap이라는 새로운 메커니즘이 도입되었습니다. JIT는 이름처럼 GC에 의해 관리되지 않는 Non-GC Heap에 관련 개체가 할당되도록 보장할 수 있습니다. JIT는 이 객체가 GC에 의해 참조되지 않고 이 객체의 수명 주기 동안 루트 객체(GC에 의해 파괴되지 않는 객체)로 남아 있는지 확인해야 합니다. 원본 텍스트: .NET8 Ultimate Performance Optimization 비GC 힙
개요
이 메커니즘을 도입하는 이유는 무엇입니까? 먼저 코드를 살펴보겠습니다.
public static string GetPrefix() => "https://";
static void Main(string[] args)
{
GetPrefix ();
}
여기서 GetPrefix 함수는 상수 문자열 값을 반환하며 해당 ASM은 다음과 같습니다.
mov rax,185CAC02068h
mov rax,qword ptr [rax]
두 개의 mov 명령어. 첫 번째는 개체 포인터에 대한 포인터이고 두 번째는 개체에 대한 포인터입니다. 두 가지 간단한 명령이지만 그 뒤에 있는 논리는 더 복잡합니다. 기본적으로
문자열 상수 값입니다. .NET7의 JIT는 또한 이 문자열 상수 값을 힙에 복사하여 문자열 개체에 할당합니다. 반환됨 개체에 대한 보조 포인터입니다. Heap 객체이기 때문에 GC에 의해 이동될 수 있고, 매번 새로운 주소를 얻어야 하므로 부담이 커지는 경우가 많습니다.
여기서 문제가 무엇입니까? 문자열 상수 값에 그렇게 많은 단계가 필요합니까? 오버헤드가 너무 크면 단순화할 수 있나요? 생각하기 쉬운 일반적인 방법이 있는데, 바로 이 문자열 상수값의 주소를 고정하는 것인데, 이 상수값을 사용해야 할 때마다 이 고정된 주소에서 직접 읽어올 수 있는데 괜찮을까요? GC 힙은 분명히 하드 코딩하여 수정할 수 없습니다.
물론 가능합니다.방법은 문자열 상수 값을 POH(Fixed Object Heap)에 넣어 GC가 이동하지 못하도록 하는 것입니다. 이는 GC 재활용 시 이동 비용을 절감하지만, 고정된 객체도 GC로 제어하기 때문에 문제를 근본적으로 해결하지는 못합니다.위 단계는 이동할 수 없다는 점만 제외하면 동일하며, POH는 루트 객체를 처리하지 않습니다. 재활용될 수 있으며 주소가 다른 데이터를 가리키며 오류가 발생합니다.
특징
이 문제를 완벽하게 해결하기 위해 이 글의 주인공인 Non-GC Heap이 등장한다. 여기에는 세 가지 특성이 있습니다.
1. JIT는 이 개체가 GC에서 참조되지 않도록 해야 합니다
. 2. 이 개체는 수명 주기 동안 항상 루트 개체였습니다
. 3. 언로드 가능한 컨텍스트의 일부가 될 수 없습니다.
GC 힙에는 작은 개체 힙(SOH - 85,000바이트보다 작은 개체), 큰 개체 힙(LOH - 85,000바이트보다 큰 개체), 고정 개체 힙(POH) 및 No-GC 힙이 분리되어 있다고 생각할 수 있습니다. GC
힙 FOH(동결 힙) 외부.
이제 JIT는 생성된 코드에서 개체에 액세스할 때 간접 참조를 방지하고 대신 개체의 주소를 직접 하드코딩합니다.
GetPrefix 함수의 ASM은 .NET8 Non-GC Heap에서 다음과 같습니다.
mov rax,26180000218h
C3 ret
26180000218h는 객체 주소이고 mov가 직접 반환됩니다. mov 하나만 단순화한 것처럼 보이지만 실제로는 하드코딩된 고정 모드 주소를 통해 전체 문자열 상수 값, 즉 문자열 상수 값이 GC 힙 대신 FOH에 할당되는 원리를 단순화합니다. 말할 필요도 없이 성능이 크게 향상되었습니다. 다음은 13배의 성능 향상을 측정합니다.
Method Job Mean Ratio
GetPrefix .NET 7 1.3450 ns
GetPrefix .NET 8 0.0729 ns
기타 비GC 힙 작업
1: typeof(T)를 사용하여 생성된 RuntimeType 개체
public Type GetTestsType() => typeof(Tests);
2: Array.Empty()를 보다 효율적으로 만들기 위해 빈 배열이 Non-GC Heap에 할당됩니다.
public string[] Test() => Array.Empty<string>();
둘 다 .NET8의 다음 ASM과 유사하며 mov가 직접 반환됩니다.
mov rax,1A0814EAEA8
ret
3: 정적 값 유형 필드와 연결된 힙 개체에는 GC에서 참조하는 필드가 포함되어 있지 않습니다.
public partial class Tests
{
private static readonly ConfigurationData s_config = ConfigurationData.ReadData();
public TimeSpan GetRefreshInterval() => s_config.RefreshInterval;
private struct ConfigurationData
{
public static ConfigurationData ReadData() => new ConfigurationData
{
Index = 0x12345,
Id = Guid.NewGuid(),
IsEnabled = true,
RefreshInterval = TimeSpan.FromSeconds(100)
};
public int Index;
public Guid Id;
public bool IsEnabled;
public TimeSpan RefreshInterval;
}
}
RefreshInterval .NET7은 다음과 같습니다.
mov rax,13D84001F78
mov rax,[rax]
mov rax,[rax+20]
ret
RefreshInterval .NET8은 다음과 같습니다.
mov rax,20D9853AE48
mov rax,[rax]
ret
4: 세대 간 GC 참조 판단
암호:
public class Tests
{
public void Write()
{
string dst = "old";
Write(ref dst, "new");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Write(ref string dst, string s) => dst = s;
}
쓰기는 .NET7 및 .NET8에서 다음과 같이 생성됩니다.
call CORINFO_HELP_CHECKED_ASSIGN_REF
nop
ret
CORINFO_HELP_CHECKED_ASSIGN_REF는 "GC 쓰기 장벽"을 포함하는 JIT 도우미 함수입니다. 이 함수는 GC가 어떤 참조가 작성되고 있는지 추적할 수 있게 해주는 작은 코드 조각입니다. 예를 들어 할당이 진행 중이기 때문에 알아야 할 수도 있습니다. made 객체는 gen0일 수 있고 대상은 gen2일 수 있습니다.
이 코드를 미세 조정하세요.
public class Tests
{
public void Write()
{
string dst = "old";
Write(ref dst);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Write(ref string dst) => dst = "new";
}
- 구현된 함수는 dst가 상수 문자열을 직접 할당한다는 점만 제외하면 동일합니다. 상수 문자열은 Non-GC Heap에 할당된다는 점을 기억하시나요? .NET7에는 도우미 기능이 여전히 필요합니다.
mov rdx,1FF0E4014A0
mov rdx,[rdx]
call CORINFO_HELP_CHECKED_ASSIGN_REF
nop
ret
그러나 .NET8에서는
mov rax,1B3814EAEC8
mov [rcx],rax
ret
.NET8은 비GC 힙에 상수 문자열이 있다는 것을 인식하므로, card_table과 마찬가지로 그 안에 어떤 코드가 있는지 확인하기 위해 GC 추적이 필요하지 않습니다. 따라서 CORINFO_HELP_CHECKED_ASSIGN_REF가 최적화되었습니다.
과거의 하이라이트:
.Net 새 버전의 성능이 C++의 90%에 도달합니까?
면접관은 .Net 개체에 null 값이 할당되면 GC에서 재활용됩니까?
.Net JIT의 멋진 운영에 대한 DNGuard HVM 원리에 대한 간략한 분석
저자: 강호 논평. 공개 계정: jianghupt 팔로우를 환영합니다. 기사가 처음 나온 곳.
OpenAI는 모든 사용자에게 ChatGPT Voice Vite 5를 무료로 공개합니다. 공식 출시됩니다. 운영자의 마법 작전: 백그라운드에서 네트워크 연결을 끊고, 광대역 계정을 비활성화하고, 사용자가 광 모뎀을 강제로 변경하도록 합니다. Microsoft 오픈 소스 터미널 채팅 프로그래머가 ETC 잔액을 조작하고 연간 260만 위안 이상 횡령 Redis의 아버지가 사용하는 Pure C 언어 코드는 Telegram Bot 프레임워크를 구현합니다. 오픈 소스 프로젝트 관리자라면 이런 답변을 어디까지 견딜 수 있습니까? Microsoft Copilot Web AI는 중국 OpenAI를 지원하는 12월 1일 공식 출시될 예정입니다 . 전 CEO이자 사장인 Sam Altman과 Greg Brockman이 Microsoft에 합류했습니다. Broadcom은 VMware의 성공적인 인수를 발표했습니다.