DateTime.Now vs DateTime.UtcNow 성능 비교 및 캐싱

개요

오늘 올라온 질문 포스트 중 DateTime.NowDateTime.UtcNow의 성능에 대한 내용이 있었어서 간단히 정리해보았습니다.

DateTime.Now vs DateTime.UtcNow.ToLocalTime

저 역시 DateTime.NowDateTime.UtcNow보다 느리다는 것을 알고 있었기에, 실무에서는 이를 개선하기 위한 캐싱 유틸리티를 만들어 사용한 경험이 있는데요, 닷넷 런타임이 업데이트 되면서 이러한 부분에 대한 성능 개선이 이루어 졌을 것으로 보고 이참에 벤치마크를 진행해봤습니다.

벤치마크 결과

※ Cached의 경우 연산 자체를 스킵하는 것이므로 소요된 절대 시간 값은 크게 의미 없습니다.

Method .NET Core 3.1 .NET FX 4.8 .NET 6.0 .NET 8.0
UTCNow 52.59 40.96 20.42 19.99
Now 65.77 55.54 29.30 25.29
UTCNowToLocal 66.16 53.85 31.37 26.45
UTCNowAddOffset 54.68 42.27 21.43 21.40
CachedNow 2.45 2.56 2.53 2.08

벤치마크 코드는 다음과 같습니다.

public class BenchmarkDateTime
{
    [Benchmark()]
    public DateTime UTCNow() => DateTime.UtcNow;

    [Benchmark()]
    public DateTime Now() => DateTime.Now;

    [Benchmark()]
    public DateTime UTCNowToLocal() => DateTime.UtcNow.ToLocalTime();

    private TimeSpan _offset = TimeZoneInfo.Local.BaseUtcOffset;

    [Benchmark()]
    public DateTime UTCNowAddOffset() 
        => DateTime.SpecifyKind(DateTime.UtcNow + _offset, DateTimeKind.Local);

    private int _lastTickCount = -1;
    private DateTime _lastDateTime;

    [Benchmark()]
    public DateTime CachedNow()
    {
        if (_lastTickCount != Environment.TickCount)
        {
            if (_lastTickCount != Environment.TickCount) // 동기화 대신 더블체크로 업데이트 횟수 감소
            {
                _lastTickCount = Environment.TickCount;
                _lastDateTime = DateTime.SpecifyKind(DateTime.UtcNow + _offset, DateTimeKind.Local);
            }
        }
        return _lastDateTime;
    }
}

대용량 측정 데이터를 CPU Full Load로 처리 하는 로직에서 처리 시간 저장을 위해 DateTime.Now를 초당 수십~수백만 번 호출했을 때 몇십 ms의 차이가 발생했던 기억이 있는데 최신 .NET Runtime에서는 기본적으로 성능 개선이 많이 이루어져서 차이가 크지 않은 것으로 보입니다.

1초에 수십 번 호출되는 경우 몇십 ns의 차이가 큰 의미가 없다면 DateTime.Now를 그대로 사용하셔도 무방할 듯합니다.

DateTime.Now 캐싱

CachedNow 케이스의 경우 애초에 DateTime.Now 함수의 정확도가 수 ms 수준으로 정확하지 않다는 점에 착안해서 호출하는데 CPU를 거의 사용하지 않는 Environment.TickCount 속성을 기준으로 DateTime.Now값을 캐싱하는 함수입니다.

자세한 내용은 아래 링크에서 참고하실 수 있습니다.

이 방법은 벌크 데이터 프로세싱과 같은 시나리오에서 10ms 내에 DateTime.Now를 반복적으로 가져와야 하는 상황에서 적합합니다.

필요에 따라 적절히 활용해 보시면 좋겠습니다.

마치며

DateTime.NowDateTime.UtcNow의 성능 차이는 닷넷 버전에 따라 다소 달라지지만, 최신 환경에서는 실질적인 차이가 크지 않다는 점을 확인할 수 있었습니다. 성능에 큰 영향이 없는 경우라면 단순히 DateTime.Now를 사용하는 것도 좋은 선택입니다. 상황에 따라 정밀도가 중요하지 않다면 캐싱을 고려할 수 있습니다.

이상으로 글을 마치며 DateTime 성능 최적화에 참고가 되길 바랍니다.

15 Likes

4.8 → 닷넷 6.0 에서 반정도 줄인거네요… 대단…

4 Likes

제가 공부했던 프로세서의 Tick 은 인터럽트 및

여러가지 환경 요소에 의해 영향을 받는 것으로 알고 있습니다.

프로세서의 Tick 을 이용한 DateTime 이란 개념이 신기해서 조금 찾아봤습니다.

Environment.TickCount vs DateTime.Now - StackOverflow


Environment.TickCount

위 글에 따르면 Environment.TickCount

WinAPI의 GetTickCount() 기능을 이용하는데,

실제 정밀도는 15.6ms 로 이보다 더 짧은 시간은 측정이 어렵다고 합니다.

또한 int 자료형을 사용하고 있어 약 49.7일 마다 롤오버가 발생하기 때문에

긴 기간을 측정하는데는 사용하지 말라는 이야기가 있네요.


DateTime.Ticks

반면에 DateTime.Ticks

WinAPI의 GetSystemTimeAsFileTime()를 이용하는데

정밀도가 100ns 로 훨씬 높은 정밀도를 지닙니다.

다만, OS 및 환경에 따라 차이를 보이는데

  • Windwos XP : 15.6ms
  • Windows 7 이상: 1ms
  • 노트북 절전모드 : 최소 15.6ms

이라고 합니다.

2 Likes

c#은 아니지만

마음만 먹으면 1ms 간격도 측정 가능

제가 처음 테스트 했을때 Console.WriteLine내부에서 DateTime.Now, UtcNow를 사용하는 바람에 ms 측정이 이상하게 된거더라구요 ㅠㅠ

다시 확인해보니 .Net8에서는 조금 차이는 있지만 (5~6ms?)
실제로 제가 회사에서 쓰는 .netFramework4.8 에서는 거의 차이가 무방해서 변경하지 않아도 될거같습니다.

좋은 테스트 해주셔서 감사합니다.
자료를 되게 깔끔하게 만드시는게 멋지십니다. 다음엔 테스트하는 방법같은것도 배울 수 있다면 좋겠네요 ㅎㅎ

1 Like