이 글은 기계 번역의 미러 문서이며, 원본 기사로 바로 이동하려면 여기를 클릭해 주세요.

보기: 537|회답: 1

[출처] [돌기]. NET/C#은 인스턴스가 차지하는 메모리 양을 어떻게 계산하나요?

[링크 복사]
2025-8-27 09:00:22에 게시됨 | | | |
CPU와 메모리가 프로그램에서 가장 중요한 두 지표라는 것은 모두 알고 있습니다. 그렇다면 얼마나 많은 사람들이 이 질문에 대해 진지하게 생각해 본 적이 있을까요: 타입(값 타입 또는 참조 타입) 인스턴스가 메모리에서 차지하는 바이트가 얼마나 되는가? 많은 사람들이 답을 못 해요. C#은 크기 계산을 위한 연산자와 API를 제공하지만, 제가 방금 물어본 문제를 완전히 해결하지는 못합니다. 이 글은 값 타입과 참조 타입 인스턴스가 차지하는 메모리 바이트 수를 계산하는 방법을 제공합니다. 소스 코드는 여기에서 다운로드됩니다.

1. 연산자의 크기
2. 마샬.크기오 방법
3. 안전하지 않음.크기Size 방법 >
4. 현장 구성원의 유형을 기반으로 계산할 수 있는가?
5. 가치 유형 및 애플리케이션 유형 배치
6. LDFLDA 지침
7. 값 유형의 바이트 수를 계산합니다.
8. 인용 유형의 바이트 수를 세기
9. 완전한 계산

1. 연산자의 크기

연산의 크기는 타입 인스턴스가 차지하는 바이트 수를 결정하는 데 사용되지만, 관리되지 않은 타입에만 적용할 수 있습니다. 이른바 관리되지 않은 유형은 다음과 같은 것으로 제한됩니다:

원시 타입: 불리언, 바이트, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, 그리고 Single)
십진법
열거 유형
포인터 타입
관리 불가능한 타입의 데이터 멤버만 포함하는 구조체
이름 그대로 관리되지 않은 타입은 값 타입이며, 해당 인스턴스는 관리된 객체에 대한 참조를 포함할 수 없습니다. 만약 이와 같은 일반적인 메서드를 정의하여 크기 연산자를 호출하면, 일반적인 매개변수 T는 해당 메서드에 미정규 제약과 안전하지 않은 태그를 추가해야 합니다.

네이티브와 열거 타입만 연산자 크기를 직접 사용할 수 있으며, 다른 타입(포인터 및 커스텀 구조체)에 적용할 경우 이를 추가해야 합니다./unsafe컴파일 태그를 포함해야 하며, 또한하지 않은맥락 속에서.

다음 구조체 Foobar는 관리되지 않은 타입이 아니므로, 프로그램은 컴파일 오류를 겪게 됩니다.

2. 마샬.크기오 방법

정적 타입 Marshal은 관리되지 않은 메모리를 할당하고 복사하며, 관리형 타입과 비관리 타입 간 변환, 그리고 비관리 메모리에 대한 일련의 연산을 수행하는 데 도움을 주는 일련의 API를 정의합니다(계산 과학에서 Marshal은 데이터 저장 또는 전송을 위한 메모리 객체를 해당 형식으로 변환하는 작업을 의미합니다). 정적(static)은 주어진 타입이나 객체의 바이트 수를 결정하기 위한 다음 4가지 SizeOf 메서드 오버로드를 포함합니다.

Marshal.SizeOf 메서드는 Unmanaged 타입에 대한 지정 타입에 제한이 없지만, 여전히 명시가 필요합니다가치 유형。 들어오는 객체가 객체라면, 값 타입의 상자여야 합니다.

다음 푸바는 다음과 같이 정의됩니다:종류따라서 두 SizeOf 메서드 모두 호출하면 ArgumentException 예외와 프롬프트가 발생합니다: 'Foobar' 타입은 관리되지 않은 구조로 마샬링할 수 없습니다; 의미 있는 크기나 오프셋은 계산할 수 없습니다.

마샬. 크기의 방법제네릭은 지원되지 않습니다하지만 지지를 지지하는 구조 배치에 대한 요구사항도 포함하고 있습니다순차그리고명시적레이아웃 모드. 아래에 표시된 Foobar 구조체는 Auto 레이아웃 모드를 채택하고 있습니다(Auto는 관리되지 않은 환경에서 메모리 배치 요구가 더 엄격하기 때문에 필드 멤버별 메모리 배치의 "동적 계획"을 지원하지 않음), SizeOf 메서드를 호출해도 위와 동일한 ArgumentException 예외를 반환합니다.

3. 안전하지 않음.크기 방법

Static Unsafe는 관리되지 않은 메모리에 대해 더 많은 저수준 연산을 제공하며, 유사한 SizeIOf 메서드도 이 유형에서 정의되어 있습니다. 메서드는 지정된 타입에 대한 제한이 없지만, 참조 타입을 지정하면 다음과 같은 것을 반환합니다포인터 바이트 수"(IntPtr.Size).

