Microsoft MVP성태의 닷넷 이야기
.NET Framework: 614. C# - DateTime.Ticks의 정밀도 [링크 복사], [링크+제목 복사],
조회: 29700
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 8개 있습니다.)
.NET Framework: 614. C# - DateTime.Ticks의 정밀도
; https://www.sysnet.pe.kr/2/0/11082

.NET Framework: 827. C# - 인터넷 시간 서버로부터 받은 시간을 윈도우에 적용하는 방법
; https://www.sysnet.pe.kr/2/0/11883

스크립트: 33. JavaScript와 C#의 시간 변환
; https://www.sysnet.pe.kr/2/0/12849

Windows: 204.  Windows 10부터 바뀐 QueryPerformanceFrequency, QueryPerformanceCounter
; https://www.sysnet.pe.kr/2/0/13035

.NET Framework: 1997. C# - nano 시간을 가져오는 방법
; https://www.sysnet.pe.kr/2/0/13036

스크립트: 47. 파이썬의 time.time() 실숫값을 GoLang / C#에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/13308

닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
; https://www.sysnet.pe.kr/2/0/13413

닷넷: 2309. C# - .NET Core에서 바뀐 DateTime.Ticks의 정밀도
; https://www.sysnet.pe.kr/2/0/13803




C# - DateTime.Ticks의 정밀도

