임의의 인스턴스 (다른 개체, 컴포지션, 단일 개체 등의 컬렉션)
크기를 바이트 단위로 어떻게 결정할 수 있습니까?
(현재 다양한 개체 모음이 있으며 집계 된 크기를 결정하려고합니다.)
편집 : 누군가 이것을 할 수있는 Object에 대한 확장 메서드를 작성 했습니까? 꽤 깔끔한 이모가 될 것입니다.
임의의 인스턴스 (다른 개체, 컴포지션, 단일 개체 등의 컬렉션)
크기를 바이트 단위로 어떻게 결정할 수 있습니까?
(현재 다양한 개체 모음이 있으며 집계 된 크기를 결정하려고합니다.)
편집 : 누군가 이것을 할 수있는 Object에 대한 확장 메서드를 작성 했습니까? 꽤 깔끔한 이모가 될 것입니다.
답변:
우선 경고 : 다음은 문서화되지 않은 추악한 해킹의 영역에 있습니다. 이 작동에 의존하지 마십시오. 지금 작동하더라도 사소한 또는 주요 .NET 업데이트로 내일 작동이 중지 될 수 있습니다.
CLR 내부에 대한이 문서의 정보를 사용할 수 있습니다. MSDN Magazine 2005 년 5 월-.NET Framework 내부로 드릴 인하여 CLR이 런타임 개체를 만드는 방법을 확인합니다. 마지막으로 확인했지만 여전히 적용 가능했습니다. 이 작업이 수행되는 방법은 다음과 같습니다 ( TypeHandle유형을 통해 내부 "기본 인스턴스 크기"필드를 검색 함 ).
object obj = new List<int>(); // whatever you want to get the size of
RuntimeTypeHandle th = obj.GetType().TypeHandle;
int size = *(*(int**)&th + 1);
Console.WriteLine(size);
이것은 3.5 SP1 32 비트에서 작동합니다. 필드 크기가 64 비트에서 동일한 지 확실하지 않습니다. 그렇지 않은 경우 유형 및 / 또는 오프셋을 조정해야 할 수 있습니다.
이것은 모든 인스턴스가 동일하고 잘 정의 된 유형을 갖는 모든 "일반"유형에 대해 작동합니다. 이것이 사실이 아닌 것은 확실히 배열과 문자열이며 나는 또한 StringBuilder. 이를 위해 포함 된 모든 요소의 크기를 기본 인스턴스 크기에 추가해야합니다.
Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Marshal.ReadInt32(type.TypeHandle.Value, 4). x86 및 x64에서 작동합니다. 구조체와 클래스 유형 만 테스트했습니다. 이는 값 유형에 대해 박스형 크기를 반환합니다 . @Pavel 아마도 답변을 업데이트 할 수 있습니다.
type와 함께 obj.GetType()자신의 예에. 어떤 프레임 워크를 사용하는지는 중요하지 않으며 어떤 CLR (v2 또는 v4 또는 CoreCLR) 만 사용합니다. CoreCLR에서 이것을 시도하지 않았습니다.
직렬화 가능한 객체로 작업하는 경우 이진 직렬화기로 직렬화하는 척 (하지만 출력을 망각으로 라우팅)하여 크기를 대략적으로 계산할 수 있습니다.
class Program
{
static void Main(string[] args)
{
A parent;
parent = new A(1, "Mike");
parent.AddChild("Greg");
parent.AddChild("Peter");
parent.AddChild("Bobby");
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
SerializationSizer ss = new SerializationSizer();
bf.Serialize(ss, parent);
Console.WriteLine("Size of serialized object is {0}", ss.Length);
}
}
[Serializable()]
class A
{
int id;
string name;
List<B> children;
public A(int id, string name)
{
this.id = id;
this.name = name;
children = new List<B>();
}
public B AddChild(string name)
{
B newItem = new B(this, name);
children.Add(newItem);
return newItem;
}
}
[Serializable()]
class B
{
A parent;
string name;
public B(A parent, string name)
{
this.parent = parent;
this.name = name;
}
}
class SerializationSizer : System.IO.Stream
{
private int totalSize;
public override void Write(byte[] buffer, int offset, int count)
{
this.totalSize += count;
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
// Nothing to do
}
public override long Length
{
get { return totalSize; }
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
}
관리되지 않는 유형 (일명 값 유형)의 경우 구조체 :
Marshal.SizeOf(object);
관리되는 개체의 경우 내가 가까워 질수록 근사치입니다.
long start_mem = GC.GetTotalMemory(true);
aclass[] array = new aclass[1000000];
for (int n = 0; n < 1000000; n++)
array[n] = new aclass();
double used_mem_median = (GC.GetTotalMemory(false) - start_mem)/1000000D;
직렬화를 사용하지 마십시오. 바이너리 포맷터는 헤더를 추가하므로 클래스를 변경하고 이전 직렬화 된 파일을 수정 된 클래스로로드 할 수 있습니다.
또한 메모리의 실제 크기를 알려주지 않으며 메모리 정렬도 고려하지 않습니다.
[편집] 클래스의 모든 속성에 대해 BiteConverter.GetBytes (prop-value)를 재귀 적으로 사용하면 클래스 또는 참조의 가중치를 계산하지 않지만 현실에 훨씬 더 가까운 바이트 단위의 내용을 얻을 수 있습니다. 크기가 중요한 경우 포인터 캐스팅을 사용하여 값에 액세스하려면 데이터에 바이트 배열을 사용하고 관리되지 않는 프록시 클래스를 사용하는 것이 좋습니다. 메모리가 정렬되지 않았으므로 오래된 컴퓨터에서는 느리지 만 현대 RAM의 거대한 데이터 세트는 RAM에서 읽을 크기를 최소화하는 것이 정렬되지 않은 것보다 더 큰 영향을 미치기 때문에 상당히 빠릅니다.
이것은 현재 .NET 구현에는 적용되지 않지만 가비지 수집 / 관리 런타임에서 명심해야 할 한 가지는 개체의 할당 된 크기가 프로그램의 수명 내내 변경 될 수 있다는 것입니다. 예를 들어 일부 세대 별 가비지 수집기 (예 : Generational / Ulterior Reference Counting Hybrid 수집기 )는 개체가 nursery에서 성숙한 공간으로 이동 된 후에 특정 정보 만 저장하면됩니다.
이로 인해 개체 크기를 노출하는 신뢰할 수있는 일반 API를 만들 수 없습니다.
일부 최적화 CyberSaving / MemoryUsage 코드가있는 안전한 솔루션 . 어떤 경우 :
/* test nullable type */
TestSize<int?>.SizeOf(null) //-> 4 B
/* test StringBuilder */
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append("わたしわたしわたしわ");
TestSize<StringBuilder>.SizeOf(sb ) //-> 3132 B
/* test Simple array */
TestSize<int[]>.SizeOf(new int[100]); //-> 400 B
/* test Empty List<int>*/
var list = new List<int>();
TestSize<List<int>>.SizeOf(list); //-> 205 B
/* test List<int> with 100 items*/
for (int i = 0; i < 100; i++) list.Add(i);
TestSize<List<int>>.SizeOf(list); //-> 717 B
클래스에서도 작동합니다.
class twostring
{
public string a { get; set; }
public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B
이것은 런타임에 불가능합니다.
그러나 객체 크기를 표시하는 다양한 메모리 프로파일 러가 있습니다.
편집 : CLR 프로파일 링 API를 사용하여 첫 번째를 프로파일 링 하고 원격 또는 기타를 통해 통신 하는 두 번째 프로그램을 작성할 수 있습니다.
명령이있는 Son Of Strike 를 사용하십시오 ObjSize.
사용 된 실제 메모리 는 개체 데이터 바로 앞에있는 ObjSize으로 인해 항상 보고서 보다 큽니다 synkblk.
여기에서 두 가지 모두에 대해 자세히 알아보십시오. MSDN Magazine Issue 2005 May-Drill Into .NET Framework Internals to See how the CLR Creates Runtime Objects .
AFAIK는 실제로 각 멤버의 크기를 바이트 단위로 계산하지 않고서는 할 수 없습니다. 그러나 다시 말하지만, (컬렉션 내부의 요소와 같은) 멤버의 크기가 개체의 크기에 포함됩니까, 아니면 해당 멤버에 대한 포인터가 개체의 크기에 포함됩니까? 정의하는 방법에 따라 다릅니다.
나는 그들이 소비 한 메모리를 기반으로 캐시의 개체를 제한하기 전에이 상황에 직면했습니다.
글쎄, 그렇게 할 수있는 트릭이 있다면 그것에 대해 알게되어 기쁠 것입니다!
값 유형의 경우 Marshal.SizeOf. 물론 관리되지 않는 메모리에서 구조를 마샬링하는 데 필요한 바이트 수를 반환하며, 이는 반드시 CLR이 사용하는 것은 아닙니다.
[Serializable]수업이 필요하지 않고 결과가 정확한 과학이 아닌 근사치 인 솔루션을 찾는 모든 사람에게 적합합니다 . 내가 찾을 수있는 가장 좋은 방법은 UTF32 인코딩을 사용하여 json을 메모리 스트림으로 직렬화하는 것입니다.
private static long? GetSizeOfObjectInBytes(object item)
{
if (item == null) return 0;
try
{
// hackish solution to get an approximation of the size
var jsonSerializerSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
MaxDepth = 10,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var formatter = new JsonMediaTypeFormatter { SerializerSettings = jsonSerializerSettings };
using (var stream = new MemoryStream()) {
formatter.WriteToStream(item.GetType(), item, stream, Encoding.UTF32);
return stream.Length / 4; // 32 bits per character = 4 bytes per character
}
}
catch (Exception)
{
return null;
}
}
아니요, 이것은 메모리에서 사용되는 정확한 크기를 제공하지 않습니다. 앞서 언급했듯이 그것은 불가능합니다. 그러나 그것은 당신에게 대략적인 추정치를 줄 것입니다.
이것은 또한 꽤 느립니다.
.NET의 다양한 컬렉션에 대한 벤치 마크 테스트를 만들었습니다. https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation
3 개의 속성이 할당 된 1,000,000 개의 개체가있는 .NET Core 2.2의 결과는 다음과 같습니다.
Testing with string: 1234567
Hashtable<TestObject>: 184 672 704 B
Hashtable<TestObjectRef>: 136 668 560 B
Dictionary<int, TestObject>: 171 448 160 B
Dictionary<int, TestObjectRef>: 123 445 472 B
ConcurrentDictionary<int, TestObject>: 200 020 440 B
ConcurrentDictionary<int, TestObjectRef>: 152 026 208 B
HashSet<TestObject>: 149 893 216 B
HashSet<TestObjectRef>: 101 894 384 B
ConcurrentBag<TestObject>: 112 783 256 B
ConcurrentBag<TestObjectRef>: 64 777 632 B
Queue<TestObject>: 112 777 736 B
Queue<TestObjectRef>: 64 780 680 B
ConcurrentQueue<TestObject>: 112 784 136 B
ConcurrentQueue<TestObjectRef>: 64 783 536 B
ConcurrentStack<TestObject>: 128 005 072 B
ConcurrentStack<TestObjectRef>: 80 004 632 B
기억력 테스트를 위해 최선을 다했습니다.
GC.GetAllocatedBytesForCurrentThread()
구조체 / 값 배열의 경우 다음과 같은 다른 결과가 있습니다.
first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;
(과도하게 단순화 된 예)
접근 방식이 무엇이든간에 .Net이 결과를 올바르게 해석하는 방법을 이해해야합니다. 예를 들어, 반환 된 요소 크기는 약간의 패딩이있는 "정렬 된"요소 크기입니다. 오버 헤드와 크기는 GC 힙, 스택, 필드, 배열 요소의 "boxed"유형 사용에 따라 다릅니다.
(나는 어떤 필드도없이 "더미"빈 구조체를 사용하여 제네릭의 "선택적"인수를 모방 할 때 메모리에 미치는 영향을 알고 싶었습니다. 빈 구조체를 포함하는 다른 레이아웃으로 테스트를 수행하면 빈 구조체가 ( 적어도) 요소 당 1 바이트; .Net은 각 필드에 대해 다른 주소가 필요하기 때문에 필드가 실제로 비어 있거나 크기가 0 인 경우 작동하지 않기 때문에 모호하게 기억합니다.
가장 간단한 방법은 다음과 같습니다. int size = *((int*)type.TypeHandle.Value + 1)
나는 이것이 구현 세부 사항이라는 것을 알고 있지만 GC는 그것에 의존하고 효율성을 위해 메소드 테이블의 시작에 가까워 야하며 GC 코드 복잡도가 앞으로 감히 아무도 그것을 변경하지 않을 것인지 고려할 필요가 있습니다. 실제로 .net 프레임 워크 + .net 코어의 모든 부 / 주 버전에서 작동합니다. (현재 1.0에 대해 테스트 할 수 없음)
보다 안정적인 방법을 원하면 [StructLayout(LayoutKind.Auto)]동일한 순서로 똑같은 필드가 있는 동적 어셈블리에서 구조체 를 내보내고 sizeof IL 명령어로 . 이 값을 단순히 반환하는 구조체 내에서 정적 메서드를 내보낼 수 있습니다. 그런 다음 개체 헤더에 2 * IntPtr.Size를 추가합니다. 정확한 가치를 제공해야합니다. 로 크기를 가져 오십시오.
그러나 클래스가 다른 클래스에서 파생 된 경우 각각을 찾아야합니다. 기본 클래스의 크기를 별도로 추가하고 헤더에 대해 다시 + 2 * Inptr.Size를 추가합니다. BindingFlags.DeclaredOnly플래그 가있는 필드를 가져 와서이를 수행 할 수 있습니다 .
배열과 문자열은 해당 크기에 길이 * 요소 크기를 추가합니다. 집계 객체의 누적 크기에 대해서는 모든 필드를 방문하고 그 내용을 검사하는보다 정교한 솔루션을 구현해야합니다.