C #에서 개체 인스턴스의 크기 (바이트) 찾기


113

임의의 인스턴스 (다른 개체, 컴포지션, 단일 개체 등의 컬렉션)

크기를 바이트 단위로 어떻게 결정할 수 있습니까?

(현재 다양한 개체 모음이 있으며 집계 된 크기를 결정하려고합니다.)

편집 : 누군가 이것을 할 수있는 Object에 대한 확장 메서드를 작성 했습니까? 꽤 깔끔한 이모가 될 것입니다.



답변:


60

우선 경고 : 다음은 문서화되지 않은 추악한 해킹의 영역에 있습니다. 이 작동에 의존하지 마십시오. 지금 작동하더라도 사소한 또는 주요 .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. 이를 위해 포함 된 모든 요소의 크기를 기본 인스턴스 크기에 추가해야합니다.


아니요.이 작업을 수행하는 "적절한"방법은 없습니다. 왜냐하면 제대로 작동하는 .NET 응용 프로그램이 처음에 관심을 가져야하는 것이 아니기 때문입니다. 위 의 내용은 특정 CLR 구현내부 데이터 구조 와 직접적으로 관련되어 있습니다 (예를 들어 다음 버전의 .NET에서 쉽게 변경 될 수 있음).
Pavel Minaev

3
이것은 C #에서 작동합니까 아니면 관리되는 C ++에서만 작동합니까? 지금까지 C #에서는 만족스럽지 않아서 시도했습니다.Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
.NET 4 버전은 안전하지 않은 코드도 필요하지 않습니다 Marshal.ReadInt32(type.TypeHandle.Value, 4). x86 및 x64에서 작동합니다. 구조체와 클래스 유형 만 테스트했습니다. 이는 값 유형에 대해 박스형 크기를 반환합니다 . @Pavel 아마도 답변을 업데이트 할 수 있습니다.
jnm2

2
@ sab669 잘 교체 type와 함께 obj.GetType()자신의 예에. 어떤 프레임 워크를 사용하는지는 중요하지 않으며 어떤 CLR (v2 또는 v4 또는 CoreCLR) 만 사용합니다. CoreCLR에서 이것을 시도하지 않았습니다.
jnm2

2
@SamGoldberg 이것을 수동으로 계산하는 것은 백만 가지의 엣지 케이스에서 많은 작업입니다. Sizeof는 객체의 런타임 그래프의 메모리 소비가 아니라 객체의 정적 크기를 알려줍니다. VS2017의 메모리 및 CPU 프로파일 링은 ReSharper 및 기타 도구와 마찬가지로 매우 훌륭하며, 이것이 제가 측정하는 데 사용하는 것입니다.
jnm2

20

직렬화 가능한 객체로 작업하는 경우 이진 직렬화기로 직렬화하는 척 (하지만 출력을 망각으로 라우팅)하여 크기를 대략적으로 계산할 수 있습니다.

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();
    }
}

6
물론 이것은 최소 크기를 얻을 수 있지만 메모리의 크기에 대해서는 아무것도 알려주지 않습니다.
John Saunders

Lol, 답장을 확인하기 위해 돌아 오기 전에 내가 가지고 있던 다음 전구는 이진 직렬 변환기를 사용하고있었습니다. John, 이것이 메모리의 실제 크기를 어떻게 제공하지 않습니까?
Janie

2
"직렬화"목적을 위해 직렬화가 원하는 크기 인 직렬화 크기를 제공합니다. 이는 "sit-in-memory"목적과 다를 가능성이 높습니다. 예를 들어 직렬 변환기는 더 작은 정수를 3 바이트로 저장할 수 있습니다.
John Saunders

4
내가 말했듯이, 그것은 단지 근사치 일뿐입니다. 완벽하지는 않지만 메모리의 크기에 대해 "아무것도"알려주지 않는다는 데 동의하지 않습니다. 나는 당신에게 주어진 것을 말할 것입니다 몇 가지 큰 직렬화는 일반적으로 더 큰 메모리 크기와 상관 관계가 될 것이다 - 아이디어. 이 어떤 관계가.
BlueMonkMN

동의합니다. .NET 객체 그래프의 크기에 대한 추정치를 얻는 것이 유용합니다.
Craig Shearer 2011 년

8

관리되지 않는 유형 (일명 값 유형)의 경우 구조체 :

        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에서 읽을 크기를 최소화하는 것이 정렬되지 않은 것보다 더 큰 영향을 미치기 때문에 상당히 빠릅니다.


5

이것은 현재 .NET 구현에는 적용되지 않지만 가비지 수집 / 관리 런타임에서 명심해야 할 한 가지는 개체의 할당 된 크기가 프로그램의 수명 내내 변경 될 수 있다는 것입니다. 예를 들어 일부 세대 별 가비지 수집기 (예 : Generational / Ulterior Reference Counting Hybrid 수집기 )는 개체가 nursery에서 성숙한 공간으로 이동 된 후에 특정 정보 만 저장하면됩니다.

이로 인해 개체 크기를 노출하는 신뢰할 수있는 일반 API를 만들 수 없습니다.


흥미 롭군. 그렇다면 사람들은 개체 / 개체 컬렉션의 크기를 동적으로 결정하기 위해 무엇을할까요?
Janie

