ลองจับโค้ดของฉันให้เร็วขึ้นไหม?


1503

ฉันเขียนโค้ดเพื่อทดสอบผลกระทบของการลองจับ แต่เห็นผลลัพธ์ที่น่าประหลาดใจ

static void Main(string[] args)
{
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

    long start = 0, stop = 0, elapsed = 0;
    double avg = 0.0;

    long temp = Fibo(1);

    for (int i = 1; i < 100000000; i++)
    {
        start = Stopwatch.GetTimestamp();
        temp = Fibo(100);
        stop = Stopwatch.GetTimestamp();

        elapsed = stop - start;
        avg = avg + ((double)elapsed - avg) / i;
    }

    Console.WriteLine("Elapsed: " + avg);
    Console.ReadKey();
}

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    for (int i = 1; i < n; i++)
    {
        n1 = n2;
        n2 = fibo;
        fibo = n1 + n2;
    }

    return fibo;
}

ในคอมพิวเตอร์ของฉันนี่จะพิมพ์ค่าประมาณ 0.96 ..

เมื่อฉันล้อมรอบ for ไว้ข้างใน Fibo () ด้วยบล็อค try-catch ดังนี้:

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    try
    {
        for (int i = 1; i < n; i++)
        {
            n1 = n2;
            n2 = fibo;
            fibo = n1 + n2;
        }
    }
    catch {}

    return fibo;
}

ตอนนี้มันพิมพ์ออกมาอย่างต่อเนื่อง 0.69 ... - มันเร็วกว่าจริง! แต่ทำไม

หมายเหตุ: ฉันรวบรวมสิ่งนี้โดยใช้การกำหนดค่า Release และเรียกใช้ไฟล์ EXE โดยตรง (นอก Visual Studio)

แก้ไข: การวิเคราะห์ที่ยอดเยี่ยมของ Jon Skeetแสดงให้เห็นว่าการทดสอบแบบจับได้ทำให้ x86 CLR ใช้การลงทะเบียน CPU ในทางที่ดีขึ้นในกรณีเฉพาะนี้ (และฉันคิดว่าเรายังไม่เข้าใจสาเหตุ) ฉันยืนยันว่าจอนพบว่า x64 CLR ไม่มีความแตกต่างนี้และมันเร็วกว่า x86 CLR ฉันทดสอบโดยใช้intชนิดภายในวิธี Fibo แทนlongชนิดแล้ว x86 CLR นั้นเร็วเท่ากับ x64 CLR


อัปเดต:ดูเหมือนว่าปัญหานี้จะได้รับการแก้ไขโดย Roslyn เครื่องเดียวกันรุ่น CLR เดียวกัน - ปัญหายังคงเหมือนเดิมเมื่อรวบรวมกับ VS 2013 แต่ปัญหาจะหายไปเมื่อรวบรวมกับ VS 2015


111
@ ลอยด์เขาพยายามหาคำตอบสำหรับคำถามของเขาว่า "มันทำงานได้เร็วขึ้นจริง ๆ ! แต่ทำไม?"
Andreas Niedermair

137
ดังนั้นตอนนี้ "ข้อยกเว้นการกลืน" ผ่านจากการฝึกฝนที่ไม่ดีไปสู่การเพิ่มประสิทธิภาพที่ดี: P
Luciano

2
สิ่งนี้อยู่ในบริบททางคณิตศาสตร์ที่ไม่ถูกตรวจสอบหรือถูกตรวจสอบหรือไม่?
Random832

7
@ taras.roshko: ในขณะที่ฉันไม่ต้องการที่จะก่อความเสียหายให้กับ Eric แต่นี่ไม่ใช่คำถาม C # - เป็นคำถามของผู้รวบรวม JIT ความยากที่สุดคือการหาสาเหตุที่ทำให้ JITT86 ไม่ใช้การลงทะเบียนจำนวนมากโดยไม่ต้องลอง / จับเช่นเดียวกับบล็อกลอง / จับ
Jon Skeet

63
น่ารักดังนั้นถ้าเราทำรังลองจับเราสามารถไปได้เร็วขึ้นใช่มั้ย
Chuck Pinkert

คำตอบ:


1053

