C # DateTime.Now precyzja

97

Właśnie napotkałem nieoczekiwane zachowanie z DateTime.UtcNow podczas wykonywania niektórych testów jednostkowych. Wydaje się, że gdy wywołujesz DateTime.Now/UtcNow w krótkich odstępach czasu, wydaje się, że zwraca tę samą wartość przez dłuższy niż oczekiwany przedział czasu, zamiast przechwytywać dokładniejsze przyrosty milisekund.

Wiem, że istnieje klasa Stopwatch, która lepiej nadawałaby się do wykonywania precyzyjnych pomiarów czasu, ale byłem ciekaw, czy ktoś mógłby wyjaśnić to zachowanie w DateTime? Czy istnieje udokumentowana oficjalna dokładność dla DateTime.Now (na przykład z dokładnością do 50 ms?)? Dlaczego DateTime.Now miałoby być mniej precyzyjne niż te, z którymi radzi sobie większość zegarów procesora? Może jest po prostu zaprojektowany dla procesora z najniższym wspólnym mianownikiem?

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
źródło
Jaki jest przedział czasu, w którym odzyskujesz tę samą wartość?
ChrisF
Kiedy uruchomiłem powyższy kod, uzyskałem tylko 3 unikalne wartości dla ticków i milisekund, a końcowy czas stopera wyniósł 147 ms, więc wygląda na to, że na moim komputerze jest dokładny tylko do około 50 ms ...
Andy White
Powinienem powiedzieć, że pętla przebiegała kilka razy, ale widziałem tylko 3 różne wartości ...
Andy White
Dla każdego, kto tu przyjedzie, oto TL; DR // Użyj funkcji QueryPerformanceCounter "Pobiera bieżącą wartość licznika wydajności, który jest znacznikiem czasu o wysokiej rozdzielczości (<1us), który może być używany do pomiarów przedziałów czasu." (Dla kodu zarządzanego, klasa System.Diagnostics.Stopwatch wykorzystuje QPC jak jego dokładnej podstawy czasu). Msdn.microsoft.com/en-us/library/windows/desktop/...
AnotherUser

Odpowiedzi:

181

Dlaczego DateTime.Now miałoby być mniej precyzyjne niż te, z którymi radzi sobie większość zegarów procesora?

Dobry zegar powinien być zarówno precyzyjny, jak i dokładny ; te są różne. Jak głosi stary żart, zatrzymany zegar jest dokładny dwa razy dziennie, a zegar spowolniony o minutę nigdy nie jest dokładny w żadnym momencie. Ale zegar spowolniony o minutę jest zawsze dokładny do najbliższej minuty, podczas gdy zatrzymany zegar nie ma żadnej użytecznej precyzji.

Dlaczego DateTime miałby być dokładny do, powiedzmy mikrosekundy, skoro nie może być dokładny do mikrosekundy? Większość ludzi nie ma żadnego źródła oficjalnych sygnałów czasu z dokładnością do mikrosekundy. Dlatego podanie sześciu cyfr po przecinku dokładności , z których pięć ostatnich to śmieci, byłoby kłamstwem .

Pamiętaj, że celem DateTime jest reprezentowanie daty i godziny . Wysoka precyzja chronometrażu nie jest w ogóle celem DateTime; jak zauważyłeś, taki jest cel StopWatch. Celem DateTime jest przedstawienie daty i godziny do celów takich jak wyświetlanie aktualnego czasu użytkownikowi, obliczanie liczby dni do następnego wtorku i tak dalej.

Krótko mówiąc, „która jest godzina?” i „jak długo to trwało?” to zupełnie inne pytania; nie używaj narzędzia przeznaczonego do udzielania odpowiedzi na jedno pytanie, aby odpowiedzieć na drugie.

Dzięki za pytanie; to będzie dobry artykuł na blogu! :-)

Eric Lippert
źródło
2
@Eric Lippert: Raymond Chen ma starą, ale ciekawą rzecz na temat różnicy między „precyzją” a „dokładnością”: blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
jason
3
Ok, dobra uwaga na temat precyzji i dokładności. Wydaje mi się, że nadal nie kupuję stwierdzenia, że ​​DateTime nie jest dokładne, ponieważ „nie musi tak być”. Jeśli mam system transakcyjny i chcę oznaczyć datę i godzinę dla każdego rekordu, wydaje mi się, że użycie klasy DateTime wydaje się intuicyjne, ale wydaje mi się, że w .NET są bardziej dokładne / precyzyjne składniki czasu, więc dlaczego DateTime miałoby być mniej zdolny. Myślę, że będę musiał jeszcze coś przeczytać ...
Andy White
11
OK @Andy, przypuśćmy, że masz taki system. Na jednym komputerze zaznaczasz transakcję jako występującą 1 stycznia, 12:34: 30.23498273. Na innym komputerze w klastrze oznaczasz transakcję jako występującą 1 stycznia, 12:34: 30.23498456. Która transakcja miała miejsce jako pierwsza? Jeśli nie wiesz, że zegary dwóch maszyn są zsynchronizowane w ciągu mikrosekundy, nie masz pojęcia, który z nich wystąpił jako pierwszy. Dodatkowa precyzja to mylące bzdury . Gdybym miał po swojemu, wszystkie daty i godziny byłyby zaokrąglane do najbliższej sekundy, tak jak w VBScript.
Eric Lippert
14
nie oznacza to, że rozwiązałoby to problem, o którym wspomniałeś, ponieważ przeciętny niezsynchronizowany komputer PC zwykle wyłącza się o kilka minut . Jeśli zaokrąglanie do 1 niczego nie rozwiązuje, to dlaczego w ogóle? Innymi słowy, nie rozumiem twojego argumentu, dlaczego wartości bezwzględne powinny mieć mniejszą precyzję niż dokładność pomiarów delta czasu.
Roman Starkov
3
Powiedzmy, że tworzę dziennik aktywności, który wymaga (1) wiedzy, kiedy coś się wydarzyło w zakresie przestrzeni kalendarza (w ciągu kilku sekund) (2) znajomości bardzo dokładnych odstępów między zdarzeniami (w ciągu około 50 milisekund). Wygląda na to, że najbezpieczniejszym rozwiązaniem byłoby użycie daty i godziny. Teraz dla znacznika czasu pierwszej czynności, a następnie użyj stopera do kolejnych działań, aby określić przesunięcie względem początkowej daty i godziny. Czy takie podejście byś doradził, Eric?
devuxer,
18