2
필요한 것이 무엇인지에 따라 다릅니다. P / Invoke (네이티브 코드 interop)의 경우 Marshal.SizeOf (typeof (T))를 사용합니다. 메모리 프로파일 링의 경우 실행 환경과 협력하여 정보를 제공하는 별도의 프로파일 러를 사용합니다. 배열의 요소 정렬에 관심이있는 경우 DynamicMethod에서 SizeOf IL opcode를 사용할 수 있습니다 (이를위한 .NET 프레임 워크에서 더 쉬운 방법이 없다고 생각합니다).
Sam Harwell

5

일부 최적화 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

이것이 내가 취할 접근 방식입니다. a) 무한 재귀를 방지하고 b) 동일한 메모리를 두 번 추가하지 않도록 그래프에 이전에 발견 된 개체 집합을 추가 할 수 있습니다.
mafu

4

이것은 런타임에 불가능합니다.

그러나 객체 크기를 표시하는 다양한 메모리 프로파일 러가 있습니다.

편집 : CLR 프로파일 링 API를 사용하여 첫 번째를 프로파일 하고 원격 또는 기타를 통해 통신 하는 두 번째 프로그램을 작성할 수 있습니다.


17
런타임에 수행 할 수없는 경우 메모리 프로파일 러가 정보를 어떻게 제공합니까?
Janie

2
프로파일 링 API를 사용합니다. 그러나, 프로그램 자체를 프로파일 링 할 수
SLaks

흥미 롭군. 객체가 너무 많은 메모리를 소비하는 경우를 코드로 처리하려면 어떻게해야합니까?
Janie

4
그렇다면 당신은 자기 인식 소프트웨어를 다루게 될 것이고 나는 매우 두려워 할 것입니다. :-) 진지하게, "단일 책임 원칙"-프로그램이 프로그램이되도록하고, 다른 코드 조각이 너무 많은 메모리를 차지하는 객체를 감시하도록합니다.
John Saunders

2
@Janie : 또한 크기의 중요성과 성능과의 관계에 대해 가정 할 것입니다. 이를 수행하기 전에 실제 저수준 CLR 성능 전문가 (Profiling API에 대해 이미 알고있는 종류)가되고 싶을 것입니다. 그렇지 않으면 이전 경험을 적용하지 않는 상황에 적용 할 수 있습니다.
John Saunders


2

AFAIK는 실제로 각 멤버의 크기를 바이트 단위로 계산하지 않고서는 할 수 없습니다. 그러나 다시 말하지만, (컬렉션 내부의 요소와 같은) 멤버의 크기가 개체의 크기에 포함됩니까, 아니면 해당 멤버에 대한 포인터가 개체의 크기에 포함됩니까? 정의하는 방법에 따라 다릅니다.

나는 그들이 소비 한 메모리를 기반으로 캐시의 개체를 제한하기 전에이 상황에 직면했습니다.

글쎄, 그렇게 할 수있는 트릭이 있다면 그것에 대해 알게되어 기쁠 것입니다!



1

리플렉션을 사용하여 모든 공용 멤버 또는 속성 정보 (객체 유형 제공)를 수집 할 수 있습니다. 그러나 개체에 대한 각 개별 데이터를 살펴 보지 않고는 크기를 결정할 수 없습니다.


1

[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;
    }
}

아니요, 이것은 메모리에서 사용되는 정확한 크기를 제공하지 않습니다. 앞서 언급했듯이 그것은 불가능합니다. 그러나 그것은 당신에게 대략적인 추정치를 줄 것입니다.

이것은 또한 꽤 느립니다.


1

Pavel 및 jnm2에서 :

private int DumpApproximateObjectSize(object toWeight)
{
   return Marshal.ReadInt32(toWeight.GetType().TypeHandle.Value, 4);
}

참고로 인접한 메모리 개체에서만 작동하므로 조심하십시오.


1

.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()

1

구조체 / 값 배열의 경우 다음과 같은 다른 결과가 있습니다.

first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;

(과도하게 단순화 된 예)

접근 방식이 무엇이든간에 .Net이 결과를 올바르게 해석하는 방법을 이해해야합니다. 예를 들어, 반환 된 요소 크기는 약간의 패딩이있는 "정렬 된"요소 크기입니다. 오버 헤드와 크기는 GC 힙, 스택, 필드, 배열 요소의 "boxed"유형 사용에 따라 다릅니다.

(나는 어떤 필드도없이 "더미"빈 구조체를 사용하여 제네릭의 "선택적"인수를 모방 할 때 메모리에 미치는 영향을 알고 싶었습니다. 빈 구조체를 포함하는 다른 레이아웃으로 테스트를 수행하면 빈 구조체가 ( 적어도) 요소 당 1 바이트; .Net은 각 필드에 대해 다른 주소가 필요하기 때문에 필드가 실제로 비어 있거나 크기가 0 인 경우 작동하지 않기 때문에 모호하게 기억합니다.


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플래그 가있는 필드를 가져 와서이를 수행 할 수 있습니다 .
배열과 문자열은 해당 크기에 길이 * 요소 크기를 추가합니다. 집계 객체의 누적 크기에 대해서는 모든 필드를 방문하고 그 내용을 검사하는보다 정교한 솔루션을 구현해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.