Может ли оператор C # 'is' пострадать при оптимизации режима выпуска на .NET 4? - PullRequest
40 голосов
/ 05 апреля 2011

Ниже приведен простой тестовый прибор. Он преуспевает в сборках Debug и терпит неудачу в сборках выпуска (VS2010, решение .NET4, x64):

[TestFixture]
public sealed class Test
{
    [Test]
    public void TestChecker()
    {
        var checker = new Checker();
        Assert.That(checker.IsDateTime(DateTime.Now), Is.True);
    }
}

public class Checker
{
    public bool IsDateTime(object o)
    {
        return o is DateTime;
    }
}

Кажется, оптимизация кода наносит некоторый ущерб; если я отключу его в сборке Release, он тоже будет работать. Это было довольно загадочно для меня. Ниже я использовал ILDASM для разборки двух версий сборки:

IL отладки:

.method public hidebysig instance bool IsDateTime(object o) cil managed
{
  // Code size       15 (0xf)
  .maxstack  2
  .locals init (bool V_0)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  isinst     [mscorlib]System.DateTime
  IL_0007:  ldnull
  IL_0008:  cgt.un
  IL_000a:  stloc.0
  IL_000b:  br.s       IL_000d
  IL_000d:  ldloc.0
  IL_000e:  ret
} // end of method Validator::IsValid

Выпуск IL:

.method public hidebysig instance bool IsDateTime(object o) cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     [mscorlib]System.DateTime
  IL_0006:  ldnull
  IL_0007:  cgt.un
  IL_0009:  ret
} // end of method Validator::IsValid

Кажется, магазин и загрузка оптимизированы. Ориентация на более ранние версии .NET Framework устранила проблему, но это может быть просто случайностью. Я обнаружил, что это поведение несколько расстраивает, кто-нибудь может объяснить, почему компилятор считает безопасным проводить оптимизацию, которая дает другое наблюдаемое поведение?

Заранее спасибо.

Ответы [ 4 ]

20 голосов
/ 05 апреля 2011

Эта ошибка уже появилась в этом ТАКОМ вопросе от Джейкоба Стэнли. Джейкоб уже сообщил об ошибке , и Microsoft подтвердила, что это действительно ошибка в CLR JIT. У Microsoft было это, чтобы сказать:

Эта ошибка будет исправлена ​​в следующей версии среды выполнения. Боюсь, еще слишком рано говорить, будет ли это в пакете обновлений или в следующем основном выпуске.

Еще раз спасибо за сообщение о проблеме.

Вы сможете обойти ошибку, добавив следующий атрибут к TestChecker():

[MethodImpl(MethodImplOptions.NoInlining)]
15 голосов
/ 05 апреля 2011

Это не связано с компилятором C #, IL идентичен. Вы нашли ошибку в оптимизаторе джиттера .NET 4.0. Вы можете воспроизвести его в Visual Studio. Инструменты + Параметры, Отладка, Общие, снимите флажок «Подавить оптимизацию JIT при загрузке модуля» и запустите сборку выпуска, чтобы воспроизвести ошибку.

Я еще недостаточно внимательно изучил его, чтобы определить ошибку. Это выглядит очень странно, оно содержит метод и полностью пропускает код для преобразования в бокс. Машинный код существенно отличается от кода, сгенерированного джиттером версии 2.

Чистый обходной путь не так прост, вы можете сделать это, подавив встраивание. Как это:

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    public bool IsDateTime(object o) {
        return o is DateTime;
    }

Вы можете сообщить об ошибке на connect.microsoft.com. Дай мне знать, если не хочешь, и я позабочусь об этом.


Неважно, это было уже сделано . Это не было исправлено в техническом выпуске, который был включен в VS2010 SP1.


Эта ошибка была исправлена, я больше не могу ее воспроизвести. Моя текущая версия clrjit.dll - 4.0.30319.237 от 17 мая 2011 года. Я не могу точно сказать, какое обновление его исправило. 5 августа 2011 года я получил обновление для системы безопасности, обновившее clrjit.dll до версии 235 с датой 12 апреля, которая будет самой ранней.