หนึ่งในโรสลินวิศวกรที่มีความเชี่ยวชาญในการเพิ่มประสิทธิภาพความเข้าใจในการใช้งานสแต็คเอาดูที่นี้และรายงานให้ฉันที่ดูเหมือนว่าจะมีปัญหาในการมีปฏิสัมพันธ์ระหว่างวิธีการเรียบเรียง C # สร้างร้านค้าตัวแปรท้องถิ่นและวิธีการJITคอมไพเลอร์ไม่ลงทะเบียน กำหนดเวลาในรหัส x86 ที่สอดคล้องกัน ผลที่ได้คือการสร้างรหัสที่ดีที่สุดในการโหลดและร้านค้าของท้องถิ่น

ด้วยเหตุผลบางอย่างที่ไม่ชัดเจนสำหรับพวกเราทุกคนเส้นทางการสร้างรหัสที่เป็นปัญหาจะถูกหลีกเลี่ยงเมื่อ JITter รู้ว่าบล็อกอยู่ในภูมิภาคที่มีการป้องกันแบบลอง

นี่มันแปลก ๆ เราจะติดตามกับทีมงาน JITter และดูว่าเราสามารถรับข้อบกพร่องได้หรือไม่เพื่อให้พวกเขาสามารถแก้ไขได้

นอกจากนี้เรากำลังดำเนินการปรับปรุงสำหรับ Roslyn กับอัลกอริทึมของคอมไพเลอร์ C # และ VB สำหรับการพิจารณาว่าเมื่อใดที่คนในท้องถิ่นสามารถทำ "ephemeral" - นั่นคือเพียงแค่ผลักและผุดบนสแต็กแทนที่จะจัดสรรตำแหน่งเฉพาะบนสแต็กสำหรับ ระยะเวลาของการเปิดใช้งาน เราเชื่อว่า JITter จะสามารถทำงานได้ดีขึ้นในการจัดสรรการลงทะเบียนและไม่ว่าเราจะให้คำแนะนำที่ดีขึ้นเกี่ยวกับเวลาที่คนในท้องถิ่นสามารถ "ตาย" ได้เร็วขึ้น

ขอขอบคุณที่แจ้งเรื่องนี้ให้เราทราบและขออภัยในความผิดปกตินี้


8
ฉันสงสัยอยู่เสมอว่าทำไมคอมไพเลอร์ C # จึงสร้างคนนอกจำนวนมาก ตัวอย่างเช่นนิพจน์การกำหนดค่าเริ่มต้นอาร์เรย์ใหม่จะสร้างโลคัลเสมอ แต่ไม่จำเป็นต้องสร้างโลคัล ถ้ามันจะช่วยให้กระวนกระวายใจในการผลิตรหัส performant วัดมากขึ้นอาจจะเรียบเรียง C # ควรจะเป็นบิตระมัดระวังมากขึ้นเกี่ยวกับการสร้างชาวบ้านไม่จำเป็น ...
Timwi

33
@Timwi: อย่างแน่นอน ในโค้ดที่ไม่ได้เพิ่มประสิทธิภาพคอมไพเลอร์จะสร้างไอเดียที่ไม่จำเป็นโดยการละทิ้งครั้งใหญ่ ในการเพิ่มประสิทธิภาพรหัสชั่วคราวไม่จำเป็นควรลบถ้าเป็นไปได้ น่าเสียดายที่เรามีข้อบกพร่องมากมายในช่วงหลายปีที่เราไม่ได้เพิ่มประสิทธิภาพของเครื่องมือเพิ่มประสิทธิภาพการกำจัดชั่วคราว วิศวกรดังกล่าวได้ทำการทำซ้ำอย่างสมบูรณ์ตั้งแต่เริ่มต้นทั้งหมดของรหัสนี้สำหรับ Roslyn และเราควรจะมีการปรับปรุงพฤติกรรมที่ดีที่สุดในตัวสร้างรหัส Roslyn
Eric Lippert

24
เคยมีการเคลื่อนไหวในเรื่องนี้หรือไม่?
Robert Harvey

10
ดูเหมือนว่า Roslyn จะแก้ไขได้
Eren Ersönmez

56
คุณพลาดโอกาสที่จะเรียกมันว่า "JITter bug"
mbomb007

734

วิธีที่คุณใช้เวลาทำสิ่งต่าง ๆ ดูน่ารังเกียจสำหรับฉัน มันจะมีเหตุผลมากขึ้นที่จะแค่วนรอบทั้งหมด:

var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
    Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);

ด้วยวิธีนี้คุณไม่ได้อยู่ในความเมตตาของการกำหนดเวลาเล็ก ๆ เลขทศนิยมและข้อผิดพลาดสะสม

