C # DateTime.Now precision

97

Acabei de ter um comportamento inesperado com DateTime.UtcNow ao fazer alguns testes de unidade. Parece que quando você chama DateTime.Now/UtcNow em rápida sucessão, parece devolver o mesmo valor por um intervalo de tempo mais longo do que o esperado, em vez de capturar incrementos de milissegundos mais precisos.

Eu sei que existe uma classe de Cronômetro que seria mais adequada para fazer medições de tempo precisas, mas estava curioso para saber se alguém poderia explicar esse comportamento em DateTime. Existe uma precisão oficial documentada para DateTime.Now (por exemplo, com precisão de 50 ms?)? Por que DateTime.Now seria menos preciso do que a maioria dos clocks da CPU poderia suportar? Talvez seja apenas projetado para o menor denominador comum da 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();
}
Andy White
fonte
Qual é o intervalo durante o qual você recebe o mesmo valor?
ChrisF
Quando executei o código acima, obtive apenas 3 valores exclusivos para ticks e milissegundos, e o tempo de parada final foi de 147 ms, então parece que na minha máquina é preciso apenas cerca de 50 ms ...
Andy White
Devo dizer que o loop foi executado várias vezes, mas só vi 3 valores distintos ...
Andy White
Para quem vem aqui, aqui está o TL; DR // Use a função QueryPerformanceCounter "Recupera o valor atual do contador de desempenho, que é um registro de data e hora de alta resolução (<1us) que pode ser usado para medições de intervalo de tempo." (Para código gerenciado, a classe System.Diagnostics.Stopwatch usa QPC como base o tempo preciso.) Msdn.microsoft.com/en-us/library/windows/desktop/...
AnotherUser

Respostas:

179

Por que DateTime.Now seria menos preciso do que a maioria dos clocks da CPU poderia suportar?

Um bom relógio deve ser preciso e exato ; esses são diferentes. Como diz a velha piada, um relógio parado é exatamente preciso duas vezes por dia, um relógio com um minuto de atraso nunca é preciso em nenhum momento. Mas o relógio um minuto atrasado é sempre preciso para o minuto mais próximo, ao passo que um relógio parado não tem nenhuma precisão útil.

Por que DateTime deveria ser preciso , digamos um microssegundo, quando não é possível que ele seja preciso em microssegundos? A maioria das pessoas não possui nenhuma fonte de sinais de hora oficiais com precisão de microssegundos. Portanto, dar seis dígitos após a casa decimal de precisão , dos quais os últimos cinco são lixo, seria mentir .

Lembre-se de que o objetivo de DateTime é representar uma data e hora . Timing de alta precisão não é o propósito de DateTime; como você observa, esse é o propósito do StopWatch. O objetivo de DateTime é representar uma data e hora para fins como exibir a hora atual para o usuário, calcular o número de dias até a próxima terça-feira e assim por diante.

Em suma, "que horas são?" e "quanto tempo isso demorou?" são questões completamente diferentes; não use uma ferramenta projetada para responder a uma pergunta para responder a outra.

Obrigado pela pergunta; este será um bom artigo de blog! :-)

Eric Lippert
fonte
2
@Eric Lippert: Raymond Chen tem algo antigo, mas bom no próprio tópico da diferença entre "precisão" e "exatidão": blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
jason
3
Ok, bom ponto sobre precisão vs. exatidão. Acho que ainda não acredito na afirmação de que DateTime não é preciso porque "não precisa ser". Se eu tiver um sistema transacional e quiser marcar uma data e hora para cada registro, para mim parece intuitivo usar a classe DateTime, mas parece que há componentes de tempo mais precisos / precisos no .NET, então por que DateTime seria tornado menos capaz. Acho que terei que ler mais um pouco ...
Andy White
11
OK @Andy, suponha que você tenha um sistema assim. Em uma máquina, você marca uma transação como ocorrida em 1º de janeiro, 12: 34: 30.23498273. Em outra máquina em seu cluster, você marca uma transação como ocorrendo em 1º de janeiro, 12: 34: 30.23498456. Qual transação ocorreu primeiro? A menos que você saiba que os relógios das duas máquinas estão sincronizados com um microssegundo um do outro, você não tem ideia de qual ocorreu primeiro. A precisão extra é um lixo enganoso . Se eu pudesse, todos os DateTimes seriam arredondados para o segundo mais próximo, como no VBScript.
Eric Lippert
14
não que isso resolvesse o problema que você mencionou, uma vez que os PCs não sincronizados normais costumam ficar atrasados ​​em minutos . Agora, se arredondar para 1s não resolve nada, então por que arredondar? Em outras palavras, não sigo seu argumento sobre por que os valores absolutos devem ter uma precisão menor do que a exatidão das medidas de tempo delta.
Roman Starkov
3
Digamos que estou criando um registro de atividades que requer (1) saber quando algo ocorreu em termos de espaço do calendário (dentro de alguns segundos) (2) saber exatamente o espaçamento entre eventos (dentro de 50 ou mais milissegundos). Parece que a aposta mais segura para isso seria usar DateTime.Now para o carimbo de data / hora da primeira ação e, em seguida, usar um cronômetro para as ações subsequentes para determinar o deslocamento do DateTime inicial. É esta a abordagem que você aconselharia, Eric?
devuxer
18

