นี่ไม่ใช่คำตอบที่สมบูรณ์ แต่ฉันมีความคิดเล็กน้อย
ฉันเชื่อว่าฉันพบคำอธิบายที่ดีเท่าที่เราจะหาได้โดยไม่มีใครสักคนจากทีมงาน.
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 กับชนิดอ้างอิงใด ๆ โปรแกรมล้มเหลว แต่ถ้าAreified ด้วยค่าชนิดใด ๆ แล้วรหัสไม่ล้มเหลว นอกจากนี้หากคุณแสดงความคิดเห็นคอน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): เหมือนกันกับในรุ่นที่ไม่ใช่ทั่วไป