เมื่อทำการเปลี่ยนแปลงแล้วให้ดูว่ารุ่น "ที่ไม่จับ" ยังคงช้ากว่ารุ่น "ที่จับได้"

แก้ไข: โอเคฉันได้ลองด้วยตัวเองแล้ว - และฉันก็เห็นผลลัพธ์เดียวกัน แปลกมาก. ฉันสงสัยว่าการลอง / จับปิดการใช้งานอินไลน์ที่ไม่ดีบางอย่าง แต่การใช้งาน[MethodImpl(MethodImplOptions.NoInlining)]แทนไม่ได้ช่วย ...

โดยทั่วไปคุณจะต้องดูรหัส JITted ที่ดีที่สุดภายใต้ Cordbg ฉันสงสัยว่า ...

แก้ไข: ข้อมูลอีกไม่กี่บิต:

  • การลอง / จับรอบ ๆn++;เส้นก็ยังช่วยเพิ่มประสิทธิภาพ แต่ไม่มากเท่ากับการวางไว้รอบบล็อกทั้งหมด
  • หากคุณพบข้อยกเว้นเฉพาะ ( ArgumentExceptionในการทดสอบของฉัน) ก็ยังเร็ว
  • หากคุณพิมพ์ข้อยกเว้นใน catch catch มันยังคงรวดเร็ว
  • หากคุณสร้างข้อยกเว้นขึ้นใหม่ในบล็อก catch มันจะช้าอีกครั้ง
  • หากคุณใช้บล็อกในที่สุดแทนบล็อกจับมันจะช้าอีกครั้ง
  • หากคุณใช้บล็อกในที่สุดเช่นเดียวกับบล็อก catch มันเร็ว

แปลก...

แก้ไข: โอเคเรามีชิ้นส่วน ...

นี่ใช้คอมไพเลอร์ C # 2 และ CLR .NET 2 (32 บิต) แยกส่วนด้วย mdbg (เนื่องจากฉันไม่มี cordbg ในเครื่องของฉัน) ฉันยังคงเห็นเอฟเฟกต์ประสิทธิภาพเดียวกันแม้จะอยู่ภายใต้ดีบักเกอร์ รุ่นที่รวดเร็วใช้tryบล็อกรอบ ๆ ทุกสิ่งระหว่างการประกาศตัวแปรและคำสั่ง return โดยมีเพียงcatch{}ตัวจัดการ เห็นได้ชัดว่ารุ่นช้านั้นเหมือนกันยกเว้นโดยไม่มีการลอง / จับ รหัสการโทร (เช่น Main) นั้นเหมือนกันในทั้งสองกรณีและมีการแสดงแอสเซมบลีที่เหมือนกัน

ถอดรหัสสำหรับรุ่นที่รวดเร็ว:

 [0000] push        ebp
 [0001] mov         ebp,esp
 [0003] push        edi
 [0004] push        esi
 [0005] push        ebx
 [0006] sub         esp,1Ch
 [0009] xor         eax,eax
 [000b] mov         dword ptr [ebp-20h],eax
 [000e] mov         dword ptr [ebp-1Ch],eax
 [0011] mov         dword ptr [ebp-18h],eax
 [0014] mov         dword ptr [ebp-14h],eax
 [0017] xor         eax,eax
 [0019] mov         dword ptr [ebp-18h],eax
*[001c] mov         esi,1
 [0021] xor         edi,edi
 [0023] mov         dword ptr [ebp-28h],1
 [002a] mov         dword ptr [ebp-24h],0
 [0031] inc         ecx
 [0032] mov         ebx,2
 [0037] cmp         ecx,2
 [003a] jle         00000024
 [003c] mov         eax,esi
 [003e] mov         edx,edi
 [0040] mov         esi,dword ptr [ebp-28h]
 [0043] mov         edi,dword ptr [ebp-24h]
 [0046] add         eax,dword ptr [ebp-28h]
 [0049] adc         edx,dword ptr [ebp-24h]
 [004c] mov         dword ptr [ebp-28h],eax
 [004f] mov         dword ptr [ebp-24h],edx
 [0052] inc         ebx
 [0053] cmp         ebx,ecx
 [0055] jl          FFFFFFE7
 [0057] jmp         00000007
 [0059] call        64571ACB
 [005e] mov         eax,dword ptr [ebp-28h]
 [0061] mov         edx,dword ptr [ebp-24h]
 [0064] lea         esp,[ebp-0Ch]
 [0067] pop         ebx
 [0068] pop         esi
 [0069] pop         edi
 [006a] pop         ebp
 [006b] ret

