Почему короткие нулевые значения преобразуются в нулевые значения int для сравнения с нулевыми? - PullRequest
40 голосов
/ 09 февраля 2012

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

short? cTestA;
if (cTestA == null) { ... }

Преобразуется компилятором в:

short? CS$0$0001 = cTestA;
int? CS$0$0002 = CS$0$0001.HasValue ? new int?(CS$0$0001.GetValueOrDefault()) : null;
if (!CS$0$0002.HasValue){ ... }

Это происходит для всех версий .NET, включая .NET 4.

Что мне здесь не хватает? В чем причина этого двойного преобразования только для проверки HasValue?

Последующие действия

Я ожидаю, что компилятор сделает простую проверку с помощью .HasValue, if (cTestA.HasValue){}. По крайней мере, это то, что я делаю в своем коде после обнаружения этого преобразования.

Почему весь этот дополнительный код добавлен для такого простого теста?

Ответы [ 2 ]

37 голосов
/ 10 февраля 2012

Re: ваше последнее обновление:

Это ошибка в арифметическом оптимизаторе, допускающем обнуляемость.

Оптимизатор, допускающий обнуление, удалит ненужное преобразование в int?, когда вы сделаете что-то вроде:

short? s = null;
int? x = s + 1;

Неоптимизированный кодоген делает эквивалент:

short? s = null;
int? x;
int? temp = s.HasValue ? new int?((int)s.Value) : new int?();
x = temp.HasValue ? new int?(x.Value + 1) : new int?();

Оптимизированный кодоген делает эквивалент:

short? s = null;
int? x;
x = s.HasValue ? new int?((int)s.Value + 1) : new int?();

Однако оптимизатор содержит ошибку;мы не удаляем ненужное преобразование для равенства.

Спасибо, что обратили на это мое внимание;мы исправим это для Roslyn .На самом деле я собираюсь написать оптимизатор Nullable для Roslyn в ближайшие пару недель.

ОБНОВЛЕНИЕ: я написал этот оптимизатор, и если вы заинтересованы в том, как он работает, я написал серию статей оэто начинается здесь:

http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

29 голосов
/ 09 февраля 2012

См. Раздел 4.1.5 спецификации языка C # 4.0 .В частности, представляет интерес:

C # поддерживает девять целочисленных типов: sbyte, byte, short , ushort, int, uint, long, ulong и char.[пропущенный текст]

Унарные и двоичные операторы целочисленного типа всегда работают с 32-разрядной точностью со знаком, 32-разрядной точностью без знака, 64-разрядной точностью со знаком или 64-разрядной точностью без знака:

  • [пропущенные пункты]

  • Для двоичного файла +, -, *, /,%, &, ^, |, == ,! =,>, <,> = И <=, операнды преобразуются в тип T, где T - это первое из int, uint, long и ulong, которое может полностью представлять все возможные значения обоих операндов,Затем операция выполняется с использованием точности типа T, а тип результата - T (или bool для реляционных операторов).Не допускается, чтобы один операнд был типа long, а другой - типа ulong с бинарными операторами. </p>

Операции, использующие short, переводятся в int, и те,операции отменены для их равноправных аналогов.(Это приводит к разделам 7.3.6.2 и 7.3.7)


Хорошо, это по замыслу, но все еще не понимаю, почему они это делают, они оптимизируют добавление строкслишком много, поэтому оставьте числа в покое и добавьте больше кода для этого простого сравнения

Это просто способ, которым разработан язык, с учетом оптимизации в современной архитектуре.Не конкретно в этом контексте, но рассмотрим слова Эрика Липперта, как указано здесь

Арифметика никогда не выполняется в шортах в C #.Арифметика может быть выполнена в типах ints, uints, long и ulongs, но арифметика никогда не выполняется в шортах.Шорты повышаются до int, а арифметика выполняется в целых числах, потому что, как я уже говорил, подавляющее большинство арифметических вычислений укладывается в int.Подавляющее большинство не вписывается в короткие.Короткая арифметика, возможно, медленнее на современном оборудовании, которое оптимизировано для целых чисел, а короткая арифметика не занимает меньше места;это будет сделано в микросхемах или длиннах на чипе.


Ваше последнее обновление:

Я ожидаю, что компилятор сделает простую проверку с помощью .HasValue if (cTestA.HasValue) {} впо крайней мере, это то, что я делаю в своем коде после обнаружения этого преобразования.Так что это то, чего я действительно не понимаю, почему бы не подумать так просто, а добавить весь этот дополнительный код.Компилятор всегда пытается оптимизировать код - почему бы избежать этой простой проверки .HasValue.Я что-то здесь упускаю наверняка ...

Мне придется обратиться к специалисту по компилятору, чтобы сказать, почему он выбрал преобразование вместо немедленной проверки HasValue, за исключением того, что могпросто будьте порядком операций.В спецификации языка говорится, что бинарные операнды операторов повышаются, и это то, что они сделали в предоставленном фрагменте.Далее в спецификации языка говорится, что проверки с помощью x == null, где x - это тип значения NULL, могут быть преобразованы в !x.HasValue, и это также то, что они сделали.В представленном вами скомпилированном коде числовое продвижение просто имеет прецедент над обнуляемым поведением.

Что касается компилятора всегда , пытающегося оптимизировать код, опять же, эксперт может уточнить, но это не так.Есть оптимизации, которые он может сделать, и другие, которые он может отнести к джиттеру.Существуют оптимизации, которые компилятор или джиттер могут выполнять, а могут и не делать, в зависимости от того, является ли это отладкой по сравнению с версией выпуска, с присоединенным отладчиком или без него.И, несомненно, есть оптимизация, которую они могли бы сделать, которую они просто решили не делать, потому что затраты по сравнению с выгодами не заканчиваются.

...