C # DateTime. Sekarang presisi

97

Saya baru saja mengalami beberapa perilaku tak terduga dengan DateTime.UtcNow saat melakukan beberapa pengujian unit. Tampaknya saat Anda memanggil DateTime.Now/UtcNow secara berurutan, tampaknya memberikan Anda nilai yang sama untuk interval waktu yang lebih lama dari yang diharapkan, daripada menangkap kenaikan milidetik yang lebih tepat.

Saya tahu ada kelas Stopwatch yang lebih cocok untuk melakukan pengukuran waktu yang tepat, tetapi saya ingin tahu apakah seseorang dapat menjelaskan perilaku ini di DateTime? Apakah ada presisi resmi yang didokumentasikan untuk DateTime.Now (misalnya, tepat dalam 50 md?)? Mengapa DateTime.Now dibuat kurang tepat daripada yang dapat ditangani oleh kebanyakan jam CPU? Mungkin itu hanya dirancang untuk CPU denominator umum terendah?

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();
}
Andy White
sumber
Berapa interval saat Anda mendapatkan kembali nilai yang sama?
ChrisF
Ketika saya menjalankan kode di atas, saya hanya mendapatkan 3 nilai unik untuk tick dan milidetik, dan waktu tonton berhenti terakhir adalah 147 ms, jadi tampaknya di mesin saya hanya tepat sekitar 50ms ...
Andy White
Saya harus mengatakan, loop berjalan beberapa kali, tetapi saya hanya melihat 3 nilai yang berbeda ...
Andy White
Bagi siapa pun yang datang ke sini, berikut adalah TL; DR // Gunakan fungsi QueryPerformanceCounter "Mengambil nilai saat ini dari penghitung kinerja, yang merupakan cap waktu resolusi tinggi (<1us) yang dapat digunakan untuk pengukuran interval waktu." (Untuk kode terkelola, kelas System.Diagnostics.Stopwatch menggunakan QPC sebagai basis waktu yang tepat.) Msdn.microsoft.com/en-us/library/windows/desktop/…
AnotherUser

Jawaban:

181

Mengapa DateTime.Now dibuat kurang tepat daripada yang dapat ditangani oleh kebanyakan jam CPU?

Jam yang bagus harus tepat dan akurat ; itu berbeda. Seperti lelucon lama, jam yang dihentikan persis dua kali sehari, jam yang lambat satu menit tidak pernah akurat setiap saat. Tetapi jam yang lambat satu menit selalu tepat ke menit terdekat, sedangkan jam yang berhenti tidak memiliki presisi yang berguna sama sekali.

Mengapa DateTime harus tepat , katakanlah satu mikrodetik padahal tidak mungkin akurat ke mikrodetik? Kebanyakan orang tidak memiliki sumber sinyal waktu resmi yang akurat untuk mikrodetik. Oleh karena itu memberikan enam digit setelah tempat desimal ketepatan , lima yang terakhir adalah sampah akan berbohong .

Ingat, tujuan DateTime adalah untuk mewakili tanggal dan waktu . Pengaturan waktu presisi tinggi bukanlah tujuan DateTime; seperti yang Anda catat, itulah tujuan StopWatch. Tujuan DateTime adalah untuk mewakili tanggal dan waktu untuk tujuan seperti menampilkan waktu saat ini kepada pengguna, menghitung jumlah hari hingga Selasa depan, dan seterusnya.

Singkatnya, "jam berapa sekarang?" dan "berapa lama waktu yang dibutuhkan?" adalah pertanyaan yang sama sekali berbeda; jangan gunakan alat yang dirancang untuk menjawab satu pertanyaan untuk menjawab pertanyaan lainnya.

Terima kasih atas pertanyaannya; ini akan menjadi artikel blog yang bagus! :-)

Eric Lippert
sumber
2
@Eric Lippert: Raymond Chen memiliki pengalaman lama tapi bagus tentang topik perbedaan antara "presisi" dan "akurasi": blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
jason
3
Oke, poin bagus tentang presisi vs. akurasi. Saya rasa saya masih tidak begitu percaya pada pernyataan bahwa DateTime tidak akurat karena "tidak harus demikian." Jika saya memiliki sistem transaksional, dan saya ingin menandai tanggal waktu untuk setiap catatan, bagi saya tampaknya intuitif untuk menggunakan kelas DateTime, tetapi tampaknya ada komponen waktu yang lebih akurat / tepat di .NET, jadi mengapa DateTime menjadi dibuat kurang mampu. Kurasa aku harus membaca lebih banyak lagi ...
Andy White
11
OK @Andy, misalkan Anda memiliki sistem seperti itu. Di satu mesin, Anda menandai transaksi sebagai terjadi pada 1 Januari, 12: 34: 30.23498273. Di komputer lain di kluster Anda, Anda menandai transaksi yang terjadi pada 1 Januari, 12: 34: 30.23498456. Transaksi mana yang lebih dulu terjadi? Kecuali Anda tahu bahwa kedua jam mesin disinkronkan dalam mikrodetik satu sama lain, Anda tidak tahu jam mana yang terjadi lebih dulu. Presisi ekstra menyesatkan sampah . Jika saya punya cara saya, semua DateTimes akan dibulatkan ke detik terdekat, seperti di VBScript.
Eric Lippert
14
bukan berarti ini akan menyelesaikan masalah yang Anda sebutkan, karena rata-rata PC yang tidak disinkronkan biasanya keluar dalam hitungan menit . Sekarang jika pembulatan ke 1 tidak menyelesaikan apa-apa, lalu mengapa pembulatan sama sekali? Dengan kata lain, saya tidak mengikuti argumen Anda mengapa nilai absolut harus memiliki presisi yang lebih kecil daripada akurasi pengukuran waktu delta.
Roman Starkov
3
Katakanlah saya membuat log aktivitas yang membutuhkan (1) mengetahui kapan sesuatu terjadi dalam hal ruang kalender (dalam beberapa detik) (2) mengetahui dengan tepat jarak antar kejadian (dalam 50 milidetik atau lebih). Kedengarannya seperti taruhan teraman untuk ini adalah menggunakan DateTime. Sekarang untuk stempel waktu dari tindakan pertama, kemudian gunakan Stopwatch untuk tindakan selanjutnya untuk menentukan offset dari DateTime awal. Apakah ini pendekatan yang akan Anda sarankan, Eric?
devuxer
18

