Может ли оператор С# '' страдать при оптимизации режима выпуска на .NET 4?
Ниже приведена простая испытательная арматура. Он успешно работает в сборках Debug и не работает в версиях Release (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, чтобы разобрать 2 версии сборки:
Отладка 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 заставила проблему уйти, но это может быть просто случайностью. Я обнаружил, что это поведение несколько раздражает, может ли кто-нибудь объяснить, почему компилятор будет считать безопасным делать оптимизацию, которая вызывает различное наблюдаемое поведение?
Спасибо заранее.
Ответы
Ответ 1
Эта ошибка уже появилась в этом СО-вопросе Джейкобом Стэнли. Джейкоб уже сообщил об ошибке, и Microsoft подтвердила, что это действительно ошибка в CLR JR. Microsoft сказала следующее:
Эта ошибка будет исправлена в будущей версии среды выполнения. Я боюсь, что слишком рано говорить, будет ли это в пакете обновления или в следующем крупном выпуске.
Еще раз спасибо за сообщение о проблеме.
Вы должны иметь возможность обойти ошибку, добавив следующий атрибут в TestChecker()
:
[MethodImpl(MethodImplOptions.NoInlining)]
Ответ 2
Это не связано с компилятором С#, IL идентичен. Вы обнаружили ошибку в оптимизаторе джиттера .NET 4.0. Вы можете воспроизвести его в Visual Studio. Tools + Options, Debugging, General, отключите опцию "Подавить оптимизацию JIT при загрузке модуля" и запустите сборку Release, чтобы воспроизвести сбой.
Я не смотрел на это достаточно близко, чтобы идентифицировать ошибку. Это выглядит очень странно, он встраивает метод и полностью пропускает код для преобразования бокса. Код машины существенно отличается от кода, генерируемого джиттером версии 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 апреля, что было бы самым ранним.
Ответ 3
Хранилище и загрузка по существу являются целыми до тех пор, пока управление потоком не идет, но, вероятно, массирует некоторые кэши процессора каким-то образом. Фактический поток просто загружает аргумент в стек, проверяет, является ли он экземпляром (который возвращает null или экземпляр), толкает нуль в стек и сравнивает (больше, чем), что приводит к тому, что в стеке остается логическое значение.
Теперь, что делает JITter, это совсем другая история (и будет зависеть от того, какую платформу вы используете. JITTER будет делать всевозможные сумасшедшие вещи во имя производительности (наша команда недавно получила удар, потому что оптимизация хвоста измененный для оптимизации границ домена, который разбил GetCallingAssembly()). Возможно, JITter вставляет IsDateTime, замечая, что нет способа, которым он не может быть DateTime, и просто нажимает true на стек.
Также возможно, что ваша версия выпуска нацелена на несколько другую структуру, поэтому DateTime в тестовой сборке не является DateTime в тестируемой сборке.
Я понимаю, что не отвечает, почему ваш код ломается.
Ответ 4
Для справки я проверил с моно
- Mono JIT-компилятор версии 2.6.7 (Debian 2.6.7-3ubuntu1)
- Mono JIT компилятор версии 2.8.2 (sehe/d1c74ad пт 18 фев 21:46:52 CET 2011)
Оба не представили никаких проблем. Вот IL с оптимизацией в 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