ถอดรหัสสำหรับรุ่นช้า:

 [0000] push        ebp
 [0001] mov         ebp,esp
 [0003] push        esi
 [0004] sub         esp,18h
*[0007] mov         dword ptr [ebp-14h],1
 [000e] mov         dword ptr [ebp-10h],0
 [0015] mov         dword ptr [ebp-1Ch],1
 [001c] mov         dword ptr [ebp-18h],0
 [0023] inc         ecx
 [0024] mov         esi,2
 [0029] cmp         ecx,2
 [002c] jle         00000031
 [002e] mov         eax,dword ptr [ebp-14h]
 [0031] mov         edx,dword ptr [ebp-10h]
 [0034] mov         dword ptr [ebp-0Ch],eax
 [0037] mov         dword ptr [ebp-8],edx
 [003a] mov         eax,dword ptr [ebp-1Ch]
 [003d] mov         edx,dword ptr [ebp-18h]
 [0040] mov         dword ptr [ebp-14h],eax
 [0043] mov         dword ptr [ebp-10h],edx
 [0046] mov         eax,dword ptr [ebp-0Ch]
 [0049] mov         edx,dword ptr [ebp-8]
 [004c] add         eax,dword ptr [ebp-1Ch]
 [004f] adc         edx,dword ptr [ebp-18h]
 [0052] mov         dword ptr [ebp-1Ch],eax
 [0055] mov         dword ptr [ebp-18h],edx
 [0058] inc         esi
 [0059] cmp         esi,ecx
 [005b] jl          FFFFFFD3
 [005d] mov         eax,dword ptr [ebp-1Ch]
 [0060] mov         edx,dword ptr [ebp-18h]
 [0063] lea         esp,[ebp-4]
 [0066] pop         esi
 [0067] pop         ebp
 [0068] ret

ในแต่ละกรณีการ*แสดงที่ debugger ป้อนใน "ขั้นตอน" ง่าย ๆ

แก้ไข: โอเคตอนนี้ฉันดูรหัสแล้วและฉันคิดว่าฉันสามารถดูว่าแต่ละรุ่นทำงานอย่างไรและฉันเชื่อว่ารุ่นที่ช้ากว่านั้นช้ากว่าเพราะใช้การลงทะเบียนน้อยลงและมีพื้นที่สแต็คมากขึ้น สำหรับค่าขนาดเล็กnที่อาจเร็วกว่า - แต่เมื่อลูปใช้เวลาเป็นกลุ่มมันจะช้ากว่า

เป็นไปได้ว่าบล็อก try / catch บังคับให้รีจิสเตอร์ถูกบันทึกและเรียกคืนมากขึ้นดังนั้น JIT จึงใช้สิ่งเหล่านั้นสำหรับลูปเช่นกัน ... ซึ่งเกิดขึ้นเพื่อปรับปรุงประสิทธิภาพโดยรวม ยังไม่ชัดเจนว่าเป็นการตัดสินใจที่สมเหตุสมผลสำหรับ JIT ที่จะไม่ใช้การลงทะเบียนมากในรหัส "ปกติ"

แก้ไข: เพิ่งลองสิ่งนี้ในเครื่อง x64 ของฉัน x64 CLR นั้นเร็วกว่ามาก (ประมาณ 3-4 เท่า) กว่า x86 CLR ในรหัสนี้และภายใต้ x64 บล็อค try / catch ไม่ได้สร้างความแตกต่างที่เห็นได้ชัดเจน


4
@GordonSimpson แต่ในกรณีที่มีเพียงข้อยกเว้นเฉพาะถูกจับแล้วข้อยกเว้นอื่น ๆ ทั้งหมดจะไม่ถูกจับดังนั้นค่าใช้จ่ายใด ๆ ที่เกี่ยวข้องในสมมติฐานของคุณสำหรับการไม่ลองจะยังคงมีความจำเป็น
Jon Hanna

45
ดูเหมือนความแตกต่างในการจัดสรรการลงทะเบียน เวอร์ชันที่รวดเร็วจะใช้esi,ediสำหรับหนึ่งในความยาวแทนสแต็ก โดยจะใช้เป็นเคาน์เตอร์ที่ช้าใช้รุ่นebx esi
Jeffrey Sax