Precyzja DateTime jest nieco specyficzna dla systemu, w którym jest uruchamiany. Precyzja jest związana z szybkością przełączania kontekstu, która zwykle wynosi około 15 lub 16 ms. (W moim systemie jest to faktycznie około 14 ms od mojego testu, ale widziałem niektóre laptopy, w których jest bliżej 35-40 ms dokładności.)

Peter Bromberg napisał artykuł o taktowaniu kodu w języku C # o wysokiej precyzji , w którym omówiono to.

Reed Copsey
źródło
2
Wszystkie 4 maszyny Win7, które miałem przez lata, miały dokładność mniej więcej 1 ms. Funkcja Now () sleep (1) Funkcja Now () zawsze powodowała zmianę daty i godziny o ~ 1 ms podczas testowania.
Bengie
Widzę również dokładność ~ 1 ms.
Timo
12

Chciałbym mieć dokładną datę i godzinę. Teraz :), więc ułożyłem to:

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
źródło
2
Podoba mi się to rozwiązanie, ale nie byłem pewien, więc zadałem własne pytanie ( stackoverflow.com/q/18257987/270348 ). Zgodnie z komentarzem / odpowiedzią Servy, nigdy nie powinieneś resetować stopera.
RobSiklos
1
Może nie w twoim kontekście, ale resetowanie ma sens w moim kontekście - po prostu upewniam się, że zostało to zrobione, zanim czas faktycznie się rozpocznie.
Jimmy
Nie potrzebujesz abonamentu i nie musisz resetować stopera. Uruchamianie tego kodu co ~ 10 ms nie jest konieczne i zużywa procesor. Ten kod nie jest w ogóle bezpieczny dla wątków. Wystarczy zainicjować myStopwatchStartTime = DateTime.UtcNow; raz, w konstruktorze statycznym.
VeganHunter
1
@VeganHunter Nie jestem pewien, czy dobrze rozumiem twój komentarz, ale wydaje ci się, że TimeChanged jest wywoływany co ~ 10 ms? Tak nie jest.
Jimmy
@Jimmy, masz rację. Mój błąd, źle zrozumiałem kod. Zdarzenie SystemEvents.TimeChanged jest wywoływane tylko wtedy, gdy użytkownik zmieni czas systemowy. To rzadkie wydarzenie.
VeganHunter
6

Z MSDN dowiesz się, że DateTime.Nowma przybliżoną rozdzielczość 10 milisekund we wszystkich systemach operacyjnych NT.

Rzeczywista precyzja zależy od sprzętu. Lepszą precyzję można uzyskać za pomocą QueryPerformanceCounter.

Kevin Montrose
źródło
5

Jeśli chodzi o to, co jest warte, poza faktycznym sprawdzeniem źródła .NET, Eric Lippert skomentował to pytanie SO, mówiąc, że DateTime jest dokładny tylko do około 30 ms. Jego zdaniem, powodem, dla którego nie jest on dokładny w nanosekundach, jest to, że „nie musi być”.

Scott Anderson
źródło
4
A mogło być gorzej. W języku VBScript funkcja Now () zaokrągla zwracany wynik do najbliższej sekundy, wyjaśniając fakt, że zwracana wartość ma dostateczną dostępną precyzję, aby była dokładna do mikrosekundy. W języku C # struktura nosi nazwę DateTime; ma reprezentować datę i godzinę w typowych rzeczywistych domenach nienaukowych, takich jak data wygaśnięcia ubezpieczenia na życie lub czas od ostatniego ponownego uruchomienia. Nie jest przeznaczony do precyzyjnego pomiaru czasu poniżej sekundy.
Eric Lippert
3

Z dokumentacji MSDN :

Rozdzielczość tej właściwości zależy od zegara systemowego.

Twierdzą też, że przybliżona rozdzielczość w systemie Windows NT 3.5 i nowszych to 10 ms :)

Tomas Vana
źródło
1

Rozdzielczość tej właściwości zależy od licznika czasu systemu, który zależy od bazowego systemu operacyjnego. Zwykle wynosi od 0,5 do 15 milisekund.

W rezultacie powtarzające się wywołania właściwości Now w krótkim przedziale czasu, na przykład w pętli, mogą zwrócić tę samą wartość.

MSDN Link

Iman
źródło