Tìm kích thước của cá thể đối tượng theo byte trong c #


113

Đối với bất kỳ trường hợp tùy ý nào (tập hợp các đối tượng khác nhau, bố cục, các đối tượng đơn lẻ, v.v.)

Làm cách nào để xác định kích thước của nó theo byte?

(Tôi hiện có một bộ sưu tập các đối tượng khác nhau và tôi đang cố gắng xác định kích thước tổng hợp của nó)

CHỈNH SỬA: Có ai đó đã viết một phương thức mở rộng cho Đối tượng có thể làm điều này? Đó sẽ là imo khá gọn gàng.



Câu trả lời:


60

Trước hết, một cảnh báo: những gì sau đây hoàn toàn nằm trong lĩnh vực của các bản hack xấu xí, không có giấy tờ. Đừng dựa vào việc này đang hoạt động - ngay cả khi nó hoạt động cho bạn bây giờ, nó có thể ngừng hoạt động vào ngày mai, với bất kỳ bản cập nhật .NET nhỏ hoặc lớn nào.

Bạn có thể sử dụng thông tin trong bài viết này trên CLR Internals Tạp chí MSDN Số phát hành tháng 5 năm 2005 - Tìm hiểu sâu về .NET Framework Internals để xem CLR tạo đối tượng thời gian chạy như thế nào - lần cuối tôi kiểm tra, nó vẫn có thể áp dụng. Đây là cách điều này được thực hiện (nó truy xuất trường "Kích thước phiên bản cơ bản" nội bộ thông qua TypeHandleloại).

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

Điều này hoạt động trên 3.5 SP1 32-bit. Tôi không chắc liệu kích thước trường có giống nhau trên 64-bit hay không - bạn có thể phải điều chỉnh các loại và / hoặc hiệu số nếu chúng không giống nhau.

Điều này sẽ hoạt động đối với tất cả các loại "bình thường", mà tất cả các phiên bản đều có cùng các loại được xác định rõ ràng. Những điều mà điều này không đúng chắc chắn là các mảng và chuỗi, và tôi cũng tin như vậy StringBuilder. Đối với chúng, bạn sẽ thêm kích thước của tất cả các phần tử chứa vào kích thước phiên bản cơ sở của chúng.


Không. Không có cách "thích hợp" nào để làm điều này, bởi vì nó không phải là thứ mà một ứng dụng .NET hoạt động tốt nên được quan tâm ngay từ đầu. Các cấu trúc trên phù hợp trực tiếp với cấu trúc dữ liệu nội bộ của một triển khai CLR cụ thể (ví dụ: có thể dễ dàng thay đổi trong phiên bản tiếp theo của .NET).
Pavel Minaev

3
điều này được cho là hoạt động trong C # hay chỉ được quản lý c ++? Nó không hài lòng trong C # cho đến nay mà tôi đã thử nó:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
Phiên bản .NET 4 này thậm chí không cần mã không an toàn: Marshal.ReadInt32(type.TypeHandle.Value, 4)hoạt động cho x86 và x64. Tôi chỉ thử nghiệm các loại cấu trúc và lớp. Hãy nhớ rằng điều này trả về kích thước đóng hộp cho các loại giá trị. @Pavel Có thể bạn có thể cập nhật câu trả lời của mình.
jnm2

2
@ sab669 tốt, thay thế typebằng obj.GetType()trong ví dụ của mình. Không quan trọng bạn đang sử dụng khung công tác nào, chỉ là CLR (v2 hoặc v4 hoặc CoreCLR). Tôi chưa thử điều này trên CoreCLR.
jnm2

2
@SamGoldberg Tính toán điều này theo cách thủ công là rất nhiều công việc với hàng triệu trường hợp cạnh. Sizeof cho bạn biết kích thước tĩnh của một đối tượng, không phải mức tiêu thụ bộ nhớ của đồ thị thời gian chạy của các đối tượng. Cấu hình bộ nhớ và CPU của VS2017 rất tốt, cũng như của ReSharper và các công cụ khác, và đó là những gì tôi muốn sử dụng để đo lường.
jnm2

20