13
@JeffreySax: มันไม่ได้เป็นเพียงที่ลงทะเบียนจะใช้ แต่วิธีการที่หลาย ๆ เวอร์ชันช้าใช้พื้นที่สแต็คมากขึ้นโดยแตะลงทะเบียนน้อยลง ฉันไม่รู้เลยว่าทำไม ...
Jon Skeet

2
เฟรมข้อยกเว้น CLR เป็นอย่างไรในแง่ของการลงทะเบียนและสแต็ค? การตั้งค่าอย่างใดอย่างหนึ่งทำให้มีการลงทะเบียนใช้งานอย่างใดอย่างหนึ่งหรือไม่?
Random832

4
IIRC x64 มีการลงทะเบียนมากกว่า x86 ความเร็วที่คุณเห็นจะสอดคล้องกับการลอง / จับบังคับให้ใช้การลงทะเบียนเพิ่มเติมภายใต้ x86
Dan Is Fiddling โดย Firelight

116

ส่วนแยกของจอนแสดงให้เห็นว่าความแตกต่างระหว่างทั้งสองรุ่นคือรุ่นเร็วใช้คู่ของรีจิสเตอร์ ( esi,edi) เพื่อเก็บตัวแปรท้องถิ่นหนึ่งตัวที่เวอร์ชันช้าไม่ได้

คอมไพเลอร์ JIT สร้างข้อสมมติฐานที่แตกต่างกันเกี่ยวกับการใช้รีจิสเตอร์สำหรับโค้ดที่มีบล็อก try-catch และโค้ดที่ไม่มี นี่ทำให้การเลือกการจัดสรรการลงทะเบียนแตกต่างกัน ในกรณีนี้สิ่งนี้เป็นประโยชน์กับรหัสด้วยบล็อค try-catch รหัสที่แตกต่างกันอาจทำให้เกิดผลตรงกันข้ามดังนั้นฉันจะไม่นับเป็นเทคนิคเร่งความเร็วทั่วไป

ในท้ายที่สุดมันยากมากที่จะบอกว่าโค้ดใดที่จะทำงานได้เร็วที่สุด บางอย่างเช่นการจัดสรรการลงทะเบียนและปัจจัยที่มีอิทธิพลต่อมันคือรายละเอียดการใช้งานในระดับต่ำซึ่งฉันไม่เห็นว่าเทคนิคเฉพาะใด ๆ ที่สามารถสร้างโค้ดได้เร็วขึ้น

ตัวอย่างเช่นพิจารณาสองวิธีต่อไปนี้ พวกเขาดัดแปลงมาจากตัวอย่างในชีวิตจริง:

interface IIndexed { int this[int index] { get; set; } }
struct StructArray : IIndexed { 
    public int[] Array;
    public int this[int index] {
        get { return Array[index]; }
        set { Array[index] = value; }
    }
}

static int Generic<T>(int length, T a, T b) where T : IIndexed {
    int sum = 0;
    for (int i = 0; i < length; i++)
        sum += a[i] * b[i];
    return sum;
}
static int Specialized(int length, StructArray a, StructArray b) {
    int sum = 0;
    for (int i = 0; i < length; i++)
        sum += a[i] * b[i];
    return sum;
}

หนึ่งเป็นรุ่นทั่วไปของอื่น ๆ การแทนที่ประเภททั่วไปด้วยStructArrayจะทำให้วิธีการเหมือนกัน เนื่องจากStructArrayเป็นชนิดของค่าจึงได้รับวิธีการคอมไพล์รุ่นทั่วไป แต่เวลาทำงานจริงนั้นนานกว่าวิธีการเฉพาะอย่างมาก แต่สำหรับ x86 เท่านั้น สำหรับ x64 การจับเวลานั้นเหมือนกันมาก ในกรณีอื่นฉันได้สังเกตเห็นความแตกต่างของ x64 เช่นกัน


6
เมื่อพูดถึง ... คุณสามารถบังคับตัวเลือกการจัดสรรการลงทะเบียนที่แตกต่างกันโดยไม่ต้องใช้ลอง / จับได้หรือไม่? ไม่ว่าจะเป็นการทดสอบสำหรับสมมติฐานนี้หรือเป็นความพยายามทั่วไปในการปรับแต่งความเร็ว?
WernerCD

