Периодически возникает необходимость замера промежутка времени в процессе работы приложения или во время тестирования, как уже было описано в статье про BenchmarkDotNet. Платформа .Net позволяет делать замеры различными способами. Я решил разобраться для каких ситуаций какой способ подходит лучше всего.. Для начала набросал приложение для замеров скорости получения текущего времени:
using System.Diagnostics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; BenchmarkRunner.Run(typeof(MyClass)); public class MyClass { [Benchmark] public DateTime DateTime_UtcNow() => DateTime.UtcNow; [Benchmark] public DateTime DateTime_Now() => DateTime.Now; [Benchmark] public DateTimeOffset DateTimeOffset_UtcNow() => DateTimeOffset.UtcNow; [Benchmark] public DateTimeOffset DateTimeOffset_Now() => DateTimeOffset.Now; [Benchmark] public int Environment_TickCount() => Environment.TickCount; [Benchmark(Baseline = true)] public long Environment_TickCount64() => Environment.TickCount64; [Benchmark] public long Stopwatch_GetTimestamp() => Stopwatch.GetTimestamp(); }
И получил следующие результаты:
Method | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|
DateTime_UtcNow | 28.025 ns | 0.1514 ns | 0.1416 ns | 19.40 | 0.17 |
DateTime_Now | 40.581 ns | 0.8331 ns | 0.8555 ns | 27.81 | 0.40 |
DateTimeOffset_UtcNow | 34.994 ns | 0.2892 ns | 0.2705 ns | 24.17 | 0.32 |
DateTimeOffset_Now | 67.920 ns | 1.3585 ns | 1.5100 ns | 46.94 | 1.32 |
Environment_TickCount | 1.304 ns | 0.0352 ns | 0.0294 ns | 0.90 | 0.02 |
Environment_TickCount64 | 1.446 ns | 0.0137 ns | 0.0115 ns | 1.00 | 0.00 |
Stopwatch_GetTimestamp | 19.771 ns | 0.0938 ns | 0.0877 ns | 13.67 | 0.14 |
- Environment.TickCount даёт максимальную производительность — всего 1,3 наносекунды. Если учесть, что ядра моего Ryzen 7 4800H в без нагрузки работают на частоте 1,3 GHz, то это выполнение за 1-2 такта процессора в зависимости от частоты. Всё потому, что TickCount выдаёт время прошедшее с момента загрузки системы, т.е. это просто счётчик. А теперь о плохом: время не в Tick’ах, а в миллисекундах. Это означает, что с помощью этого метода можно измерять события длящиеся десятые, сотые и тысячные доли секунды, как например запросы к различным внешним источникам данных, но более скоротечные события измерить не получиться. Отмечу также, что значение метода отсчитывается от 0 до Int32.MaxValue примерно за 24,9 дня, затем значение переключается на Int32.MinValue и увеличивается до нуля ещё за 24,9 дня. Однако разницу в значениях можно считать без плясок с бубном простым hightestValue — lowestValue за счёт переполнения.
- Environment.TickCount64 аналогично предыдущему, только возвращает Int64 вместо Int32, а значит полного обращения придётся ждать немногим меньше 300-х сот миллионов лет. Минус тот же — миллисекунды. Иногда скоростные показатели почти такие же как и у предыдущего метода.
- Stopwatch.GetTimestamp возвращает значение счетчика тактов базового механизма таймера, т.е. кол-во «попугаев» от некоторого события (загрузка?). В тактах (Ticks). Напомню, что в одном такте 10 000 миллисекунд. Точность значительно выше, но и отрабатывает метод значительно медленней чем предыдущие два. И хотя в количественном выражении это в 15 раз медленнее — мы говорим о 20 наносекундах, т.е. 20 * 10-9, в то время как один такт(Tick) = 10 * 10-6.
- DateTime.UtcNow самый быстрой из предоставленных вариантов получения текущего времени.В 20 раз медленнее TickCount, но всего лишь 30 наносекунд в абсолютном выражении.
Выводы: если очень хочется максимального быстродействия при невысокой точности или приходится оперировать методами принимающими миллисекунды — можно заморочиться с Environment.TickCount или Environment.TickCount64. В остальных случаях использование DateTime или DateTimeOffset с методами Now или UtcNow более чем оправдано.
Очень круто всё описано, делайте ещё материалы на эту тему. Мне приходилось очень долго разбираться со всем этим, теперь просто хожу сюда как в домашнюю библиотеку с пометками