Почему короткие нулевые значения преобразуются в целочисленные нулевые значения для сравнения с нулевым?

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

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){}. По крайней мере, это то, что я делаю в своем коде после того, как обнаружил это преобразование.

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


person Aristos    schedule 09.02.2012    source источник


Ответы (2)


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

Это ошибка арифметического оптимизатора, допускающего значение NULL.

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

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

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

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?();

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

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

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

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

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

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

person Eric Lippert    schedule 09.02.2012
comment
Я хотел бы сказать вам, что большую часть времени мы используем шорт в качестве данных, которые мы получаем из базы данных, и если он равен нулю, мы делаем это, если нет, мы вызываем другие данные из базы данных. Никаких арифметических действий, только данные, которые отражают данные базы данных. И они короткие, потому что не хватает базы данных, чтобы получить место. Аналогично с меньшими типами. Таким образом, код, который только читает короткое, делает только один тест, а затем использует его, вполне возможен. - person Aristos; 14.02.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 с бинарными операторами.

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


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

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

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


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

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

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

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

person Anthony Pegram    schedule 09.02.2012
comment
Хорошо, это по дизайну +1, но до сих пор не понимаю, почему они это делают, они слишком сильно оптимизировали строку, почему оставили числа в покое и добавили больше кода для этого простого сравнения? - person Aristos; 09.02.2012
comment
Я проверял это в случайном сложном коде, а также при отладке и при выпуске. - person Aristos; 10.02.2012
comment
@Aristos, я связался с Эриком Липпертом по его контактной ссылке в его блоге, надеюсь, он ответит на ваши последние вопросы, почему. Он более или менее однозначно может ответить на эти вопросы (среди участников Stack Overflow). - person Anthony Pegram; 10.02.2012