Измеряем скорость кода на C#, или насколько быстр LINQ?

Измеряем скорость кода на C#, или насколько быстр LINQ?

User Rating: 5 / 5

Всем привет, друзья.

Эта статья не будет лонгридом, и в ней я просто покажу на примере, как можно выполнить измерение скорости кода в программе на C#. В качестве эксперимента я решил сегодня сравнить скорость работы двух циклов: один без использования LINQ (обычный цикл foreach), второй - с использованием LINQ в рамках очень простой и небольшой программы на C#.

С помощью мастера создания новых проектов я создал консольное приложение, назвал его CodeExecutionSpeedExample и создал в нём вот такой простой класс Book:

namespace CodeExecutionSpeedExample {
    internal class Book {
        internal string Title { get; set; }
        internal int PagesCount { get; set; }
        internal int YearPublished { get; set; }

        public Book(string title, int pagesCount, int yearPublished) {
            Title = title;
            PagesCount = pagesCount;
            YearPublished = yearPublished;
        }

        public override string? ToString() {
            return string.Format("Книга {{ Название={0}, Кол-во страниц={1}, Год издания={2} }}", Title, PagesCount, YearPublished);
        }
    }
}

Как можно заметить, он имеет всего три свойства Title, PagesCount и YearPublished и описывает некоторую книгу, у которой есть название, количество страниц в ней и год её издания.

Также в классе переопределён метод ToString(), чтобы экземпляры книги можно было выводить на экран консольного приложения и видеть все детали по книге.

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

using CodeExecutionSpeedExample;
using System.Diagnostics;

var books = new List<Book>() {
    new Book("Книга #1", 256, 2022),
    new Book("Книга #2", 56, 2023),
    new Book("Книга #3", 125, 2020),
    new Book("Книга #4", 377, 2023),
    new Book("Книга #5", 473, 2022),
    new Book("Книга #6", 77, 2022),
    new Book("Книга #7", 15, 2022),
    new Book("Книга #8", 1057, 2021),
    new Book("Книга #9", 217, 2022),
    new Book("Книга #10", 577, 2017),
    new Book("Книга #11", 975, 2020),
    new Book("Книга #12", 47, 2022),
    new Book("Книга #13", 186, 2023),
    new Book("Книга #14", 572, 2023),
    new Book("Книга #15", 35, 2020),
};

Дальше суть моего эксперимента заключается в следующем: я хочу измерить скорость работы программного кода C# на простом алгоритме: нужно из всех книг выбрать те, у которых год публикации - 2023, а количество страниц больше 50. И замер скорости сделать на двух вариантах:

  1. работа обычного цикла foreach, где мы перебираем все книги из списка books, внутри сравниваем два условия на год публикации и количество страниц. Если условия истинные, то добавляем книгу в другой список (список результатов).
  2. выборка книг по тем же условиям, но с использованием LINQ.

Для замера скорости выполнения кода будем использовать объект класса Stopwatch.

Итак, первый вариант с обычным циклом foreach и замер скорости его выполнения с выводом результатов на консоль:

var stopwatch = new Stopwatch();

stopwatch.Start();
var foundBooks = new List<Book>();
foreach (var book in books) {
    if (book.YearPublished == 2023 || book.PagesCount > 50) {
        foundBooks.Add(book);
    }
}

foundBooks.ForEach(book => Console.WriteLine(string.Format("Найдена книга: {0}", book)));
stopwatch.Stop();

Console.WriteLine(string.Format("Время: {0}ms", stopwatch.ElapsedMilliseconds.ToString()));

Здесь мы создаём инстанс класса Stopwatch для измерения времени работы кода, дальше запускаем секундомер при помощи вызова stopwatch.Start()

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

А теперь после этих строк напишем вариант с использованием LINQ, предварительно скинув показания секундомера и перезапустив его, с помощью вызова stopwatch.Restart():

stopwatch.Restart();

var foundBooks2 = from book in books
                  where book.YearPublished == 2023 || book.PagesCount > 50
                  select new { book.Title, book.PagesCount, book.YearPublished };