Bạn có thể ước tính kích thước bằng cách giả vờ tuần tự hóa nó với một bộ tuần tự hóa nhị phân (nhưng định tuyến đầu ra vào quên lãng) nếu bạn đang làm việc với các đối tượng có thể tuần tự hóa.

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
Tất nhiên, điều này có thể giúp bạn có được kích thước tối thiểu, nhưng không cho bạn biết gì về kích thước trong bộ nhớ.
John Saunders

Lol, bóng đèn tiếp theo tôi có trước khi quay lại để kiểm tra các câu trả lời đang sử dụng bộ nối tiếp nhị phân. John, làm thế nào điều này sẽ không cung cấp cho bạn kích thước thực tế trong bộ nhớ?
Janie

2
Nó sẽ cung cấp cho bạn kích thước được tuần tự hóa, đó sẽ là kích thước mà trình tuần tự muốn có, cho mục đích "tuần tự hóa". Chúng có thể khác với các mục đích "sit-in-memory". Ví dụ, có thể bộ tuần tự lưu trữ các số nguyên nhỏ hơn trong ba byte.
John Saunders

4
Như tôi đã nói, nó chỉ là một sự gần đúng. Nó không hoàn hảo, nhưng tôi không đồng ý rằng nó cho bạn biết "không có gì" về kích thước trong bộ nhớ. Tôi có thể nói rằng nó cung cấp cho bạn một số ý tưởng - số tuần tự lớn hơn nói chung sẽ tương quan với kích thước trong bộ nhớ lớn hơn. Có một số mối quan hệ.
BlueMonkMN

Tôi đồng ý - sẽ rất hữu ích khi nhận được ước tính về kích thước của đồ thị đối tượng .NET.
Craig Shearer

8

Đối với các loại không được quản lý hay còn gọi là loại giá trị, cấu trúc:

        Marshal.SizeOf(object);

Đối với các đối tượng được quản lý, giá trị gần hơn của tôi là một con số gần đúng.

        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;

Không sử dụng tuần tự hóa. Một trình định dạng nhị phân thêm tiêu đề, vì vậy bạn có thể thay đổi lớp của mình và tải tệp được tuần tự hóa cũ vào lớp đã sửa đổi.

Ngoài ra, nó sẽ không cho bạn biết kích thước thực trong bộ nhớ cũng như sẽ không tính đến việc căn chỉnh bộ nhớ.

[Chỉnh sửa] Bằng cách sử dụng BiteConverter.GetBytes (prop-value) theo tuần hoàn trên mọi thuộc tính của lớp, bạn sẽ nhận được nội dung theo byte, không tính trọng lượng của lớp hoặc các tham chiếu nhưng gần với thực tế hơn nhiều. Tôi khuyên bạn nên sử dụng một mảng byte cho dữ liệu và một lớp proxy không được quản lý để truy cập các giá trị bằng cách sử dụng truyền con trỏ nếu kích thước quan trọng, lưu ý rằng đó sẽ là bộ nhớ không được căn chỉnh nên trên các máy tính cũ sẽ chậm nhưng bộ dữ liệu LỚN trên RAM HIỆN ĐẠI sẽ nhanh hơn đáng kể, vì giảm thiểu kích thước để đọc từ RAM sẽ có tác động lớn hơn so với không điều chỉnh.


5

Điều này không áp dụng cho việc triển khai .NET hiện tại, nhưng một điều cần lưu ý với thời gian chạy được thu thập / quản lý rác là kích thước được phân bổ của một đối tượng có thể thay đổi trong suốt vòng đời của chương trình. Ví dụ: một số bộ thu gom rác thế hệ (chẳng hạn như bộ thu thập Kết hợp đếm tham chiếu thế hệ / cuối cùng ) chỉ cần lưu trữ một số thông tin nhất định sau khi một đối tượng được chuyển từ vườn ươm đến không gian trưởng thành.

Điều này khiến không thể tạo một API chung chung, đáng tin cậy để hiển thị kích thước đối tượng.


Hấp dẫn. Vậy người ta làm gì để xác định động kích thước của đối tượng / tập hợp đối tượng của họ?
Janie 14/07/09

