Environment.TickCount против DateTime.Now

Можно ли использовать 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");

Ответ 2

Environment.TickCount основана на GetTickCount() функции WinAPI. Он в миллисекундах Но фактическая точность составляет около 15,6 мс. Таким образом, вы не можете измерить более короткие интервалы времени (или вы получите 0)

Примечание. Возвращаемое значение - Int32, поэтому этот счетчик перевертывает каждые ~ 49,7 дня. Вы не должны использовать его для измерения таких длинных интервалов.

DateTime.Ticks основана на GetSystemTimeAsFileTime() функции WinAPI. Это в 100s наносекундах (десятых долей микрокосмонов). Фактическая точность DateTime.Ticks зависит от системы. В XP прирост системных часов составляет около 15,6 мс, то же, что и в среде Environment.TickCount. В Windows 7 его точность составляет 1 мс (в то время как Environemnt.TickCount по-прежнему составляет 15,6 мс), однако, если используется схема энергосбережения (обычно на ноутбуках), она может также снизиться до 15,6 мс.

Секундомер основан на QueryPerformanceCounter() Функция WinAPI (но если счетчик производительности с высоким разрешением не поддерживается вашей системой DateTime. Используются клещи)

Перед использованием StopWatch обратите внимание на две проблемы:

  • он может быть ненадежным в многопроцессорных системах (см. MS kb895980, kb896256)
  • он может быть ненадежным, если частота процессора изменяется (прочитайте эту статью).

Вы можете оценить точность своей системы с помощью простого теста:

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();
}

Ответ 3

Почему вас беспокоит опрокидывание? Пока продолжительность, которую вы измеряете, составляет менее 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, позволяет процессору справиться с этим.

Это обычная ситуация, с которой я столкнулся раньше, с учетом времени во встроенных системах. Я бы никогда не сравнил это, например, послепродажного обслуживания. Я всегда выполнял вычитание, чтобы найти продолжительность, которая учитывает опрокидывание, а затем основывать любые другие вычисления на длительности.

Ответ 5

Если вы ищете функциональность Environment.TickCount, но без накладных расходов на создание новых объектов Stopwatch, вы можете использовать статический метод Stopwatch.GetTimestamp() (вместе с Stopwatch.Frequency) для вычисления длинных промежутков времени. Поскольку GetTimestamp() возвращает long, он не будет переполняться очень и очень долго (более 100 000 лет на машине, которую я использую, чтобы написать это). Он также намного точнее, чем Environment.TickCount, который имеет максимальное разрешение от 10 до 16 миллисекунд.

Ответ 6

Используйте

System.Diagnostics.Stopwatch

Он имеет свойство, называемое

EllapsedMilliseconds

Ответ 7

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();
}

Ответ 8

Ниже приведено обновленное и обновленное резюме наиболее полезных ответов и комментариев в этом потоке + дополнительные критерии и варианты:

Прежде всего: как отмечали другие в комментариях, в последние годы все изменилось, и с "современными" Windows (Win XP ++) и .NET, а также современным оборудованием нет или нет причин не использовать секундомер(). Подробнее см. MSDN. Котировки:

"На точность QPC влияет изменение частоты процессора, вызванное управлением питанием или технологией Turbo Boost?
Нет. Если процессор имеет инвариантный TSC, QPC не подвержен этим изменениям. Если процессор не имеет инвариантного TSC, QPC вернется к аппаратным таймерам платформы, на которые не будут влиять изменения частоты процессора или технология Turbo Boost.

Защищает ли QPC многопроцессорные системы, многоядерные системы и системы с гиперпотоком?
Да

Как определить и подтвердить, что QPC работает на моей машине?
Вам не нужно выполнять такие проверки.

Какие процессоры имеют неинвариантные TSC? [..Подробнее..]"

Но если вам не нужна точность секундомера() или, по крайней мере, хотите точно знать о производительности секундомера (статический или основанный на экземплярах) и других возможных вариантах, продолжайте читать:

Я взял тест выше из cskwg и расширил код для большего количества вариантов. Я измерил несколько лет i7 4700 MQ и С# 7 с VS 2017 (точнее, скомпилированный с .NET 4.5.2, несмотря на бинарные литералы, это С# 6 (используется для этого: строковые литералы и "использование статических '). Особенно заметно, что производительность секундомера() улучшена по сравнению с упомянутым эталоном.

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

32 бит, режим освобождения без оптимизации:

Измерено: GetTickCount64() [ms]: 275
Измерено: Environment.TickCount [ms]: 45
Измерено: DateTime.UtcNow.Ticks [ms]: 167
Измерено: Секундомер:.ElapsedTicks [мс]: 277
Измерено: Секундомер:.ElapsedMilliseconds [ms]: 548
Измерено: статическое секундомер .GetTimestamp [мс]: 193
Измерено: секундомер + преобразование в DateTime [мс]: 551
Сравните это с DateTime.Now.Ticks [ms]: 9010

