Разница в производительности равенство? ((object) obj1 == (object) obj2) vs. object.ReferenceEquals(obj1, obj2)

Есть ли дополнительные накладные расходы при использовании стилей метода object.ReferenceEquals, используя ((object)obj1 == (object)obj2)?

В первом случае будет задействован вызов статического метода, и в обоих случаях будет задействована некоторая форма каста для объекта.

Даже если компилятор уравновешивает эти методы, как насчет неравенства?

(object)obj != null

по сравнению с...

!object.ReferenceEquals(obj,null)

Я предполагаю, что в какой-то момент произойдет логическое отрицание либо внутри оператора! =, либо применительно к результату метода ReferenceEquals. Как вы думаете?

Также есть проблема читаемости. ReferenceEquals кажется более четким при проверке равенства, но для неравенства можно пропустить !, предшествующий object.ReferenceEquals, тогда как != в первом варианте трудно упустить.

Ответы

Ответ 1

Есть ли дополнительные накладные расходы при использовании объекта. МетодReferenceEquals

Нет. Метод непосредственно содержит минимальное описание IL для выполнения контрольной проверки равенства (для записи: это эквивалентно оператору VB Is) и часто будет встроен JIT (особенно при таргетинге на x64), поэтому существует no служебный.

О читаемости: Я лично считаю, что object.ReferenceEquals потенциально более читабельна (даже в отрицательной форме), потому что она явно выражает свою семантику. Приведение к object может смущать некоторых программистов.

Я только что нашел статью, обсуждающую это. Он предпочитает (object)x == y, потому что след IL меньше. Он утверждает, что это могло бы облегчить вложение метода X с использованием этого сравнения. Однако (без каких-либо подробных знаний JIT, но логически и интуитивно) я считаю, что это неправильно: если JIT ведет себя как оптимизирующий компилятор С++, он рассмотрит этот метод после наложения вызова на ReferenceEquals, поэтому (ради метода inlining X) объем памяти будет одинаковым в любом случае.

То есть: выбор одного способа над другим не будет иметь никакого влияния на JIT и, следовательно, на производительность.

Ответ 2

В отличие от ответов здесь я нашел (object) == быстрее, чем object.ReferenceEquals. Что касается того, как быстрее, очень незначительно!

Испытательный стенд:

Я знаю, что вам нужна эталонная проверка равенства, но я включаю статический метод object.Equals(,), а также в случае классов, где его не переопределять.

Платформа: x86; Конфигурация: релиз сборки

class Person {
}

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}

Тест:

Person p1 = new Person();
Person p2 = new Person();
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //~ 1250ms
    b = object.Equals(p1, p2); //2100ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~4000ms

}, 100000000);

Person p1 = new Person();
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //990 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); // 1230 ~ 1260ms
    b = object.Equals(p1, p2); //1250 ~ 1300ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = null;
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1270ms
    b = object.Equals(p1, p2); //1180 ~ 1220ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = new Person();
Person p2 = p1;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1280ms
    b = object.Equals(p1, p2); //1150 ~ 1200ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //3700 ~ 3800ms

}, 100000000);

object.Equals(,) вызывает ReferenceEquals внутренне, и если они не равны, это вызовет переопределенный виртуальный Equals метод класса, и, следовательно, вы увидите уведомление о разнице в скорости.

Результаты были согласованы в конфигурации Debug тоже...

Как уже отмечалось, акцент должен делаться на удобочитаемости/значимости/выявлении намерений.

Ответ 3

Накладные расходы Object.ReferenceEquals находятся только в загрузке аргументов, которые будут удалены в большинстве сценариев. После этого оба объекта Object.ReferenceEquals и operator == сводятся к одному оператору IL ceq. В худшем случае разница будет незначительной.

Что еще более важно, Object.ReferenceEquals гораздо более интенсивно раскрывает, чем (object) o1 == (object) o2. Он четко указывает на код "Я тестирую для ссылочного равенства/идентичности", а не скрываю намерение под кучей отбросов.

Ответ 4

Добавление моих двух центов после многих поздних часов в первичном критическом коде на очень больших кодовых базах с иногда сумасшедшими глубокими глубинами вызовов.

Вне "микроэлемента" в реальном мире JIT имеет гораздо больше проблем и проблем, а niether обладает роскошью времени компиляции С++ WPO, а также простотой компиляторов С# более прямых переводов, и все же все проблемы, связанные с отсутствием всего контекста после компилятора С#.

"Педантичные" формы:

if ((object)a == (object)b) { }     // ref equals

if (!((object)a == (object)b)) { }  // ref not equals

Если у вас действительно есть перманентные вопросы от честного до бога, взвешенные и мешающие, или вам нужно оказать давление на JIT для нескольких действительно больших широко распространенных классов, это может помочь тонне. То же самое верно с NullOrEmpty vs '(object) str == null || str.Length == 0 '.

Не имея роскоши WPO и во всех случаях, когда многие не знают о том, какие сборки могут загружаться или выгружаться после того, как они взломали JITing, происходят нечеткие недетерминированные вещи в отношении того, что оптимизируется и как.

Это огромная тема, но вот несколько моментов:

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

  • ! ((object) a == (объект) b) и (объект) a!= (объект) b не всегда скомпилированы до одного и того же кода, как это верно certianly for! (a == b) и a!= b, даже без каких-либо явных операторов или переопределений Equals. A '(объект) a!= (Объект) b' гораздо чаще запускает .Net имеет больше педантичного звонка в среду выполнения, что очень дорого.

  • Охрана рано и часто с помощью "(объекта)" или "RefEquals", если это очень полезно для JIT, даже если в настоящее время у вас нет переопределения оператора или Equals. (объект) еще лучше. Если вы думаете о том, что должен делать JIT, чтобы определить, может ли тип иметь переопределения, и иметь дело с правилами блаженства (sp) Equality и whatnot, его, как мини-ад, и anyting, которые могут быть опубликованы позже, и вы намерены ref равенство вы спасете от внезапного замедления или коварного кода позже. Если он уже является общедоступным и не запечатанным, JIT can not quarentee, что правила будут или будут иметь время преследовать их.

  • Защита более широко распространенных "нулевых" проверок является еще более важной, хотя и не является частью вопроса OP, поскольку в целом применяются те же правила и проблемы. '(object) a == null' и '! ((object) a == null)' являются эквивалентами педантичности.

Ответ 5

Ранее упомянутая статья об операторе == лучше отображала неполную информацию, по крайней мере, на .NET 4.0 (хорошо она была написана в версии 2.0 раза).

В нем указано, что ReferenceEquals не оптимизируется/встраивается, что верно, только если вы создаете проект с конфигурацией AnyCPU. Установка для "x86" или "x64" делает (object) obj == null и ReferenceEquals (object, null) в конечном итоге идентичной IL, где оба метода являются просто одной командой "ceq" IL.

Итак, ответ: IL, созданный обоими методами, идентичен в версиях Release/x86 или x64.

ReferenceEquals определенно более читабельна, по крайней мере, на мой вкус.