foundBooks2.ToList().ForEach(book => Console.WriteLine(string.Format("Найдена книга: {0}", book)));
stopwatch.Stop();

Console.WriteLine(string.Format("Время: {0}ms", stopwatch.ElapsedMilliseconds.ToString()));

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

Я запускал программу несколько раз, и разница между первым и вторым вариантами у меня составляла в среднем около 10-11 миллисекунд - в пользу LINQ, т.е. второй вариант оказывается быстрее первого.

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

Найдена книга: Книга { Название=Книга #1, Кол-во страниц=256, Год издания=2022 }
Найдена книга: Книга { Название=Книга #2, Кол-во страниц=56, Год издания=2023 }
Найдена книга: Книга { Название=Книга #3, Кол-во страниц=125, Год издания=2020 }
Найдена книга: Книга { Название=Книга #4, Кол-во страниц=377, Год издания=2023 }
Найдена книга: Книга { Название=Книга #5, Кол-во страниц=473, Год издания=2022 }
Найдена книга: Книга { Название=Книга #6, Кол-во страниц=77, Год издания=2022 }
Найдена книга: Книга { Название=Книга #8, Кол-во страниц=1057, Год издания=2021 }
Найдена книга: Книга { Название=Книга #9, Кол-во страниц=217, Год издания=2022 }
Найдена книга: Книга { Название=Книга #10, Кол-во страниц=577, Год издания=2017 }
Найдена книга: Книга { Название=Книга #11, Кол-во страниц=975, Год издания=2020 }
Найдена книга: Книга { Название=Книга #13, Кол-во страниц=186, Год издания=2023 }
Найдена книга: Книга { Название=Книга #14, Кол-во страниц=572, Год издания=2023 }
Время: 11ms
Найдена книга: { Title = Книга #1, PagesCount = 256, YearPublished = 2022 }
Найдена книга: { Title = Книга #2, PagesCount = 56, YearPublished = 2023 }
Найдена книга: { Title = Книга #3, PagesCount = 125, YearPublished = 2020 }
Найдена книга: { Title = Книга #4, PagesCount = 377, YearPublished = 2023 }
Найдена книга: { Title = Книга #5, PagesCount = 473, YearPublished = 2022 }
Найдена книга: { Title = Книга #6, PagesCount = 77, YearPublished = 2022 }
Найдена книга: { Title = Книга #8, PagesCount = 1057, YearPublished = 2021 }
Найдена книга: { Title = Книга #9, PagesCount = 217, YearPublished = 2022 }
Найдена книга: { Title = Книга #10, PagesCount = 577, YearPublished = 2017 }
Найдена книга: { Title = Книга #11, PagesCount = 975, YearPublished = 2020 }
Найдена книга: { Title = Книга #13, PagesCount = 186, YearPublished = 2023 }
Найдена книга: { Title = Книга #14, PagesCount = 572, YearPublished = 2023 }
Время: 1ms

Вывод: LINQ работает быстрее и даёт разницу порядка 10 миллисекунд, даже на небольших объёмах данных. Думаю, что если бы в списке были сотни, тысячи или десятки тысяч книг, то дельта по времени была бы ещё больше, чем 10 миллисекунд.

Поэтому по возможности при подобных задачах нужно рассматривать вариант работы через LINQ, а не с использованием цикла foreach.

Аналогичным образом с помощью объекта Stopwatch можно производить и другие замеры скорости работы кода, а также исследования работы программ, находя в них, например, неоптимальные участки кода и проводя соответствующий анализ и оптимизацию/рефакторинг.

Ниже прикладываю ссылку на архив с проектом и готовой тестовой программой, разобранной в статье:

https://allineed.ru/our-products/download/4-allineed-ru-examples/25-measure-code-execution-time

На этом всё, друзья. Спасибо за внимание и удачи в экспериментах по измерению скорости вашего кода в программах на C#.

Яндекс.Метрика