(이 글은 .NET Framework 환경에서 유효합니다. .NET Core 버전인 경우는 C# - .NET Core에서 바뀐 DateTime.Ticks의 정밀도 글을 참고하세요.)




지난 글에서,

윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점
; https://www.sysnet.pe.kr/2/0/11063

윈도우 운영체제의 시간 함수 (2) - Sleep 함수의 동작 방식
; https://www.sysnet.pe.kr/2/0/11065

윈도우 운영체제의 시간 함수 (3) - QueryInterruptTimePrecise, QueryInterruptTime 함수
; https://www.sysnet.pe.kr/2/0/11066

윈도우 운영체제의 시간 함수 (4) - RTC, TSC, PM Clock, HPET Timer
; https://www.sysnet.pe.kr/2/0/11067

윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter
; https://www.sysnet.pe.kr/2/0/11068

운영체제 수준에서 제공되는 시간 정밀도를 살펴봤는데요. 그럼, C#의 경우에는 어떻게 될까요? 오늘은 그 첫 번째로 DateTime.Ticks를 살펴보겠습니다.

간단하게 소스 코드를 보면, 답이 나오는데요.

// .NET Framework

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime();

public static DateTime UtcNow
{
    get
    {
        return new DateTime((ulong) ((GetSystemTimeAsFileTime() + 0x701ce1722770000L) | 0x4000000000000000L));
    }
}

다름 아닌 GetSystemTimeAsFileTime의 정밀도를 기반으로 만들어졌습니다. 이는 "Current timer interval"의 변화에 따라 정밀도가 변화한다는 것을 의미합니다. 즉, timeBeginPeriod/timeEndPeriod가 호출되지 않은 일반적인 윈도우 시스템의 15.625ms 기본 상태에서는 DateTime.Ticks 속성도 15.625ms 단위마다 시간이 바뀌게 된다는 것을 의미합니다.

실제로 다음과 같이 테스트해볼 수 있습니다.

// .NET Framework

using System;

class Program
{
    static void Main(string[] args)
    {
        int count = 100000;
        long[] tickBuf = new long[count];

        for (int i = 0; i < count; i++)
        {
            tickBuf[i] = DateTime.Now.Ticks;
        }

        long oldTime = tickBuf[0];
        long elapsed;
        for (int i = 1; i < count; i++)
        {
            elapsed = tickBuf[i] - oldTime;
            oldTime = tickBuf[i];

            if (elapsed != 0)
            {
                Console.WriteLine(elapsed);
            }
        }
    }
}

/*
출력 결과:

156292
*/

위에서 100,000번의 루프를 도는 동안 DateTime.Now.Ticks로 계속 시간을 재보지만 15.625ms의 타이머 간격을 가진 윈도우에서는 tickBuf 배열에 동일한 값만을 가지다가 어쩌다 시간이 바뀔 때가 되면 15.625에 가까운 시간 값이 출력됩니다.

위의 출력 결과에 보면 156,292값이 나오는데, DateTime.Ticks의 1은 100ns이므로 10,000으로 나누어 15.6292ms 값이 나옵니다. (대충 값이 비슷합니다.)

물론, timeBeginPeriod / timeEndPeriod로 clockres를 조절하면 다음과 같이 1ms 수준으로 정밀도를 낮출 수 있습니다.

// .NET Framework

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
    public static extern uint timeBeginPeriod(uint uMilliseconds);

    [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
    public static extern uint timeEndPeriod(uint uMilliseconds);

    static void Main(string[] args)
    {
        timeBeginPeriod(1);

        int count = 100000;
        long[] tickBuf = new long[count];

        for (int i = 0; i < count; i++)
        {
            tickBuf[i] = DateTime.Now.Ticks;
        }

        long oldTime = tickBuf[0];
        long elapsed;
        for (int i = 1; i < count; i++)
        {
            elapsed = tickBuf[i] - oldTime;
            oldTime = tickBuf[i];

            if (elapsed != 0)
            {
                Console.WriteLine(elapsed);
            }
        }

        timeEndPeriod(1);
    }
}
/*
출력 결과:

10013
9965
9992
10001
10007
10014
9989
10001
9998
10052
9941
10001
*/

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




정리하자면!

타이머 간격이 (기본 설정인) 15.625ms인 시스템을 기준으로, DateTime.Ticks로 특정 코드의 실행 성능을 재는 경우 15ms 이내에 수행되면 측정값이 0ms로 결과가 나올 수 있습니다. 또는, 운(?) 좋게 tick이 중간에 실행하는 경우 실제로 코드 수행에 소요된 시간은 2ms인데도 15ms 이상으로 나올 수 있는 부작용도 있습니다.

따라서, DateTime.Ticks로 실행 성능을 측정하고 싶다면 초 단위 수준(또는 적어도 100ms 정도)의 정밀도가 필요한 경우가 좋습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/7/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2019-03-21 11시25분
[Syong] 안녕하세요. 좋은 글 감사합니다.
질문사항이 있는데요, 말씀하신 대로라면 timeBeginPeriod / timeEndPeriod 이용해서 clockres 조절하면 DateTime.Ticks 이용해도 1ms도 무관한거 아닌가요?
QueryPerformancecounter를 사용하고 있는데, 많은 사용은 부하를 준다고 하여 tick으로 변경하려고 하는 중이라 문의드립니다.
[guest]
2019-03-21 10시39분
"1ms도 무관한 거"라는 것이 무슨 의미인가요?
정성태
2019-03-23 09시32분
[Syong] 마지막 말씀하신, "따라서, DateTime.Ticks로 실행 성능을 측정하고 싶다면 초 단위 수준(또는 적어도 100ms 정도)의 정밀도가 필요한 경우가 좋습니다. "에 대해서 질문드립니다. timeBeginPeriod / timeEndPeriod 이용해서 clockres 조절하면 1ms 수준의 정밀도를 요구하는 측정에 사용해도 상관없는 것 아닌가요?
[guest]
2019-03-23 10시14분
넵 당연히 조정하면 DateTime.Ticks로도 좋겠죠.
정성태

... 106  107  [108]  109  110  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11224정성태6/13/201718090.NET Framework: 661. Json.NET의 DeserializeObject 수행 시 속성 이름을 동적으로 바꾸는 방법파일 다운로드1
11223정성태6/12/201716809개발 환경 구성: 318. WCF Service Application과 WCFTestClient.exe
11222정성태6/10/201720515오류 유형: 399. WCF - A property with the name 'UriTemplateMatchResults' already exists.파일 다운로드1
11221정성태6/10/201717389오류 유형: 398. Fakes - Assembly 'Jennifer5.Fakes' with identity '[...].Fakes, [...]' uses '[...]' which has a higher version than referenced assembly '[...]' with identity '[...]'
11220정성태6/10/201722833.NET Framework: 660. Shallow Copy와 Deep Copy [1]파일 다운로드2
11219정성태6/7/201718197.NET Framework: 659. 닷넷 - TypeForwardedFrom / TypeForwardedTo 특성의 사용법
11218정성태6/1/201720973개발 환경 구성: 317. Hyper-V 내의 VM에서 다시 Hyper-V를 설치: Nested Virtualization
11217정성태6/1/201716856오류 유형: 397. initerrlog: Could not open error log file 'C:\...\MSSQL12.MSSQLSERVER\MSSQL\Log\ERRORLOG'
11216정성태6/1/201718980오류 유형: 396. Activation context generation failed
11215정성태6/1/201719889오류 유형: 395. 관리 콘솔을 실행하면 "This app has been blocked for your protection" 오류 발생 [1]
11214정성태6/1/201717604오류 유형: 394. MSDTC 서비스 시작 시 -1073737712(0xC0001010) 오류와 함께 종료되는 문제 [1]
11213정성태5/26/201722403오류 유형: 393. TFS - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
11212정성태5/26/201721740오류 유형: 392. Windows Server 2016에 KB4019472 업데이트가 실패하는 경우
11211정성태5/26/201720763오류 유형: 391. BeginInvoke에 전달한 람다 함수에 CS1660 에러가 발생하는 경우
11210정성태5/25/201721275기타: 65. ActiveX 없는 전자 메일에 사용된 "개인정보 보호를 위해 암호화된 보안메일"의 암호화 방법
11209정성태5/25/201768131Windows: 143. Windows 10의 Recovery 파티션을 삭제 및 새로 생성하는 방법 [16]
11208정성태5/25/201727878오류 유형: 390. diskpart의 set id 명령어에서 "The specified type is not in the correct format." 오류 발생
11207정성태5/24/201728164Windows: 142. Windows 10의 복구 콘솔로 부팅하는 방법
11206정성태5/24/201721423오류 유형: 389. DISM.exe - The specified image in the specified wim is already mounted for read/write access.
11205정성태5/24/201721252.NET Framework: 658. C#의 tail call 구현은? [1]
11204정성태5/22/201730786개발 환경 구성: 316. 간단하게 살펴보는 Docker for Windows [7]
11203정성태5/19/201718732오류 유형: 388. docker - Host does not exist: "default"
11202정성태5/19/201719794오류 유형: 387. WPF - There is no registered CultureInfo with the IetfLanguageTag 'ug'.
11201정성태5/16/201722514오류 유형: 386. WPF - .NET 3.5 이하에서 TextBox에 한글 입력 시 TextChanged 이벤트의 비정상 종료 문제 [1]파일 다운로드1
11200정성태5/16/201719285오류 유형: 385. WPF - 폰트가 없어 System.IO.FileNotFoundException 예외가 발생하는 경우
11199정성태5/16/201721122.NET Framework: 657. CultureInfo.GetCultures가 반환하는 값
... 106  107  [108]  109  110  111  112  113  114  115  116  117  118  119  120  ...