4. 현장 구성원의 유형을 기반으로 계산할 수 있는가?

값과 참조 유형 모두 연속적인 조각으로 매핑되거나 레지스터에 직접 저장된다는 것을 알고 있습니다. 타입의 목적은 객체의 메모리 레이아웃을 지정하는 것이며, 같은 타입의 인스턴스들은 동일한 배열을 가지며 바이트 수도 자연스럽게 동일합니다(참조 타입 필드의 경우, 이 비트 시퀀스에서 참조된 주소만 저장합니다). 바이트 길이는 타입에 의해 결정되므로, 각 필드 멤버의 타입을 알 수 있다면 그 타입에 해당하는 바이트 수를 계산할 수 있지 않을까요? 사실, 그럴 수 없습니다.

예를 들어, 바이트, 단, 정수, 긴 바이트가 1, 2, 4, 8이므로 바이트 이진 바이트 수는 2이지만, 바이트 + 단축, 바이트 + 정수, 바이트 + 롱의 타입 조합에서는 해당 바이트가 3, 5, 9가 아니라 3, 8, 16입니다. 왜냐하면 이것은 기억 정렬 문제와 관련이 있기 때문입니다.

5. 값 유형 및 참조 유형 배치

참조 타입과 서브타입 인스턴스가 차지하는 바이트 수도 동일한 데이터 멤버의 경우 다릅니다. 다음 이미지에서 보듯이, 가치 유형 인스턴스의 바이트 순서모두 현장 멤버들로 보관을 담당합니다。 참조 타입의 경우, 해당 메서드 테이블의 주소도 필드 바이트 시퀀스 앞에 저장됩니다. 메서드 테이블은 타입을 설명하는 거의 모든 메타데이터를 제공하며, 이 참조를 통해 인스턴스가 어떤 타입에 속하는지 결정합니다. 맨 앞에는 추가 바이트도 있는데, 이를 이를 '객체 헤더객체의 잠긴 상태를 저장하는 데뿐만 아니라, 해시 값도 이곳에 캐시할 수 있습니다. 참조 타입 변수를 만들면, 이 변수는이 방법은 인스턴스가 차지하는 첫 번째 메모리 바이트를 가리키는 것이 아니라, 메서드 테이블 주소가 저장된 위치를 가리킵니다



6. LDFLDA 지침

앞서 언급했듯이, 정적 타입 Marshal/Unsafe가 제공하는 sizeof 연산자와 SizeOf 메서드는 인스턴스가 차지하는 바이트 길이 계산을 실제로 해결하지 못합니다. 제가 알기로는 이 문제는 C# 필드만으로는 해결할 수 없지만, IL 수준에서 제공됩니다Ldflda설명서가 이 문제를 해결하는 데 도움이 될 수 있습니다. 이름에서 알 수 있듯이, Ldflda는 Load Field Address의 약자로, 인스턴스 내 필드의 주소를 얻는 데 도움을 줍니다. 이 IL 명령어는 C#에 대응하는 API가 없기 때문에, IL Emit을 사용해 다음 형태로만 사용할 수 있습니다.

위 코드 스니펫에서 보듯이, SizeCalculator 타입에 GenerateFieldAddressAccessor 메서드가 있는데, 이 메서드는 지정된 타입의 필드 리스트를 기반으로 Func<object?, long[]> 타입의 delegate를 생성하여 지정된 객체와 모든 필드의 메모리 주소를 반환하는 데 도움을 줍니다. 객체 자체의 주소와 각 필드의 주소를 통해 각 필드의 오프셋을 자연스럽게 얻고, 전체 인스턴스가 차지하는 메모리 바이트 수를 쉽게 계산할 수 있습니다.

7. 값 유형의 바이트 수를 계산합니다.

값 유형과 참조 유형은 메모리 내 레이아웃이 다르기 때문에 서로 다른 계산을 사용해야 합니다. struct의 바이트가 메모리 내 모든 필드의 내용이기 때문에, 우리는 이를 계산하는 영리한 방법을 사용합니다. 예를 들어, 타입 T의 struct의 바이트 수를 정해야 한다면, ValueTuple<T,T> 튜플을 만들고, 그 두 번째 필드 Item2의 오프셋은 struct T의 바이트 수가 됩니다. 구체적인 계산 방법은 다음 CalculateValueTypeInstance 방법에 반영되어 있습니다.

위 코드 스니펫에서 보듯, 계산해야 할 구조체 타입이 T라고 가정할 때, GetDefaultAsObject 메서드를 호출해 반사 형태의 default(T) 객체를 얻고, ValueTuple<T,T>튜플을 생성합니다. 인스턴스와 필드 주소를 계산하기 위해 Func<object?, long[]> delegate를 얻기 위해 GenerateFieldAddressAccessor 메서드를 호출한 후, 이 delegate를 인수로 부릅니다. 세 개의 메모리 주소에 대해 코드 튜플과 필드 1, 2의 주소가 동일하며, Item2에서 첫 번째 주소를 뺀 세 번째 주소를 사용해 원하는 결과를 얻습니다.

