Как получить временную метку точности тика в .NET / C #? - PullRequest
60 голосов
/ 13 сентября 2009

До сих пор я использовал DateTime.Now для получения меток времени, но я заметил, что если вы напечатаете DateTime.Now в цикле, вы увидите, что он увеличивается в дискретных скачках прибл. 15 мс Но для определенных сценариев в моем приложении мне нужно получить максимально точную временную метку, желательно с точностью до тика (= 100 нс). Есть идеи?

Обновление:

Очевидно, StopWatch / QueryPerformanceCounter - это путь, но его можно использовать только для измерения времени, поэтому я думал о вызове DateTime.Now, когда приложение запускается, а затем просто запускает StopWatch а затем просто добавьте прошедшее время с StopWatch к начальному значению, возвращенному с DateTime.Now. По крайней мере, это должно дать мне точное относительное время, верно? Что вы думаете об этом (хак)?

Примечание:

StopWatch.ElapsedTicks отличается от StopWatch.Elapsed.Ticks! Я использовал первый, предполагая, что 1 тик = 100 нс, но в этом случае 1 тик = 1 / StopWatch.Frequency. Поэтому для получения тиков, эквивалентных DateTime, используйте StopWatch.Elapsed.Ticks. Я только усвоил это трудным путем.

ПРИМЕЧАНИЕ 2:

Используя подход StopWatch, я заметил, что он не синхронизирован с реальным временем. Примерно через 10 часов он опередил на 5 секунд. Так что я думаю, что нужно будет повторно синхронизировать его каждые X или около того, где X может составлять 1 час, 30 минут, 15 минут и т. Д. Я не уверен, каким будет оптимальное время для повторной синхронизации, поскольку каждая повторная синхронизация будет изменять смещение, которое может быть до 20 мс

Ответы [ 10 ]

56 голосов
/ 13 сентября 2009

Значение системных часов, которое читает DateTime.Now, обновляется только каждые 15 мс или около того (или 10 мс в некоторых системах), поэтому время квантуется за эти интервалы. Существует дополнительный эффект квантования, возникающий из-за того, что ваш код выполняется в многопоточной ОС, и, таким образом, существуют участки, где ваше приложение не является «живым» и, следовательно, не измеряет реальное текущее время.

Поскольку вы ищете сверхточное значение метки времени (а не просто синхронизацию произвольной длительности), класс Stopwatch сам по себе не будет делать то, что вам нужно. Я думаю, что вам придется сделать это самостоятельно с чем-то вроде DateTime / Stopwatch. Когда ваше приложение запускается, вы сохраняете текущее значение DateTime.UtcNow (т. Е. Время грубого разрешения при запуске приложения), а затем также запускаете объект Stopwatch, например:

DateTime _starttime = DateTime.UtcNow;
Stopwatch _stopwatch = Stopwatch.StartNew();

Затем, когда вам понадобится значение DateTime высокого разрешения, вы получите его следующим образом:

DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);

Вам также может понадобиться периодически сбрасывать _starttime и _stopwatch, чтобы не дать полученному времени слишком сильно не синхронизироваться с системным временем (хотя я не уверен, что это действительно произойдет, и это займет много времени, чтобы случиться в любом случае).

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

public class HiResDateTime
{
    private static DateTime _startTime;
    private static Stopwatch _stopWatch = null;
    private static TimeSpan _maxIdle = 
        TimeSpan.FromSeconds(10);

    public static DateTime UtcNow
    {
        get
        {
            if ((_stopWatch == null) || 
                (_startTime.Add(_maxIdle) < DateTime.UtcNow))
            {
                Reset();
            }
            return _startTime.AddTicks(_stopWatch.Elapsed.Ticks);
        }
    }

    private static void Reset()
    {
        _startTime = DateTime.UtcNow;
        _stopWatch = Stopwatch.StartNew();
    }
}

Если вы сбрасываете гибридный таймер через некоторый регулярный интервал (скажем, каждый час или что-то в этом роде), вы рискуете установить время назад до последнего времени чтения, что-то вроде миниатюрной проблемы летнего времени.

20 голосов
/ 22 декабря 2014

Чтобы получить счетчик тиков высокого разрешения, используйте статический Stopwatch.GetTimestamp () - метод:

long tickCount = System.Diagnostics.Stopwatch.GetTimestamp();
DateTime highResDateTime = new DateTime(tickCount);

просто взгляните на исходный код .NET:

    public static long GetTimestamp() {
        if(IsHighResolution) {
            long timestamp = 0;    
            SafeNativeMethods.QueryPerformanceCounter(out timestamp);
            return timestamp;
        }
        else {
            return DateTime.UtcNow.Ticks;
        }   
    }

Исходный код здесь: http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4

19 голосов
/ 17 января 2013

[Принятый ответ не представляется потокобезопасным, и по своему собственному признанию может идти назад во времени, вызывая дублирование меток времени, поэтому этот альтернативный ответ]

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

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long orig, newval;
           do
           {
               orig = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newval = Math.Max(now, orig + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newval, orig) != orig);

           return newval;
       }
   }
}
11 голосов
/ 03 декабря 2014

