Насколько увеличивается расход вычислительных ресурсов при обкладывании полей свойствами с 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 шт):
Вот как раз здесь проще сделать бенчмарк, чем погружаться в споры об идеологической правильности. 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 метода-геттера или сеттера, что в любом случае дороже прямого обращения к полю.
А дальше обычно действуют по обстоятельствам.
Использование интерфейсов, классов, свойств, функций, паттернов, всяких итераторов, DI контейнеров всегда добавляет какой то оверхед. Большинство подобных вещей существуют не для производительности, а для для других вещей, таких как сложность, скорость разработки, поддержки, модульность, гибкость и прочие вещи. Другими словами, для удобства разработки ПО, а не его скорости работы. Почему это важно? Потому что как только начинаются ужесточения требований к производительности, почти сразу усложняются все остальные вещи, увеличивается время разработки, требуются соотвествующе кадры для подобного. Другими словами, чем жестче требования к производительности, тем дороже выходит разработка такой системы.
Потому, всесто того, чтобы тратить время и деньги на оптимизацию, производимое ПО держат на уровне "достаточном" для того, чтобы им пользоваться, а время и деньги тратятся на развитие, новые фичи и так далее. В итоге ПО "достаточно" быстрое для использования и с необходимым набором фишек - выигрывают все.
Поэтому в общем случае, если у вас выбор между микрооптимизациями и простотой кода/поддержки, по умолчанию выбирайте простоту. Я бы не советовал никому менять свой стиль программирования или отступать от общепринятого без наличия веской приччины, ведь то, что вы нагородите, надо будет годами поддерживать и вероятно не вам.
Подобные оптимизации типа использования полей вместо свойств, могут быть полезны только в самых критически важных для производительности местах, когда структурные и алгоритмические приемы исчерпаны. Я знаю команду, которая по причине производительности, практически не использует классы как таковые и очень редко когда может использовать структуры данных, отличные от массивов - все это по причине критической важности производительности, но это не касается 99.9999% проектов, поверье, если вы попадете в проект с критическими требованиями по производительсности, дилемма поля vs свойства будет наименьшей вашей головной болью.
Что же касается крупных коммерческих приложений, я работал над такими и имел множество полей, и я вам скажу, что нет никакой абсолютно разницы конечному юзеру, откроется у вас форма на 10-100-1000 миллисекунд раньше или нет, зато код у вас будет более поддерживаемым без лишних выкрутасов и бесполезных оптимизаций.
Если вам действительно интересно узнать способы ускорения приложений, то я бы советовал сначала обратить внимание на тему алгоритмов, на всякие возможности тестирования производительности, поиска "бутылочного горлышка", так как абсолютное большинство тормозов происходят из за работы с вводом/выводом (сеть, БД, файлы) или линейным поиском в списке вместо константного в хештаблице/хешсете (я сбился со счета сколько раз я подобное правил).
Если вам интересен мой опыт оптимизации программ, в этом году я рассказывал о нем в интервью на хабре. (не сочтите за рекламу, мне оттуда уже никакого профита)
Согласен, в самом первом бенчмарке много лишнего шума - создание экземпляра класса, сложение. Его можно сильно упростить, и выполнить энное количество раз (проход по энумератору 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.