Environment.TickCount против DateTime.Now - PullRequest
       32

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

59 голосов
/ 28 октября 2008

Можно ли использовать Environment.TickCount для расчета промежутков времени?

int start = Environment.TickCount;
// Do stuff
int duration = Environment.TickCount - start;
Console.WriteLine("That took " + duration " ms");

Поскольку TickCount подписано и будет перенесено через 25 дней (для достижения всех 32 битов требуется 50 дней, но вы должны отбросить бит со знаком, если вы хотите понять математику), похоже слишком рискованно, чтобы быть полезным.

Вместо этого я использую DateTime.Now. Это лучший способ сделать это?

DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");

Ответы [ 12 ]

86 голосов
/ 15 января 2012

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

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

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

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

Перед использованием 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();
}
67 голосов
/ 28 октября 2008

Использовать класс Секундомер. На 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;
24 голосов
/ 03 июля 2009

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

10 голосов
/ 28 октября 2008

Вы, вероятно, хотите System.Diagnostics.StopWatch.

9 голосов
/ 10 июня 2011

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

8 голосов
/ 28 октября 2008

Использование

System.Diagnostics.Stopwatch

Имеет свойство под названием

EllapsedMilliseconds
6 голосов
/ 11 апреля 2017

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();
}
5 голосов
/ 04 мая 2017

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

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

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

Надежно ли работает QPC на многопроцессорных системах, многоядерных системах и системах с гиперпоточностью?
Да

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

Какие процессоры имеют неинвариантные TSC? [..Читай дальше ..] «

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

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

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

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

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

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

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

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

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

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

Измерено: GetTickCount64 () [мс]: 148
Измерено: Environment.TickCount [мс]: 31 (стоит ли оно того?)
Измерено: DateTime.UtcNow.Ticks [мс]: 76 (!)
Измерено: Секундомер: .ElapsedTicks [мс]: 178
Измерено: секундомер: .ElapsedMilliseconds [мс]: 226
Измерено: статический секундомер. GetTimestamp [мс]: 175
Измерено: Секундомер + преобразование в DateTime [мс]: 246
Сравните это с DateTime.Now.Ticks [мс]: 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();
    }
0 голосов
/ 17 марта 2009

Для синхронизации одним выстрелом еще проще написать

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

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

0 голосов
/ 28 октября 2008

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

...