1
มีสาเหตุหลายประการที่ทำให้กรณีเฉพาะนี้อาจแตกต่างกัน บางทีมันอาจจะเป็นแบบลองก็ได้ บางทีมันอาจเป็นความจริงที่ว่าตัวแปรถูกใช้ซ้ำในขอบเขตด้านใน ไม่ว่าด้วยเหตุผลใดก็ตามมันเป็นรายละเอียดการใช้งานที่คุณไม่สามารถคาดหวังได้ว่าจะรักษาไว้แม้ว่ารหัสเดียวกันนี้จะถูกเรียกในโปรแกรมอื่น
Jeffrey Sax

4
@WernerCD ฉันจะบอกว่าข้อเท็จจริงที่ว่า C และ C ++ มีคำสำคัญสำหรับการแนะนำสิ่งที่ (A) ถูกเพิกเฉยโดยคอมไพเลอร์สมัยใหม่จำนวนมากและ (B) มีการตัดสินใจว่าจะไม่ใส่ C # แสดงว่านี่ไม่ใช่สิ่งที่เรา ' จะเห็นในทางตรงใด ๆ เพิ่มเติม
Jon Hanna

2
@WernerCD - เฉพาะในกรณีที่คุณเขียนการประชุมด้วยตัวเอง
OrangeDog

72

ดูเหมือนว่ากรณีของการขีดเส้นใต้เป็นไปไม่ดี บนแกน x86 ตัวกระวนกระวายใจมีการลงทะเบียน ebx, edx, esi และ edi สำหรับการจัดเก็บวัตถุประสงค์ทั่วไปของตัวแปรท้องถิ่น ลงทะเบียน ecx จะมีอยู่ในวิธีการแบบคงที่มันไม่ได้มีการจัดเก็บนี้ การลงทะเบียน eax นั้นจำเป็นสำหรับการคำนวณ แต่สิ่งเหล่านี้คือรีจิสเตอร์แบบ 32 บิตสำหรับตัวแปรประเภทยาวมันต้องใช้รีจิสเตอร์คู่หนึ่ง Edx: eax สำหรับการคำนวณและ edi: ebx สำหรับการจัดเก็บข้อมูลคืออะไร

ซึ่งเป็นสิ่งที่โดดเด่นในการถอดแยกชิ้นส่วนสำหรับรุ่นช้าไม่ใช้ edi หรือ ebx

เมื่อตัวกระวนกระวายใจไม่สามารถหารีจิสเตอร์เพียงพอที่จะเก็บตัวแปรในตัวเครื่องได้มันจะต้องสร้างรหัสเพื่อโหลดและเก็บไว้ในสแต็กเฟรม ที่ช้าลงรหัสจะป้องกันการเพิ่มประสิทธิภาพของโปรเซสเซอร์ชื่อ "register renaming" ซึ่งเป็นเคล็ดลับการเพิ่มประสิทธิภาพโปรเซสเซอร์หลักภายในที่ใช้หลายสำเนาของการลงทะเบียนและอนุญาตให้ดำเนินการ super-scalar ซึ่งอนุญาตให้มีคำสั่งหลายคำสั่งให้ทำงานพร้อมกันแม้ว่าจะใช้การลงทะเบียนเดียวกันก็ตาม การมีการลงทะเบียนไม่เพียงพอเป็นปัญหาที่พบบ่อยในแกน x86 ที่ระบุใน x64 ซึ่งมีการลงทะเบียนเพิ่มเติม 8 รายการ (r9 ถึง r15)

ผู้กระวนกระวายใจจะพยายามอย่างดีที่สุดที่จะใช้การเพิ่มประสิทธิภาพการสร้างรหัสอื่นมันจะพยายามอินไลน์วิธีการ Fibo () ของคุณ กล่าวอีกนัยหนึ่งไม่ทำการเรียกวิธี แต่สร้างรหัสสำหรับวิธีการแบบอินไลน์ในวิธีการหลัก () การเพิ่มประสิทธิภาพที่สำคัญสวย ๆ ที่หนึ่งทำให้คุณสมบัติของคลาส C # ฟรีให้พวกเขาสมบูรณ์แบบของเขตข้อมูล มันหลีกเลี่ยงค่าใช้จ่ายในการโทรวิธีการและการตั้งค่ากรอบสแต็กของมันช่วยสองนาโนวินาที

