Результат более менее, чем я ожидал, но мне интересно, почему вместо версии с коротким замыканием (&&) используется версия логики и оператора без замыкания (&). Мне кажется, что последняя была бы более эффективной - если одна сторона сравнения уже известна, тогда нет необходимости оценивать другую сторону. Есть ли причина, по которой требуется использование оператора &, или это просто детали реализации, которые не настолько важны, чтобы их беспокоить?
Это отличный вопрос.
Во-первых, я работал над генераторами кода как для кода, предшествующего обнулению, так и для исходной реализации в Roslyn. Это сложный код с множеством возможностей для ошибок и пропущенных оптимизаций. Я написал длинную серию статей в блоге о том, как работает оптимизатор понижаемого значения Roslyn, который начинается здесь:
https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/
Если эта тема вас интересует, эта серия статей, вероятно, очень поможет. Третья часть особенно уместна, поскольку в ней обсуждается связанный с ней вопрос: для арифметики, допускающей значение NULL, мы генерируем (x.HasValue & y.HasValue) ? new int?(x.Value + y.Value) : new int?()
или используем &&
или используем GetValueOrDefault
или как? (Ответ, конечно, таков: я попробовал все из них и выбрал тот, который создает самый быстрый наименьший код.) Однако в этой серии не рассматривается ваш конкретный вопрос, касающийся равноправного равенства. У равноправного равенства есть несколько иные правила, чем у обычной поднятой арифметики.
Конечно, я не был в Microsoft с 2012 года, и они могли изменить его с тех пор; Я бы не знал. (ОБНОВЛЕНИЕ: Глядя на связанную проблему в комментариях выше, представляется вероятным, что я пропустил оптимизацию в моей первоначальной реализации в 2011 году и что это было исправлено в 2017 году.)
Чтобы ответить на ваш конкретный вопрос: проблема с &&
заключается в том, что она дороже , чем &
, когда работа, выполняемая с правой стороны оператора, дешевле чем тест-и-ветка. Тест и ветка - это не просто инструкции. Это, очевидно, ветвь, которая обладает множеством эффектов. На уровне процессора ответвления требуют прогнозирования ветвлений, а ответвления могут быть предсказаны неправильно. Ветви означают более простые блоки, и помните, что оптимизатор jit работает во время выполнения , что означает, что оптимизатор jit должен быть fast . Разрешается говорить «слишком много базовых блоков в этом методе, я собираюсь пропустить некоторые оптимизации», и поэтому, возможно, добавление дополнительных базовых блоков неоправданно плохо.
Короче говоря, компилятор C # будет сгенерировать операции "и" как активные, если правая сторона не имеет побочных эффектов, и компилятор считает, что оценка left & right
будет быстрее и короче, чем оценка left ? right : false
. Часто стоимость оценки right
настолько низкая, что стоимость ветки на дороже , чем простая арифметика.
Вы можете увидеть это в областях, отличных от обнуляемого оптимизатора; например, если у вас есть
bool x = X();
bool y = Y();
bool z = x && y;
, тогда это будет сгенерировано как z = x & y
, потому что компилятор знает, что нет дорогостоящей операции для сохранения; Y()
уже уже вызвано.