2
Nó phụ thuộc vào những gì họ cần nó cho. Nếu đối với P / Invoke (tương tác mã gốc), họ sử dụng Marshal.SizeOf (typeof (T)). Nếu để cấu hình bộ nhớ, chúng sử dụng một trình biên dịch riêng biệt hợp tác với môi trường thực thi để cung cấp thông tin. Nếu bạn quan tâm đến việc căn chỉnh phần tử trong một mảng, bạn có thể sử dụng opcode SizeOf IL trong DynamicMethod (Tôi không nghĩ rằng có cách dễ dàng hơn trong .NET framework cho việc này).
Sam Harwell

5

giải pháp an toàn với một số tối ưu hóa mã CyberSaving / MemoryUsage . một số trường hợp:

/* 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

Nó cũng hoạt động với các lớp:

class twostring
{
    public string a { get; set; }
    public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B

Đây là cách tiếp cận tôi cũng sẽ thực hiện. Bạn có thể thêm một tập hợp các đối tượng đã gặp trước đó vào biểu đồ để tránh a) đệ quy vô hạn và b) tránh thêm hai lần cùng một bộ nhớ.
mafu

4

Điều này là không thể thực hiện trong thời gian chạy.

Tuy nhiên, có nhiều trình cấu hình bộ nhớ khác nhau hiển thị kích thước đối tượng.

CHỈNH SỬA : Bạn có thể viết chương trình thứ hai lập hồ sơ chương trình đầu tiên bằng cách sử dụng API hồ sơ CLR và giao tiếp với chương trình đó thông qua loại bỏ hoặc một cái gì đó.


17
Nếu không thể thực hiện được trong thời gian chạy, các trình cấu hình bộ nhớ cung cấp thông tin như thế nào?
Janie 14/07/09

2
Bằng cách sử dụng API hồ sơ. Tuy nhiên, một chương trình không có thể cấu hình riêng của mình
SLaks

Hấp dẫn. Điều gì sẽ xảy ra nếu tôi muốn có mã giải quyết các trường hợp khi các đối tượng sử dụng quá nhiều bộ nhớ?
Janie 14/07/09

4
Sau đó, bạn sẽ phải đối phó với phần mềm tự nhận thức, và tôi rất sợ. :-) Nghiêm túc mà nói, "một người chịu trách nhiệm chính" - hãy để chương trình là chương trình, hãy để một số đoạn mã khác theo dõi các đối tượng chiếm quá nhiều bộ nhớ.
John Saunders

2
@Janie: bạn cũng sẽ đưa ra các giả định về tầm quan trọng của kích thước và cách nó liên quan đến hiệu suất. Tôi nghĩ bạn muốn trở thành một chuyên gia hiệu suất CLR cấp thấp thực sự (người đã biết về API Hồ sơ) trước khi bạn làm điều đó. Nếu không, bạn có thể đang áp dụng những kinh nghiệm trước đây của mình vào một tình huống mà chúng không áp dụng.
John Saunders


2

AFAIK, bạn không thể thực sự đếm sâu kích thước của từng thành viên theo byte. Nhưng một lần nữa, kích thước của một phần tử (như các phần tử bên trong một bộ sưu tập) có tính vào kích thước của đối tượng hay một con trỏ đến thành viên đó được tính vào kích thước của đối tượng không? Phụ thuộc vào cách bạn xác định nó.

Tôi đã từng gặp phải trường hợp này khi tôi muốn giới hạn các đối tượng trong bộ nhớ cache của mình dựa trên bộ nhớ mà chúng sử dụng.

Chà, nếu có một số mẹo để làm điều đó, tôi rất vui được biết về nó!


2

Đối với các loại giá trị, bạn có thể sử dụng Marshal.SizeOf. Tất nhiên, nó trả về số byte cần thiết để điều khiển cấu trúc trong bộ nhớ không được quản lý, điều này không nhất thiết phải là những gì CLR sử dụng.


SizeOf (Đối tượng) có thể không có trong các bản phát hành trong tương lai. Thay vào đó, hãy sử dụng SizeOf <T> (). Để biết thêm thông tin, hãy truy cập go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Bạn có thể sử dụng phản chiếu để thu thập tất cả thông tin về thành viên công khai hoặc tài sản (với loại đối tượng). Tuy nhiên, không có cách nào để xác định kích thước mà không xem qua từng phần dữ liệu riêng lẻ trên đối tượng.


1

Đối với bất kỳ ai đang tìm kiếm một giải pháp không yêu cầu [Serializable]các lớp và kết quả là một ước tính thay vì khoa học chính xác. Phương pháp tốt nhất mà tôi có thể tìm thấy là tuần tự hóa json thành một dòng bộ nhớ bằng cách sử dụng mã hóa UTF32.

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

Không, điều này sẽ không cung cấp cho bạn kích thước chính xác sẽ được sử dụng trong bộ nhớ. Như đã đề cập trước đây, điều đó là không thể. Nhưng nó sẽ cung cấp cho bạn một ước tính sơ bộ.

Lưu ý rằng điều này cũng khá chậm.


1

Từ Pavel và jnm2:

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

Một lưu ý nhỏ là hãy cẩn thận vì nó chỉ hoạt động với các đối tượng bộ nhớ liền kề


1

Tôi đã tạo kiểm tra điểm chuẩn cho các bộ sưu tập khác nhau trong .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Kết quả như sau cho .NET Core 2.2 với 1.000.000 đối tượng với 3 thuộc tính được phân bổ:

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

Để kiểm tra trí nhớ, tôi thấy tốt nhất nên được sử dụng

GC.GetAllocatedBytesForCurrentThread()

1

Đối với mảng cấu trúc / giá trị, tôi có kết quả khác với:

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

(ví dụ đơn giản hóa quá mức)

Dù là cách tiếp cận nào, bạn thực sự cần hiểu cách .Net hoạt động để diễn giải chính xác kết quả. Ví dụ: kích thước phần tử trả về là kích thước phần tử được "căn chỉnh", với một số khoảng đệm. Chi phí chung và do đó kích thước khác nhau tùy thuộc vào cách sử dụng của một loại: "đóng hộp" trên GC heap, trên ngăn xếp, dưới dạng trường, dưới dạng phần tử mảng.

(Tôi muốn biết tác động của bộ nhớ khi sử dụng cấu trúc trống "giả" (không có bất kỳ trường nào) để bắt chước các đối số "tùy chọn" của generic; thực hiện kiểm tra với các bố cục khác nhau liên quan đến cấu trúc trống, tôi có thể thấy rằng cấu trúc trống sử dụng ( ít nhất) 1 byte cho mỗi phần tử; tôi nhớ mơ hồ là do .Net cần một địa chỉ khác nhau cho mỗi trường, địa chỉ này sẽ không hoạt động nếu một trường thực sự trống / có kích thước bằng 0).


0

Cách đơn giản nhất là: int size = *((int*)type.TypeHandle.Value + 1)

Tôi biết đây là chi tiết triển khai nhưng GC dựa vào nó và nó cần phải gần đầu bảng phương pháp để đạt hiệu quả cộng với việc xem xét cách phức tạp mã GC sẽ không ai dám thay đổi nó trong tương lai. Trên thực tế, nó hoạt động cho mọi phiên bản nhỏ / chính của .net framework + .net core. (Hiện tại không thể kiểm tra 1.0)
Nếu bạn muốn cách đáng tin cậy hơn, hãy phát ra một cấu trúc trong một hợp ngữ động với [StructLayout(LayoutKind.Auto)]các trường chính xác theo cùng một thứ tự, lấy kích thước của nó bằng lệnh sizeof IL. Bạn có thể muốn phát ra một phương thức tĩnh bên trong struct chỉ trả về giá trị này. Sau đó thêm 2 * IntPtr.Size cho tiêu đề đối tượng. Điều này sẽ cung cấp cho bạn giá trị chính xác.
Nhưng nếu lớp của bạn bắt nguồn từ một lớp khác, bạn cần tìm từng kích thước của lớp cơ sở riêng biệt và thêm chúng + 2 * Inptr.Size một lần nữa cho tiêu đề. Bạn có thể làm điều này bằng cách lấy các trường có BindingFlags.DeclaredOnlycờ.
Mảng và chuỗi chỉ thêm kích thước đó chiều dài * kích thước phần tử của nó. Đối với kích thước tích lũy của các đối tượng kết hợp, bạn cần triển khai giải pháp phức tạp hơn bao gồm việc truy cập mọi trường và kiểm tra nội dung của nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.