ในNoda Time v2 เรากำลังเคลื่อนไปสู่ความละเอียดระดับนาโนวินาที นั่นหมายความว่าเราไม่สามารถใช้จำนวนเต็ม 8 ไบต์เพื่อแสดงช่วงเวลาทั้งหมดที่เราสนใจได้อีกต่อไปนั่นทำให้ฉันต้องตรวจสอบการใช้หน่วยความจำของโครงสร้าง (จำนวนมาก) ของ Noda Time ซึ่งทำให้ฉัน เพื่อเปิดเผยความแปลกประหลาดเล็กน้อยในการตัดสินใจจัดตำแหน่งของ CLR
ประการแรกผมรู้ว่านี่คือการตัดสินใจการดำเนินงานและการทำงานเริ่มต้นอาจมีการเปลี่ยนแปลงได้ตลอดเวลา ฉันรู้ว่าฉันสามารถแก้ไขได้โดยใช้[StructLayout]
และ[FieldOffset]
แต่ฉันอยากจะหาวิธีแก้ปัญหาที่ไม่ต้องการสิ่งนั้นถ้าเป็นไปได้
สถานการณ์หลักของฉันที่ฉันมีstruct
ซึ่งมีข้อมูลอ้างอิงและแบบสองช่องค่าชนิดอื่น ๆ int
ที่มีเขตข้อมูลเหล่านั้นห่อง่ายสำหรับ ฉันหวังว่าจะแสดงเป็น 16 ไบต์บน 64-bit CLR (8 สำหรับการอ้างอิงและ 4 สำหรับแต่ละอัน) แต่ด้วยเหตุผลบางประการจึงใช้ 24 ไบต์ ฉันกำลังวัดพื้นที่โดยใช้อาร์เรย์ - ฉันเข้าใจว่าการจัดวางอาจแตกต่างกันในสถานการณ์ที่แตกต่างกัน แต่นี่เป็นจุดเริ่มต้นที่สมเหตุสมผล
นี่คือโปรแกรมตัวอย่างที่แสดงให้เห็นถึงปัญหา:
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);
}
}
และการรวบรวมและผลลัพธ์บนแล็ปท็อปของฉัน:
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
ดังนั้น:
- หากคุณไม่มีฟิลด์ประเภทการอ้างอิง CLR ยินดีที่จะ
Int32Wrapper
รวมฟิลด์เข้าด้วยกัน (TwoInt32Wrappers
มีขนาด 8) - แม้ว่าจะมีฟิลด์ประเภทการอ้างอิง CLR ก็ยังยินดีที่จะ
int
รวมฟิลด์เข้าด้วยกัน (RefAndTwoInt32s
มีขนาด 16) - เมื่อรวมทั้งสอง
Int32Wrapper
ฟิลด์แต่ละฟิลด์จะมีเบาะ / จัดแนวเป็น 8 ไบต์ (RefAndTwoInt32Wrappers
มีขนาด 24. ) - การรันโค้ดเดียวกันในดีบักเกอร์ (แต่ยังคงเป็นรุ่นที่วางจำหน่าย) จะแสดงขนาดเป็น 12
การทดลองอื่น ๆ บางส่วนให้ผลลัพธ์ที่คล้ายกัน:
- การใส่ฟิลด์ประเภทการอ้างอิงหลังฟิลด์ประเภทค่าไม่ได้ช่วยอะไร
- การใช้
object
แทนstring
ไม่ช่วยอะไร (ฉันคาดว่าเป็น "ประเภทอ้างอิงใด ๆ ") - การใช้โครงสร้างอื่นเป็น "wrapper" รอบการอ้างอิงไม่ได้ช่วยอะไร
- การใช้โครงสร้างทั่วไปเป็นกระดาษห่อหุ้มรอบการอ้างอิงไม่ได้ช่วยอะไร
- ถ้าฉันเพิ่มฟิลด์ (เป็นคู่ ๆ เพื่อความเรียบง่าย)
int
ฟิลด์จะยังคงนับเป็น 4 ไบต์และInt32Wrapper
ฟิลด์จะนับเป็น 8 ไบต์ - การเพิ่ม
[StructLayout(LayoutKind.Sequential, Pack = 4)]
ทุกโครงสร้างที่มองเห็นไม่ได้ทำให้ผลลัพธ์เปลี่ยนไป
ใครมีคำอธิบายเกี่ยวกับเรื่องนี้ (ควรมีเอกสารอ้างอิง) หรือคำแนะนำว่าฉันจะบอกใบ้ CLR ได้อย่างไรว่าฉันต้องการให้ฟิลด์ถูกบรรจุโดยไม่ระบุค่าชดเชยฟิลด์คงที่หรือไม่?
TwoInt32Wrappers
หรือInt64
และTwoInt32Wrappers
? แล้วถ้าคุณสร้างแบบทั่วไปPair<T1,T2> {public T1 f1; public T2 f2;}
แล้วสร้างPair<string,Pair<int,int>>
และPair<string,Pair<Int32Wrapper,Int32Wrapper>>
ล่ะ? ชุดค่าผสมใดที่บังคับให้ JITter วางแผ่นสิ่งต่างๆ
Pair<string, TwoInt32Wrappers>
ไม่ให้เพียง 16 ไบต์เพื่อที่จะแก้ไขปัญหา มโนหร
Marshal.SizeOf
จะส่งคืนขนาดของโครงสร้างที่จะส่งผ่านไปยังโค้ดเนทีฟซึ่งไม่จำเป็นต้องมีความสัมพันธ์ใด ๆ กับขนาดของโครงสร้างในโค้ด. NET
Ref<T>
แต่กำลังใช้string
แทนไม่ใช่ว่าควรสร้างความแตกต่าง