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

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

Update:

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

Примечание:

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

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

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

Ответ 1

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

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

Обновить: поскольку кажется, что секундомер не синхронизируется с системным временем (на полсекунды в час), я думаю, что имеет смысл reset гибридный 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();
    }
}

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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


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

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

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

t = t1 + (tt - tt0)

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

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

Ответ 7

15 ms (на самом деле это может быть 15-25 мс) точность основана на разрешении таймера Windows 55 Hz/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 ns. Этого должно быть достаточно для вашей цели.

Ответ 8

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

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

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

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

Ответ 9

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

Ответ 10

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

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

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

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

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

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

Очевидно, что первое ограничение для этой переменной:

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

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

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

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

Синтаксиs >= DateTime.UtcNow разрешение

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

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

Возьмем пример:

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

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

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

Итак, по пунктам:

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

Если вы reset каждые 10 секунд, представьте, что вы на 9.999s, 1ms до reset. Здесь вы делаете звонок в течение этого интервала. Время, в течение которого ваша функция будет построена, опережает: 0.5/3600 * 9.999s eq 1.39ms. Вы увидите время 10.000390 сек. После отметки UtcNow, если вы сделаете звонок в течение 390 микросекунды, у вас будет число, уступающее предыдущему. Хуже, если это соотношение вне синхронизации является случайным в зависимости от загрузки процессора или других вещей.

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

Выполнение того же мышления поставило бы меня впереди времени на 0.139 микросекунды, уступая той точности, которую я хочу. Поэтому, если я вызываю функцию в 9,999 мс, поэтому 1microsec до reset я буду строить 9.999. И сразу после того, как я заложу 10.000. У меня будет хороший порядок.

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

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

Итак, краткое описание:

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

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

С приведенным выше соотношением и разрешением таймера 1 мс. Если вам нужен уровень с одним тиком Precision level (0.1microsec), вам необходимо иметь коэффициент несинхронизации не более: 180 мс/час.

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

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

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

Лучший прецизионный уровень: для примера выше он 0,27 микросекунда.

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

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

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