Эти предложения выглядят слишком сложно! Если вы используете Windows 8 или Server 2012 или выше, используйте GetSystemTimePreciseAsFileTime следующим образом:

[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern void GetSystemTimePreciseAsFileTime(out long filetime);

public DateTimeOffset GetNow()
{
    long fileTime;
    GetSystemTimePreciseAsFileTime(out fileTime);
    return DateTimeOffset.FromFileTime(fileTime);
}

Это имеет намного лучшую точность, чем DateTime.Now без каких-либо усилий.

См. MSDN для получения дополнительной информации: http://msdn.microsoft.com/en-us/library/windows/desktop/hh706895(v=vs.85).aspx

8 голосов
/ 13 сентября 2009

Возвращает самые точные дату и время, известные операционной системе.

Операционная система также обеспечивает более высокое разрешение по времени через QueryPerformanceCounter и QueryPerformanceFrequency (класс .NET Stopwatch). Они позволяют вам определить интервал времени, но не дают даты и времени суток. Вы можете утверждать, что они могли бы дать вам очень точное время и день, но я не уверен, насколько сильно они искажаются в течение длительного интервала.

4 голосов
/ 13 сентября 2009

1). Если вам нужна абсолютная точность высокого разрешения: вы не можете использовать DateTime.Now когда он основан на часах с интервалом 15 мс (если только он можно «сдвинуть» фазу).

Вместо этого внешний источник с лучшим разрешением абсолютный Время точности (например, NTP), t1 ниже, может быть объединено с высоким таймер разрешения (StopWatch / QueryPerformanceCounter).


2). Если вам просто нужно высокое разрешение:

Пример DateTime.Now (t1) один раз вместе со значением из таймер высокого разрешения (StopWatch / QueryPerformanceCounter) (tt0).

Если текущее значение таймера высокого разрешения tt, то текущее время t:

t = t1 + (tt - tt0)

3). Альтернативой может быть распутывание абсолютного времени и порядок финансовых событий: одно значение для абсолютного времени (Разрешение 15 мс, возможно, отключено на несколько минут) и один значение для заказа (например, увеличение значения на единицу каждый время и хранить это). Начальное значение для заказа может быть основанный на некотором глобальном значении системы или быть выведен абсолютное время запуска приложения.

Это решение будет более надежным, поскольку оно не зависит от базовая аппаратная реализация часов / таймеров (может варьироваться в зависимости от системы).

2 голосов
/ 29 декабря 2010

Это слишком много работы для чего-то такого простого. Просто вставьте DateTime в вашу базу данных с торговлей. Затем для получения торгового ордера используйте столбец идентификаторов, значение которого должно увеличиваться.

Если вы вставляете в несколько баз данных и пытаетесь согласовать после факта, то вы будете неправильно рассчитывать торговый ордер из-за небольшого отклонения в времени вашей базы данных (даже с увеличением ns, как вы сказали)

Чтобы решить проблему с несколькими базами данных, вы можете выставить один сервис, по которому каждая сделка попадает, чтобы получить ордер. Служба может вернуть DateTime.UtcNow.Ticks и увеличивающийся номер сделки.

Даже при использовании одного из вышеуказанных решений любой, кто совершает сделки из местоположения в сети с большей задержкой, может разместить сделки в первую очередь (в реальном мире), но из-за задержки они поступают в неправильном порядке. По этой причине сделка должна считаться размещенной в базе данных, а не на консолях пользователей.

2 голосов
/ 13 сентября 2009

Точность 15 мс (на самом деле это может быть 15-25 мс) основана на разрешении таймера Windows 55 Гц / 65 Гц. Это также основной период TimeSlice. Затрагивается Windows.Forms.Timer , Threading.Thread.Sleep , Threading.Timer и т. Д.

Чтобы получить точные интервалы, вы должны использовать Класс секундомера . Он будет использовать высокое разрешение, если доступно. Попробуйте следующие утверждения, чтобы узнать:

Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution);
Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency);
Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency);

Я получаю R = 6E-08 сек, или 60 нс. Это должно быть достаточно для вашей цели.

1 голос
/ 09 марта 2014

Я бы добавил следующее относительно MusiGenesis Ответ за Время повторной синхронизации.

Значение: какое время я должен использовать для повторной синхронизации (_maxIdle в MusiGenesis ответов)

Вы знаете, что с этим решением вы не совсем точны, поэтому вы выполняете повторную синхронизацию. Но то, что вы неявно хотите, - это то же самое, что решение Ian Mercer :

уникальная метка времени, расположенная в строгом порядке возрастания

Следовательно, промежуток времени между двумя повторными синхронизациями (_maxIdle Давайте назовем это SyncTime ) должен быть функцией 4 вещей:

  • DateTime.UtcNow разрешение
  • требуемый коэффициент точности
  • уровень точности, который вы хотите
  • Оценка коэффициента несинхронизации вашей машины

Очевидно, что первое ограничение на эту переменную будет:

коэффициент несинхронизации <= коэффициент точности </strong>