A precisão do DateTime é um tanto específica do sistema em que está sendo executado. A precisão está relacionada à velocidade de uma troca de contexto, que tende a ser em torno de 15 ou 16 ms. (No meu sistema, demora cerca de 14 ms do meu teste, mas eu vi alguns laptops com uma precisão de 35-40 ms.)

Peter Bromberg escreveu um artigo sobre temporização de código de alta precisão em C #, que discute isso.

Reed Copsey
fonte
2
As 4 máquinas Win7 que tive ao longo dos anos tiveram uma precisão de cerca de 1 ms. Now () sleep (1) Now () sempre resultou em uma mudança de ~ 1ms no datetime quando eu estava testando.
Bengie
Também estou vendo a precisão de ~ 1ms.
Timo
12

Eu gostaria de um Datetime preciso.Agora :), então preparei isso:

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
fonte
2
Gosto dessa solução, mas não tinha certeza, então fiz minha própria pergunta ( stackoverflow.com/q/18257987/270348 ). De acordo com o comentário / resposta de Servy, você nunca deve zerar o cronômetro.
RobSiklos de
1
Talvez não em seu contexto, mas a redefinição faz sentido em meu contexto - eu apenas me certifico de que seja feito antes que o tempo realmente comece.
Jimmy de
Você não precisa da assinatura e não precisa zerar o cronômetro. A execução desse código a cada ~ 10 ms não é necessária e consome CPU. E esse código não é seguro para thread. Basta inicializar myStopwatchStartTime = DateTime.UtcNow; uma vez, no construtor estático.
VeganHunter
1
@VeganHunter Não tenho certeza se estou entendendo seu comentário direito, mas você parece pensar que TimeChanged é chamado a cada ~ 10ms? Não é verdade.
Jimmy
@Jimmy, você está certo. Que pena, não entendi o código. O evento SystemEvents.TimeChanged é chamado apenas se o usuário alterar a hora do sistema. É um evento raro.
VeganHunter
6

No MSDN você descobrirá que DateTime.Nowtem uma resolução aproximada de 10 milissegundos em todos os sistemas operacionais NT.

A precisão real depende do hardware. Melhor precisão pode ser obtida usando QueryPerformanceCounter.

Kevin Montrose
fonte
5

Pelo que vale a pena, sem realmente verificar o código-fonte .NET, Eric Lippert forneceu um comentário sobre esta questão do SO dizendo que DateTime tem precisão de aproximadamente 30 ms. O raciocínio para não ter precisão de nanossegundos, em suas palavras, é que "não precisa ser".

Scott Anderson
fonte
4
E poderia ser pior. No VBScript, a função Now () arredonda o resultado retornado para o segundo mais próximo, apesar do fato de que o valor retornado tem precisão disponível suficiente para ser preciso até o microssegundo. Em C #, a estrutura é chamada DateTime; destina-se a representar uma data e uma hora para domínios não científicos típicos do mundo real, como quando seu seguro de vida expira ou quanto tempo se passou desde sua última reinicialização. Não se destina a cronometragem de sub-segundos de alta precisão.
Eric Lippert
3

Da documentação do MSDN :

A resolução desta propriedade depende do cronômetro do sistema.

Eles também afirmam que a resolução aproximada no Windows NT 3.5 e posterior é de 10 ms :)

Tomas Vana
fonte
1

A resolução dessa propriedade depende do cronômetro do sistema, que depende do sistema operacional subjacente. Tende a estar entre 0,5 e 15 milissegundos.

Como resultado, chamadas repetidas para a propriedade Now em um curto intervalo de tempo, como em um loop, podem retornar o mesmo valor.

Link MSDN

Iman
fonte