Разница в производительности эталонного равенства? ((объект) obj1 == (объект) obj2) против object.ReferenceEquals (obj1, obj2) - PullRequest
19 голосов
/ 09 апреля 2009

Существуют ли дополнительные издержки при использовании стихов метода object.ReferenceEquals с использованием ((object)obj1 == (object)obj2)?

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

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

(object)obj != null

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

!object.ReferenceEquals(obj,null)

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

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

Ответы [ 6 ]

22 голосов
/ 09 апреля 2009

Есть ли дополнительные затраты при использовании метода object.ReferenceEquals

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

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

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

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

5 голосов
/ 16 декабря 2012

Вопреки ответам здесь, я нашел (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 голосов
/ 27 декабря 2012

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

За пределами «микро-эталона» в реальном мире, JIT имеет гораздо больше проблем и проблем, и ни у кого нет роскоши времени компиляции C ++ WPO, ни у простоты компиляторов C # более прямые переводы, и все же все проблемы, связанные с отсутствием необходимого контекста после компилятора C #.

«педантичные» формы:

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, происходят странные недетерминированные вещи в отношении того, что оптимизируется и как .

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

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

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

  3. Ранняя и частая защита с помощью '(object)' или 'RefEquals', если она очень полезна для JIT , даже если в настоящее время у вас нет переопределений оператора или Equals. (объект) еще лучше. Если вы думаете о том, что JIT должен сделать, чтобы определить, может ли тип иметь переопределения, и иметь дело с правилами bizantine (sp) Equality и тому подобным, это как мини-ад, и что угодно, что может быть обнародовано позже, и вы намереваетесь ref Равенство вы спасете себя от внезапного замедления или шаткого кода позже. Если оно уже является публичным и не запечатано, JIT не может гарантировать, что правила будут или успеют их преследовать.

  4. Защита обычно более распространенных «нулевых» проверок, вероятно, еще более важна , хотя и не является частью вопроса ОП, поскольку обычно применяются те же правила и проблемы. «(объект) a == ноль» и «! ((объект) a == ноль)» являются «педантичными» эквивалентами.

3 голосов
/ 09 апреля 2009

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

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

2 голосов
/ 14 февраля 2011

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

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

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

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

0 голосов
/ 02 апреля 2015
public static bool ReferenceEquals (Object objA, Object objB) {
        return objA == objB;
    }

http://referencesource.microsoft.com/#mscorlib/system/object.cs,4d607d6d56a93c7e

...