Как мы определяем «квант» для DateTime.Now, то есть наименьший интервал между тиками? - PullRequest
2 голосов
/ 08 октября 2011

Есть ли способ определить наименьшее время, прошедшее между двумя тиками DateTime.Now?Я мог бы рассчитать время с помощью секундомера, но есть ли какой-нибудь способ для операционной системы сообщить об этом в .NET?

Другими словами - насколько точен DateTime.Now?

Обновление

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

Update

Несколько человек спросили, зачем вообще использовать DateTime, если вы сравниваете два раза.

Причина в том, что StopWatch будет медленно дрейфовать в течение часов или дней, пока вашвремя составляет секунд out.

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

Таким образом, у меня есть:

  • Точность микросекунды при сравнении времен A и B с разницей в несколько секунд;
  • Точность миллисекунды при сравнении времен A и B, которыеразнесены дни.

В моем классе таймеров, если два раза сравниваются для вычисления дельты, точка переключения определяется метрикой, которая учитывает System.Runtime.IOThreadTimer.GetSystemTimeResolution().

Абсолютная ошибка дельты между двумя значениями A и B:

  • Начинается с точностью до микросекунды;
  • Смещается в течение нескольких часов или дней до максимум из System.Runtime.IOThreadTimer.GetSystemTimeResolution(), что составляет несколько десятков миллисекунд.

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

Обновление

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

