C # DateTime ตอนนี้แม่นยำ


97

ฉันเพิ่งพบพฤติกรรมที่ไม่คาดคิดบางอย่างกับ DateTime.UtcNow ขณะทำการทดสอบหน่วย ดูเหมือนว่าเมื่อคุณเรียก DateTime.Now/UtcNow อย่างต่อเนื่องดูเหมือนว่าจะให้ค่าเดิมกลับมาเป็นช่วงเวลาที่นานกว่าที่คาดไว้แทนที่จะจับภาพทีละมิลลิวินาทีที่แม่นยำยิ่งขึ้น

ฉันรู้ว่ามีคลาสนาฬิกาจับเวลาที่เหมาะกว่าสำหรับการวัดเวลาที่แม่นยำ แต่ฉันอยากรู้ว่ามีใครสามารถอธิบายพฤติกรรมนี้ใน DateTime ได้หรือไม่? มีเอกสารความแม่นยำอย่างเป็นทางการสำหรับ DateTime หรือไม่ตอนนี้ (ตัวอย่างเช่นแม่นยำภายใน 50 ms?) เหตุใด DateTime.Now จึงมีความแม่นยำน้อยกว่าที่นาฬิกา CPU ส่วนใหญ่สามารถจัดการได้ อาจจะออกแบบมาสำหรับ CPU ตัวหารร่วมที่ต่ำที่สุด?

public static void Main(string[] args)
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int i=0; i<1000; i++)
    {
        var now = DateTime.Now;
        Console.WriteLine(string.Format(
            "Ticks: {0}\tMilliseconds: {1}", now.Ticks, now.Millisecond));
    }

    stopwatch.Stop();
    Console.WriteLine("Stopwatch.ElapsedMilliseconds: {0}",
        stopwatch.ElapsedMilliseconds);

    Console.ReadLine();
}

ช่วงเวลาที่คุณได้รับค่าเดิมกลับมาคือเท่าไร?
ChrisF


เมื่อฉันรันโค้ดด้านบนฉันได้รับค่าเฉพาะ 3 ค่าสำหรับเห็บและมิลลิวินาทีและเวลาในการรับชมสุดท้ายคือ 147 มิลลิวินาทีดังนั้นดูเหมือนว่าบนเครื่องของฉันแม่นยำแค่ประมาณ 50 มิลลิวินาทีเท่านั้น ...
Andy White

ฉันควรจะบอกว่าลูปทำงานหลายครั้ง แต่ฉันเห็นค่าที่แตกต่างกันเพียง 3 ค่าเท่านั้น ...
Andy White

สำหรับใครก็ตามที่มาที่นี่นี่คือ TL; DR // ใช้ฟังก์ชัน QueryPerformanceCounter "ดึงค่าปัจจุบันของตัวนับประสิทธิภาพซึ่งเป็นการประทับเวลาความละเอียดสูง (<1us) ที่สามารถใช้สำหรับการวัดช่วงเวลา" (สำหรับโค้ดที่มีการจัดการคลาส System.Diagnostics.Stopwatch ใช้ QPC เป็นพื้นฐานเวลาที่แม่นยำ) msdn.microsoft.com/en-us/library/windows/desktop/…
AnotherUser

คำตอบ:


186

เหตุใด DateTime.Now จึงมีความแม่นยำน้อยกว่าที่นาฬิกา CPU ส่วนใหญ่สามารถจัดการได้

นาฬิกาที่ดีควรจะเป็นได้ทั้งที่แม่นยำและถูกต้อง ; ที่แตกต่างกัน เมื่อพูดถึงเรื่องตลกเก่า ๆ นาฬิกาที่หยุดทำงานจะมีความแม่นยำวันละสองครั้งนาฬิกาที่เดินช้านาทีละหนึ่งนาทีจะไม่เที่ยงตรงตลอดเวลา แต่นาฬิกาที่เดินช้าไปหนึ่งนาทีนั้นจะแม่นยำถึงนาทีที่ใกล้ที่สุดเสมอในขณะที่นาฬิกาที่หยุดนิ่งไม่มีความแม่นยำที่เป็นประโยชน์เลย

