Можно ли когда-нибудь использовать Environment.TickCount для расчета промежутков времени?
int start = Environment.TickCount;
// Do stuff
int duration = Environment.TickCount - start;
Console.WriteLine("That took " + duration " ms");
Поскольку TickCount подписан и будет обновлен через 25 дней (требуется 50 дней, чтобы задействовать все 32 бита, но вы должны отказаться от подписанного бита, если хотите разобраться в математике), кажется, что это слишком рискованно, чтобы быть полезным .
Вместо этого я использую DateTime.Now. Это лучший способ сделать это?
DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
@Scott: Подумал, что стоит упомянуть: даже с UtcNow существует проблема запланированных синхронизаций NTP: не так уж редко системное время изменяется примерно на 10 секунд после этих обновлений (на моем ПК).
Математика на самом деле не так уж и сложна ... int duration = unchecked((int)((uint)Environment.TickCount - (uint)start)); ... даст вам правильный ответ независимо от опрокидывания. (В идеале вы можете пропустить трансляцию, чтобы вернуться к int, если это вам абсолютно не нужно.)
@AnorZaken: вам действительно не нужен Любые этого кастинга, как объяснил в другом месте на этой странице.
@GlennSlayden Вы правы - мои приведения uint бессмысленны и, так как я возвращаюсь к int, это все равно не имеет никакого смысла. Хороший улов. (Кроме того, возврат к int внутри непроверенного контекста был сомнительным выбором с моей стороны.)