8. 인용 유형의 바이트 수를 세기

참조 타입에 대한 바이트 계산은 이 아이디어를 사용해 더 복잡합니다: 인스턴스 자체와 각 필드의 주소를 얻은 후, 마지막 필드의 오프셋을 얻기 위해 주소를 정렬합니다. 이 오프셋을 마지막 필드 자체의 바이트 수에 더한 후, 원하는 결과에 필요한 "첫 번째와 마지막 바이트"를 더하자. 이는 다음 CalculateReferneceTypeInstance 메서드에 반영된다.

위 코드 스니펫에서 보듯, 지정된 타입에 필드가 정의되어 있지 않으면, CalculateReferneceTypeInstance는 참조 타입 인스턴스의 최소 바이트 수를 반환합니다: 주소 포인터 바이트 수의 3배입니다. x86 아키텍처의 경우, 애플리케이션 유형 객체는 최소 12바이트를 차지하며, 여기에는 ObjectHeader(4바이트), 메서드 테이블 포인터(바이트), 그리고 최소 4바이트의 필드 콘텐츠가 포함됩니다(이 4바이트는 필드가 없는 타입이 정의되지 않더라도 필요합니다). x64 아키텍처의 경우, 메서드 테이블 포인터와 최소 필드 내용이 8바이트가 되기 때문에 최소 바이트 수는 24개가 됩니다. 하지만 ObjectHeader의 유효 콘텐츠는 4바이트만 차지하지만, 앞에 4바이트의 패딩이 추가됩니다.

마지막 필드가 차지하는 바이트의 정리도 매우 간단합니다: 만약 타입이 값 타입이라면, 앞서 정의한 CalculateValueTypeInstance 메서드를 호출해 계산하고, 참조 타입이라면 필드에 저장된 내용은 대상 객체의 메모리 주소만이므로 길이는 IntPtr.Size입니다. 참조 타입 인스턴스는 기본적으로 메모리에서 IntPtr.Size와 정렬되어 있기 때문에, 여기서도 이 작업이 이루어집니다. 마지막으로, 참조 타입 인스턴스의 참조는 메모리 첫 바이트가 아니라 메서드 테이블 포인터를 저장하는 바이트를 가리키므로, ObjecthHeader (IntPtr.Size)의 바이트 수를 추가해야 합니다.

9. 완전한 계산

값 타입과 참조 타입 인스턴스의 바이트 수를 계산하는 두 가지 방법은 다음 SizeOf 메서드에서 사용됩니다. Ldflda 명령어 호출이 대응하는 인스턴스를 제공해야 하므로, 이 메서드는 대상 타입을 제공하는 것 외에도 해당 인스턴스를 얻는 위임자를 제공합니다. 이 대리자에 대응하는 매개변수는 기본값으로 설정할 수 있으며, 값 유형에는 기본값을 사용할 것입니다. 참조 타입의 경우, 기본 생성자로 대상 객체를 생성하려고 시도할 것입니다. 이 대리자 객체가 제공되지 않고 대상 인스턴스를 생성할 수 없으면, SizeOf 메서드는 예외를 던집니다. 대상 인스턴스를 제공해야 하지만, 계산된 결과는 타입과 관련이 있으므로 계산된 결과를 캐시합니다. 호출 편의를 위해 또 다른 일반적인 SizeOf 메서드도 제공합니다<T>.

아래 코드 스니펫에서는 동일한 필드 정의를 가진 두 구조체와 타입의 바이트 수를 출력하는 데 사용합니다. 다음 글에서는 계산된 바이트 수를 기반으로 메모리에 있는 인스턴스의 전체 이진 내용을 추가로 확인할 예정이니, 계속 지켜봐 주세요.

원본 링크:하이퍼링크 로그인이 보입니다.




이전의:프론트엔드 프레임워크는 Component-Party 오픈 소스 프로젝트를 학습합니다
다음:MinIO 저장소 (iii) 로컬 파일을 minio 버킷으로 복사-업로드(마이그레이션)
 집주인| 2025-8-27 09:33:22에 게시됨 |
C# 컬렉션은 10,000개의 데이터를 삽입하여 메모리를 차지합니다




코드:


면책 조항:
Code Farmer Network에서 발행하는 모든 소프트웨어, 프로그래밍 자료 또는 기사는 학습 및 연구 목적으로만 사용됩니다; 위 내용은 상업적 또는 불법적인 목적으로 사용되지 않으며, 그렇지 않으면 모든 책임이 사용자에게 부담됩니다. 이 사이트의 정보는 인터넷에서 가져온 것이며, 저작권 분쟁은 이 사이트와는 관련이 없습니다. 위 내용은 다운로드 후 24시간 이내에 컴퓨터에서 완전히 삭제해야 합니다. 프로그램이 마음에 드신다면, 진짜 소프트웨어를 지원하고, 등록을 구매하며, 더 나은 진짜 서비스를 받아주세요. 침해가 있을 경우 이메일로 연락해 주시기 바랍니다.

Mail To:help@itsvse.com