. NET JIT เกิดข้อผิดพลาดหรือไม่


404

รหัสต่อไปนี้ให้ผลลัพธ์ที่แตกต่างกันเมื่อใช้งานรีลีสภายใน Visual Studio และรันรีลีสภายนอก Visual Studio ฉันใช้ Visual Studio 2008 และกำหนดเป้าหมาย. NET 3.5 ฉันเคยลอง. NET 3.5 SP1 แล้ว

เมื่อทำงานนอก Visual Studio JIT ควรเริ่มต้นด้วย (ก) มีบางสิ่งบางอย่างเกิดขึ้นกับ C # ที่ฉันหายไปหรือ (b) JIT นั้นผิดพลาดจริง ๆ ฉันสงสัยว่า JIT สามารถไปผิดได้ แต่ฉันหมดความเป็นไปได้อื่น ๆ ...

เอาต์พุตเมื่อทำงานภายใน Visual Studio:

    0 0,
    0 1,
    1 0,
    1 1,

เอาต์พุตเมื่อเรียกใช้รีลีสนอก Visual Studio:

    0 2,
    0 2,
    1 2,
    1 2,

เหตุผลคืออะไร

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    struct IntVec
    {
        public int x;
        public int y;
    }

    interface IDoSomething
    {
        void Do(IntVec o);
    }

    class DoSomething : IDoSomething
    {
        public void Do(IntVec o)
        {
            Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+",");
        }
    }

    class Program
    {
        static void Test(IDoSomething oDoesSomething)
        {
            IntVec oVec = new IntVec();
            for (oVec.x = 0; oVec.x < 2; oVec.x++)
            {
                for (oVec.y = 0; oVec.y < 2; oVec.y++)
                {
                    oDoesSomething.Do(oVec);
                }
            }
        }

        static void Main(string[] args)
        {
            Test(new DoSomething());
            Console.ReadLine();
        }
    }
}

8
ใช่แล้วล่ะ: การหาจุดบกพร่องที่ร้ายแรงในบางสิ่งที่สำคัญพอ ๆ กับ. NET JIT - ยินดีด้วย!
Andras Zoltan

73
สิ่งนี้ดูเหมือนว่าจะทำซ้ำในกรอบโครงสร้าง 4.0 ของฉันในวันที่ 9 ธันวาคม ฉันจะส่งต่อไปยังทีมที่กระวนกระวายใจ ขอบคุณ!
Eric Lippert

28
นี่เป็นหนึ่งในคำถามไม่กี่ข้อที่ควรได้รับตราทองคำ
Mehrdad Afshari

28
ความจริงที่ว่าเราทุกคนมีความสนใจในคำถามนี้แสดงให้เห็นว่าเราไม่ได้คาดหวังข้อบกพร่องใน. NET JIT ทำได้ดี Microsoft
Ian Ringrose

2
เราทุกคนรอคอย Microsoft ตอบอย่างใจจดใจจ่อ .....
Talha

คำตอบ:


211

เป็นข้อบกพร่องของเครื่องมือเพิ่มประสิทธิภาพ JIT มันกำลังคลี่วงใน แต่ไม่อัพเดทค่า oVec.y อย่างถูกต้อง:

      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
