0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Продолжаем разговор об анализе быстродействия типовых операций языка C# на платформах DOT.NET и Mono. В<a>первой части статьи</a>мы подробно поговорили о методологии и реализации тестового окружения. Сейчас пришло время приступать к измерениям.</p>
1
<p>Продолжаем разговор об анализе быстродействия типовых операций языка C# на платформах DOT.NET и Mono. В<a>первой части статьи</a>мы подробно поговорили о методологии и реализации тестового окружения. Сейчас пришло время приступать к измерениям.</p>
2
<p>Измерение проводилось на ноутбуке ASUS X556UQ: i7-7500U, 2.7 GHz, 20Г ОЗУ, Windows 10 x64.</p>
2
<p>Измерение проводилось на ноутбуке ASUS X556UQ: i7-7500U, 2.7 GHz, 20Г ОЗУ, Windows 10 x64.</p>
3
<p>Для оценки быстродействия реализованного тестового окружения были выбраны такие операции, как обращение к функциям, полям и свойствам класса (табл. 1 и 2). Как можно заметить, быстродействие тестовой инфраструктуры примерно сопоставимо с обычным обращением к функции или переменной. Кроме того, несмотря на предпринятые меры по усложнению оптимизации вычислений, в простых операциях заметен разгон при выполнении нескольких однотипных замеров подряд. В дальнейшем было принято решение рассматривать и сопоставлять результаты по медианному времени, поскольку оно более стабильно, чем среднее, т. к. не подвержено влиянию исключительных случаев. Хотя, как ни странно, из-за особенностей разброса результатов (и сдвига в большую сторону при чётном количестве тестов) в некоторых замерах медианный результат получен больше среднего.</p>
3
<p>Для оценки быстродействия реализованного тестового окружения были выбраны такие операции, как обращение к функциям, полям и свойствам класса (табл. 1 и 2). Как можно заметить, быстродействие тестовой инфраструктуры примерно сопоставимо с обычным обращением к функции или переменной. Кроме того, несмотря на предпринятые меры по усложнению оптимизации вычислений, в простых операциях заметен разгон при выполнении нескольких однотипных замеров подряд. В дальнейшем было принято решение рассматривать и сопоставлять результаты по медианному времени, поскольку оно более стабильно, чем среднее, т. к. не подвержено влиянию исключительных случаев. Хотя, как ни странно, из-за особенностей разброса результатов (и сдвига в большую сторону при чётном количестве тестов) в некоторых замерах медианный результат получен больше среднего.</p>
4
<p><em>Таблица 1. Результаты тестовых замеров на примере проекта WPF в Release-режиме:</em></p>
4
<p><em>Таблица 1. Результаты тестовых замеров на примере проекта WPF в Release-режиме:</em></p>
5
<p>Далее приведем результаты замеров в разных режимах выполнения (табл. 2), поскольку на второй фазе компиляции при создании релиз-приложения используются дополнительные механизмы оптимизации [3].</p>
5
<p>Далее приведем результаты замеров в разных режимах выполнения (табл. 2), поскольку на второй фазе компиляции при создании релиз-приложения используются дополнительные механизмы оптимизации [3].</p>
6
<p><em>Таблица 2. Замеры в WPF в разных режимах выполнения:</em></p>
6
<p><em>Таблица 2. Замеры в WPF в разных режимах выполнения:</em></p>
7
<p>Как можно заметить, элементарные операции довольно существенно оптимизируются при переводе проекта в релиз. Устраняются лишние сложности вызова свойств, и они начинают работать так же быстро, как обычные поля, удаляются вызовы пустых функций. Расхождения в замерах быстродействия становятся обусловлены в большей степени случайными флуктуациями. При этом оптимизатор настолько хорош, что можно заметить постепенное ускорение быстродействия для похожих операций, несмотря на их вызов через делегаты.</p>
7
<p>Как можно заметить, элементарные операции довольно существенно оптимизируются при переводе проекта в релиз. Устраняются лишние сложности вызова свойств, и они начинают работать так же быстро, как обычные поля, удаляются вызовы пустых функций. Расхождения в замерах быстродействия становятся обусловлены в большей степени случайными флуктуациями. При этом оптимизатор настолько хорош, что можно заметить постепенное ускорение быстродействия для похожих операций, несмотря на их вызов через делегаты.</p>
8
<p>Далее для сопоставления фреймворков будем рассматривать наиболее актуальный режим запуска -<strong>Release</strong>.</p>
8
<p>Далее для сопоставления фреймворков будем рассматривать наиболее актуальный режим запуска -<strong>Release</strong>.</p>
9
<p>На рисунке 3 приведены результаты замеров выполнения указанных функций в релиз-проектах на WPF, Windows Forms и Unity. Как можно заметить, WPF и Windows Forms показывают примерно одинаковые результаты (в среднем по рассмотренным операциям формы медленнее на 6 %) ввиду того, что обе платформы реализованы на классическом .Net (при этом в дебаг-режиме разница между ними более существенная). В то же время, на Unity некоторые операции производятся с существенной разницей в скорости ввиду того, что основаны на MONO-Framwork-e (Unity, в среднем, медленнее на 220 %). В .Net Core-реализации заметна не менее ощутимая разница в быстродействии, как в большую, так и в меньшую сторону по разным операциям (в среднем по рассмотренным функциям на 77 % медленнее). Однако выборка функций не является достаточно презентабельной и не даёт полномочий судить о производительности того или иного фреймворка в целом.</p>
9
<p>На рисунке 3 приведены результаты замеров выполнения указанных функций в релиз-проектах на WPF, Windows Forms и Unity. Как можно заметить, WPF и Windows Forms показывают примерно одинаковые результаты (в среднем по рассмотренным операциям формы медленнее на 6 %) ввиду того, что обе платформы реализованы на классическом .Net (при этом в дебаг-режиме разница между ними более существенная). В то же время, на Unity некоторые операции производятся с существенной разницей в скорости ввиду того, что основаны на MONO-Framwork-e (Unity, в среднем, медленнее на 220 %). В .Net Core-реализации заметна не менее ощутимая разница в быстродействии, как в большую, так и в меньшую сторону по разным операциям (в среднем по рассмотренным функциям на 77 % медленнее). Однако выборка функций не является достаточно презентабельной и не даёт полномочий судить о производительности того или иного фреймворка в целом.</p>
10
<p><em>Рисунок 3. Быстродействие обращения к методам, полям и свойствам в различных окружениях:</em></p>
10
<p><em>Рисунок 3. Быстродействие обращения к методам, полям и свойствам в различных окружениях:</em></p>
11
<p>Здесь заметны некоторые сложности с обращением к свойствам в Unity, а также заметен более медленный расчет остатка от деления в Unity и .Net Core - примерно в 10 раз медленнее, чем в обычном .Net, при том, что остальные операции выполняются примерно с той же скоростью. В .Net Core тестовое окружение (вызов делегата) выполнялось медленнее, учитывая это, можно отметить, что остальные обращения и вычисления в нём производились с той же скоростью, что и в обычном .Net.</p>
11
<p>Здесь заметны некоторые сложности с обращением к свойствам в Unity, а также заметен более медленный расчет остатка от деления в Unity и .Net Core - примерно в 10 раз медленнее, чем в обычном .Net, при том, что остальные операции выполняются примерно с той же скоростью. В .Net Core тестовое окружение (вызов делегата) выполнялось медленнее, учитывая это, можно отметить, что остальные обращения и вычисления в нём производились с той же скоростью, что и в обычном .Net.</p>
12
<h2>Быстродействие математических операций</h2>
12
<h2>Быстродействие математических операций</h2>
13
<p>Расссмотрим несколько наиболее популярных математических операций стандартного .Net класса Math (табл. 3 и рис. 4).</p>
13
<p>Расссмотрим несколько наиболее популярных математических операций стандартного .Net класса Math (табл. 3 и рис. 4).</p>
14
<p><em>Таблица 3. Замеры быстродействия математических операций в различных окружениях:</em></p>
14
<p><em>Таблица 3. Замеры быстродействия математических операций в различных окружениях:</em></p>
15
<p>Как можно заметить, различные математические операции выполняются в разных средах разработки с существенными отличиями в быстродействии. Например, Sin, Cos, корень, логарифм и получение модуля намного медленнее работают в Unity, чем в других версиях фреймворка, да и в среднем математика в Unity работает несколько медленнее. Хотя, например, арктангенс вычисляется быстрее.</p>
15
<p>Как можно заметить, различные математические операции выполняются в разных средах разработки с существенными отличиями в быстродействии. Например, Sin, Cos, корень, логарифм и получение модуля намного медленнее работают в Unity, чем в других версиях фреймворка, да и в среднем математика в Unity работает несколько медленнее. Хотя, например, арктангенс вычисляется быстрее.</p>
16
<p>Интересен тот факт, что возведение в степень с помощью функции Pow работает с одной скоростью для целых и дробных степеней и на пару порядков медленнее умножения (за исключением .Net Core, где оно приближается по скорости к простым арифметическим операциям).</p>
16
<p>Интересен тот факт, что возведение в степень с помощью функции Pow работает с одной скоростью для целых и дробных степеней и на пару порядков медленнее умножения (за исключением .Net Core, где оно приближается по скорости к простым арифметическим операциям).</p>
17
<p><em>Рисунок 4. Замеры быстродействия математических операций в различных окружениях:</em></p>
17
<p><em>Рисунок 4. Замеры быстродействия математических операций в различных окружениях:</em></p>
18
<h2>Быстродействие функций работы с коллекциями</h2>
18
<h2>Быстродействие функций работы с коллекциями</h2>
19
<p>Приведем результаты исследования работы с коллекциями коротко на примере массивов из 1 и 1000 элементов и таких часто используемых функций, как Contains - поиск элемента, FirstOrDefault - аналогичная функция в LINQ-расширениях, Count - подсчёт с помощью LINQ. ExistElement - случай для элемента, который присутствует в массиве, а NotExistElement - соответственно, для несуществующего элемента.</p>
19
<p>Приведем результаты исследования работы с коллекциями коротко на примере массивов из 1 и 1000 элементов и таких часто используемых функций, как Contains - поиск элемента, FirstOrDefault - аналогичная функция в LINQ-расширениях, Count - подсчёт с помощью LINQ. ExistElement - случай для элемента, который присутствует в массиве, а NotExistElement - соответственно, для несуществующего элемента.</p>
20
<p><em>Таблица 4. Замеры быстродействия работы с массивами в различных окружениях:</em></p>
20
<p><em>Таблица 4. Замеры быстродействия работы с массивами в различных окружениях:</em></p>
21
<p>Как можно заметить, различия в скорости выполнения присутствуют и увеличиваются с увеличением объёма массива. Практически во всех рассмотренных случаях Mono реализация Unity несколько уступает по скорости.</p>
21
<p>Как можно заметить, различия в скорости выполнения присутствуют и увеличиваются с увеличением объёма массива. Практически во всех рассмотренных случаях Mono реализация Unity несколько уступает по скорости.</p>
22
<p>Что интересно, проверка наличия элемента с помощью встроенной функции Contains проходит быстрее, чем с помощью LINQ-расширения примерно в 3-5 раз в классическом и Core-фреймворках и в 3 раза медленнее Юнити на 1000 элементах. А на 1 элементе LINQ в 1,5 раза быстрее в классическом фреймворке и быстрее в Юнити.</p>
22
<p>Что интересно, проверка наличия элемента с помощью встроенной функции Contains проходит быстрее, чем с помощью LINQ-расширения примерно в 3-5 раз в классическом и Core-фреймворках и в 3 раза медленнее Юнити на 1000 элементах. А на 1 элементе LINQ в 1,5 раза быстрее в классическом фреймворке и быстрее в Юнити.</p>
23
<p>В любом случае, быстродействие работы с коллекциями - это тема для отдельного большого исследования.</p>
23
<p>В любом случае, быстродействие работы с коллекциями - это тема для отдельного большого исследования.</p>
24
<h2>Быстродействие работы со строками</h2>
24
<h2>Быстродействие работы со строками</h2>
25
<p>На рисунке 5 приведены замеры быстродействия строковых операций. Здесь Str1 = "1", Str10 = "1234567890", Str100 = "1234567890123…90" - до длины в 100 символов.</p>
25
<p>На рисунке 5 приведены замеры быстродействия строковых операций. Здесь Str1 = "1", Str10 = "1234567890", Str100 = "1234567890123…90" - до длины в 100 символов.</p>
26
<p>Можно отметить, что операции сложения строк в целом довольно тяжеловесны, также, как и преобразования чисел в строки. Быстродействие методов String.Format уступает по скорости интерполяции строк примерно на 15 %, а интерполяция, в свою очередь, уступает конкатенации примерно на столько же. String.Join, естественно существенно опережает обе из перечисленных функций.</p>
26
<p>Можно отметить, что операции сложения строк в целом довольно тяжеловесны, также, как и преобразования чисел в строки. Быстродействие методов String.Format уступает по скорости интерполяции строк примерно на 15 %, а интерполяция, в свою очередь, уступает конкатенации примерно на столько же. String.Join, естественно существенно опережает обе из перечисленных функций.</p>
27
<p><em>Рисунок 5. Быстродействие работы со строками в различных окружениях:</em></p>
27
<p><em>Рисунок 5. Быстродействие работы со строками в различных окружениях:</em></p>
28
<h2>Выводы</h2>
28
<h2>Выводы</h2>
29
<p>При сравнении результатов быстродействия WPF и WindowsForms в релиз-режиме получено, что средняя разница быстродействия операций по разным группам составляет до 10 %, что может быть обусловлено погрешностями измерений. В целом же все операции выполняются примерно с одинаковой скоростью, что неудивительно, ввиду единого фреймворка. Это довольно очевидно и без исследования и приведено в большей степени для того, чтобы можно было со стороны оценить погрешность измерения.</p>
29
<p>При сравнении результатов быстродействия WPF и WindowsForms в релиз-режиме получено, что средняя разница быстродействия операций по разным группам составляет до 10 %, что может быть обусловлено погрешностями измерений. В целом же все операции выполняются примерно с одинаковой скоростью, что неудивительно, ввиду единого фреймворка. Это довольно очевидно и без исследования и приведено в большей степени для того, чтобы можно было со стороны оценить погрешность измерения.</p>
30
<p>Что касается сравнения с Юнити и .Net Core, то фреймворк уже отличается, ввиду чего и отличия более существенные.</p>
30
<p>Что касается сравнения с Юнити и .Net Core, то фреймворк уже отличается, ввиду чего и отличия более существенные.</p>
31
<p>Вызовы пустых функций и обращения к переменным в Юнити выполняются, в среднем, на 43 % медленнее (за счет обращения к свойствам), математические вычисления - на 25 % (за счет таких функций, как sin, cos, tan, sqrt, abs - медленнее в 10-20 раз, тогда как atan, random, pow выполнялись быстрее в 1,5-2 раза), работа с коллекциями - на 25 %, а работа со строками на 10 % медленнее. Первоначальные замеры показывали, что работа со строками в Юнити происходит медленнее, чем в WPF, на 98 % по формуле 1 (в 100 раз), но после минимизации вызовов сборщика мусора этот результат существенно улучшился. Тем не менее при относительно долгом функционировании, сборка мусора в любом случае внесёт свой вклад в быстродействие реальной программы.</p>
31
<p>Вызовы пустых функций и обращения к переменным в Юнити выполняются, в среднем, на 43 % медленнее (за счет обращения к свойствам), математические вычисления - на 25 % (за счет таких функций, как sin, cos, tan, sqrt, abs - медленнее в 10-20 раз, тогда как atan, random, pow выполнялись быстрее в 1,5-2 раза), работа с коллекциями - на 25 %, а работа со строками на 10 % медленнее. Первоначальные замеры показывали, что работа со строками в Юнити происходит медленнее, чем в WPF, на 98 % по формуле 1 (в 100 раз), но после минимизации вызовов сборщика мусора этот результат существенно улучшился. Тем не менее при относительно долгом функционировании, сборка мусора в любом случае внесёт свой вклад в быстродействие реальной программы.</p>
32
<p>В .Net Core базовые операции в среднем на 43 % медленнее, математические имеют довольно большой разброс и выполняются на 17 % быстрее (до 20 раз быстрее для atan, pow и до 4 раз медленнее для log), коллекции работают немного медленнее с малыми объемами и немного быстрее с большими - в среднем одинаково. Что же касается строк, то здесь среднее быстродействие то же, что и в WPF. Однако быстродействие выполнения отдельного функционала различается до 2-3 раз (наибольшие различия: IsNullOrWhiteSpace в WPF быстрее в 6 раз, а Contains в 3 раза медленнее).</p>
32
<p>В .Net Core базовые операции в среднем на 43 % медленнее, математические имеют довольно большой разброс и выполняются на 17 % быстрее (до 20 раз быстрее для atan, pow и до 4 раз медленнее для log), коллекции работают немного медленнее с малыми объемами и немного быстрее с большими - в среднем одинаково. Что же касается строк, то здесь среднее быстродействие то же, что и в WPF. Однако быстродействие выполнения отдельного функционала различается до 2-3 раз (наибольшие различия: IsNullOrWhiteSpace в WPF быстрее в 6 раз, а Contains в 3 раза медленнее).</p>
33
<p>Таким образом, можно заметить, что при близких результатах измерений по большинству рассмотренных операций даже в родственных средах разработки в отдельных случаях есть принципиальные различия быстродействия часто используемых операций, которые имеет смысл учитывать при написании ПО.</p>
33
<p>Таким образом, можно заметить, что при близких результатах измерений по большинству рассмотренных операций даже в родственных средах разработки в отдельных случаях есть принципиальные различия быстродействия часто используемых операций, которые имеет смысл учитывать при написании ПО.</p>
34
<p>Список литературы: 1. Моё разочарование в софте [Электронный ресурс] // Хабр.<a>Прочитать</a>2. Публичный репозиторий с кодом проекта [Электронный ресурс].<a>Прочитать</a>3. Четверина О. А. Повышение производительности кода при однофазной компиляции // Программирование. 2016. № 1. С. 51-59.</p>
34
<p>Список литературы: 1. Моё разочарование в софте [Электронный ресурс] // Хабр.<a>Прочитать</a>2. Публичный репозиторий с кодом проекта [Электронный ресурс].<a>Прочитать</a>3. Четверина О. А. Повышение производительности кода при однофазной компиляции // Программирование. 2016. № 1. С. 51-59.</p>
35
35