Ketepatan DateTime agak spesifik untuk sistem yang dijalankannya. Presisi terkait dengan kecepatan sakelar konteks, yang cenderung sekitar 15 atau 16 ms. (Di sistem saya, sebenarnya sekitar 14 ms dari pengujian saya, tetapi saya telah melihat beberapa laptop di mana akurasi mendekati 35-40 ms.)

Peter Bromberg menulis artikel tentang pewaktuan kode presisi tinggi di C #, yang membahas hal ini.

Reed Copsey
sumber
2
4 mesin Win7 yang saya miliki selama bertahun-tahun semuanya memiliki akurasi sekitar 1ms. Now () sleep (1) Now () selalu menghasilkan ~ 1ms perubahan datetime saat saya menguji.
Bengie
Saya juga melihat akurasi ~ 1ms.
Timo
12

Saya ingin Datetime yang tepat. Sekarang :), jadi saya memasak ini:

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); } }
}
Jimmy
sumber
2
Saya suka solusi ini, tetapi saya tidak yakin, jadi saya mengajukan pertanyaan saya sendiri ( stackoverflow.com/q/18257987/270348 ). Sesuai komentar / jawaban dari Servy, Anda tidak boleh mengatur ulang stopwatch.
RobSiklos
1
Mungkin tidak dalam konteks Anda, tetapi pengaturan ulang masuk akal dalam konteks saya - saya hanya memastikan itu dilakukan sebelum waktu benar-benar dimulai.
Jimmy
Anda tidak perlu berlangganan dan tidak perlu mengatur ulang stopwatch. Menjalankan kode ini setiap ~ 10ms tidak diperlukan dan menghabiskan CPU. Dan kode ini sama sekali tidak aman untuk thread. Cukup inisialisasi myStopwatchStartTime = DateTime.UtcNow; sekali, dalam konstruktor statis.
VeganHunter
1
@VeganHunter Saya tidak yakin apakah saya memahami komentar Anda dengan benar, tetapi Anda tampaknya berpikir bahwa TimeChanged dipanggil setiap ~ 10ms? Tidak.
Jimmy
@Jimmy, kamu benar. Saya buruk, saya salah paham kode. Peristiwa SystemEvents.TimeChanged dipanggil hanya jika pengguna mengubah waktu sistem. Ini adalah peristiwa langka.
VeganHunter
6

Dari MSDN Anda akan menemukan bahwa DateTime.Nowmemiliki perkiraan resolusi 10 milidetik pada semua sistem operasi NT.

Presisi sebenarnya bergantung pada perangkat keras. Presisi yang lebih baik dapat diperoleh dengan menggunakan QueryPerformanceCounter.

Kevin Montrose
sumber
5

Untuk apa nilainya, selain benar-benar memeriksa sumber .NET, Eric Lippert memberikan komentar pada pertanyaan SO ini yang mengatakan bahwa DateTime hanya akurat hingga kira-kira 30 ms. Alasan tidak akuratnya nanodetik, dalam kata-katanya, adalah karena "tidak perlu".

Scott Anderson
sumber
4
Dan itu bisa lebih buruk. Dalam VBScript, fungsi Now () membulatkan hasil yang dikembalikan ke detik terdekat, mengabaikan fakta bahwa nilai yang dikembalikan memiliki ketepatan yang tersedia cukup untuk tepat ke mikrodetik. Dalam C #, struktur tersebut disebut DateTime; ini dimaksudkan untuk mewakili tanggal dan waktu untuk domain non-ilmiah dunia nyata yang khas, seperti kapan asuransi jiwa Anda kedaluwarsa atau sudah berapa lama sejak reboot terakhir Anda. Ini tidak dimaksudkan untuk waktu sub-detik presisi tinggi.
Eric Lippert
3

Dari dokumentasi MSDN :

Resolusi properti ini tergantung pada timer sistem.

Mereka juga mengklaim bahwa perkiraan resolusi pada Windows NT 3.5 dan yang lebih baru adalah 10 ms :)

Tomas Vana
sumber
1

Resolusi properti ini bergantung pada timer sistem, yang bergantung pada sistem operasi yang mendasarinya. Ini cenderung antara 0,5 dan 15 milidetik.

Akibatnya, panggilan berulang ke properti Now dalam interval waktu yang singkat, seperti dalam satu loop, dapat mengembalikan nilai yang sama.

Tautan MSDN

Iman
sumber