32 бит, режим деблокирования, оптимизирован:

Измерено: GetTickCount64() [ms]: 198
Измерено: Environment.TickCount [ms]: 39
Измерено: DateTime.UtcNow.Ticks [ms]: 66 (!)
Измерено: Секундомер:.ElapsedTicks [мс]: 175
Измерено: Секундомер:.ElapsedMilliseconds [ms]: 491
Измерено: статический секундомер .GetTimestamp [мс]: 175
Измерено: секундомер + преобразование в DateTime [мс]: 510
Сравните это с DateTime.Now.Ticks [ms]: 8460

64 бит, режим выпуска без оптимизации:

Измерено: GetTickCount64() [ms]: 205
Измерено: Environment.TickCount [ms]: 39
Измерено: DateTime.UtcNow.Ticks [ms]: 127
Измерено: Секундомер:.ElapsedTicks [мс]: 209
Измерено: Секундомер:.ElapsedMilliseconds [ms]: 285
Измерено: статическое секундомер .GetTimestamp [мс]: 187
Измерено: секундомер + преобразование в DateTime [мс]: 319
Сравните это с DateTime.Now.Ticks [ms]: 3040

64 бит, режим деблокирования, оптимизирован:

Измерено: GetTickCount64() [ms]: 148
Измерено: Environment.TickCount [ms]: 31 (стоит ли еще?)
Измерено: DateTime.UtcNow.Ticks [ms]: 76 (!)
Измерено: Секундомер:.ElapsedTicks [ms]: 178
Измерено: Секундомер:.ElapsedMilliseconds [ms]: 226
Измерено: статический секундомер .GetTimestamp [мс]: 175
Измерено: Секундомер + преобразование в DateTime [мс]: 246
Сравните это с DateTime.Now.Ticks [ms]: 3020

Может быть очень интересно, что создание значения DateTime для печати времени секундомера, похоже, почти не требует затрат. Интересным в более академичном, чем практический образом, является то, что статический секундомер немного быстрее (как и ожидалось). Некоторые точки оптимизации весьма интересны. Например, я не могу объяснить, почему Stopwatch.ElapsedMilliseconds только с 32-битным настолько медленным по сравнению с ним другими вариантами, например статическими. Это и 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();
    }
    [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();
    }

Ответ 9

Вместо этого вы должны использовать Секундомер.

Ответ 10

Я использую Environment.TickCount, потому что:

  • Класс Stopwatch отсутствует в Compact Framework.
  • Секундомер использует тот же основной механизм синхронизации, что и TickCount, поэтому результаты не будут более или менее точными.
  • Проблема с оберткой с TickCount вряд ли удастся поразить (вам придется оставить свой компьютер на 27 дней, а затем попытаться измерить время, которое только что произойдет, чтобы охватить обертку -рабочий момент), и даже если вы ударили его, результатом будет огромный отрицательный временной интервал (так что это будет выделяться).

При этом я также рекомендую использовать секундомер, если он вам доступен. Или вы можете занять около 1 минуты и написать класс, похожий на секундомер, который обертывает Environment.TickCount.

Кстати, я ничего не вижу в документации Секундомера, в которой упоминается проблема обертывания с основным механизмом таймера, поэтому я не удивлюсь, чтобы обнаружить, что Секундомер страдает от той же проблемы. Но опять же, я не стал бы тратить время на это.

Ответ 11

Я собирался сказать об этом в класс секундомера, но Грзенио уже сказал правильно, поэтому я дам ему всплеск. Такая инкапсуляция определяет решение о том, какой путь лучше, и это может измениться во времени. Я помню, как я был шокирован тем, насколько дорого он может получить время на некоторых системах, поэтому иметь одно место, которое может реализовать наилучшую технику, может быть очень важным.

Ответ 12

Для однократного отсчета времени еще проще записать

Stopwatch stopWatch = Stopwatch.StartNew();
...dostuff...
Debug.WriteLine(String.Format("It took {0} milliseconds",
                              stopWatch.EllapsedMilliseconds)));

Я бы предположил, что космически маловероятный обход в TickCount еще менее беспокоит StopWatch, учитывая, что поле ElapsedTicks длинное. На моей машине StopWatch имеет высокое разрешение - 2.4e9 тиков в секунду. Даже в этом случае потребовалось бы более 121 года, чтобы переполнить поле клещей. Конечно, я не знаю, что происходит под крышками, так что возьми это с солью. Тем не менее, я замечаю, что в документации для StopWatch даже не упоминается проблема с оберткой, в то время как документ для TickCount делает.