5 голосов
/ 05 апреля 2011

Хранение и загрузка, по сути, не имеют смысла в управлении потоком данных, но, вероятно, каким-то образом изнашивают некоторые кэши ЦП.Фактический поток просто загружает аргумент в стек, проверяет, является ли он экземпляром (который возвращает ноль или экземпляр), помещает ноль в стек и сравнивает (больше чем), что приводит к тому, что логическое значение остается в стеке.

Теперь, что JITter делает с ним, это совсем другая история (и это будет зависеть от того, какую платформу вы используете. JITter будет делать всякие сумасшедшие вещи во имя производительности (наша команда недавно пострадала, потому чтоОптимизация вызовов изменена, чтобы оптимизировать границы домена, что нарушило GetCallingAssembly ()). Возможно, JITter встраивает IsDateTime, замечая, что нет способа не быть DateTime и просто помещает в стек значение true.Также возможно, что ваша версия выпуска нацелена на несколько иную платформу, поэтому DateTime в тестовой сборке не является DateTime в тестируемой сборке.

Я понимаю, что это не отвечает, почему ваш код ломается.

3 голосов
/ 05 апреля 2011

Для справки я проверил с моно

  • Моно JIT-компилятор версии 2.6.7 (Debian 2.6.7-3ubuntu1)
  • Mono JIT-компилятор версии 2.8.2 (sehe / d1c74ad пт, 18 февраля 21:46:52 CET 2011)

Оба не представляли никаких проблем вообще. Вот ИЛ с оптимизацией в 2.8.2

.method public hidebysig 
       instance default bool IsDateTime (object o)  cil managed 
{
    // Method begins at RVA 0x2130
    // Code size 10 (0xa)
    .maxstack 8
    IL_0000:  ldarg.1 
    IL_0001:  isinst [mscorlib]System.DateTime
    IL_0006:  ldnull 
    IL_0007:  cgt.un 
    IL_0009:  ret 
} // end of method Checker::IsDateTime

Без оптимизации точно тоже самое

Вот результат монофонического кода для этого IL:

00000130 <TestData_Checker_IsDateTime_object>:
     130:       55                      push   %ebp
     131:       8b ec                   mov    %esp,%ebp
     133:       53                      push   %ebx
     134:       56                      push   %esi
     135:       83 ec 10                sub    $0x10,%esp
     138:       e8 00 00 00 00          call   13d <TestData_Checker_IsDateTime_object+0xd>
     13d:       5b                      pop    %ebx
     13e:       81 c3 03 00 00 00       add    $0x3,%ebx
     144:       8b 45 0c                mov    0xc(%ebp),%eax
     147:       89 45 f4                mov    %eax,-0xc(%ebp)
     14a:       8b 75 0c                mov    0xc(%ebp),%esi
     14d:       83 7d 0c 00             cmpl   $0x0,0xc(%ebp)
     151:       74 1a                   je     16d <TestData_Checker_IsDateTime_object+0x3d>
     153:       8b 45 f4                mov    -0xc(%ebp),%eax
     156:       8b 00                   mov    (%eax),%eax
     158:       8b 00                   mov    (%eax),%eax
     15a:       8b 40 08                mov    0x8(%eax),%eax
     15d:       8b 48 08                mov    0x8(%eax),%ecx
     160:       8b 93 10 00 00 00       mov    0x10(%ebx),%edx
     166:       33 c0                   xor    %eax,%eax
     168:       3b ca                   cmp    %edx,%ecx
     16a:       0f 45 f0                cmovne %eax,%esi
     16d:       85 f6                   test   %esi,%esi
     16f:       0f 97 c0                seta   %al
     172:       0f b6 c0                movzbl %al,%eax
     175:       8d 65 f8                lea    -0x8(%ebp),%esp
     178:       5e                      pop    %esi
     179:       5b                      pop    %ebx
     17a:       c9                      leave  
     17b:       c3                      ret    
     17c:       8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
...