Dưới đây là một (IMO) chương trình ngắn nhưng đầy đủ hơi gọn gàng để chứng minh điều tương tự:
using System;
class Test
{
const int Size = 100000;
static void Main()
{
object[] array = new object[Size];
long initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < Size; i++)
{
array[i] = new string[0];
}
long finalMemory = GC.GetTotalMemory(true);
GC.KeepAlive(array);
long total = finalMemory - initialMemory;
Console.WriteLine("Size of each element: {0:0.000} bytes",
((double)total)/Size);
}
}
Nhưng tôi nhận được kết quả tương tự - overhead cho bất kỳ mảng kiểu tham chiếu là 16 byte, trong khi chi phí cho bất kỳ mảng giá trị nào là 12 byte. Tôi vẫn đang cố gắng giải thích tại sao đó là, với sự trợ giúp của đặc tả CLI. Đừng quên rằng các mảng kiểu tham chiếu là biến thể, có thể có liên quan ...
EDIT: Với sự giúp đỡ của cordbg, tôi có thể xác nhận câu trả lời của Brian - con trỏ kiểu của mảng kiểu tham chiếu là như nhau bất kể loại phần tử thực tế. Có lẽ có một số funkiness trong object.GetType()
(đó là không ảo, hãy nhớ) để giải thích cho điều này.
Như vậy, với quy tắc:
object[] x = new object[1];
string[] y = new string[1];
int[] z = new int[1];
z[0] = 0x12345678;
lock(z) {}
Chúng tôi kết thúc với một cái gì đó như sau:
Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>
Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z
Lưu ý rằng tôi đã đổ bộ nhớ 1 từ trước giá trị của biến chính nó.
Đối x
và y
, các giá trị là:
- Khối đồng bộ, sử dụng cho các khóa mã băm (hoặc một khóa mỏng - xem bình luận của Brian)
- Loại trỏ
- Kích của mảng
- Con trỏ kiểu phần tử
- Tham chiếu rỗng (phần tử đầu tiên)
Đối z
, các giá trị là:
- Sync chặn
- Loại trỏ
- Kích thước của mảng
- 0x12345678 (phần tử đầu tiên)
mảng kiểu giá trị khác nhau (byte [], int [] vv) kết thúc với các con trỏ kiểu khác nhau, trong khi tất cả các mảng kiểu tham chiếu sử dụng cùng kiểu po liên, nhưng có một con trỏ kiểu phần tử khác. Con trỏ kiểu phần tử có cùng giá trị như bạn sẽ tìm thấy như là con trỏ kiểu cho một đối tượng thuộc loại đó. Vì vậy, nếu chúng ta nhìn vào bộ nhớ của một đối tượng chuỗi trong chạy trên, nó sẽ có một con trỏ kiểu 0x00329134.
Từ trước con trỏ loại chắc chắn có một cái gì đó để làm với một trong hai màn hình hoặc mã băm: gọi GetHashCode()
populates rằng bit của bộ nhớ, và tôi tin mặc định object.GetHashCode()
có được một khối đồng bộ để đảm bảo băm đang độc đáo cho tuổi thọ của vật thể. Tuy nhiên, chỉ cần làm lock(x){}
không làm bất cứ điều gì, điều đó làm tôi ngạc nhiên ...
Tất cả điều này chỉ hợp lệ đối với các loại "vectơ", trong CLR, loại "vectơ" là một mảng chiều có giới hạn thấp hơn 0. Các mảng khác sẽ có bố cục khác - cho một thứ, chúng sẽ cần giới hạn dưới được lưu trữ ...
Cho đến nay điều này đã được thử nghiệm, nhưng đây là phỏng đoán - lý do cho hệ thống đang được thực hiện theo cách của nó. Từ đây, tôi thực sự đoán.
- Tất cả các mảng
object[]
đều có thể chia sẻ cùng một mã JIT. Chúng sẽ hoạt động theo cách tương tự về phân bổ bộ nhớ, truy cập mảng, thuộc tính Length
và (quan trọng) bố trí các tham chiếu cho GC. So sánh điều đó với mảng loại giá trị, trong đó các loại giá trị khác nhau có thể có dấu chân "GC" khác nhau (ví dụ: có thể có byte và sau đó là tham chiếu, các giá trị khác sẽ không có tham chiếu nào cả, v.v.).
Mỗi lần bạn chỉ định giá trị trong một thời gian là object[]
thời gian chạy cần kiểm tra xem nó có hợp lệ hay không. Nó cần phải kiểm tra xem loại đối tượng có tham chiếu bạn đang sử dụng cho giá trị phần tử mới có tương thích với loại phần tử của mảng hay không. Ví dụ:
object[] x = new object[1];
object[] y = new string[1];
x[0] = new object(); // Valid
y[0] = new object(); // Invalid - will throw an exception
Đây là hiệp phương sai tôi đã đề cập trước đó. Bây giờ cho rằng điều này sẽ xảy ra cho mỗi nhiệm vụ duy nhất, nó làm cho tinh thần để giảm số lượng các indirections. Đặc biệt, tôi nghi ngờ bạn không thực sự muốn thổi bộ nhớ cache bằng cách phải đi đến đối tượng kiểu cho mỗi assigment để có được kiểu phần tử. Tôi nghi ngờ (và x86, lắp ráp của tôi là không đủ tốt để xác minh điều này) rằng xét nghiệm này cái gì đó như:
- Là giá trị được sao chép một tham chiếu null? Nếu vậy, đó là tốt. (Xong.)
- Tìm nạp con trỏ loại của đối tượng mà điểm tham chiếu tại.
- Con trỏ kiểu đó có giống với con trỏ kiểu phần tử (kiểm tra bình đẳng nhị phân đơn giản) không? Nếu vậy, đó là tốt. (Xong.)
- Con trỏ kiểu đó có tương thích với con trỏ kiểu phần tử không? (Kiểm tra phức tạp hơn nhiều, với sự thừa kế và các giao diện liên quan.) Nếu vậy, điều đó là tốt - nếu không, hãy ném một ngoại lệ.
Nếu chúng ta có thể chấm dứt tìm kiếm trong ba bước đầu tiên, không có nhiều gián tiếp - điều này tốt cho điều gì đó sẽ xảy ra thường xuyên như các phép gán mảng. Không có điều nào trong số này cần phải xảy ra đối với các bài tập kiểu giá trị, bởi vì nó có thể kiểm chứng được.
Vì vậy, đó là lý do tại sao tôi tin rằng mảng loại tham chiếu hơi lớn hơn mảng loại giá trị.
Câu hỏi hay - thực sự thú vị khi nghiên cứu về nó :)
Có phải trong bản dựng Gỡ lỗi hoặc Bản phát hành không? –
Hmm, tôi thực sự không biết, tôi đã sử dụng SnippetCompiler. Khi tôi chuyển sang Visual Studio, kết quả thay đổi một chút: 11,92 cho int [1] và 15,94 cho đối tượng [1], bất kể đó là một bản dựng Debug hay Release. – Qwertie
Ồ, tôi cũng đã thay đổi thành 100000 để giảm ảnh hưởng của độ chi tiết phân bổ trong trình quản lý bộ nhớ. – Qwertie