Насколько увеличивается расход вычислительных ресурсов при обкладывании полей свойствами с C#?

Свойства с C# могут быть чрезвычайно полезными во многих ситуациях, но какова плата производительностью за их использование?

Логики в геттерах и сеттерах в принципе может быть сколько угодно много, но возьмём минимальных случай - по геттеру и сеттеру для каждого поля, которые не делают ничего, кроме выдачи и установки значения соответственно.

Итак, насколько инстансы класса

public class Person
{

  public required uint ID { get; init; }

  public required string Name { get; set; }
  
  public required string Email { get; set; }

  public required string PhoneNumber { get; set; }

  public byte? Age { get; set; }

  // И ещё штук 100-200, если это реальное коммерческое приложение

}

будут тормозить приложение больше, чем нижеследующая реализация?

public class Person
{

  public required uint ID;

  public required string Name;
  
  public required string Email;

  public required string PhoneNumber;

  public byte? Age;

  // И ещё штук 100-200, если это реальное коммерческое приложение

}

Естественно, что дать ответ в миллисекундах я Вас не прошу, достаточно одной из следующих качественных оценок:

  • Влияние пренебрежимо мало даже для крупных коммерческих и государственных приложений, работающих с большими объёмами данных
  • Влияние пренебрежимо мало в средних приложениях, но начинает ощущаться в крупных коммерческих и государственных приложениях, работающих с большими объёмами данных
  • Влияние пренебрежимо мало в маленьких приложения, но начинает чувствоваться в средних приложениях

Ну, я не думаю что это сколько-то ощутимо в маленьких приложениях, поэтому данного варианта не предлагаю.


Ответы (3 шт):

Автор решения: user11354240

Вот как раз здесь проще сделать бенчмарк, чем погружаться в споры об идеологической правильности. https://pastebin.com/pnTvuxFV (использовалась кодогенерация, исходник достаточно большой, поэтому не привожу его здесь), результат:

Method Mean Error StdDev
AccessClassWithFields 406.6 ns 2.275 ns 1.900 ns
AccessClassWithProps 2,951.5 ns 58.379 ns 102.245 ns

Результаты вполне объяснимы - при обращении к свойствам на уровне IL производится callvirt метода-геттера или сеттера, что в любом случае дороже прямого обращения к полю. А дальше обычно действуют по обстоятельствам.

→ Ссылка
Автор решения: tym32167

Использование интерфейсов, классов, свойств, функций, паттернов, всяких итераторов, DI контейнеров всегда добавляет какой то оверхед. Большинство подобных вещей существуют не для производительности, а для для других вещей, таких как сложность, скорость разработки, поддержки, модульность, гибкость и прочие вещи. Другими словами, для удобства разработки ПО, а не его скорости работы. Почему это важно? Потому что как только начинаются ужесточения требований к производительности, почти сразу усложняются все остальные вещи, увеличивается время разработки, требуются соотвествующе кадры для подобного. Другими словами, чем жестче требования к производительности, тем дороже выходит разработка такой системы.

Потому, всесто того, чтобы тратить время и деньги на оптимизацию, производимое ПО держат на уровне "достаточном" для того, чтобы им пользоваться, а время и деньги тратятся на развитие, новые фичи и так далее. В итоге ПО "достаточно" быстрое для использования и с необходимым набором фишек - выигрывают все.

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

Подобные оптимизации типа использования полей вместо свойств, могут быть полезны только в самых критически важных для производительности местах, когда структурные и алгоритмические приемы исчерпаны. Я знаю команду, которая по причине производительности, практически не использует классы как таковые и очень редко когда может использовать структуры данных, отличные от массивов - все это по причине критической важности производительности, но это не касается 99.9999% проектов, поверье, если вы попадете в проект с критическими требованиями по производительсности, дилемма поля vs свойства будет наименьшей вашей головной болью.

Что же касается крупных коммерческих приложений, я работал над такими и имел множество полей, и я вам скажу, что нет никакой абсолютно разницы конечному юзеру, откроется у вас форма на 10-100-1000 миллисекунд раньше или нет, зато код у вас будет более поддерживаемым без лишних выкрутасов и бесполезных оптимизаций.

Если вам действительно интересно узнать способы ускорения приложений, то я бы советовал сначала обратить внимание на тему алгоритмов, на всякие возможности тестирования производительности, поиска "бутылочного горлышка", так как абсолютное большинство тормозов происходят из за работы с вводом/выводом (сеть, БД, файлы) или линейным поиском в списке вместо константного в хештаблице/хешсете (я сбился со счета сколько раз я подобное правил).

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

→ Ссылка
Автор решения: user11354240

Согласен, в самом первом бенчмарке много лишнего шума - создание экземпляра класса, сложение. Его можно сильно упростить, и выполнить энное количество раз (проход по энумератору Repeat будет учтен в результатах для каждого варианта):

public class Program
{
  static void Main()
  {
    BenchmarkRunner.Run<Program>();
  }

  ClassWithProps _classWithProps;
  СlassWithFields _classWithFields;
  [GlobalSetup]
  public void GlobalSetup()
  {
    _classWithProps = new ClassWithProps();
    _classWithFields = new СlassWithFields();
  }

  [Benchmark]
  public bool AccessClassWithFields()
  {
    foreach(var n in Enumerable.Repeat(1, 100000))
    {
      _classWithFields.A = 1;
      int x = _classWithFields.A;
    }
    return true;
  }

  [Benchmark]
  public bool AccessClassWithProps()
  {
    foreach(var n in Enumerable.Repeat(1, 100000))
    {
      _classWithProps.A = 1;
      int x = _classWithProps.A;
    }
    return false;
  }

  public class ClassWithProps
  {
    public int A { get; set; }
  }

  public class СlassWithFields
  {
    public int A;
  }
}

(я проверил, оптимизатор не выкинул ничего существенного). Разница в результатах получается ещё меньше: значение Mean у AccessClassWithProps равно 400.7, у AccessClassWithFields равно 345.2.

→ Ссылка