นี่ไม่ใช่คำตอบที่สมบูรณ์ แต่ฉันมีความคิดเล็กน้อย
ฉันเชื่อว่าฉันพบคำอธิบายที่ดีเท่าที่เราจะหาได้โดยไม่มีใครสักคนจากทีมงาน.
UPDATE
ฉันมองลึกลงไปอีกเล็กน้อยและฉันเชื่อว่าฉันได้พบแหล่งที่มาของปัญหา มันดูเหมือนว่าจะเกิดจากการรวมกันของข้อผิดพลาดในตรรกะเริ่มต้นประเภท JIT และการเปลี่ยนแปลงในคอมไพเลอร์ C # ที่ขึ้นอยู่กับสมมติฐานที่ว่า JIT ทำงานตามที่ตั้งใจไว้ ฉันคิดว่าข้อผิดพลาด JIT มีอยู่ใน. NET 4.0 แต่ถูกเปิดเผยโดยการเปลี่ยนแปลงในคอมไพเลอร์สำหรับ. NET 4.5
ฉันไม่คิดว่าbeforefieldinit
เป็นปัญหาเดียวที่นี่ ฉันคิดว่ามันง่ายกว่านั้น
ประเภท System.String
ใน mscorlib.dll จาก. NET 4.0 ประกอบด้วยตัวสร้างแบบคงที่:
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr ""
IL_0005: stsfld string System.String::Empty
IL_000a: ret
} // end of method String::.cctor
ใน mscorlib.dll รุ่น. NET 4.5 String.cctor
(ตัวสร้างแบบคงที่) ขาดหายไปอย่างชัดเจน:
..... ไม่มีตัวสร้างแบบคงที่ :( .....
ในทั้งสองเวอร์ชันString
ประเภทจะประดับด้วยbeforefieldinit
:
.class public auto ansi serializable sealed beforefieldinit System.String
ฉันพยายามสร้างประเภทที่จะคอมไพล์กับ IL ในทำนองเดียวกัน (เพื่อให้มันมีเขตข้อมูลแบบคงที่ แต่ไม่มีตัวสร้างแบบคงที่.cctor
) แต่ฉันไม่สามารถทำได้ ประเภททั้งหมดเหล่านี้มี.cctor
วิธีการใน IL:
public class MyString1 {
public static MyString1 Empty = new MyString1();
}
public class MyString2 {
public static MyString2 Empty = new MyString2();
static MyString2() {}
}
public class MyString3 {
public static MyString3 Empty;
static MyString3() { Empty = new MyString3(); }
}
ฉันเดาว่าสองสิ่งเปลี่ยนไประหว่าง. NET 4.0 และ 4.5:
ครั้งแรก: EE มีการเปลี่ยนแปลงเพื่อที่จะเริ่มต้นโดยอัตโนมัติString.Empty
จากรหัสที่ไม่มีการจัดการ อาจมีการเปลี่ยนแปลงนี้สำหรับ. NET 4.0
ข้อที่สอง: คอมไพเลอร์เปลี่ยนไปเพื่อที่มันจะไม่ปล่อยตัวสร้างสแตติกสำหรับสตริงโดยรู้ว่า String.Empty
จะถูกกำหนดจากด้านที่ไม่มีการจัดการ การเปลี่ยนแปลงนี้ดูเหมือนจะเกิดขึ้นกับ. NET 4.5
ปรากฏว่า EE ไม่ได้กำหนดString.Empty
เร็วพอตามเส้นทางการปรับให้เหมาะสมสูงสุด การเปลี่ยนแปลงที่เกิดขึ้นกับคอมไพเลอร์ (หรือสิ่งที่เปลี่ยนแปลงเพื่อทำให้String.cctor
หายไป) คาดว่า EE จะทำการมอบหมายนี้ก่อนที่รหัสผู้ใช้จะดำเนินการ แต่ปรากฏว่า EE ไม่ได้ทำการมอบหมายนี้มาก่อนString.Empty
จะใช้ในวิธีการ
สุดท้ายฉันเชื่อว่าข้อผิดพลาดนั้นบ่งบอกถึงปัญหาที่ลึกกว่าในตรรกะการเริ่มต้นประเภท JIT ดูเหมือนว่าการเปลี่ยนแปลงในคอมไพเลอร์เป็นกรณีพิเศษสำหรับSystem.String
แต่ฉันสงสัยว่า JIT ได้ทำกรณีพิเศษที่นี่เพื่อSystem.String
ได้ทำให้เป็นกรณีพิเศษที่นี่สำหรับ
เป็นต้นฉบับ
ก่อนอื่นเลยว้าวคน BCL มีความคิดสร้างสรรค์อย่างมากพร้อมการเพิ่มประสิทธิภาพบางอย่าง หลายของString
วิธีการที่จะดำเนินการในขณะนี้ใช้แคชคงกระทู้StringBuilder
วัตถุ
ฉันติดตามลูกค้าเป้าหมายนั้นมาระยะหนึ่งแล้ว แต่StringBuilder
ไม่ได้ใช้กับTrim
พา ธ โค้ดดังนั้นฉันจึงตัดสินใจว่ามันจะไม่ใช่ปัญหาคงที่ของเธรด
ฉันคิดว่าฉันพบอาการแปลก ๆ ของข้อผิดพลาดเดียวกัน
รหัสนี้ล้มเหลวด้วยการละเมิดการเข้าถึง:
class A<T>
{
static A() { }
public A(out string s) {
s = string.Empty;
}
}
class B
{
static void Main() {
string s;
new A<object>(out s);
//new A<int>(out s);
System.Console.WriteLine(s.Length);
}
}
อย่างไรก็ตามหากคุณไม่ใส่เครื่องหมายข้อคิดเห็น//new A<int>(out s);
ไว้Main
รหัสก็ใช้งานได้ดี ในความเป็นจริงหากA
มีการ reified กับชนิดอ้างอิงใด ๆ โปรแกรมล้มเหลว แต่ถ้าA
reified ด้วยค่าชนิดใด ๆ แล้วรหัสไม่ล้มเหลว นอกจากนี้หากคุณแสดงความคิดเห็นคอนA
สตรัคเตอร์คงที่ของรหัสจะไม่ล้มเหลว หลังจากขุดลงไปTrim
และFormat
ก็เป็นที่ชัดเจนว่าปัญหาที่ว่าLength
จะถูก inlined และในตัวอย่างเหล่านี้ข้างต้นString
ประเภทยังไม่ได้รับการเริ่มต้น โดยเฉพาะอย่างยิ่งภายในร่างกายของA
คอนสตรัค 's, string.Empty
ไม่ได้ถูกกำหนดอย่างถูกต้องถึงแม้ว่าภายในร่างกายของMain
, string.Empty
ที่ได้รับมอบหมายได้อย่างถูกต้อง
มันน่าอัศจรรย์สำหรับฉันที่การกำหนดค่าเริ่มต้นของชนิดString
ขึ้นอยู่กับว่าA
จะมีการปรับเปลี่ยนประเภทค่าหรือไม่ ทฤษฎีเดียวของฉันคือมีบางเส้นทางรหัส JIT เพิ่มประสิทธิภาพสำหรับการเริ่มต้นประเภททั่วไปที่ใช้ร่วมกันในทุกประเภทและเส้นทางที่ทำให้สมมติฐานเกี่ยวกับประเภทอ้างอิง BCL ("ประเภทพิเศษ?") และรัฐของพวกเขา ดูอย่างรวดเร็วแม้ว่าคลาส BCL อื่น ๆ ที่มีpublic static
ฟิลด์แสดงให้เห็นว่าโดยทั่วไปแล้วพวกเขาทั้งหมดใช้ตัวสร้างแบบคงที่ (แม้จะมีตัวสร้างที่ว่างเปล่าและไม่มีข้อมูลเช่นSystem.DBNull
และSystem.Empty
. ประเภทค่า BCL กับpublic static
เขตข้อมูลดูเหมือนจะไม่ใช้ตัวสร้างแบบคงที่System.IntPtr
เป็นต้น) ดูเหมือนว่าสิ่งนี้จะบ่งชี้ว่า JIT สร้างสมมติฐานบางอย่างเกี่ยวกับการเริ่มต้นประเภทการอ้างอิง BCL
FYI นี่คือรหัส JITed สำหรับทั้งสองเวอร์ชัน:
A<object>.ctor(out string)
:
public A(out string s) {
00000000 push rbx
00000001 sub rsp,20h
00000005 mov rbx,rdx
00000008 lea rdx,[FFEE38D0h]
0000000f mov rcx,qword ptr [rcx]
00000012 call 000000005F7AB4A0
s = string.Empty;
00000017 mov rdx,qword ptr [FFEE38D0h]
0000001e mov rcx,rbx
00000021 call 000000005F661180
00000026 nop
00000027 add rsp,20h
0000002b pop rbx
0000002c ret
}
A<int32>.ctor(out string)
:
public A(out string s) {
00000000 sub rsp,28h
00000004 mov rax,rdx
s = string.Empty;
00000007 mov rdx,12353250h
00000011 mov rdx,qword ptr [rdx]
00000014 mov rcx,rax
00000017 call 000000005F691160
0000001c nop
0000001d add rsp,28h
00000021 ret
}
ส่วนที่เหลือของรหัส ( Main
) เหมือนกันระหว่างสองรุ่น
แก้ไข
นอกจากนี้ IL จากทั้งสองรุ่นเป็นเหมือนกันยกเว้นสำหรับการเรียกร้องให้A.ctor
ในB.Main()
ที่ IL สำหรับรุ่นแรกประกอบด้วย:
newobj instance void class A`1<object>::.ctor(string&)
กับ
... A`1<int32>...
ในวินาที
สิ่งที่ควรทราบอีกประการหนึ่งคือรหัส JITed สำหรับA<int>.ctor(out string)
: เหมือนกันกับในรุ่นที่ไม่ใช่ทั่วไป