Поле против собственности.Оптимизация производительности - PullRequest
62 голосов
/ 23 марта 2012

Обратите внимание, что этот вопрос касается только производительности. Давайте пропустим рекомендации по дизайну, философию, совместимость, переносимость и все, что не связано с чистой производительностью. Спасибо.

Теперь к вопросу. Я всегда предполагал, что, поскольку геттеры / сеттеры C # действительно являются замаскированными методами, чтение открытого поля должно быть быстрее, чем вызов геттера.

Итак, чтобы убедиться, что я сделал тест (код ниже). Однако этот тест дает только ожидаемые результаты (т. Е. поля быстрее, чем получатели на 34% ) , если вы запускаете его из Visual Studio.

Когда вы запускаете его из командной строки, он показывает примерно то же время ...

Единственное объяснение может быть в том, что CLR выполняет дополнительную оптимизацию (поправьте меня, если я ошибаюсь).

Я не верю, что в реальном приложении, где эти свойства используются гораздо более изощренным образом, они будут оптимизированы таким же образом.

Пожалуйста, помогите мне доказать или опровергнуть идею о том, что в реальной жизни свойства медленнее полей.

Вопрос в том, как мне изменить тестовые классы, чтобы поведение CLR изменилось так, чтобы открытое поле превзошло геттеры. ИЛИ покажите мне, что любое свойство без внутренней логики будет работать так же, как поле (по крайней мере, на получателе)

РЕДАКТИРОВАТЬ: я говорю только о выпуске сборки x64.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PropertyVsField
{
    class Program
    {
        static int LEN = 20000000;
        static void Main(string[] args)
        {
            List<A> a = new List<A>(LEN);
            List<B> b = new List<B>(LEN);

            Random r = new Random(DateTime.Now.Millisecond);

            for (int i = 0; i < LEN; i++)
            {
                double p = r.NextDouble();
                a.Add(new A() { P = p });
                b.Add(new B() { P = p });
            }

            Stopwatch sw = new Stopwatch();

            double d = 0.0;

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += a[i].P;
            }

            sw.Stop();

            Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += b[i].P;
            }

            sw.Stop();

            Console.WriteLine("      field. {0}. {1}.", sw.ElapsedTicks, d);

            Console.ReadLine();
        }
    }

    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
}

Ответы [ 6 ]

53 голосов
/ 23 марта 2012

Как уже упоминалось, геттеры встроены .

Если вы хотите избежать встраивания, вам нужно

  • заменить автоматические свойства на ручные:

    class A 
    {
        private double p;
        public double P
        {
            get { return p; }
            set { p = value; }
        }
    } 
    
  • и скажите компилятору, чтобы он не включал геттер (или оба, если хотите):

            [MethodImpl(MethodImplOptions.NoInlining)]
            get { return p; }
    

Обратите внимание, что первое изменение не влияет на производительность, тогда как второе изменение показывает явные накладные расходы при вызове метода:

Ручные свойства:

auto getter. 519005. 10000971,0237547.
      field. 514235. 20001942,0475098.

Нет встроенного геттера:

auto getter. 785997. 10000476,0385552.
      field. 531552. 20000952,077111.
24 голосов
/ 23 марта 2012

Посмотрите на Свойства против полей - Почему это важно? (Джонатан Анеджа) статья в блоге одного из членов команды VB на MSDN. Он обрисовывает в общих чертах аргумент «свойство против полей», а также объясняет тривиальные свойства следующим образом:

Один аргумент, который я слышал об использовании полей над свойствами, заключается в том, что «Поля быстрее», но для тривиальных свойств это на самом деле не правда, поскольку компилятор CLR Just-In-Time (JIT) встроит доступ к собственности и генерировать код, который так же эффективен, как доступ к поле напрямую.

12 голосов
/ 23 марта 2012

JIT встроит любой метод (не только получатель), который, по его внутренним показателям, будет встроен быстрее. Учитывая, что стандартным свойством является return _Property;, оно будет вставлено в каждом случае.

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

Вы также забываете правило производительности номер один, тестирование превосходит мышление. Например, даже если быстрая сортировка асимптотически быстрее, чем сортировка вставкой, сортировка вставкой на самом деле быстрее для очень маленьких входных данных.

6 голосов
/ 23 марта 2012

Единственное объяснение может быть в том, что CLR выполняет дополнительную оптимизацию (исправьте меня, если я ошибаюсь).

Да, это называется встраиванием. Это делается в компиляторе (уровень машинного кода - т.е. JIT). Поскольку метод получения / установки тривиален (то есть очень простой код), вызовы методов уничтожаются, а метод получения / установки записывается в окружающий код.

Этого не происходит в режиме отладки, чтобы поддерживать отладку (т. Е. Возможность установить точку останова в получателе или установщике).

В visual studio нет способа сделать это в отладчике. Скомпилируйте релиз, запустите его без подключенного отладчика, и вы получите полную оптимизацию.

Я не верю, что в реальном приложении, где эти свойства используются гораздо более изощренно, они будут оптимизированы таким же образом.

Мир полон ложных иллюзий. Они будут оптимизированы, поскольку они все еще тривиальны (т. Е. Простой код, поэтому они встроены).

3 голосов
/ 09 августа 2013

Следует отметить, что в Visual Studio можно увидеть «реальную» производительность.

  1. Компиляция в режиме выпуска с включенной оптимизацией.
  2. Перейдите в раздел «Отладка -> Параметры и настройки» и снимите флажок «Подавлять оптимизацию JIT при загрузке модуля (только для управляемого)».
  3. При желании снимите флажок «Включить только мой код», иначе вы не сможете войти в код.

Теперь объединенная сборка будет такой же, даже если подключен отладчикпозволяя вам вступить в оптимизированную разборку, если вы так, пожалуйста.Это важно для понимания того, как CLR оптимизирует код.

0 голосов
/ 21 марта 2019

После прочтения всех ваших статей, я решил сделать тест с этим кодом:

    [TestMethod]
    public void TestFieldVsProperty()
    {
        const int COUNT = 0x7fffffff;
        A a1 = new A();
        A a2 = new A();
        B b1 = new B();
        B b2 = new B();
        C c1 = new C();
        C c2 = new C();
        D d1 = new D();
        D d2 = new D();
        Stopwatch sw = new Stopwatch();

        long t1, t2, t3, t4;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            a1.P = a2.P;
        }

        sw.Stop();

        t1 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            b1.P = b2.P;
        }

        sw.Stop();


        t2 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            c1.P = c2.P;
        }

        sw.Stop();

        t3 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            d1.P = d2.P;
        }

        sw.Stop();


        t4 = sw.ElapsedTicks;
        long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));

        Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
        Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
        Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
        Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");

    }
    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
    class C
    {
        private double p;
        public double P
        {
            get => p;
            set => p = value;
        }
    }
    class D
    {
        public double P
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get;
            [MethodImpl(MethodImplOptions.NoInlining)]
            set;
        }
    }

При тестировании в режиме отладки я получил такой результат:

auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.

но при переключении в режим разблокировки результат будет другим, чем раньше.

auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.

, кажется, свойство auto - лучший способ.

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