เหตุใด DateTime จึงต้องแม่นยำกล่าวว่าหนึ่งไมโครวินาทีในเมื่อไม่สามารถแม่นยำถึงไมโครวินาทีได้ คนส่วนใหญ่ไม่มีแหล่งที่มาสำหรับสัญญาณเวลาอย่างเป็นทางการที่แม่นยำถึงไมโครวินาที ดังนั้นการให้ตัวเลขหกหลักหลังจากที่ทศนิยมตำแหน่งของความแม่นยำ , ช่วงห้าซึ่งเป็นขยะที่จะโกหก

โปรดจำไว้ว่าจุดประสงค์ของการ DateTime คือการแสดงวันที่และเวลา การกำหนดเวลาที่มีความแม่นยำสูงไม่ได้เป็นไปตามวัตถุประสงค์ของ DateTime ดังที่คุณทราบนั่นคือจุดประสงค์ของ StopWatch วัตถุประสงค์ของ DateTime คือการแสดงวันที่และเวลาเพื่อจุดประสงค์เช่นการแสดงเวลาปัจจุบันให้กับผู้ใช้คำนวณจำนวนวันจนถึงวันอังคารหน้าและอื่น ๆ

พูดสั้น ๆ ว่า "กี่โมงแล้ว" และ "ใช้เวลานานแค่ไหน" เป็นคำถามที่แตกต่างกันอย่างสิ้นเชิง อย่าใช้เครื่องมือที่ออกแบบมาเพื่อตอบคำถามหนึ่งเพื่อตอบคำถามอื่น ๆ

ขอบคุณสำหรับคำถาม; นี่จะเป็นบทความบล็อกที่ดี! :-)


2
@ เอริกลิปเพิร์ต: เรย์มอนด์เฉินมีแฟนเก่า แต่เป็นคนดีในหัวข้อของความแตกต่างระหว่าง "ความแม่นยำ" และ "ความแม่นยำ": blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
สัน

3
โอเคเป็นประเด็นที่ดีเกี่ยวกับความแม่นยำเทียบกับความแม่นยำ ฉันเดาว่าฉันยังคงไม่ซื้อข้อความที่ว่า DateTime ไม่ถูกต้องเพราะ "ไม่จำเป็นต้องเป็น" ถ้าฉันมีระบบธุรกรรมและฉันต้องการทำเครื่องหมายวันที่และเวลาสำหรับแต่ละระเบียนสำหรับฉันแล้วดูเหมือนว่าจะใช้งานคลาส DateTime ได้ง่าย แต่ดูเหมือนว่าจะมีส่วนประกอบเวลาที่ถูกต้อง / แม่นยำมากกว่าใน. NET ดังนั้นทำไม DateTime จึงเป็น ทำให้มีความสามารถน้อยลง ฉันเดาว่าฉันจะต้องอ่านเพิ่มเติม ...
Andy White

11
ตกลง @Andy สมมติว่าคุณมีระบบดังกล่าว ในเครื่องหนึ่งเครื่องคุณทำเครื่องหมายธุรกรรมว่าเกิดขึ้นในวันที่ 1 มกราคมเวลา 12: 34: 30.23498273 ในเครื่องอื่นในคลัสเตอร์ของคุณคุณทำเครื่องหมายธุรกรรมว่าเกิดขึ้นในวันที่ 1 มกราคม 12: 34: 30.23498456 ธุรกรรมใดเกิดขึ้นก่อน หากคุณไม่ทราบว่านาฬิกาทั้งสองเครื่องจะซิงโครไนซ์ภายในหน่วยไมโครวินาทีของกันและกันคุณจะไม่รู้ว่านาฬิกาใดเกิดขึ้นก่อน ความแม่นยำเป็นพิเศษทำให้เข้าใจผิดขยะ ถ้าฉันมีวิธีของฉัน DateTimes ทั้งหมดจะถูกปัดเศษเป็นวินาทีที่ใกล้ที่สุดเหมือนใน VBScript
Eric Lippert

