HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Теги: c#, производительность, unity, быстродействие, оптимизация кода, wpf, windows forms, dot.net framework, core</p>
1 <p>Теги: c#, производительность, unity, быстродействие, оптимизация кода, wpf, windows forms, dot.net framework, core</p>
2 <p>Статья посвящена изучению быстродействия часто используемых функций стандартных классов языка C# в разных окружениях, таких как WPF, Windows forms, Unity и ASP.NET. Реализован асинхронный механизм инструментальной оценки быстродействия участков кода. Рассмотрены несколько версий фреймворка, включая Mono, Core и традиционный .NET Framework, чтобы выявить разницу в скорости выполнения тех или иных функциональных возможностей платформ.</p>
2 <p>Статья посвящена изучению быстродействия часто используемых функций стандартных классов языка C# в разных окружениях, таких как WPF, Windows forms, Unity и ASP.NET. Реализован асинхронный механизм инструментальной оценки быстродействия участков кода. Рассмотрены несколько версий фреймворка, включая Mono, Core и традиционный .NET Framework, чтобы выявить разницу в скорости выполнения тех или иных функциональных возможностей платформ.</p>
3 <h2>Введение</h2>
3 <h2>Введение</h2>
4 <p>Оптимизации быстродействия ПО многими разработчиками в настоящее время уделяется недостаточно внимания. Это хорошо заметно по медленной работе некоторых современных приложений и их большим размерам. В качестве примера можно привести несколько самых распространенных по назначению приложений, реализующих такие функции как набор текстов, просмотр страниц в сети Интернет, голосовая связь (рис. 1), с которым справлялись и машины конца прошлого века, обладавшие в десятки и сотни раз меньшими мощностями, чем нынешние [1].</p>
4 <p>Оптимизации быстродействия ПО многими разработчиками в настоящее время уделяется недостаточно внимания. Это хорошо заметно по медленной работе некоторых современных приложений и их большим размерам. В качестве примера можно привести несколько самых распространенных по назначению приложений, реализующих такие функции как набор текстов, просмотр страниц в сети Интернет, голосовая связь (рис. 1), с которым справлялись и машины конца прошлого века, обладавшие в десятки и сотни раз меньшими мощностями, чем нынешние [1].</p>
5 <p><em>Рисунок 1. Пример объёмов памяти, занимаемых современным ПО:</em></p>
5 <p><em>Рисунок 1. Пример объёмов памяти, занимаемых современным ПО:</em></p>
6 <p>Проблема кроется, зачастую, в использовании излишне тяжелых библиотек и компонентов, которые тяжелы из-за того, что сами используют другие библиотеки и это дерево уходит своими корнями глубоко в историю. Не меньшее влияние оказывает и неправильное применение структур данных, не учитывающие сложности алгоритмов при обработке множеств элементов. Элементарные операции и типовые функции из стандартных библиотек, на первый взгляд, влияют на быстродействие в меньшей степени, однако они, ввиду частого использования тоже, порой, вносят своё воздействие в изменение быстродействия. Особенно это становится заметно при обработке больших объемов данных.</p>
6 <p>Проблема кроется, зачастую, в использовании излишне тяжелых библиотек и компонентов, которые тяжелы из-за того, что сами используют другие библиотеки и это дерево уходит своими корнями глубоко в историю. Не меньшее влияние оказывает и неправильное применение структур данных, не учитывающие сложности алгоритмов при обработке множеств элементов. Элементарные операции и типовые функции из стандартных библиотек, на первый взгляд, влияют на быстродействие в меньшей степени, однако они, ввиду частого использования тоже, порой, вносят своё воздействие в изменение быстродействия. Особенно это становится заметно при обработке больших объемов данных.</p>
7 <p>Данная статья посвящена изучению быстродействия элементарных операций и часто используемых функций языка C# в разных окружениях, таких как WPF, Windows forms, Unity. Мы рассмотрим несколько версий фреймворка и видов проекта, чтобы увидеть, есть ли разница в скорости выполнения того или иного функционала.</p>
7 <p>Данная статья посвящена изучению быстродействия элементарных операций и часто используемых функций языка C# в разных окружениях, таких как WPF, Windows forms, Unity. Мы рассмотрим несколько версий фреймворка и видов проекта, чтобы увидеть, есть ли разница в скорости выполнения того или иного функционала.</p>
8 <p>Для изучения напишем небольшой тестовый класс, разместим его в переносимой библиотеке и будем ее подключать в разные среды выполнения. Компактная переносимая библиотека классов, подключаемая к разным средам выполнения с исходным кодом размещена в открытом доступе на сервисе Bitbucket [2].</p>
8 <p>Для изучения напишем небольшой тестовый класс, разместим его в переносимой библиотеке и будем ее подключать в разные среды выполнения. Компактная переносимая библиотека классов, подключаемая к разным средам выполнения с исходным кодом размещена в открытом доступе на сервисе Bitbucket [2].</p>
9 <h2>Методология и реализация тестового окружения</h2>
9 <h2>Методология и реализация тестового окружения</h2>
10 <p>Основной функционал класса, реализованного для изменения быстродействия, следующий:</p>
10 <p>Основной функционал класса, реализованного для изменения быстродействия, следующий:</p>
11 <ul><li>поток, постоянно выполняющий изучаемую функцию из её делегата private Action TestAction, что позволяет частично обойти оптимизацию повторяющихся операций механизмами .Net Framework-а;</li>
11 <ul><li>поток, постоянно выполняющий изучаемую функцию из её делегата private Action TestAction, что позволяет частично обойти оптимизацию повторяющихся операций механизмами .Net Framework-а;</li>
12 <li>функция замера, принимающая делегат - замеряет количество выполнений этого делегата потоком в течение 1 миллисекунды;</li>
12 <li>функция замера, принимающая делегат - замеряет количество выполнений этого делегата потоком в течение 1 миллисекунды;</li>
13 <li>функция подсчета - накапливает результаты одинаковых замеров в словаре для последующего устранения пиковых результатов, усреднения, вычисления медианного значения;</li>
13 <li>функция подсчета - накапливает результаты одинаковых замеров в словаре для последующего устранения пиковых результатов, усреднения, вычисления медианного значения;</li>
14 <li>механизм минимизации вызова сборщика мусора и замера частоты его во время тестов.</li>
14 <li>механизм минимизации вызова сборщика мусора и замера частоты его во время тестов.</li>
15 </ul><p>Класс тестировщика производительности представлен ниже:</p>
15 </ul><p>Класс тестировщика производительности представлен ниже:</p>
16 public class TimeTestAsync { public int TimeMilliseconds = 1; private Action TestAction = () =&gt; { }; private Thread Thread; public int Count = 0; public readonly Dictionary&lt;string, List&lt;TimeResult&gt;&gt; Results = new Dictionary&lt;string, List&lt;TimeResult&gt;&gt;(); private void Init() { if (Thread != null) return; Thread = new Thread(TestFunk) { IsBackground = true }; Thread.Start(); } ~TimeTestAsync() =&gt; Stop(); public void Zamer(string info, Action action) { Init(); if (!Results.ContainsKey(info)) Results.Add(info, new List&lt;TimeResult&gt;()); var z = Zamer(action); Results[info].Add(z); } private DateTime _start; private TimeResult Zamer(Action action) { var gc = GC.CollectionCount(0); TestAction = action; Count = 0; _start = DateTime.UtcNow; Thread.Sleep(TimeMilliseconds); var end = DateTime.UtcNow.Subtract(_start); return new TimeResult() { Count = Count, Time = end, GC = GC.CollectionCount(0) - gc }; } private void TestFunk() { while (true) { TestAction.Invoke(); ++Count; } } public void Stop() { Thread?.Abort(); Thread = null; } }<p><strong>Результат замера</strong>выглядит следующим образом:</p>
16 public class TimeTestAsync { public int TimeMilliseconds = 1; private Action TestAction = () =&gt; { }; private Thread Thread; public int Count = 0; public readonly Dictionary&lt;string, List&lt;TimeResult&gt;&gt; Results = new Dictionary&lt;string, List&lt;TimeResult&gt;&gt;(); private void Init() { if (Thread != null) return; Thread = new Thread(TestFunk) { IsBackground = true }; Thread.Start(); } ~TimeTestAsync() =&gt; Stop(); public void Zamer(string info, Action action) { Init(); if (!Results.ContainsKey(info)) Results.Add(info, new List&lt;TimeResult&gt;()); var z = Zamer(action); Results[info].Add(z); } private DateTime _start; private TimeResult Zamer(Action action) { var gc = GC.CollectionCount(0); TestAction = action; Count = 0; _start = DateTime.UtcNow; Thread.Sleep(TimeMilliseconds); var end = DateTime.UtcNow.Subtract(_start); return new TimeResult() { Count = Count, Time = end, GC = GC.CollectionCount(0) - gc }; } private void TestFunk() { while (true) { TestAction.Invoke(); ++Count; } } public void Stop() { Thread?.Abort(); Thread = null; } }<p><strong>Результат замера</strong>выглядит следующим образом:</p>
17 public class TimeResult { public int Count; public TimeSpan Time; public int GC; public double Nanoseconds() { return Time.TotalMilliseconds / Count * 1000000; } public override string ToString() { return "\t" + Nanoseconds() + "\t" + Count; } }<p>По времени замера и количеству операций он определяет время выполнения тестируемой функции в наносекундах.</p>
17 public class TimeResult { public int Count; public TimeSpan Time; public int GC; public double Nanoseconds() { return Time.TotalMilliseconds / Count * 1000000; } public override string ToString() { return "\t" + Nanoseconds() + "\t" + Count; } }<p>По времени замера и количеству операций он определяет время выполнения тестируемой функции в наносекундах.</p>
18 <p>И, собственно, применение этого класса замеров возможно, например, в таком виде:</p>
18 <p>И, собственно, применение этого класса замеров возможно, например, в таком виде:</p>
19 public class Tests { readonly TimeTestAsync Tester = new TimeTestAsync(); private const string Br = "\t"; private float ClassProperty { get; set; } static float StaticProperty { get; set; } static float StaticField = 0; float ClassField = 0; float ClassField2 = 0; string ClassStr = ""; bool ClassBool = false; private const int Min = 50, Max = 100; … public string Test() { var localRandom = new Random(); for (int i = 0; i &lt;= 10; i++) { ClassStr = ""; ClassStringBuilder = new StringBuilder(); ClassField = localRandom.Next(Min, Max); … GC.Collect(); Tester.Zamer("() =&gt; { ClassField++; }", () =&gt; { ClassField++; }); Tester.Zamer("() =&gt; ClassStr = \"S1\"", () =&gt; ClassStr = "S1"); Tester.Zamer("() =&gt; ClassStr = \"S1\" + ++ClassField", () =&gt; ClassStr = "S1" + ++ClassField); GC.Collect(); … if (i == 0) Tester.Results.Clear(); StaticField += localField; } Tester.Stop(); var tempCount = ClassField + StaticField; string s = $"Функция{Br}" + $"Среднее время на выполнение, нс{Br}" + $"Медианное время на выполнение, нс{Br}" + $"Среднеквадратичное отклонение{Br}" + $"Среднее к-во запусков за тест, раз{Br}" + $"Среднее Время теста, мс{Br}" + $"К-во тестов, раз{Br}" + $"Среднее к-во вызовов сборки мусора на тест, раз\n"; foreach (var r in Tester.Results) { var withResults = r.Value.Where(x =&gt; x.Count &gt; 0).ToArray(); var nano = withResults.Select(x =&gt; x.Nanoseconds()).ToList(); s += $"{r.Key}{Br}" + $"{nano.Average(x =&gt; x):F2}{Br}" + $"{nano.OrderBy(x =&gt; x).ToArray()[nano.Count / 2]:F2}{Br}" + $"{StandardDeviation(nano):F2}{Br}" + $"{withResults.Average(x =&gt; (double)x.Count):F0}{Br}" + $"{withResults.Average(x =&gt; x.Time.TotalMilliseconds):F2}{Br}" + $"{withResults.Length}{Br}" + $"{withResults.Average(x =&gt; (double)x.GC):F2}\n"; } s += "\n\n" + tempCount;// + "\n\n" + Tester.GcCount return s; }<p>Простейший интерфейс позволяет скопировать результаты в Excel и там их обработать.</p>
19 public class Tests { readonly TimeTestAsync Tester = new TimeTestAsync(); private const string Br = "\t"; private float ClassProperty { get; set; } static float StaticProperty { get; set; } static float StaticField = 0; float ClassField = 0; float ClassField2 = 0; string ClassStr = ""; bool ClassBool = false; private const int Min = 50, Max = 100; … public string Test() { var localRandom = new Random(); for (int i = 0; i &lt;= 10; i++) { ClassStr = ""; ClassStringBuilder = new StringBuilder(); ClassField = localRandom.Next(Min, Max); … GC.Collect(); Tester.Zamer("() =&gt; { ClassField++; }", () =&gt; { ClassField++; }); Tester.Zamer("() =&gt; ClassStr = \"S1\"", () =&gt; ClassStr = "S1"); Tester.Zamer("() =&gt; ClassStr = \"S1\" + ++ClassField", () =&gt; ClassStr = "S1" + ++ClassField); GC.Collect(); … if (i == 0) Tester.Results.Clear(); StaticField += localField; } Tester.Stop(); var tempCount = ClassField + StaticField; string s = $"Функция{Br}" + $"Среднее время на выполнение, нс{Br}" + $"Медианное время на выполнение, нс{Br}" + $"Среднеквадратичное отклонение{Br}" + $"Среднее к-во запусков за тест, раз{Br}" + $"Среднее Время теста, мс{Br}" + $"К-во тестов, раз{Br}" + $"Среднее к-во вызовов сборки мусора на тест, раз\n"; foreach (var r in Tester.Results) { var withResults = r.Value.Where(x =&gt; x.Count &gt; 0).ToArray(); var nano = withResults.Select(x =&gt; x.Nanoseconds()).ToList(); s += $"{r.Key}{Br}" + $"{nano.Average(x =&gt; x):F2}{Br}" + $"{nano.OrderBy(x =&gt; x).ToArray()[nano.Count / 2]:F2}{Br}" + $"{StandardDeviation(nano):F2}{Br}" + $"{withResults.Average(x =&gt; (double)x.Count):F0}{Br}" + $"{withResults.Average(x =&gt; x.Time.TotalMilliseconds):F2}{Br}" + $"{withResults.Length}{Br}" + $"{withResults.Average(x =&gt; (double)x.GC):F2}\n"; } s += "\n\n" + tempCount;// + "\n\n" + Tester.GcCount return s; }<p>Простейший интерфейс позволяет скопировать результаты в Excel и там их обработать.</p>
20 <p><em>Рисунок 2. Результат измерения в простом окне WPF для последующего копирования в Excel</em>:</p>
20 <p><em>Рисунок 2. Результат измерения в простом окне WPF для последующего копирования в Excel</em>:</p>
21 <p>Относительные результаты измерения быстродействия по рассмотренным группам операций оценивались относительно WPF по специальной формуле.</p>
21 <p>Относительные результаты измерения быстродействия по рассмотренным группам операций оценивались относительно WPF по специальной формуле.</p>
22 <p><em>Ознакомиться с подробными результатами измерений в табличном виде вы сможете<strong>во второй части статьи</strong>.</em></p>
22 <p><em>Ознакомиться с подробными результатами измерений в табличном виде вы сможете<strong>во второй части статьи</strong>.</em></p>
23 <p>Список литературы: 1. Моё разочарование в софте [Электронный ресурс] // Хабр.<a>Прочитать</a>2. Публичный репозиторий с кодом проекта [Электронный ресурс].<a>Прочитать</a>3. Четверина О. А. Повышение производительности кода при однофазной компиляции // Программирование. 2016. № 1. С. 51-59.</p>
23 <p>Список литературы: 1. Моё разочарование в софте [Электронный ресурс] // Хабр.<a>Прочитать</a>2. Публичный репозиторий с кодом проекта [Электронный ресурс].<a>Прочитать</a>3. Четверина О. А. Повышение производительности кода при однофазной компиляции // Программирование. 2016. № 1. С. 51-59.</p>
24  
24