Вот бета-код, который у меня есть:

    using System;
    using System.Diagnostics;
    using System.IO;
    using MyLogType;
    using ProtoBuf;

    namespace DateTimePrecisionNamespace
    {               
        /// <summary>
        /// This class returns a precision time.
        /// This class combines the best of both worlds, both precision and accuracy.
        /// - It contains a DateTime.Now, which gives us good absolute time during the day.
        /// - It contains the output from a stopwatch, in ticks, which gives us good relative time during the day.
        /// - It contains the ticks per second of the stopwatch, which means this time record is portable across multiple PC architectures,
        ///   and we can easily tell how accurate the original time was.
        /// Class always deals in non-UTC, this is a design decision as we are working with Eurex and we want to reduce the possibility of errors.
        /// Class is serialized using Google Protocol Buffers, so any files created by this serializer are highly compatible, and can be read by:
        /// - Future versions of .NET (ISerializable is only guaranteed to support the current version of .NET).
        /// - Other .NET languages such as Python .NET, etc.
        /// - Other non-.NET languages such as C++, Java, Python, etc.
        /// - Other hardware platforms such as Linux, Mac, etc.
        /// - Other endians (what if the platform is BigEndian or LittleEndian?).
        /// - Future versions of the struct which may add additional fields or change existing fields 
        ///   (the numbering of elements means its backwards and fowards compatible without necessarily breaking anything).
        /// </summary>
        [ProtoContract] // Serializable with Google Protocol Buffers, see protobuf-net.
        public struct MyDateTimePrecision : IEquatable<MyDateTimePrecision>
        {
            [ProtoMember(1)]
            public DateTime MyDateTime;

            // Debug: display the expiration date as a string.
            public string MyDateTimeAsString { get { return MyDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff"); } }
            public long MyDateTimeAsTicks { get { return MyDateTime.Ticks; } }

            [ProtoMember(2)]
            public int StopwatchTicksPerSecondConstant;

            [ProtoMember(3)]
            public long StopwatchTicksSinceProgramStart;


            public MyDateTimePrecision(DateTime myDateTime, Int64 stopwatchTicksSinceProgramStart)
            {
                MyDateTime = myDateTime;

                // This is always a constant. We need to embed this metric in the timestamp so this is portable to different PC hardware in the future.
                StopwatchTicksPerSecondConstant = MyDateTimePrecisionStatic.MyGetStopwatchTicksPerSecondConstant;

                StopwatchTicksSinceProgramStart = stopwatchTicksSinceProgramStart;          
            }

            public MyDateTimePrecision(DateTime myDateTime, Int32 stopwatchTicksPerSecondConstant, Int64 stopwatchTicksSinceProgramStart)
            {
                MyDateTime = myDateTime;

                // This is always a constant. We need to embed this metric in the timestamp so this is portable to different PC hardware in the future.
                StopwatchTicksPerSecondConstant = stopwatchTicksPerSecondConstant;

                StopwatchTicksSinceProgramStart = stopwatchTicksSinceProgramStart;
            }

            /// <summary>
            /// Returns the current precision time.
            /// </summary>
            public static MyDateTimePrecision Now
            {
                get
                {
                    return new MyDateTimePrecision(
                        MyDateTimePrecisionStatic.MyGetDateTime,
                        MyDateTimePrecisionStatic.MyGetStopwatchTicksPerSecondConstant,
                        MyDateTimePrecisionStatic.MyGetStopwatchTicksSinceProgramStart);    
                }           
            }

            /// <summary>
            /// Returns the current time, in ticks, since the program has started.
            /// </summary>
            public static long NowTicksSinceProgramStart
            {
                get { return MyDateTimePrecisionStatic.MyGetStopwatchTicksSinceProgramStart; }
            }

            /// <summary>
            /// Returns the the amount of ticks per second, as a constant.
            /// </summary>
            public static long NowTicksPerSecondConstant
            {
                get { return MyDateTimePrecisionStatic.MyGetStopwatchTicksPerSecondConstant; }
            }

            /// <summary>
            /// Returns the relative time, in seconds since the class was instantiated.
            /// This method is only used to gauge the difference between two points in time, accurate to 300ns.
            /// To get the absolute time, use DateTimeUtcAbsolute.
            /// </summary>
            public double SecondsRelative
            {
                get
                {
                    return ((double)StopwatchTicksSinceProgramStart/StopwatchTicksPerSecondConstant);   
                }           
            }

            #region Google Protocol Buffers serializer.
            /// <summary>
            /// Serialize using Google Protocol Buffers.
            /// </summary>
            public byte[] SerializeUsingProtobuf()
            {
                byte[] data;
                using (var ms = new MemoryStream())
                {
                    Serializer.Serialize(ms, this);
                    data = ms.ToArray();
                }
                return data;
            }
            #endregion

            #region Google Protocol Buffers deserializer.
            /// <summary>
            /// Deserialize using Google Protocol Buffers.
            /// </summary>
            public static MyDateTimePrecision DeserializeUsingProtobuf(byte[] data)
            {
                MyDateTimePrecision result;
                using (var ms = new MemoryStream(data))
                {
                    result = Serializer.Deserialize<MyDateTimePrecision>(ms);
                }
                return result;
            }
            #endregion

            #region SerializeUsingPointers
            /// <summary>
            /// Serialize using pointers, and raw binary format.
            /// This method is blindingly fast, but not guaranteed to be compatible with anything other than the current version of the .NET runtime.
            /// </summary>
            public byte[] SerializeUsingPointers()
            {
                unsafe
                {
                    const int bufferLength = 8+4+8;
                    byte[] buffer = new byte[bufferLength];
                    fixed (byte* constPointerToBufferStart = buffer)
                    {
                        byte* pointerToBuffer = constPointerToBufferStart;

                        (*(Int64*)pointerToBuffer) = this.MyDateTime.ToBinary();
                        pointerToBuffer += sizeof(Int64);

                        (*(Int32*) pointerToBuffer) = this.StopwatchTicksPerSecondConstant;
                        pointerToBuffer += sizeof(Int32);

                        (*(Int64*)pointerToBuffer) = this.StopwatchTicksSinceProgramStart;
    #if UNITTEST
                        pointerToBuffer += sizeof(Int64);
                        if (pointerToBuffer - constPointerToBufferStart != bufferLength)
                        {
                            MyLog.LogFatalAndThrowAndExit("Error E20111004-1731. Buffer is not the expected length within SerializeUsingPointers.\n");
                        }
    #endif
                    }
                    return buffer;
                }               
            }
            #endregion

            /// <summary>
            /// Deserialize using pointers.
            /// This method is blindingly fast, but not guaranteed to be compatible with anything other than the current version of the .NET runtime.
            /// </summary>
            public static MyDateTimePrecision DeserializeUsingPointers(byte[] buffer)
            {
                MyDateTimePrecision result;
    #if UNITTEST
                const int bufferLength = 8 + 4 + 8;
    #endif
                unsafe
                {
                    fixed (byte* constPointerToBufferStart = buffer)
                    {
                        byte* pointerToBuffer = constPointerToBufferStart;

                        result.MyDateTime = DateTime.FromBinary((*(Int64*)pointerToBuffer));
                        pointerToBuffer += sizeof(Int64);

                        result.StopwatchTicksPerSecondConstant = (*(Int32*)pointerToBuffer);
                        pointerToBuffer += sizeof(Int32);

                        result.StopwatchTicksSinceProgramStart = (*(Int64*)pointerToBuffer);
    #if UNITTEST
                        pointerToBuffer += sizeof(Int64);
                        if ((pointerToBuffer - constPointerToBufferStart != buffer.Length) || (buffer.Length != bufferLength))
                        {
                            MyLog.LogFatalAndThrowAndExit("Error E20111004-1732. Buffer is not the expected length within DeserializeUsingPointers.\n");
                        }
    #endif
                    }           
                }
                return result;
            }

            /// <summary>
            /// Checksum for the data contained in this structure, based on SerializeUsingPointers.
            /// </summary>
            /// <returns>Checksum.</returns>
            public long ChecksumFromProtobuf()
            {
                return SerializeUsingProtobuf().MyToChecksum();
            }

            /// <summary>
            /// Checksum for the data contained in this structure, based on XORing the contents of this structure.
            /// </summary>
            /// <returns>Checksum.</returns>
            public long ChecksumFromXor()
            {
                return this.MyDateTime.Ticks
                       ^ this.StopwatchTicksPerSecondConstant
                       ^ this.StopwatchTicksSinceProgramStart;
            }

            /// <summary>
            /// Indicates whether the current object is equal to another object of the same type.
            /// </summary>
            /// <returns>
            /// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
            /// </returns>
            /// <param name="other">An object to compare with this object.</param>
            public bool Equals(MyDateTimePrecision other)
            {
                return other.MyDateTime.Equals(MyDateTime) && other.StopwatchTicksPerSecondConstant == StopwatchTicksPerSecondConstant && other.StopwatchTicksSinceProgramStart == StopwatchTicksSinceProgramStart;
            }

            /// <summary>
            /// Override operator == to compare two MyDateTimePrecision variables.
            /// </summary>
            /// <param name="c1">First MyDateTimePrecision.</param>
            /// <param name="c2">Second MyDateTimePrecision.</param>
            /// <returns>True if equal, false if not.</returns>
            public static bool operator ==(MyDateTimePrecision c1, MyDateTimePrecision c2)
            {
                return c1.Equals(c2);
            }

            /// <summary>
            /// Override operator != to compare two MyDateTimePrecision variables.
            /// </summary>
            /// <param name="c1">First MyDateTimePrecision.</param>
            /// <param name="c2">Second MyDateTimePrecision.</param>
            /// <returns>True if not equal, false if equal.</returns>
            public static bool operator !=(MyDateTimePrecision c1, MyDateTimePrecision c2)
            {
                return !c1.Equals(c2);
            }

            /// <summary>
            /// Print out both the absolute and the relative time.
            /// Useful, as the debugger in Visual Studio displays this string when you observe the variable.
            /// </summary>
            /// <returns>The string.</returns>
            public new string ToString()
            {
                return String.Format("Abs:{0:yyyy-MM-dd HH:mm:ss},Rel:{1:0.000000}sec", MyDateTime, SecondsRelative);
            }

            #region Unit test.
            /// <summary>
            /// Unit test for this entire class.
            /// </summary>
            /// <returns>False if there is no errors (false is the default for all new unit tests; saves typing).</returns>
            public static bool Unit()
            {
                // Check serialization using pointers.
                {
                    MyDateTimePrecision first = MyDateTimePrecision.Now;
                    MyDateTimePrecision second = first;
                    Debug.Assert(first == second);

                    {
                        byte[] sFirst = first.SerializeUsingPointers();
                        MyDateTimePrecision third = MyDateTimePrecision.DeserializeUsingPointers(sFirst);
                        Debug.Assert(first == third);
                        Debug.Assert(first.ChecksumFromProtobuf() == third.ChecksumFromProtobuf());                 
                    }

                    {
                        byte[] sFirst = first.SerializeUsingProtobuf();
                        MyDateTimePrecision third = MyDateTimePrecision.DeserializeUsingProtobuf(sFirst);
                        Debug.Assert(first == third);
                        Debug.Assert(first.ChecksumFromProtobuf() == third.ChecksumFromProtobuf());                 
                    }

                    {
                        try
                        {
                            byte[] sFirst = first.SerializeUsingProtobuf();
                            MyDateTimePrecision third = MyDateTimePrecision.DeserializeUsingPointers(sFirst);
                            // Program should never get to here as this should throw an error for an unknown buffer length.
                            Debug.Assert(true == false);
                        }
                        catch (Exception)
                        {   
                            // Program should get to here.
                            Debug.Assert(true);
                        }
                    }

                    {
                        MyDateTimePrecision third = MyDateTimePrecision.Now;
                        Debug.Assert(first != third);
                        Debug.Assert(first.ChecksumFromProtobuf() != third.ChecksumFromProtobuf());
                    }
                }
                return false;
            }
            #endregion

            #region Windows serializer.
            /*
            /// <summary>
            /// Serialize this object into a string.
            /// Observe that this method creates binary code that is only portable within the same version of .NET.
            /// Recommend using a faster serializer that is language, hardware, and .NET version independent, such as Google Protocol Buffers (see protobuf-net).
            /// </summary>
            /// <returns></returns>
            public string SerializeToString()
            {
                MyDateTimePrecision obj = this;
                string result;
                IFormatter formatter = new BinaryFormatter();
                using (Stream stream = new MemoryStream())
                {
                    formatter.Serialize(stream, obj);

                    result = stream.ToString();
                }
                return result;
            }

            /// <summary>
            /// Serialize this object into a byte array.
            /// Observe that this method creates binary code that is only portable within the same version of .NET.
            /// Recommend using a faster that is language, hardware, and .NET version independent, such as Google Protocol Buffers (see protobuf-net).
            /// </summary>
            /// <returns></returns>
            public byte[] SerializeToByteArray()
            {
                MyDateTimePrecision obj = this;
                byte[] bytes;
                IFormatter formatter = new BinaryFormatter();
                using (MemoryStream stream = new MemoryStream())
                {
                    formatter.Serialize(stream, obj);

                    bytes = stream.ToArray();
                }
                return bytes;
            }
            */
            #endregion

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (obj.GetType() != typeof (MyDateTimePrecision)) return false;
                return Equals((MyDateTimePrecision) obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    int result = MyDateTime.GetHashCode();
                    result = (result*397) ^ StopwatchTicksPerSecondConstant;
                    result = (result*397) ^ StopwatchTicksSinceProgramStart.GetHashCode();
                    return result;
                }
            }
        }       

                    /// <summary>
        /// This class starts a stopwatch when the program starts. We can query this value in MyDateTimePrecision.
        /// </summary>
        static public class MyDateTimePrecisionStatic
        {
            /// <summary>
            /// When this static class is instantiated for the first time (once on program start), start the stopwatch.
            /// This stopwatch is accurate to 300ns, unlike DateTime which is accurate to only 30ms.
            /// </summary>
            private static readonly Stopwatch stopwatchSinceClassInstantiated;
            static MyDateTimePrecisionStatic()
            {
                stopwatchSinceClassInstantiated = new Stopwatch();
                stopwatchSinceClassInstantiated.Start();
            }       

            /// <summary>
            /// Return current time, non-UTC.
            /// </summary>
            public static DateTime MyGetDateTime
            {
                get { return DateTime.Now; }
            }

            /// <summary>
            /// Return the number of ticks per second in the stopwatch.
            /// </summary>
            public static int MyGetStopwatchTicksPerSecondConstant
            {
                // We can safely downcast this to int. Typically its ~3.3 million on an Intel i7, its unlikely to get beyond int.Max on PC hardware anytime soon.
                get { return (int)Stopwatch.Frequency; }
            }

            /// <summary>
            /// Return the number of ticks since the program has started (or this static class has been instantiated).
            /// </summary>
            public static long MyGetStopwatchTicksSinceProgramStart
            {
                get { return stopwatchSinceClassInstantiated.ElapsedTicks; }
            }

            /// <summary>
            /// Return timespan since the program has started (or this static class has been instantied).
            /// </summary>
            public static TimeSpan MyGetTimespanSinceProgramStart
            {
                get { return stopwatchSinceClassInstantiated.Elapsed; }
            }
        }
    }

Ответы [ 3 ]

7 голосов
/ 08 октября 2011

Если вам небезразличен уровень точности, то вам не следует использовать DateTime.Now в первую очередь. Это полезно для хронометража в человеческом масштабе вещей, например, «пришло ли время» пойти домой и посмотреть доктора Кто? и бесполезно для таких вещей, как "пора ли повторно синхронизировать растровую линию на этом ЭЛТ?"

Консервативное предположение состоит в том, что он точен с точностью до четверти секунды. Точность или нет зависит исключительно от того, насколько точно вы установили системные часы и как часто вы сверяете их с известным источником точного времени; это не имеет ничего общего с точностью DateTime.Now.

Чтобы ответить на ваш вопрос: точность DateTime.Now обычно составляет около 16 миллисекунд, но она может варьироваться от машины к машине.

4 голосов
/ 08 октября 2011

Существует внутренний метод .NET 4, который получает это, System.Runtime.IOThreadTimer.GetSystemTimeResolution () в сборке System.Runtime.DurableInstancing.Ничего, что вы могли бы получить для себя, но вы можете использовать те же функции winapi, которые он использует.GetSystemTimeAdjustment (), аргумент lpTimeIncrement возвращает интервал между тактами прерывания такта с шагом 100 наносекунд.На большинстве компьютеров значение по умолчанию составляет 1/64 секунды.

[DllImport("kernel32.dll")]
private static extern bool GetSystemTimeAdjustment(out uint lpTimeAdjustment,
   out uint lpTimeIncrement, out bool lpTimeAdjustmentDisabled);
4 голосов
/ 08 октября 2011

У меня нет источника точности DateTime.Now, но известно, что он хуже точности Класс секундомера . Если вы хотите измерить промежуток времени между двумя моментами времени, вы всегда должны использовать класс секундомера. Вы можете определить точность класса секундомера в вашей системе, проверив Поле секундомера. Частота .

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