Trong Noda Time v2, chúng tôi đang chuyển sang độ phân giải nano giây. Điều đó có nghĩa là chúng tôi không còn có thể sử dụng số nguyên 8 byte để đại diện cho toàn bộ khoảng thời gian mà chúng tôi quan tâm. Điều đó đã thúc đẩy tôi điều tra việc sử dụng bộ nhớ của (nhiều) cấu trúc của Noda Time, điều này đã dẫn tôi để phát hiện ra một chút kỳ lạ trong quyết định liên kết của CLR.
Thứ nhất, tôi nhận thấy rằng đây là một quyết định triển khai và hành vi mặc định có thể thay đổi bất cứ lúc nào. Tôi nhận ra rằng tôi có thể sửa đổi nó bằng cách sử dụng [StructLayout]
và [FieldOffset]
, nhưng tôi muốn đưa ra một giải pháp không yêu cầu điều đó nếu có thể.
Kịch bản cốt lõi của tôi là tôi có một trường struct
chứa một trường kiểu tham chiếu và hai trường kiểu giá trị khác, trong đó những trường đó là trình bao bọc đơn giản int
. Tôi đã hy vọng rằng nó sẽ được biểu diễn dưới dạng 16 byte trên CLR 64 bit (8 cho tham chiếu và 4 cho mỗi cái khác), nhưng vì một số lý do nó sử dụng 24 byte. Nhân tiện, tôi đang đo không gian bằng cách sử dụng các mảng - tôi hiểu rằng bố cục có thể khác nhau trong các tình huống khác nhau, nhưng điều này cảm thấy như một điểm khởi đầu hợp lý.
Đây là một chương trình mẫu chứng minh vấn đề:
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);
}
}
Và quá trình biên dịch và xuất trên máy tính xách tay của tôi:
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
Vì thế:
- Nếu bạn không có trường loại tham chiếu, CLR rất vui khi đóng gói
Int32Wrapper
các trường lại với nhau (TwoInt32Wrappers
có kích thước 8) - Ngay cả với trường kiểu tham chiếu, CLR vẫn rất vui khi đóng gói
int
các trường lại với nhau (RefAndTwoInt32s
có kích thước là 16) - Kết hợp cả hai, mỗi
Int32Wrapper
trường dường như được đệm / căn chỉnh thành 8 byte. (RefAndTwoInt32Wrappers
có kích thước là 24.) - Chạy cùng một mã trong trình gỡ lỗi (nhưng vẫn là một bản dựng phát hành) sẽ hiển thị kích thước là 12.
Một số thí nghiệm khác cũng cho kết quả tương tự:
- Đặt trường loại tham chiếu sau các trường loại giá trị không hữu ích
- Sử dụng
object
thay vìstring
không hữu ích (tôi mong đợi đó là "bất kỳ loại tham chiếu nào") - Sử dụng một cấu trúc khác làm "trình bao bọc" xung quanh tham chiếu không hữu ích
- Sử dụng cấu trúc chung chung làm trình bao bọc xung quanh tham chiếu không hữu ích
- Nếu tôi tiếp tục thêm các trường (theo cặp cho đơn giản),
int
các trường vẫn được tính là 4 byte vàInt32Wrapper
các trường được tính là 8 byte - Thêm
[StructLayout(LayoutKind.Sequential, Pack = 4)]
vào mọi cấu trúc trong tầm nhìn không thay đổi kết quả
Có ai có bất kỳ lời giải thích nào cho điều này (lý tưởng là với tài liệu tham khảo) hoặc gợi ý về cách tôi có thể nhận được gợi ý về CLR mà tôi muốn các trường được đóng gói mà không chỉ định độ lệch trường không đổi?
TwoInt32Wrappers
hoặc một Int64
và một TwoInt32Wrappers
? Còn nếu bạn tạo chung chung Pair<T1,T2> {public T1 f1; public T2 f2;}
rồi tạo Pair<string,Pair<int,int>>
và Pair<string,Pair<Int32Wrapper,Int32Wrapper>>
? Những kết hợp nào buộc JITter phải độn mọi thứ?
Pair<string, TwoInt32Wrappers>
không đưa ra chỉ 16 byte, do đó sẽ giải quyết vấn đề. Hấp dẫn.
Marshal.SizeOf
sẽ trả về kích thước của cấu trúc sẽ được chuyển cho mã gốc, không cần có bất kỳ mối liên hệ nào với kích thước của cấu trúc trong mã .NET.
Ref<T>
nhưng đang sử dụngstring
thay thế, không phải là nó sẽ tạo ra sự khác biệt.