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

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

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

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

(object)obj != null

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

!object.ReferenceEquals(obj,null)

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

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

6 ответов

Решение

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

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

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

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

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

Вопреки ответам здесь я нашел (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 конфигурация тоже...

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

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

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

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

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

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

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

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

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

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

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

public static bool ReferenceEquals (Object objA, Object objB) {
        return objA == objB;
    }

http://referencesource.microsoft.com/

Другие вопросы по тегам