14
ไม่ว่าเรื่องนี้จะแก้ไขปัญหาที่คุณพูดถึงตั้งแต่พีซีหมู่เฉลี่ยมักจะออกโดยนาที ตอนนี้ถ้าการปัดเศษเป็น 1s ไม่ได้แก้ปัญหาอะไรแล้วทำไมต้องปัดเลย กล่าวอีกนัยหนึ่งฉันไม่ได้ติดตามข้อโต้แย้งของคุณว่าเหตุใดค่าสัมบูรณ์จึงควรมีความแม่นยำน้อยกว่าความแม่นยำของการวัดเวลาเดลต้า
Roman Starkov

3
สมมติว่าฉันกำลังสร้างบันทึกกิจกรรมที่ต้องการให้ (1) รู้ว่ามีอะไรเกิดขึ้นในแง่ของพื้นที่ปฏิทิน (ภายในไม่กี่วินาที) (2) รู้ระยะห่างระหว่างเหตุการณ์ที่แน่นอน (ภายใน 50 หรือมิลลิวินาที) ดูเหมือนว่าการเดิมพันที่ปลอดภัยที่สุดคือการใช้ DateTime ตอนนี้สำหรับการประทับเวลาของการกระทำแรกจากนั้นใช้นาฬิกาจับเวลาสำหรับการดำเนินการในภายหลังเพื่อกำหนดค่าชดเชยจาก DateTime เริ่มต้น นี่คือแนวทางที่คุณจะแนะนำ Eric หรือไม่?
devuxer

18

ความแม่นยำของ DateTime ค่อนข้างเฉพาะเจาะจงสำหรับระบบที่กำลังทำงานอยู่ ความแม่นยำเกี่ยวข้องกับความเร็วของสวิตช์บริบทซึ่งมีแนวโน้มที่จะอยู่ที่ประมาณ 15 หรือ 16 มิลลิวินาที (ในระบบของฉันจริง ๆ แล้วมันอยู่ที่ประมาณ 14 ms จากการทดสอบของฉัน แต่ฉันเคยเห็นแล็ปท็อปบางเครื่องที่มีความแม่นยำใกล้เคียงกับ 35-40 มิลลิวินาที)

Peter Bromberg เขียนบทความเกี่ยวกับการจับเวลารหัสที่มีความแม่นยำสูงใน C # ซึ่งกล่าวถึงเรื่องนี้


2
เครื่อง Win7 4 เครื่องที่ฉันมีในช่วงหลายปีที่ผ่านมามีความแม่นยำประมาณ 1 มิลลิวินาที Now () sleep (1) Now () ส่งผลให้วันที่และเวลาเปลี่ยนแปลง ~ 1ms เสมอเมื่อฉันทำการทดสอบ
Bengie

ฉันเห็นความแม่นยำ ~ 1ms เช่นกัน
Timo

12

ฉันต้องการ Datetime ที่แม่นยำตอนนี้ :) ดังนั้นฉันจึงปรุงสิ่งนี้:

public class PreciseDatetime
{
    // using DateTime.Now resulted in many many log events with the same timestamp.
    // use static variables in case there are many instances of this class in use in the same program
    // (that way they will all be in sync)
    private static readonly Stopwatch myStopwatch = new Stopwatch();
    private static System.DateTime myStopwatchStartTime;

    static PreciseDatetime()
    {
        Reset();

        try
        {
            // In case the system clock gets updated
            SystemEvents.TimeChanged += SystemEvents_TimeChanged;
        }
        catch (Exception)
        {                
        }
    }

    static void SystemEvents_TimeChanged(object sender, EventArgs e)
    {
        Reset();
    }

    // SystemEvents.TimeChanged can be slow to fire (3 secs), so allow forcing of reset
    static public void Reset()
    {
        myStopwatchStartTime = System.DateTime.Now;
        myStopwatch.Restart();
    }

    public System.DateTime Now { get { return myStopwatchStartTime.Add(myStopwatch.Elapsed); } }
}

