에서 노다 시간 V2, 우리는 나노초 해상도로 이동하고 있습니다. 이는 우리가 관심있는 전체 시간 범위를 나타 내기 위해 더 이상 8 바이트 정수를 사용할 수 없음을 의미합니다. 이로 인해 Noda Time의 (많은) 구조체의 메모리 사용량을 조사하게되었고 결과적으로 저를 이끌었습니다. CLR의 정렬 결정에서 약간의 이상한 점을 발견했습니다.
첫째, 나는 이것이 실현 이다 기본 동작은 언제든지 변경 될 수 있습니다 구현 결정하고있다. 나는 것을 깨닫게 수 사용하여 수정 [StructLayout]
하고 [FieldOffset]
,하지만 난 오히려 가능하면이 필요하지 않은 솔루션을 가지고 올 것입니다.
내 핵심 시나리오는 struct
참조 유형 필드와 두 개의 다른 값 유형 필드를 포함하는이 필드에 대한 간단한 래퍼입니다 int
. 나는 그것이 64 비트 CLR에서 16 바이트로 표현 되기를 바랐 지만 (참조 용으로 8 바이트, 나머지 용으로 4 바이트) 어떤 이유로 24 바이트를 사용하고 있습니다. 그런데 저는 배열을 사용하여 공간을 측정하고 있습니다. 상황에 따라 레이아웃이 다를 수 있다는 것을 이해하지만 이것은 합리적인 시작점처럼 느껴졌습니다.
다음은 문제를 보여주는 샘플 프로그램입니다.
using System;
using System.Runtime.InteropServices;
#pragma warning disable 0169
struct Int32Wrapper
{
int x;
}
struct TwoInt32s
{
int x, y;
}
struct TwoInt32Wrappers
{
Int32Wrapper x, y;
}
struct RefAndTwoInt32s
{
string text;
int x, y;
}
struct RefAndTwoInt32Wrappers
{
string text;
Int32Wrapper x, y;
}
class Test
{
static void Main()
{
Console.WriteLine("Environment: CLR {0} on {1} ({2})",
Environment.Version,
Environment.OSVersion,
Environment.Is64BitProcess ? "64 bit" : "32 bit");
ShowSize<Int32Wrapper>();
ShowSize<TwoInt32s>();
ShowSize<TwoInt32Wrappers>();
ShowSize<RefAndTwoInt32s>();
ShowSize<RefAndTwoInt32Wrappers>();
}
static void ShowSize<T>()
{
long before = GC.GetTotalMemory(true);
T[] array = new T[100000];
long after = GC.GetTotalMemory(true);
Console.WriteLine("{0}: {1}", typeof(T),
(after - before) / array.Length);
}
}
그리고 내 랩톱의 컴파일 및 출력 :
c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.
c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24
그래서:
- 참조 유형 필드가없는 경우 CLR은
Int32Wrapper
필드를 함께 압축 할 수 있습니다 (TwoInt32Wrappers
크기는 8). - 참조 유형 필드가 있어도 CLR은 여전히
int
필드를 함께 압축 할 수 있습니다 (RefAndTwoInt32s
크기는 16). - 두 가지를 결합하면 각
Int32Wrapper
필드가 8 바이트로 채워지거나 정렬 된 것처럼 보입니다. (RefAndTwoInt32Wrappers
크기는 24입니다.) - 디버거에서 동일한 코드를 실행하면 (하지만 여전히 릴리스 빌드) 크기가 12로 표시됩니다.
몇 가지 다른 실험에서도 비슷한 결과가 나왔습니다.
- 값 유형 필드 뒤에 참조 유형 필드를 두는 것은 도움이되지 않습니다.
object
대신 사용 하는string
것은 도움 이 되지 않습니다 ( "모든 참조 유형"일 것으로 예상).- 참조 주위의 "래퍼"로 다른 구조체를 사용하는 것은 도움이되지 않습니다.
- 참조 주위의 래퍼로 일반 구조체를 사용하는 것은 도움이되지 않습니다.
- 필드를 계속 추가하면 (단순성을 위해 쌍으로)
int
필드는 여전히 4 바이트로Int32Wrapper
계산 되고 필드는 8 바이트로 계산됩니다. [StructLayout(LayoutKind.Sequential, Pack = 4)]
시야에있는 모든 구조체에 추가해도 결과가 변경되지는 않습니다.
누구든지 이에 대한 설명 (이상적으로는 참조 문서 포함)이 있거나 상수 필드 오프셋 을 지정 하지 않고 필드를 압축하고 싶다는 CLR에 대한 힌트를 얻을 수있는 방법에 대한 제안이 있습니까?
TwoInt32Wrappers
거나 an Int64
and a TwoInt32Wrappers
? 어떻게 일반적인를 만들 경우에 대한 Pair<T1,T2> {public T1 f1; public T2 f2;}
다음 생성 Pair<string,Pair<int,int>>
및 Pair<string,Pair<Int32Wrapper,Int32Wrapper>>
? 어떤 조합이 JITter가 물건을 채우도록 강요합니까?
Pair<string, TwoInt32Wrappers>
않습니다 단지 16 바이트를주고 그 문제를 해결 할 수 있도록. 매혹적인.
Marshal.SizeOf
.NET 코드의 구조 크기와 관련이없는 네이티브 코드로 전달되는 구조의 크기를 반환합니다.
Ref<T>
않지만string
대신 사용 하고 있습니다.