Как получить временную метку точности тиков в.NET / C#?

До сих пор я использовал 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 мс

11 ответов

Решение

Значение системных часов, которые 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();
    }
}

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

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

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

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

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

Эти предложения выглядят слишком сложно! Если вы используете 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

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

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

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

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


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

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

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

t = t1 + (tt - tt0)

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

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

Точность 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 нс. Это должно быть достаточно для вашей цели.

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

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

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

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

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

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

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

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

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

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

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

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

Например: я не хочу, чтобы моя точность была хуже 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. У меня будет хороший заказ.

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

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

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

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

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

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

Думаю, это лучший способ решить эту проблему:

      long timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
Другие вопросы по тегам