0000000a  xor         esi,esi                         ; oVec.x = 0
        for (oVec.y = 0; oVec.y < 2; oVec.y++) {
0000000c  mov         edi,2                           ; oVec.y = 2, WRONG!
          oDoesSomething.Do(oVec);
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[00170210h]        ; first unrolled call
0000001b  push        edi                             ; WRONG! does not increment oVec.y
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[00170210h]        ; second unrolled call
      for (oVec.x = 0; oVec.x < 2; oVec.x++) {
00000025  inc         esi  
00000026  cmp         esi,2 
00000029  jl          0000000C 

ข้อผิดพลาดจะหายไปเมื่อคุณปล่อยให้ oVec.y เพิ่มขึ้นเป็น 4 นั่นเป็นการโทรที่ไม่สามารถเปิดได้มากเกินไป

วิธีหนึ่งคือ:

  for (int x = 0; x < 2; x++) {
    for (int y = 0; y < 2; y++) {
      oDoesSomething.Do(new IntVec(x, y));
    }
  }

อัปเดต: ตรวจสอบอีกครั้งในเดือนสิงหาคม 2012 ข้อผิดพลาดนี้ได้รับการแก้ไขในเวอร์ชัน 4.0.30319 แต่ยังคงมีอยู่ใน v2.0.50727 กระวนกระวายใจ ดูเหมือนว่าไม่น่าที่พวกเขาจะแก้ไขปัญหานี้ในเวอร์ชันเก่าหลังจากนี้มานาน


3
+1 เป็นข้อผิดพลาดอย่างแน่นอน - ฉันอาจระบุเงื่อนไขสำหรับข้อผิดพลาด (ไม่ได้บอกว่า nobugz พบเพราะฉัน แต่!) แต่นี่ (และคุณนิคดังนั้น +1 สำหรับคุณด้วย) แสดงให้เห็นว่า JIT เป็นผู้กระทำผิด น่าสนใจว่าการปรับให้เหมาะสมจะถูกลบออกหรือแตกต่างกันเมื่อ IntVec ถูกประกาศเป็นคลาส แม้ว่าคุณจะกำหนดค่าเริ่มต้นให้เขตข้อมูล struct เป็น 0 อย่างชัดเจนก่อนที่การวนซ้ำจะมีลักษณะการทำงานเดียวกัน น่ารังเกียจ!
Andras Zoltan

3
@Hans Passant คุณใช้เครื่องมือใดในการส่งออกรหัสชุดประกอบ

3
@Joan - เพียงแค่ Visual Studio คัดลอก / วางจากหน้าต่าง Disassembly ของตัวดีบักและข้อคิดเห็นที่เพิ่มด้วยมือ
Hans Passant

82

ฉันเชื่อว่านี่เป็นข้อผิดพลาดในการรวบรวม JIT ของแท้ ฉันจะรายงานต่อ Microsoft และดูสิ่งที่พวกเขาพูด น่าสนใจฉันพบว่า x64 JIT ไม่มีปัญหาเดียวกัน

นี่คือการอ่านของฉันของ x86 JIT

// save context
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  

// put oDoesSomething pointer in ebx
00000006  mov         ebx,ecx 

// zero out edi, this will store oVec.y
00000008  xor         edi,edi 

// zero out esi, this will store oVec.x
0000000a  xor         esi,esi 

// NOTE: the inner loop is unrolled here.
// set oVec.y to 2
0000000c  mov         edi,2 

// call oDoesSomething.Do(oVec) -- y is always 2!?!
00000011  push        edi  
00000012  push        esi  
00000013  mov         ecx,ebx 
00000015  call        dword ptr ds:[002F0010h] 

// call oDoesSomething.Do(oVec) -- y is always 2?!?!
0000001b  push        edi  
0000001c  push        esi  
0000001d  mov         ecx,ebx 
0000001f  call        dword ptr ds:[002F0010h] 

// increment oVec.x
00000025  inc         esi  

// loop back to 0000000C if oVec.x < 2
00000026  cmp         esi,2 
00000029  jl          0000000C 

// restore context and return
0000002b  pop         ebx  
0000002c  pop         esi  
0000002d  pop         edi  
0000002e  pop         ebp  
0000002f  ret     

ดูเหมือนว่าการเพิ่มประสิทธิภาพไม่ดีสำหรับฉัน ...


23

ฉันคัดลอกรหัสของคุณลงในแอพคอนโซลใหม่

  • แก้ไขข้อผิดพลาด Build
    • แก้ไขเอาต์พุตด้วยตัวดีบั๊กและไม่มีบั๊ก
  • เปลี่ยนเป็น Release Build
    • อีกครั้งให้แก้ไขเอาต์พุตทั้งสองครั้ง
  • สร้างการกำหนดค่า x86 ใหม่ (ฉันใช้ X64 Windows 2008 และใช้ 'Any CPU')
  • แก้ไขข้อผิดพลาด Build
    • มีเอาต์พุตที่ถูกต้องทั้ง F5 และ CTRL + F5
  • ปล่อยสร้าง
    • แก้ไขเอาต์พุตด้วยการแนบ Debugger
    • ไม่มีการดีบักเกอร์ - มีเอาต์พุตที่ไม่ถูกต้อง

ดังนั้นมันคือ x86 JIT ที่สร้างรหัสไม่ถูกต้อง ลบข้อความดั้งเดิมของฉันเกี่ยวกับการเรียงลำดับของลูปเป็นต้นคำตอบอื่น ๆ สองสามข้อในที่นี้ยืนยันว่า JIT คลี่คลายลูปไม่ถูกต้องเมื่ออยู่บน x86

เพื่อแก้ไขปัญหาคุณสามารถเปลี่ยนการประกาศของ IntVec เป็นคลาสและใช้ได้กับทุกรสชาติ

คิดว่านี่ต้องใช้กับ MS Connect ....

-1 ถึง Microsoft!


1
แนวคิดที่น่าสนใจ แต่แน่นอนว่านี่ไม่ใช่ "การเพิ่มประสิทธิภาพ" แต่เป็นข้อบกพร่องที่สำคัญมากในคอมไพเลอร์หากเป็นเช่นนั้น จะได้รับการพบโดยตอนนี้จะไม่ได้หรือไม่
David M

ฉันเห็นด้วยกับคุณ. การเรียงลำดับลูปซ้ำเช่นนี้อาจทำให้เกิดปัญหาที่บอกเล่าไม่ได้ ที่จริงเรื่องนี้ดูเหมือนจะเป็นไปได้น้อยลงเพราะลูปไม่สามารถไปถึง 2 ได้
Andras Zoltan

2
ดูเหมือนว่าหนึ่งใน Heisenbugs ที่น่ารังเกียจเหล่านี้: P
arul

CPU ใด ๆ จะไม่ทำงานหาก OP (หรือใครก็ตามที่ใช้แอปพลิเคชันของเขา) มีเครื่อง x86 แบบ 32 บิต ปัญหาคือว่า x86 JIT พร้อมการเปิดใช้งานการเพิ่มประสิทธิภาพสร้างรหัสที่ไม่ดี
Nick Guerrera
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.