Используйте класс секундомера. На msdn есть достойный пример: http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx
Stopwatch stopWatch = Stopwatch.StartNew();
Thread.Sleep(10000);
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;
Класс Stopwatch доступен только в .NET Framework 2.0 и выше. В более старых версиях вы использовали TickCount, возможно, в сочетании с TimeSpan, чтобы проверить, не пересекли ли вы 25-дневную границу.
В более ранних версиях вы бы использовали вызовы p / invoke, помеченные как безопасные и msdn.microsoft.com/en-us/library/ms901807.aspx, а не TickCount.
Если на оборудовании, на котором работает ваша программа, отсутствует таймер с высоким разрешением, секундомер так же опасен, как и DateTime. См. connect.microsoft.com/VisualStudio/feedback/details/741848/…, а также ответ Джоэла ниже.
Мне не удалось найти секундомер для переносимой библиотеки классов, поэтому вам придется использовать обходной путь.
Использовать
System.Diagnostics.Stopwatch
У него есть свойство, называемое
EllapsedMilliseconds
Вместо этого вы должны использовать класс Секундомер.
Я использую Environment.TickCount, потому что:
При этом я бы также рекомендовал использовать секундомер, если он вам доступен. Или вы могли бы потратить около 1 минуты и написать класс, похожий на секундомер, который обертывает Environment.TickCount.
Кстати, я не вижу в документации по секундомеру ничего, что упоминало бы о проблеме циклического перехода с базовым механизмом таймера, поэтому я не удивлюсь, если обнаружу, что секундомер страдает той же проблемой. Но опять же, я бы не стал беспокоиться об этом.
№3 действительно неверен, у Мэтьюза есть лучшее объяснение. Речь идет не об относительных временах, близких к циклу, это проблема только в том случае, если вы в мера раз больше, чем двадцать с чем-то дней.
# 3 даже совершенно неверно, это очень большая проблема, если вы запускаете что-либо, что может быть использовано в качестве службы на сервере, или даже приложения на сервере терминалов. Даже мой домашний компьютер иногда работает в течение двух или более месяцев без перезагрузки (не знаю, как режим ожидания и гибернация влияют на тики, но я подозреваю, что они не сбрасывают их).
@Lucero и Freed: вам определенно стоит нет использовать Stopwatch (или TickCount, если на то пошло) для измерения таких длинных интервалов времени. Несмотря на высокую степень детализации, Stopwatch имеет очень низкую долгосрочную точность, начиная с 5-10 секунд (не миллисекунды) в день.
Меня просто сбило с толку, когда мой алгоритм завис. Этот компьютер работал / находился в спящем режиме> 24 дней. Как противно!
@usr: это твоя вина за использование TickCount для измерения такого длительного промежутка времени. :)
Конечно было! Я хотел поделиться, чтобы другие не были укушены этим.
Оборачивание через 50 дней не подходит для сервера. Надеюсь, вас не дежурят, когда это произойдет.
Я собирался посоветовать превратить это в урок секундомера, но Грзенио уже сказал правильную вещь, так что я дам ему всплеск. Такая инкапсуляция влияет на решение о том, какой путь лучше, и это может измениться со временем. Я помню, как был шокирован тем, насколько дорого обходится время для некоторых систем, поэтому очень важно иметь одно место, где можно реализовать лучшую технику.
Для одноразового тайминга еще проще написать
Stopwatch stopWatch = Stopwatch.StartNew();
...dostuff...
Debug.WriteLine(String.Format("It took {0} milliseconds",
stopWatch.EllapsedMilliseconds)));
Я предполагаю, что космически маловероятный переход в TickCount еще меньше беспокоит StopWatch, учитывая, что поле ElapsedTicks является длинным. На моей машине StopWatch имеет высокое разрешение, 2,4e9 тика в секунду. Даже при таких темпах потребуется более 121 года, чтобы заполнить поле тиков. Конечно, я не знаю, что происходит под одеялом, так что относитесь к этому с недоверием. Однако я заметил, что в документации для StopWatch даже не упоминается проблема переноса, в то время как в документации для TickCount упоминается.
Почему вас беспокоит опрокидывание? Пока измеряемая продолжительность составляет менее 24,9 дней и вы рассчитываете продолжительность относительный, все в порядке. Не имеет значения, как долго работает система, если вы заботитесь только о своей части этого времени работы (в отличие от прямого выполнения сравнений «меньше или больше» в начальной и конечной точках). Т.е. это:
int before_rollover = Int32.MaxValue - 5;
int after_rollover = Int32.MinValue + 7;
int duration = after_rollover - before_rollover;
Console.WriteLine("before_rollover: " + before_rollover.ToString());
Console.WriteLine("after_rollover: " + after_rollover.ToString());
Console.WriteLine("duration: " + duration.ToString());
правильно печатает:
before_rollover: 2147483642
after_rollover: -2147483641
duration: 13
Вам не нужно беспокоиться о знаковом бите. C#, как и C, позволяет процессору справиться с этим.
Это обычная ситуация, с которой я раньше сталкивался с подсчетом времени во встроенных системах. Я бы никогда не сравнивал, например, beforerollover <afterrollover напрямую. Я всегда выполнял вычитание, чтобы найти продолжительность, учитывающую опрокидывание, а затем основывал любые другие вычисления на этой продолжительности.
Лучший ответ ИМО. Он также не будет «взорваться через 50 дней», если только вы не попытаетесь использовать его для отслеживания периода времени, превышающего 50 дней, что кажется очень маловероятным. +1 за твердое доказательство того, что это работает легко проверяемым способом. StopWatch - бесполезный класс из-за проблем с многоядерными процессорами, которые в наши дни есть практически на каждом ПК.
Примечание. Этот пример будет работать только в том случае, если код построен без проверки арифметического переполнения / потери значимости (по умолчанию она отключена). Если включить (Настройки проекта> Сборка> Дополнительно ...> Проверить арифметическое переполнение / потерю значимости) код поднимет System.OverflowException : Arithmetic operation resulted in an overflow
@eselk StopWatch в настоящее время надежен: см. stackoverflow.com/questions/243351/…
Если вы ищете функциональные возможности Environment.TickCount, но без накладных расходов на создание новых объектов Stopwatch, вы можете использовать статический метод Stopwatch.GetTimestamp() (вместе с Stopwatch.Frequency) для расчета длительных интервалов времени. Поскольку GetTimestamp() возвращает long, он не переполнится очень и очень долго (более 100 000 лет на машине, которую я использую для записи этого). Он также намного точнее, чем Environment.TickCount с максимальным разрешением от 10 до 16 миллисекунд.
К сожалению, Stopwatch.GetTimestamp() не так удобен, как Environment.TickCount, потому что он возвращает некоторые абстрактные тики, а не обычные единицы времени.
Если есть соображения высокой производительности, следует отметить, что Environment.TickCount значительно быстрее. Тесты здесь (blog.tedd.no/2015/08/17/…) показывают следующее относительное время: Environment.TickCount: 7 мс, DateTime.Now.Ticks: 1392 мс, Stopwatch.ElapsedMilliseconds: 933 мс.
@SpecialSauce, но обратите внимание, что это не эквивалентные вещи. Также обратите внимание, что в моей системе Stopwatch.ElapsedMilliseconds примерно в 35 раз быстрее, чем его система, в то время как мой Environment.TickCount всего в ~ 2 раза быстрее. Ваше оборудование может (очевидно) отличаться.
Environment.TickCount основан на функции GetTickCount () WinAPI. Это в миллисекундах Но реальная точность составляет около 15,6 мс. Таким образом, вы не можете измерить более короткие временные интервалы (или вы получите 0)
Примечание: Возвращаемое значение - Int32, поэтому этот счетчик обновляется каждые ~ 49,7 дней. Вы не должны использовать его для измерения таких длинных интервалов.
DateTime.Ticks основан на функции WinAPI GetSystemTimeAsFileTime (). Это в 100 наносекунд (десятые доли микросекунд). Фактическая точность DateTime.Ticks зависит от системы. В XP приращение системных часов составляет около 15,6 мс, как и в Environment.TickCount. В Windows 7 его точность составляет 1 мс (в то время как Environemnt.TickCount по-прежнему составляет 15,6 мс), однако, если используется схема энергосбережения (обычно на ноутбуках), она также может снизиться до 15,6 мс.
Секундомер основан на функции QueryPerformanceCounter () WinAPI (но если счетчик производительности с высоким разрешением не поддерживается вашей системой, используется DateTime.Ticks)
Перед использованием StopWatch обратите внимание на две проблемы:
Вы можете оценить точность вашей системы с помощью простого теста:
static void Main(string[] args)
{
int xcnt = 0;
long xdelta, xstart;
xstart = DateTime.UtcNow.Ticks;
do {
xdelta = DateTime.UtcNow.Ticks - xstart;
xcnt++;
} while (xdelta == 0);
Console.WriteLine("DateTime:\t{0} ms, in {1} cycles", xdelta / (10000.0), xcnt);
int ycnt = 0, ystart;
long ydelta;
ystart = Environment.TickCount;
do {
ydelta = Environment.TickCount - ystart;
ycnt++;
} while (ydelta == 0);
Console.WriteLine("Environment:\t{0} ms, in {1} cycles ", ydelta, ycnt);
Stopwatch sw = new Stopwatch();
int zcnt = 0;
long zstart, zdelta;
sw.Start();
zstart = sw.ElapsedTicks; // This minimizes the difference (opposed to just using 0)
do {
zdelta = sw.ElapsedTicks - zstart;
zcnt++;
} while (zdelta == 0);
sw.Stop();
Console.WriteLine("StopWatch:\t{0} ms, in {1} cycles", (zdelta * 1000.0) / Stopwatch.Frequency, zcnt);
Console.ReadKey();
}
Просто примечание для вашего цикла - вместо цикла for вы должны использовать Thread.SpinWait, это гарантирует, что цикл не будет оптимизирован (вы указываете, что цикл «отходов» - это то, что вы хотите семантически). В остальном он должен быть более или менее эквивалентен циклу for.
@Luaan: спасибо, я изменил пример кода, чтобы показать точность.
Очень подробное объяснение. На моем рабочем столе win10 результаты аналогичны вашему отчету Win7. Секундомер выглядит очень точным. DateTime: 10278 мс, за 21272 цикла Среда: 16 мс, за 4786363 цикла Секундомер: 0,00087671156014329 мс, за 1 цикл
Небольшое исправление в вашем превосходном коде: поскольку Stopwatch полностью пассивен (в том смысле, что он никогда не запускает какой-либо код сам по себе), ваш вызов sw.Stop() бессмысленен, вызывая только шквал бесполезных вызовов Win32. Обычно этот вопрос не стоит упоминания, но здесь он привлекает внимание к тому, что некоторым может показаться нелогичным, а именно, что Stopwatch неактивен (также не IDisposable) и от него можно в любой момент отказаться. Оскорбленная «интуиция» состоит в том, что что-то «оставили запущенным», но понимание может упростить проектирование системы, связанной с (например) остановом, обычно сложной областью.
@GlennSlayden, вы правы. Я считаю, что это рудимент самой первой версии кода, который печатал sw.ElapsedTicks на stdout после sw.Stop ()
QueryPerformanceCounter () в настоящее время надежен: см. blogs.msdn.microsoft.com/oldnewthing/20140822-01/?p=163 и msdn.microsoft.com/en-us/library/windows/desktop/…: «Q: Надежно ли работает QPC в многопроцессорных системах, многоядерных системах и системах с гиперпоточностью? A: Да», «Q: На точность QPC влияют изменения частоты процессора, вызванные управлением питанием или технологией Turbo Boost? A: Нет "
Отмеченные вами "проблемы" в наши дни по сути неактуальны. Они применимы к Windows 2000, Windows 2003 и Windows XP. Если вы используете эти системы, у вас будут большие проблемы, чем точность ваших таймеров.
Environment.TickCount кажется намного быстрее, чем другие решения:
Environment.TickCount 71
DateTime.UtcNow.Ticks 213
sw.ElapsedMilliseconds 1273
Измерения производились с помощью следующего кода:
static void Main( string[] args ) {
const int max = 10000000;
//
//
for ( int j = 0; j < 3; j++ ) {
var sw = new Stopwatch();
sw.Start();
for ( int i = 0; i < max; i++ ) {
var a = Environment.TickCount;
}
sw.Stop();
Console.WriteLine( $"Environment.TickCount {sw.ElapsedMilliseconds}" );
//
//
sw = new Stopwatch();
sw.Start();
for ( int i = 0; i < max; i++ ) {
var a = DateTime.UtcNow.Ticks;
}
sw.Stop();
Console.WriteLine( $"DateTime.UtcNow.Ticks {sw.ElapsedMilliseconds}" );
//
//
sw = new Stopwatch();
sw.Start();
for ( int i = 0; i < max; i++ ) {
var a = sw.ElapsedMilliseconds;
}
sw.Stop();
Console.WriteLine( $"sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}" );
}
Console.WriteLine( "Done" );
Console.ReadKey();
}
+1 от меня, потому что это то, что я хотел узнать. Если кто-то читает это: почему в этом ответе нет комментариев и нет других голосов за / против ?? (по состоянию на 5/2017) Если что-то не так, прокомментируйте, если считаете, что это правильно, проголосуйте за.
Я расширил тест, смотрите мой ответ. Кстати, есть интересные различия, если вы выполняете эти тесты в режиме выпуска или отладки (последнее вам, вероятно, не следует делать для «реальных» результатов, но интересно посмотреть, какие тесты действительно оптимизируются компилятором, а какие нет. ).
Вот своего рода обновленное и обновленное резюме того, какие ответы и комментарии могут быть наиболее полезными в этой теме, а также дополнительные тесты и варианты:
Первым делом: как другие отмечали в комментариях, за последние годы все изменилось, и с «современной» Windows (Win XP ++) и .NET и современным оборудованием нет или почти нет причин не использовать Stopwatch (). Подробнее см. MSDN. Цитаты:
"Is QPC accuracy affected by processor frequency changes caused by power management or Turbo Boost technology?
No. If the processor has an invariant TSC, the QPC is not affected by these sort of changes. If the processor doesn't have an invariant TSC, QPC will revert to a platform hardware timer that won't be affected by processor frequency changes or Turbo Boost technology.Does QPC reliably work on multi-processor systems, multi-core system, and systems with hyper-threading?
YesHow do I determine and validate that QPC works on my machine?
You don't need to perform such checks.Which processors have non-invariant TSCs? [..Read further..] "
Но если вам не нужна точность секундомера () или, по крайней мере, вы хотите точно знать о производительности секундомера (статический или на основе экземпляра) и других возможных вариантах, продолжайте читать:
Я взял на себя вышеприведенный тест от cskwg и расширил код для большего количества вариантов. Я измерил несколько лет назад i7 4700 MQ и C# 7 с VS 2017 (точнее, скомпилирован с .NET 4.5.2, несмотря на двоичные литералы, это C# 6 (используется это: строковые литералы и 'using static В частности, производительность Stopwatch () кажется улучшенной по сравнению с упомянутым тестом.
Это пример результатов 10 миллионов повторений в цикле, как всегда, абсолютные значения не важны, но даже относительные значения могут отличаться на другом оборудовании:
32 бит, режим выпуска без оптимизации:
Measured: GetTickCount64() [ms]: 275
Measured: Environment.TickCount [ms]: 45
Measured: DateTime.UtcNow.Ticks [ms]: 167
Measured: Stopwatch: .ElapsedTicks [ms]: 277
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 548
Measured: static Stopwatch.GetTimestamp [ms]: 193
Measured: Stopwatch+conversion to DateTime [ms]: 551
Compare that with DateTime.Now.Ticks [ms]: 9010
32 бит, режим выпуска, оптимизирован:
Measured: GetTickCount64() [ms]: 198
Measured: Environment.TickCount [ms]: 39
Measured: DateTime.UtcNow.Ticks [ms]: 66 (!)
Measured: Stopwatch: .ElapsedTicks [ms]: 175
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 491
Measured: static Stopwatch.GetTimestamp [ms]: 175
Measured: Stopwatch+conversion to DateTime [ms]: 510
Compare that with DateTime.Now.Ticks [ms]: 8460
64 бит, режим выпуска без оптимизации:
Measured: GetTickCount64() [ms]: 205
Measured: Environment.TickCount [ms]: 39
Measured: DateTime.UtcNow.Ticks [ms]: 127
Measured: Stopwatch: .ElapsedTicks [ms]: 209
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 285
Measured: static Stopwatch.GetTimestamp [ms]: 187
Measured: Stopwatch+conversion to DateTime [ms]: 319
Compare that with DateTime.Now.Ticks [ms]: 3040
64 бит, режим выпуска, оптимизирован:
Measured: GetTickCount64() [ms]: 148
Measured: Environment.TickCount [ms]: 31 (is it still worth it?)
Measured: DateTime.UtcNow.Ticks [ms]: 76 (!)
Measured: Stopwatch: .ElapsedTicks [ms]: 178
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 226
Measured: static Stopwatch.GetTimestamp [ms]: 175
Measured: Stopwatch+conversion to DateTime [ms]: 246
Compare that with DateTime.Now.Ticks [ms]: 3020
Может быть очень интересно, что создание значения DateTime для печати времени секундомера, похоже, почти не требует затрат. Интересно скорее с академической, чем с практической точки зрения, то, что статический секундомер работает немного быстрее (как и ожидалось). Некоторые моменты оптимизации весьма интересны. Например, я не могу объяснить, почему только 32-битный Stopwatch.ElapsedMilliseconds настолько медленный по сравнению с другими вариантами, например статическим. Это и DateTime.Now более чем удваивают их скорость с 64-битной версией.
Вы можете видеть: только для миллионов казней время секундомера начинает иметь значение. Если это действительно так (но будьте осторожны с микрооптимизацией слишком рано), может быть интересно, что с GetTickCount64 (), но особенно с DateTime.UtcNow, у вас есть 64-битный (длинный) таймер с меньшей точностью, чем секундомер, но быстрее, поэтому что вам не нужно возиться с 32-битным "уродливым" Environment.TickCount.
Как и ожидалось, DateTime.Now намного медленнее всех.
Если вы запустите его, код также получит вашу текущую точность секундомера и многое другое.
Вот полный код теста:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using static System.Environment;
[...]
[DllImport("kernel32.dll") ]
public static extern UInt64 GetTickCount64(); // Retrieves a 64bit value containing ticks since system start
static void Main(string[] args)
{
const int max = 10_000_000;
const int n = 3;
Stopwatch sw;
// Following Process&Thread lines according to tips by Thomas Maierhofer: https://codeproject.com/KB/testing/stopwatch-measure-precise.aspx
// But this somewhat contradicts to assertions by MS in: https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); // Use only the first core
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Thread.Sleep(2); // warmup
Console.WriteLine($"Repeating measurement {n} times in loop of {max:N0}:{NewLine}");
for (int j = 0; j < n; j++)
{
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var tickCount = GetTickCount64();
}
sw.Stop();
Console.WriteLine($"Measured: GetTickCount64() [ms]: {sw.ElapsedMilliseconds}");
//
//
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var tickCount = Environment.TickCount; // only int capacity, enough for a bit more than 24 days
}
sw.Stop();
Console.WriteLine($"Measured: Environment.TickCount [ms]: {sw.ElapsedMilliseconds}");
//
//
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var a = DateTime.UtcNow.Ticks;
}
sw.Stop();
Console.WriteLine($"Measured: DateTime.UtcNow.Ticks [ms]: {sw.ElapsedMilliseconds}");
//
//
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var a = sw.ElapsedMilliseconds;
}
sw.Stop();
Console.WriteLine($"Measured: Stopwatch: .ElapsedMilliseconds [ms]: {sw.ElapsedMilliseconds}");
//
//
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var a = Stopwatch.GetTimestamp();
}
sw.Stop();
Console.WriteLine($"Measured: static Stopwatch.GetTimestamp [ms]: {sw.ElapsedMilliseconds}");
//
//
DateTime dt=DateTime.MinValue; // just init
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var a = new DateTime(sw.Elapsed.Ticks); // using variable dt here seems to make nearly no difference
}
sw.Stop();
//Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}");
Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [ms]: {sw.ElapsedMilliseconds}");
Console.WriteLine();
}
//
//
sw = new Stopwatch();
var tickCounterStart = Environment.TickCount;
sw.Start();
for (int i = 0; i < max/10; i++)
{
var a = DateTime.Now.Ticks;
}
sw.Stop();
var tickCounter = Environment.TickCount - tickCounterStart;
Console.WriteLine($"Compare that with DateTime.Now.Ticks [ms]: {sw.ElapsedMilliseconds*10}");
Console.WriteLine($"{NewLine}General Stopwatch information:");
if (Stopwatch.IsHighResolution)
Console.WriteLine("- Using high-resolution performance counter for Stopwatch class.");
else
Console.WriteLine("- Using high-resolution performance counter for Stopwatch class.");
double freq = (double)Stopwatch.Frequency;
double ticksPerMicroSec = freq / (1000d*1000d) ; // microsecond resolution: 1 million ticks per sec
Console.WriteLine($"- Stopwatch accuracy- ticks per microsecond (1000 ms): {ticksPerMicroSec:N1}");
Console.WriteLine(" (Max. tick resolution normally is 100 nanoseconds, this is 10 ticks/microsecond.)");
DateTime maxTimeForTickCountInteger= new DateTime(Int32.MaxValue*10_000L); // tickCount means millisec -> there are 10.000 milliseconds in 100 nanoseconds, which is the tick resolution in .NET, e.g. used for TimeSpan
Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}");
// this conversion from seems not really accurate, it will be between 24-25 days.
Console.WriteLine($"{NewLine}Done.");
while (Console.KeyAvailable)
Console.ReadKey(false);
Console.ReadKey();
}
Как было сказано ранее, опрокидывание может произойти через 24,9 дня или, если вы используете безымянный слепок, через 49,8 дня. Поскольку я не хотел использовать pInvoke GetTickCount64, я написал эту компенсацию переполнения. В примере кода используется «байт», чтобы числа были под рукой. Пожалуйста, взгляните на него, он все еще может содержать ошибки:
using System;
namespace ConsoleApp1 {
class Program {
//
static byte Lower = byte.MaxValue / 3;
static byte Upper = 2 * byte.MaxValue / 3;
//
///<summary>Compute delta between two TickCount values reliably, because TickCount might wrap after 49.8 days.</summary>
static short Delta( byte next, byte ticks ) {
if ( next < Lower ) {
if ( ticks > Upper ) {
return (short) ( ticks - ( byte.MaxValue + 1 + next ) );
}
}
if ( next > Upper ) {
if ( ticks < Lower ) {
return (short) ( ( ticks + byte.MaxValue + 1 ) - next );
}
}
return (short) ( ticks - next );
}
//
static void Main( string[] args ) {
// Init
Random rnd = new Random();
int max = 0;
byte last = 0;
byte wait = 3;
byte next = (byte) ( last + wait );
byte step = 0;
// Loop tick
for ( byte tick = 0; true; ) {
//
short delta = Delta( next, tick );
if ( delta >= 0 ) {
Console.WriteLine( "RUN: last: {0} next: {1} tick: {2} delta: {3}", last, next, tick, delta );
last = tick;
next = (byte) ( last + wait );
}
// Will overflow to 0 automatically
step = (byte) rnd.Next( 0, 11 );
tick += step;
max++; if ( max > 99999 ) break;
}
}
}
}
Я не измерял и не сравнивал производительность этого алгоритма компенсации.
Проведя несколько быстрых измерений этой новой функции, я обнаружил (оптимизировано, выпуск 64-бит, циклы 1000mio):
Environment.TickCount: 2265
Environment.TickCount64: 2531
DateTime.UtcNow.Ticks: 69016
Измерения для неоптимизированного кода были аналогичными. Код теста:
static void Main( string[] args ) {
long start, end, length = 1000 * 1000 * 1000;
start = Environment.TickCount64;
for ( int i = 0; i < length; i++ ) {
var a = Environment.TickCount;
}
end = Environment.TickCount64;
Console.WriteLine( "Environment.TickCount: {0}", end - start );
start = Environment.TickCount64;
for ( int i = 0; i < length; i++ ) {
var a = Environment.TickCount64;
}
end = Environment.TickCount64;
Console.WriteLine( "Environment.TickCount64: {0}", end - start );
start = Environment.TickCount64;
for ( int i = 0; i < length; i++ ) {
var a = DateTime.UtcNow.Ticks;
}
end = Environment.TickCount64;
Console.WriteLine( "DateTime.UtcNow.Ticks: {0}", end - start );
}
Кроме того, если вы ДЕЙСТВИТЕЛЬНО используете DateTime для математических вычислений, связанных с датой, всегда используйте DateTime.UtcNow как DateTime.Now подвержен переходу на летнее время ... ваши расчеты могут отличаться на час или, что еще хуже, с отрицательными числами.