มีกฎหลายข้อที่กำหนดอย่างแน่นอนว่าเมื่อใดที่สามารถอินไลน์ได้ พวกเขาจะไม่ถูกบันทึกไว้อย่างแน่นอน แต่ได้รับการกล่าวถึงในโพสต์บล็อก กฎข้อหนึ่งก็คือว่ามันจะไม่เกิดขึ้นเมื่อร่างกายวิธีการมีขนาดใหญ่เกินไป นั่นเอาชนะความได้เปรียบจากการอินไลน์มันสร้างโค้ดมากเกินไปซึ่งไม่พอดีกับแคชคำสั่ง L1 กฎฮาร์ดอีกข้อที่ใช้ที่นี่คือวิธีการจะไม่ถูกแทรกเมื่อมีคำสั่งลอง / จับ พื้นหลังที่อยู่เบื้องหลังนั้นเป็นรายละเอียดการใช้งานของข้อยกเว้นพวกเขากลับไปยัง Windows ในตัวรองรับ SEH (การจัดการข้อยกเว้นโครงสร้าง) ซึ่งเป็นสแต็กเฟรม

พฤติกรรมหนึ่งของอัลกอริทึมการจัดสรรการลงทะเบียนในกระวนกระวายใจสามารถอนุมานจากการเล่นกับรหัสนี้ ดูเหมือนว่าจะทราบเมื่อตัวกระวนกระวายใจพยายามอินไลน์วิธีการ กฎข้อหนึ่งดูเหมือนว่าจะใช้เฉพาะคู่ edx: eax register สำหรับโค้ดแบบอินไลน์ที่มีตัวแปรโลคอลชนิดยาว แต่ไม่ใช่ edi: ebx ไม่ต้องสงสัยเลยว่านั่นจะเป็นอันตรายต่อการสร้างรหัสสำหรับวิธีการโทรทั้ง edi และ ebx นั้นเป็นที่เก็บข้อมูลสำคัญ

ดังนั้นคุณจะได้รับเวอร์ชันที่รวดเร็วเนื่องจากตัวกระวนกระวายรู้ล่วงหน้าว่าเมธอด body มีคำสั่ง try / catch มันรู้ดีว่ามันไม่สามารถถูกแทรกได้ดังนั้นจึงใช้ edi: ebx เพื่อเก็บข้อมูลสำหรับตัวแปรที่มีความยาว คุณได้เวอร์ชั่นช้าเพราะตัวกระวนกระวายไม่ทราบล่วงหน้าว่าการอินไลน์ไม่ทำงาน พบได้หลังจากสร้างรหัสสำหรับเนื้อความของเมธอด

ข้อบกพร่องคือว่ามันไม่ได้กลับไปและสร้างรหัสใหม่สำหรับวิธีการ ซึ่งเป็นที่เข้าใจได้เนื่องจากข้อ จำกัด ด้านเวลาที่ต้องดำเนินการ

การชะลอตัวลงนี้ไม่ได้เกิดขึ้นใน x64 เพราะจะมีการลงทะเบียนอีก 8 รายการ สำหรับอีกอันหนึ่งเพราะสามารถเก็บไว้ได้นานในการลงทะเบียนเพียงครั้งเดียว (เช่น rax) และการชะลอตัวไม่ได้เกิดขึ้นเมื่อคุณใช้ int แทนที่จะใช้เวลานานเพราะกระวนกระวายใจมีความยืดหยุ่นในการเลือกการลงทะเบียนมากขึ้น


21

ฉันได้ใส่ข้อความนี้ไว้ในความคิดเห็นเนื่องจากฉันไม่แน่ใจว่านี่เป็นเรื่องจริง แต่เนื่องจากฉันจำได้ว่ามันไม่ใช่คำสั่งลอง / ยกเว้นที่เกี่ยวข้องกับการปรับเปลี่ยนวิธีการกำจัดขยะของ คอมไพเลอร์ใช้งานได้ในการล้างการจัดสรรหน่วยความจำวัตถุในทางที่เกิดซ้ำจากกองซ้อน อาจไม่มีวัตถุที่จะทำการล้างข้อมูลในกรณีนี้หรือ for for loop อาจเป็นการปิดที่กลไกการรวบรวมขยะจะรับรู้เพียงพอที่จะบังคับใช้วิธีการรวบรวมที่แตกต่างกัน อาจจะไม่ แต่ฉันคิดว่ามันคุ้มค่าพูดถึงเพราะฉันไม่ได้เห็นมันพูดถึงที่อื่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.