2010-01-13 23 views
381

Đoạn mã sau cho đầu ra khác nhau khi chạy bản phát hành bên trong Visual Studio và chạy bản phát hành bên ngoài Visual Studio. Tôi đang sử dụng Visual Studio 2008 và nhắm mục tiêu .NET 3.5. Tôi cũng đã thử .NET 3.5 SP1.Lỗi tiềm năng NET JIT?

Khi chạy bên ngoài Visual Studio, JIT sẽ khởi động. Hoặc (a) có điều gì đó tinh tế xảy ra với C# mà tôi bị thiếu hoặc (b) JIT thực sự là do lỗi. Tôi nghi ngờ rằng JIT có thể đi sai, nhưng tôi đang chạy ra khỏi possiblities khác ...

Output khi chạy bên trong Visual Studio:

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

Output khi chạy phát hành bên ngoài của Visual Studio:

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

Lý do là gì?

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(); 
     } 
    } 
} 
+14

+1 - một vấn đề lớn nhỏ này là :) –

+8

Vâng - làm thế nào về điều đó: tìm một lỗi nghiêm trọng trong một cái gì đó thiết yếu như .Net JIT - chúc mừng! –

+73

Điều này xuất hiện để repro trong xây dựng ngày 9 tháng 12 của tôi về khung 4.0 trên x86. Tôi sẽ chuyển nó cho nhóm jitter. Cảm ơn! –

Trả lời

202

Đây là một lỗi JIT ưu. Nó được unrolling vòng lặp bên trong nhưng không cập nhật giá trị oVec.y đúng:

 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 

Lỗi biến mất khi bạn hãy tăng oVec.y đến 4, đó là quá nhiều cuộc gọi đến cuộn.

Một workaround là thế này:

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

UPDATE: tái kiểm tra trong tháng 8 năm 2012, lỗi này đã được cố định trong phiên bản 4.0.30319 jitter. Nhưng vẫn còn hiện diện trong jitter v2.0.50727. Có vẻ như họ sẽ không sửa lỗi này trong phiên bản cũ sau thời gian dài này.

+11

+1 Đây chắc chắn là một lỗi. Tôi đã đăng nhiều hay ít câu trả lời giống như một câu trả lời cho câu trả lời của tôi trong khi điều này xuất hiện. Có vẻ như trả lời câu hỏi ở đây thường là ... –

+3

+1, chắc chắn là lỗi - tôi có thể đã xác định các điều kiện cho lỗi (không nói rằng nobugz tìm thấy nó vì tôi, mặc dù!), nhưng điều này (và của bạn, Nick, vì vậy 1 cho bạn quá) cho thấy rằng JIT là thủ phạm. thú vị là việc tối ưu hóa hoặc bị loại bỏ hoặc khác nhau khi IntVec được khai báo là một lớp. Ngay cả khi bạn khởi tạo một cách rõ ràng các trường struct thành 0 đầu tiên trước vòng lặp thì cùng một hành vi được nhìn thấy. Khó chịu! –

+3

@Hans Passant Bạn đã sử dụng công cụ nào để xuất mã assembly? –

22

Tôi đã sao chép mã của bạn vào một ứng dụng Console mới.

  • gỡ lỗi Build
    • đầu ra đúng với cả debugger và không debugger
  • Chuyển sang Thả Xây dựng
    • Một lần nữa, đúng ra cả hai lần
  • Tạo một cấu hình x86 mới (tôi đang chạy trên runnin g X64 Windows 2008 và được sử dụng 'Bất kỳ CPU')
  • gỡ lỗi Build
    • Got đầu ra đúng cả F5 và CTRL + F5
  • phát hành Build
    • đầu ra đúng với Debugger kèm theo
    • Không có trình gỡ lỗi - Có đầu ra không chính xác

Vì vậy, JIT x86 không chính xác tạo mã. Đã xóa văn bản gốc của tôi về sắp xếp lại các vòng lặp vv Một vài câu trả lời khác trên đây đã xác nhận rằng JIT đang tháo vòng lặp không chính xác khi trên x86.

Để khắc phục sự cố, bạn có thể thay đổi tuyên bố IntVec thành một lớp và hoạt động ở tất cả các hương vị.

Hãy nghĩ điều này cần phải thực hiện trên MS Connect ....

-1 cho Microsoft!

+1

Ý tưởng thú vị, nhưng chắc chắn đây không phải là "tối ưu hóa" nhưng là một lỗi rất lớn trong trình biên dịch nếu đây là trường hợp? Có thể đã được tìm thấy bởi bây giờ phải không? –

+0

Tôi đồng ý với bạn. Sắp xếp lại các vòng lặp như thế này có thể gây ra các vấn đề chưa được giải quyết. Trên thực tế điều này dường như thậm chí ít có khả năng hơn, bởi vì các vòng lặp không thể đạt được 2. –

+2

Trông giống như một trong những Heisenbugs khó chịu này: P – arul

79

Tôi tin rằng đây là lỗi bản dịch JIT chính hãng. Tôi sẽ báo cáo cho Microsoft và xem họ nói gì. Thật thú vị, tôi thấy rằng x64 JIT không có cùng một vấn đề.

Đây là bài đọc của tôi về JIT x86.

// 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  

này trông giống như một tối ưu hóa xấu đi với tôi ...

Các vấn đề liên quan