Например: я не хочу, чтобы моя точность была хуже 0,5 с / ч или 1 мс / день и т. Д. (На плохом английском: я не хочу быть более неправильной, чем 0,5 с / ч = 12 с / день).

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

Другим ограничением является минимальное время между двумя повторными синхронизациями:

Synctime> = DateTime.UtcNow разрешение

Здесь точность и точность связаны между собой, потому что если вы используете высокую точность (например, для хранения в БД), но с меньшей точностью, вы можете нарушить оператор Иана Мерсера , который является строгим порядком возрастания.

Примечание: Похоже, DateTime.UtcNow может иметь большее значение по умолчанию Res, чем 15 мс (1 мс на моем компьютере) Перейдите по ссылке: Высокая точность DateTime.UtcNow

Давайте рассмотрим пример:

Представьте себе коэффициент несинхронизации, прокомментированный выше.

Примерно через 10 часов он опередил на 5 секунд.

Скажи, я хочу точность в микросекундах. Мой таймер res составляет 1 мс (см. Примечание выше)

Итак, точка за точкой:

  • DateTime.UtcNow разрешение: 1 мс
  • коэффициент точности> = коэффициент несинхронизации, давайте возьмем максимально точную, так что: коэффициент точности = коэффициент несинхронизации
  • требуемый уровень точности: 1 мкс
  • Оценка коэффициента несинхронизации вашей машины: 0,5 с / час (это тоже моя точность)

Если вы сбрасываете каждые 10 секунд, представьте, что вы на 9,999 с, 1 мс до сброса. Здесь вы делаете звонок в течение этого интервала. Время, в течение которого ваша функция будет отображаться, будет впереди: 0,5 / 3600 * 9,999 с или 1,39 мс. Вы бы отобразили время 10.000390сек. После отметки UtcNow, если вы позвоните в течение 390 микросекунд, ваш номер будет уступать предыдущему. Хуже, если это несинхронизированное отношение является случайным в зависимости от загрузки процессора или других факторов.

Теперь, допустим, я поставил SyncTime на минимальное значение> Я повторно синхронизирую каждые 1 мс

При таком же размышлении я опередил бы меня на 0,139 мкс <ниже, чем желаемая точность. Поэтому, если я вызову функцию в 9,999 мс, то есть за 1 микросекунд до сброса я нанесу график 9,999. И сразу после того, как я буду строить 10.000. У меня будет хороший заказ. </p>

Таким образом, здесь другое ограничение: коэффициент точности x SyncTime <уровень точности, скажем, чтобы быть уверенным, поскольку число может быть округлено до того, что <strong>коэффициент точности x SyncTime <уровень точности / 2 </strong> является хорошим.

Проблема решена.

Итак, быстрое резюме будет:

  • Получить разрешение по таймеру.
  • Вычислить оценку несинхронизированного отношения.
  • коэффициент точности> = оценка несинхронного отношения, Наилучшая точность = коэффициент несинхронизации
  • Выберите уровень точности, учитывая следующее:
  • разрешение по таймеру <= SyncTime <= PrecisionLevel / (2 * коэффициент точности) </strong>
  • Наилучшая точность, которую вы можете достичь, - разрешение по таймеру * 2 * Несинхронизированный коэффициент

Для указанного выше соотношения (0,5 / час) правильный SyncTime будет 3,6 мс, поэтому округляется до 3 мс .

При вышеуказанном соотношении и разрешении таймера 1 мс. Если вам нужен уровень точности в один такт (0,1 микросекунды), вам необходимо соотношение несинхронизации не более: 180 мс / час.

В своем последнем ответе на свой собственный ответ MusiGenesis состояние:

@ Hermann: я проводил аналогичный тест в течение последних двух часов (без коррекции сброса), и таймер на основе секундомера работает только примерно на 400 мс вперед через 2 часа, поэтому сам перекос кажется переменная (но все еще довольно серьезная). Я очень удивлен, что перекос это плохо; Я думаю, именно поэтому секундомер в System.Diagnostics. - MusiGenesis

Таким образом, точность секундомера близка к 200 мс / час, почти к нашим 180 мс / час. Есть ли какая-нибудь связь с тем, почему наш номер и этот номер так близки? Не знаю Но этой точности нам достаточно для достижения Tick-Precision

Наилучший уровень точности: для приведенного выше примера это 0,27 мкс.

Однако что произойдет, если я позвоню несколько раз между 9,999 мс и повторной синхронизацией.

2 обращения к функции могут закончиться тем, что будет возвращен один и тот же TimeStamp, время будет 9,999 для обоих (поскольку я не вижу большей точности). Чтобы обойти это, вы не можете коснуться уровня точности, потому что он связан с SyncTime вышеупомянутым отношением. Таким образом, вы должны реализовать решение Ian Mercer для этих случаев.

Пожалуйста, не стесняйтесь комментировать мой ответ.

0 голосов
/ 13 сентября 2009

Если вам нужна временная метка для выполнения тестов, используйте StopWatch , которая имеет гораздо лучшую точность, чем DateTime.Now.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...