2
ฉันชอบโซลูชันนี้ แต่ไม่แน่ใจฉันจึงถามคำถามของตัวเอง ( stackoverflow.com/q/18257987/270348 ) ตามความคิดเห็น / คำตอบจาก Servy คุณไม่ควรรีเซ็ตนาฬิกาจับเวลา
RobSiklos

1
อาจจะไม่ได้อยู่ในบริบทของคุณ แต่การรีเซ็ตมีความหมายในบริบทของฉัน - ฉันแค่ตรวจสอบให้แน่ใจก่อนที่เวลาจะเริ่มขึ้นจริง
Jimmy

คุณไม่จำเป็นต้องสมัครสมาชิกและคุณไม่จำเป็นต้องรีเซ็ตนาฬิกาจับเวลา การรันโค้ดนี้ทุกๆ ~ 10ms ไม่จำเป็นและสิ้นเปลือง CPU และรหัสนี้ไม่ปลอดภัยต่อเธรดเลย เพียงแค่เริ่มต้น myStopwatchStartTime = DateTime.UtcNow; ครั้งเดียวในตัวสร้างแบบคงที่
VeganHunter

1
@VeganHunter ฉันไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณถูกต้องหรือเปล่า แต่ดูเหมือนคุณจะคิดว่า TimeChanged ถูกเรียกทุกๆ ~ 10ms? มันไม่
Jimmy

@ จิมมี่คุณพูดถูก ฉันไม่ดีฉันเข้าใจรหัสผิด เหตุการณ์ SystemEvents.TimeChanged จะถูกเรียกก็ต่อเมื่อผู้ใช้เปลี่ยนเวลาของระบบ นับเป็นเหตุการณ์ที่หายาก
VeganHunter

6

จากMSDNคุณจะพบว่าDateTime.Nowมีความละเอียดโดยประมาณ 10 มิลลิวินาทีในระบบปฏิบัติการ NT ทั้งหมด

ความแม่นยำที่แท้จริงขึ้นอยู่กับฮาร์ดแวร์ QueryPerformanceCounterความแม่นยำที่ดีขึ้นสามารถรับใช้


5

สำหรับสิ่งที่คุ้มค่าโดยไม่ต้องตรวจสอบแหล่งที่มา. NET จริง Eric Lippert ให้ความคิดเห็นเกี่ยวกับคำถาม SO นี้โดยบอกว่า DateTime มีความแม่นยำเพียงประมาณ 30 ms ในคำพูดของเขาก็คือการให้เหตุผลที่ไม่แม่นยำในระดับนาโนวินาทีคือ "ไม่จำเป็นต้องเป็น"


4
และมันอาจแย่กว่านั้น ใน VBScript ฟังก์ชัน Now () จะปัดเศษผลลัพธ์ที่ส่งคืนไปเป็นวินาทีที่ใกล้ที่สุดให้ดูว่าค่าที่ส่งคืนมีความแม่นยำเพียงพอที่จะแม่นยำถึงไมโครวินาที ใน C # โครงสร้างเรียกว่า DateTime; มีจุดมุ่งหมายเพื่อแสดงวันที่และเวลาสำหรับโดเมนที่ไม่ใช่ทางวิทยาศาสตร์ในโลกแห่งความจริงโดยทั่วไปเช่นเวลาที่ประกันชีวิตของคุณหมดอายุหรือนานแค่ไหนนับตั้งแต่การรีบูตครั้งสุดท้าย ไม่ได้มีไว้สำหรับช่วงเวลาย่อยวินาทีที่มีความแม่นยำสูง
Eric Lippert


1

ความละเอียดของคุณสมบัตินี้ขึ้นอยู่กับตัวจับเวลาของระบบซึ่งขึ้นอยู่กับระบบปฏิบัติการพื้นฐาน มีแนวโน้มที่จะอยู่ระหว่าง 0.5 ถึง 15 มิลลิวินาที

ด้วยเหตุนี้การเรียกซ้ำไปยังคุณสมบัติ Now ในช่วงเวลาสั้น ๆ เช่นในลูปอาจส่งคืนค่าเดียวกัน

ลิงค์ MSDN

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.