Календарный момент, длительность и измеренное прошедшее время описывают разные величины. DateTime задаёт положение на принятой временной шкале, TimeSpan — знаковый интервал между моментами либо продолжительность, а Stopwatch — результат наблюдения за выполнением программного участка. Момент можно сравнить с моментом, к моменту можно прибавить длительность, а разность двух моментов снова является длительностью.
Числовое представление времени всегда связано с единицей. Компоненты Days, Hours, Minutes и Seconds описывают части TimeSpan, свойства TotalDays, TotalHours и TotalSeconds — полную величину в выбранной единице, а Ticks — дискретное представление с фиксированной шкалой TimeSpan. Такты Stopwatch.ElapsedTicks принадлежат другой шкале и переводятся в секунды через Stopwatch.Frequency; равенство названия Ticks не означает равенства единиц.
Измерение производительности является экспериментом, а не единичным вызовом таймера. Измеряемая граница, конфигурация сборки, входные данные, прогрев, число повторов, порядок сравнения, состояние изменяемых входов и способ агрегации фиксируются заранее. Наблюдаемое время сопоставляется с точным числом операций и асимптотическим классом, но не заменяет их: шум, разрешение таймера, постоянные затраты и ограниченный диапазон размеров определяют границы допустимого вывода.
Опора — Раздел 21: время, длительность и измерение. Дополнительно используются классы роста и инварианты из Раздела 12, массивы, поиск и сортировка из Раздела 15, строки и StringBuilder из Раздела 19, структуры для календарных интервалов и результатов эксперимента из Раздела 20.
Каждая задача оформляется отдельной консольной программой полным шаблоном курса и компилируется с
<Nullable>enable</Nullable>. Разрешены только конструкции Разделов 1–21. Календарные значения одной задачи создаются с одинаковымDateTimeKindи интерпретируются в одной заранее принятой временной шкале; часовые пояса, переходы локального времени, календарные системы,DateOnlyиTimeOnlyне рассматриваются. Замеры выполняются в конфигурацииReleaseбез подключённого отладчика; версия.NET, операционная система и основные параметры среды записываются в отчёт.Random, многопоточность,Thread.Sleep, принудительный запуск сборщика мусора,BenchmarkDotNet, обработка исключений и внешние профилировщики запрещены. Подготовка входов, создание копий, вывод и проверка результата находятся вне измеряемого участка, если условие прямо не требует измерить полную стратегию. Каждое вычислительное ядро возвращает либо накапливает наблюдаемую контрольную сумму, а слишком дорогая серия прекращается по заранее установленному пределу.
База практических заданий
Уровень I. Базовый
Тип временного значения, календарная операция, границы интервала, измеряемый участок и последовательность действий таймера заданы условием. Требуется точно воспроизвести готовую модель, не смешать компоненты и полные величины, исключить перечисленную постороннюю работу и выполнить предписанную серию. Ученик не проектирует расписание или эксперимент: сложность возрастает от одиночных моментов и длительностей к массивам временных данных, жизненному циклу
Stopwatchи таблице измерений по размерам.
1. Создание, форматирование и сравнение моментов: Создать четыре значения DateTime через конструктор: начало и конец двух работ. Все значения имеют одинаковый DateTimeKind. Через операторы сравнения определить:
- хронологический порядок;
- совпадение границ;
- самый ранний момент;
- самый поздний момент.
Вывести значения в стабильном формате:
yyyy-MM-dd HH:mm:ss
через InvariantCulture. Отдельно вывести Ticks и Kind. Объяснить, почему календарный момент не является продолжительностью и почему форматированный текст не изменяет само значение.
2. Компоненты, полные величины и такты TimeSpan: Создать длительность:
2 дня 3 часа 40 минут 15 секунд 250 миллисекунд
через сложение результатов TimeSpan.FromDays, FromHours, FromMinutes, FromSeconds и FromMilliseconds. Вывести:
Days,Hours,Minutes,Seconds,Milliseconds;TotalDays,TotalHours,TotalMinutes,TotalSeconds;Ticks;- результат деления
Ticks / TimeSpan.TicksPerSecond.
Подтвердить восстановление длительности через TimeSpan.FromTicks. Объяснить различие между компонентом Hours и полной величиной TotalHours.
3. Арифметика момента и длительности: Даны начало 2026-07-10 22:35:00 и конец 2026-07-12 01:20:15. Получить:
TimeSpan duration = end - start;
Затем проверить:
start + duration == end
end - duration == start
Создать отрицательную длительность start - end, сравнить её с TimeSpan.Zero и вывести полное число секунд. Объяснить, почему знак разности зависит от порядка операндов.
4. Последовательность этапов: Работа начинается в заданный момент и состоит из трёх этапов: подготовка, вычисление и передача результата. Длительности создаются через TimeSpan.FromMinutes и TimeSpan.FromSeconds. Вычислить:
- начало и конец каждого этапа;
- общую длительность;
- окончание всей работы;
- восстановленное начало через вычитание общей длительности.
Подтвердить, что сумма трёх этапов равна разности общего конца и начала.
5. Полуоткрытые интервалы: Даны два непустых интервала [start, end) с start < end. По заданной системе ветвей различить:
- первый расположен строго раньше второго;
- второй расположен строго раньше первого;
- интервалы касаются границами;
- интервалы пересекаются на положительной длительности.
При пересечении вычислить:
Подтвердить intersectionStart < intersectionEnd. Объяснить, почему общий граничный момент не принадлежит обоим полуоткрытым интервалам одновременно.
6. Периодические контрольные точки: Даны начальный момент, положительный шаг TimeSpan и число точек count >= 0. Построить DateTime[]:
Текущий момент обновлять прибавлением шага. Предусловие гарантирует представимость всех результатов и произведения step.Ticks * (count - 1). Для непустого результата проверить последнюю точку независимой формулой. Инвариант: перед итерацией i текущий момент равен t_i, а префикс [0, i) уже заполнен правильными значениями.
7. Агрегирование длительностей: Дан непустой TimeSpan[] stages. Вычислить:
- сумму через
TimeSpan.Zero; - минимум;
- максимум;
- среднюю длительность через целочисленное среднее тактов;
- число отрицательных, нулевых и положительных длительностей.
Вывести результаты в полных секундах и компонентах. Указать, что деление суммарного числа тактов может отбросить остаток меньше одного такта.
8. Снимок текущего момента: Один раз получить:
DateTime captured = DateTime.Now;
На основе только этого значения вычислить срок через 90 минут и сформировать все строки одного отчёта. Затем получить второй текущий момент и сравнить его с captured. Объяснить, почему несколько независимых вызовов DateTime.Now не образуют один согласованный снимок и почему фактический текущий момент нельзя использовать как фиксированный ожидаемый результат теста.
9. Жизненный цикл Stopwatch: Создать Stopwatch и последовательно исследовать:
- состояние после создания;
Start;- выполнение заданного цикла;
Stop;- повторный
Startбез сброса; - второй одинаковый участок;
Stop;Reset;Restart;- третью работу и окончательный
Stop.
После каждого этапа вывести IsRunning, ElapsedTicks и Elapsed. Объяснить накопление после повторного Start, различие Reset и Restart и невозможность считать показание до остановки автоматически окончательным.
10. Единицы Stopwatch: Для одного вычислительного участка сохранить:
long counterTicks = stopwatch.ElapsedTicks;
TimeSpan elapsed = stopwatch.Elapsed;
Вычислить секунды двумя согласованными способами:
Сравнить значения с разумным допуском и отдельно вывести TimeSpan.TicksPerSecond и Stopwatch.Frequency. Не использовать TimeSpan.FromTicks(counterTicks). Объяснить, почему такты счётчика и такты TimeSpan нельзя считать одной единицей без проверки шкалы.
11. Изоляция измеряемого участка: До запуска таймера построить отсортированный массив из 100_000 целых и массив фиксированных запросов. Между Start и Stop выполнять только многократный линейный поиск. Подготовка входов, печать и проверка индексов выполняются снаружи. Внутри измерения накапливается контрольная сумма найденных индексов; она выводится после остановки и сверяется с заранее вычисленным ожидаемым результатом.
12. Заданная серия по размерам: Для метода суммы массива использовать размеры:
2^10, 2^12, 2^14, 2^16, 2^18, 2^20
Для каждого размера:
- создать детерминированный массив вне таймера;
- выполнить три прогревочных вызова;
- провести
15измеряемых повторов; - перед повтором вызвать
Restart, после ядра —Stop; - сохранить
ElapsedTicks; - вычислить минимум, максимум, среднее и размах;
- вывести среднее число тактов на элемент;
- проверить одинаковую сумму всех повторов.
Протокол задан полностью; требуется только точная реализация и объяснение ожидаемого линейного роста числа операций.
Уровень II. Продвинутый
Условие задаёт календарную систему либо исследовательский вопрос, но не готовое представление интервалов, число проходов, измеряемую границу и способ агрегации повторов. Требуется самостоятельно спроектировать расписание или эксперимент, обеспечить представимость дат, одинаковость входов и воспроизводимость состояния и связать временные данные с числом операций. В отличие от Уровня I ученик определяет сам протокол и достаточное состояние решения.
1. Хронология контрольных событий: Дан неупорядоченный DateTime[] с возможными повторами. Построить упорядоченную независимую копию и определить:
- самый ранний и поздний моменты;
- полную продолжительность наблюдения;
- число пар соседних совпадающих моментов;
- все положительные промежутки между соседними различными событиями;
- минимальный, максимальный и средний положительный промежуток.
Если положительных промежутков нет, вернуть отдельный статус. Исходный массив сохраняется.
2. Объединение занятых интервалов: Объявить:
public struct TimeInterval
{
public DateTime Start;
public DateTime End;
}
Инвариант:
Для массива корректных полуоткрытых интервалов:
- создать независимую копию;
- устойчиво отсортировать её по
Start, затем поEnd; - слить пересекающиеся и соприкасающиеся интервалы;
- сначала использовать буфер максимальной длины с логическим размером;
- затем вернуть массив точного размера.
Вычислить суммарную занятую длительность и массив свободных промежутков между объединёнными интервалами.
3. Периодическое расписание с запретами: Даны начало, конец, положительный шаг и массив корректных запрещённых полуоткрытых интервалов. Сформировать все моменты:
которые строго меньше end и не принадлежат ни одному запрету. Число результата неизвестно заранее, поэтому организовать два прохода: подсчёт и заполнение. Вычисление следующего момента должно завершаться до выхода за допустимый диапазон DateTime. Доказать завершимость через положительность шага и строгое продвижение момента.
4. Пиковая одновременность: Дан массив непустых полуоткрытых интервалов. Для каждого создать два события:
- начало:
+1; - конец:
-1.
Устойчиво отсортировать события по моменту, а при равенстве обработать завершения раньше начал. Последовательным проходом определить:
- максимальное число активных интервалов;
- первый момент достижения максимума;
- все полуоткрытые участки, на которых сохранялся максимум.
Обосновать порядок событий на общей границе.
5. Безопасный календарный сдвиг: Спроектировать:
static bool TryShift(
DateTime moment,
TimeSpan delta,
out DateTime result)
Обработка исключений запрещена. До вызова AddTicks проверить, что сумма:
лежит между DateTime.MinValue.Ticks и DateTime.MaxValue.Ticks. Для проверки разрешено временно использовать decimal, чтобы сложение двух long не переполнилось. При отказе вернуть false и default; при успехе сохранить DateTimeKind исходного момента. Проверить сдвиги около обеих границ диапазона.
6. Сводка серии измерений: Спроектировать:
public struct TimingSummary
{
public int InputSize;
public int Repetitions;
public long MinimumTicks;
public double MedianTicks;
public long MaximumTicks;
public double MeanTicks;
public double RelativeRange;
}
Метод анализа:
- не изменяет исходный массив;
- создаёт копию для сортировки;
- поддерживает нечётное и чётное число повторов;
- вычисляет медиану;
- определяет относительный размах как
(max - min) / mean; - возвращает отдельный статус, если серия пуста либо среднее равно нулю.
Автоматически удалять «выбросы» запрещено: большой разброс является результатом эксперимента.
7. Линейный и бинарный поиск: Для размеров:
построить одинаковые отсортированные массивы и фиксированный набор присутствующих и отсутствующих запросов без Random. Реализовать оба поиска со счётчиками сравнений. Самостоятельно определить прогрев, число повторов, порядок чередования и контрольную сумму. Сопоставить:
- среднее число сравнений;
- медиану времени;
- разброс;
- классы и .
8. Сортировка вставками и Array.Sort: Для нескольких размеров подготовить три детерминированные формы входа:
- упорядоченную;
- обратную;
- пилообразную.
Перед каждым повтором вне таймера создавать одинаковые копии исходного массива. Измерять только сортировку; после остановки проверять упорядоченность и совпадение результатов. Для сортировки вставками считать сравнения и перемещения и сопоставить лучший и худший случаи с и . Array.Sort рассматривать как по контракту курса.
9. Конкатенация и StringBuilder: Для возрастающих n построить строку из n одинаковых символов:
- повторной конкатенацией;
- через
StringBuilderс начальной ёмкостьюn.
Размеры выбираются заранее; серия прекращается, если одна стратегия превышает установленный предел длительности. Выполнить прогрев, чередовать порядок стратегий, измерять только построение и проверять равенство строк. Сопоставить наблюдения с суммой длин копируемых префиксов:
10. Пять классов роста: Реализовать пять вычислительных ядер с наблюдаемой контрольной суммой:
- постоянное число действий;
- один проход длины
n; - последовательное деление состояния на
2; - двойной проход
n × n; - внешний проход
1..nс внутренним делением текущего индекса на2.
Для нескольких размеров:
- посчитать точное число основных операций;
- провести серии измерений;
- выбрать отдельный безопасный диапазон для квадратичного ядра;
- классифицировать ядра как , , , и ;
- объяснить случаи, в которых время малых размеров ещё не отражает класс роста.
11. Честный протокол для изменяющего алгоритма: Исследовать сортировку вставками в серии повторов двумя способами:
- ошибочно сортировать один и тот же массив повторно;
- перед каждым измерением восстанавливать одинаковый исходный порядок вне таймера.
До запуска предсказать число сравнений первого и последующих повторов ошибочной серии. Затем спроектировать корректный протокол, в котором подготовка копии не входит в измеряемое ядро, но учитывается отдельной контрольной серией. Объяснить загрязнение входа предыдущим повтором.
12. Цена предварительной сортировки: Сравнить две полные стратегии обработки неизменяемого массива длины n:
- выполнить
qлинейных поисков; - создать копию, отсортировать её и выполнить
qбинарных поисков.
Для каждого (n, q) обе стратегии получают одинаковый набор запросов. Во второй стратегии измеряются копирование, сортировка и поиски вместе. Построить серии для нескольких n и q, определить наблюдаемый диапазон окупаемости и сопоставить:
Отдельно сравнить с результатами II.7, где предварительная обработка не входила в поисковое ядро.
Уровень III. Экспертный
Исследуется не прикладное расписание и не само время алгоритма, а достоверность измерительного вывода. До запуска требуется записать гипотезу, единицу наблюдения, измеряемую границу, ожидаемое число операций, возможные искажения и критерий принятия вывода; после — разделить устойчивое наблюдение, шум, ошибку протокола и утверждения, которые данные не подтверждают.
1. Календарные часы и монотонный измеритель: Один короткий вычислительный участок измерить многократно:
- разностью двух последовательных
DateTime.Now; - отдельным
Stopwatch.
Для каждого способа подсчитать:
- число нулевых результатов;
- число отрицательных результатов;
- минимум положительного значения;
- число различных наблюдавшихся значений.
Затем повторить опыт, объединяя в один замер 1, 10, 100 и 1000 выполнений ядра. Объяснить назначение настенных календарных часов, монотонного счётчика, дискретность наблюдения и необходимость пакетирования слишком короткой работы.
2. Такты двух разных шкал: Получить Stopwatch.ElapsedTicks, Stopwatch.Frequency, Stopwatch.Elapsed.Ticks и TimeSpan.TicksPerSecond. До запуска предсказать, какие отношения обязаны выполняться, а какие зависят от платформы. Вычислить длительность в секундах:
- через
ElapsedTicks / Frequency; - через
Elapsed.TotalSeconds.
Затем намеренно интерпретировать ElapsedTicks через TimeSpan.FromTicks и показать расхождение, если частоты различаются. Сформулировать общий вывод: числовое значение без единицы и шкалы неполно.
3. Прогрев и порядок сравнения: Сравнить два алгоритма на одинаковых входах четырьмя протоколами:
- без прогрева в фиксированном порядке
A → B; - с прогревом в том же порядке;
- с чередованием
A → BиB → A; - отдельными сериями.
До запуска перечислить возможные искажения первого вызова и постоянного порядка. После сравнить медианы и разбросы и объяснить, почему совпадение одного протокола с гипотезой не делает его универсально истинным.
4. Загрязнение изменяемого входа: Для сортировки, разворота массива и удаления повторов на месте построить намеренно ошибочные серии, в которых каждый повтор получает результат предыдущего. До запуска проследить состояние входа и число операций хотя бы для первых трёх повторов. Затем исправить протокол двумя способами:
- восстанавливать копию вне таймера и измерять ядро;
- измерять полную операцию «копия + ядро».
Показать, что оба исправленных протокола корректны, но отвечают на разные исследовательские вопросы.
5. Зависимость от расположения данных: Для линейного поиска использовать цель:
- в первой позиции;
- в средней позиции;
- в последней позиции;
- отсутствующую.
Для сортировки вставками использовать упорядоченный, обратный и пилообразный массив. До запуска вывести точное либо асимптотическое число сравнений и перемещений. После объяснить, почему размер входа не определяет единственную длительность, а обозначение или требует уточнения рассматриваемого случая.
6. Накладные расходы и пакетирование: Отдельными сериями измерить:
- пустой участок между
StartиStop; - одну простую арифметическую операцию;
- пакеты из
10,10^2,10^3и10^4одинаковых операций.
Для каждого пакета вычислить медиану и условное число тактов на операцию. Базовую серию вывести рядом, но не вычитать как точную поправку. Объяснить, при каких размерах накладные расходы и шум сравнимы с полезной работой и почему нестабильная либо отрицательная оценка после простого вычитания не доказывает отрицательное время операции.
7. Аудит ошибочных измерений: Для каждого протокола найти не менее двух дефектов и предложить исправление:
- один запуск в
Debugпод отладчиком; - вывод каждой итерации внутри таймера;
- разные входы для сравниваемых алгоритмов;
- повторная сортировка одного массива;
- сравнение средних без публикации разброса;
- перевод
ElapsedTicksчерезTimeSpan.FromTicks; - объявление более низкого времени доказательством лучшей асимптотики на одном размере;
- удаление всех медленных повторов без заранее заданного правила.
Для каждого случая указать, какой именно вывод остаётся допустимым после исправления, а не только переписать код.
8. Полный протокол проверки роста: Выбрать три ранее реализованных алгоритма с ожидаемыми классами:
До эксперимента зафиксировать:
- конфигурацию;
- версию среды;
- детерминированные входы;
- не менее шести размеров;
- прогрев;
21измеряемый повтор;- порядок сравнения;
- измеряемую границу;
- контроль правильности;
- критерий прекращения слишком дорогой серии.
Для каждого размера сохранить медиану и размах и вычислить:
Определить, какая нормировка стабилизируется лучше. Итог разделить на три части: подтверждённое наблюдение в измеренном диапазоне, обнаруженные отклонения и утверждения, которые нельзя переносить на другие размеры, платформы и реализации.
Обязательные контрольные наборы
| Задача | Исходные данные |
|---|---|
| I.1 | 2028-02-29 23:45:30, 2028-03-01 00:00:00, 2028-03-01 08:15:00, повтор одной границы |
| I.2 | длительность из условия |
| I.3 | начало 2026-07-10 22:35:00, конец 2026-07-12 01:20:15 |
| I.4 | начало 2026-07-10 09:30:00; этапы 25 мин, 80 мин, 150 с |
| I.5 | раздельные, касающиеся, частично пересекающиеся и вложенные интервалы |
| I.6 | начало 2026-01-01 08:00:00, шаг 00:17:30, count = 0,1,5,20 |
| I.7 | 00:00:30, 01:15:00, 2.12:00:00, -00:05:00, 00:00:00 |
| I.8 | один запуск программы с текущим моментом среды |
| I.9 | три одинаковых цикла по 10_000_000 итераций |
| I.10 | вычислительное ядро не короче нескольких тысяч итераций |
| I.11 | длина массива 100_000, запросы [0,50_000,99_999,-1,100_001] |
| I.12 | размеры и 15 повторов из условия |
| II.1 | события с повторами и переходом через полночь |
| II.2 | пять интервалов: раздельные, касающиеся, пересекающиеся и вложенный |
| II.3 | окно 08:00–18:00, шаг 15 мин, два запрещённых интервала |
| II.4 | [09:00,10:00), [09:30,11:00), [10:00,10:30), [11:00,12:00) |
| II.5 | DateTime.MinValue, DateTime.MaxValue и сдвиги -1, 0, 1 такт и ±1 день |
| II.6 | [10,11,9,12,100], [5,5,5,5], [4,8,6,10], пустая серия |
| II.7 | размеры 2^10–2^20; цели в начале, середине, конце и вне массива |
| II.8 | размеры 128, 512, 2048, 8192; три формы входа |
| II.9 | n = 10^2,10^3,5·10^3,10^4,2·10^4 с заранее заданным пределом серии |
| II.10 | n = 16,64,256,1024,4096 с отдельным диапазоном квадратичного ядра |
| II.11 | обратный массив длины 2048; не менее 15 повторов |
| II.12 | n = 10^3,10^4,10^5, q = 1,10,10^2,10^3,10^4 |
| III.1 | пакеты 1, 10, 100, 1000; не менее 101 повтора |
| III.2 | один участок длительностью, достаточной для ненулевого ElapsedTicks |
| III.3 | линейный и бинарный поиск либо две сортировки на идентичных копиях |
| III.4 | сортировка, разворот и уплотнение на месте |
| III.5 | размеры 32, 128, 512, 2048; все расположения и формы из условия |
| III.6 | пакеты 1, 10, 10^2, 10^3, 10^4; не менее 31 повтора |
| III.7 | все восемь ошибочных протоколов |
| III.8 | не менее шести возрастающих размеров и 21 повтор для каждого алгоритма |
Итог модуля
Ученик различает календарный момент, знаковую длительность и монотонно измеренное прошедшее время, создаёт и сравнивает DateTime, выполняет арифметику через TimeSpan, строит полуоткрытые интервалы, расписания и системы одновременности и проверяет представимость календарного сдвига. Он управляет полным жизненным циклом Stopwatch, различает такты счётчика и TimeSpan, изолирует вычислительное ядро и проектирует серии с прогревом, одинаковыми входами, восстановлением изменяемого состояния, контрольной суммой, медианой и разбросом. Наблюдаемое время сопоставляется с точным числом операций и классами роста только в пределах зафиксированной конфигурации, размеров и протокола, а ошибочные измерительные выводы распознаются по нарушению единицы, границы эксперимента, состояния входа или правил сравнения.
Покрытие опоры и границы
- Создание, форматирование и сравнение
DateTime: I.1, I.3–I.6, II.1–II.5. TimeSpan, компоненты, полные величины,TicksиTimeSpan.Zero: I.2–I.7, II.1–II.5.- Текущий момент и согласованный снимок: I.8, III.1.
- Полуоткрытые интервалы, объединение, запреты и одновременность: I.5, II.2–II.4.
- Представимость календарной арифметики: I.6, II.3, II.5.
Stopwatch,Start,Stop,Reset,Restart,ElapsedиIsRunning: I.9–I.12.ElapsedTicks,Stopwatch.Frequencyи отличие от тактовTimeSpan: I.10, III.2.- Изоляция измеряемого участка и наблюдаемая контрольная сумма: I.11–I.12, II.7–II.12.
- Прогрев, серия повторов, одинаковые входы и чередование порядка: I.12, II.7–II.12, III.3–III.4.
- Минимум, медиана, среднее, максимум и разброс: I.12, II.6–II.12, III.1–III.8.
- Изменяемые входы и восстановление состояния: II.8, II.11, III.4.
- Ядро против полной стратегии: II.11–II.12, III.4.
- Зависимость стоимости от формы и расположения входа: II.8, III.5.
- Постоянные затраты, разрешение таймера и пакетирование: III.1–III.2, III.6.
- Классы , , , и : I.12, II.7–II.12, III.5 и III.8.
- Аудит протокола и границы подтверждённого вывода: III.3–III.8.
За границей: часовые пояса, переходы летнего времени, DateTimeOffset, DateOnly, TimeOnly, расширенные календарные правила и платформенные политики преобразования времени относятся к последующим курсам. Высокоточные микроизмерения, аппаратные счётчики, профилировщики, BenchmarkDotNet, статистическое моделирование шума, многопоточность и анализ пауз сборщика мусора также не входят в модуль. Stopwatch позволяет наблюдать длительность в текущей среде, но не доказывает универсальную скорость алгоритма и не заменяет доказательство корректности и асимптотическую оценку.

