Windows 定时的精度

网上有人研究了很多关于1ms定时的,据说Sleep(1)时实际是10多ms,那么我们做个测试

我们给出如下一些Sleep的实现

typedef LONG(__stdcall *__PFN_NtDelayExecution)(BOOLEAN Alertable, PLARGE_INTEGER Interval);
void DSSleep(DWORD dwMs)
{
	static __PFN_NtDelayExecution fn = 0;
	static bool bInited = false;

	if (!bInited)
	{
		HMODULE hnt = ::GetModuleHandleW(L"ntdll.dll");
		fn = (__PFN_NtDelayExecution)(::GetProcAddress(hnt, "NtDelayExecution"));
		bInited = true;
	}

	if (fn)
	{
		LARGE_INTEGER ll;
		ll.QuadPart = -1 * ((LONGLONG)dwMs) * 10000;
		fn(TRUE, &ll);
	}
	else
	{
		::Sleep(dwMs);
	}
}

void USSleep(DWORD dwUs)
{
	static __PFN_NtDelayExecution fn = 0;
	static bool bInited = false;

	if (!bInited)
	{
		HMODULE hnt = ::GetModuleHandleW(L"ntdll.dll");
		fn = (__PFN_NtDelayExecution)(::GetProcAddress(hnt, "NtDelayExecution"));
		bInited = true;
	}

	if (fn)
	{
		LARGE_INTEGER ll;
		ll.QuadPart = -1 * ((LONGLONG)dwUs) * 10;
		fn(TRUE, &ll);
	}
	else
	{
		::Sleep(dwUs);
	}
}

class CEventWaitObj
{
public:
	HANDLE hEvent = 0;
	CEventWaitObj() { hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); }
	~CEventWaitObj() { if (hEvent)CloseHandle(hEvent); }
	void Wait(DWORD dwMs) {
		if (hEvent)
		{
			WaitForSingleObject(hEvent, dwMs);
		}
		else
		{
			Sleep(dwMs);
		}
	}
};
CEventWaitObj g_waitObj;
void MsSleep(int nMs)
{
	g_waitObj.Wait(nMs);
}

然后写一个测试代码

    DWORD64 dwStart = GetMicroSecondTickCount();
    Sleep(1);
    DWORD dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
    Debug_View(L"Sleep: cost=%lfms", 0.001*dwCost);

    dwStart = GetMicroSecondTickCount();
    DSSleep(1);
    dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
    Debug_View(L"DSSleep: cost=%lfms", 0.001*dwCost);

    dwStart = GetMicroSecondTickCount();
    USSleep(1);
    dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
    Debug_View(L"USSleep: cost=%lfms", 0.001*dwCost);

    dwStart = GetMicroSecondTickCount();
    MsSleep(1);
    dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
    Debug_View(L"MsSleep: cost=%lfms", 0.001*dwCost);

运行看一下结果

 看到Sleep函数并不准

我们看看微软介绍

 提到了timeBeginPeriod这个函数,我们使用一下看看

void TestStringConvDlg::OnBnClickedBTimercapd()
{
	TIMECAPS caps = {};
	MMRESULT mmr = 0;
	if (TIMERR_NOERROR == (mmr = timeGetDevCaps(&caps, sizeof(caps))))
	{
		Debug_View(L"TIMER: max=%d, min=%d", caps.wPeriodMax, caps.wPeriodMin);
		mmr = timeBeginPeriod(caps.wPeriodMin);
		Debug_View(L"set timer perioid=%d, ret=%d", caps.wPeriodMin, mmr);
	}
	else
	{
		Debug_View(L"!timeGetDevCaps mmr=%d", mmr);
	}

}

 看到了,居然能够实现我们的预期功能了,这个函数在winmm.dll,于是我们找到xp泄露的代码,代码实现上其实是调用的

NTSTATUS NtSetTimerResolution ( IN ULONGRequestedResolution, IN BOOLEANSet, OUT PULONGActualResolution );

这样我们可以不用导入winmm.dll直接使用这个函数

按照微软的介绍,这个函数在XP上能够设置最小1ms的分辨率,我们试试

typedef NTSTATUS(__stdcall* PFN_NtSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution);
void TestStringConvDlg::OnBnClickedBNttimerres()
{
	// TODO: 在此添加控件通知处理程序代码
	PFN_NtSetTimerResolution fn = (PFN_NtSetTimerResolution)::GetProcAddress(GetModuleHandle(L"NTDLL"), "NtSetTimerResolution");
	if (fn)//一般都能设置成1ms 有的能设置成0.5ms
	{
		ULONG Now = 0;
		LONG ns = fn(10000, TRUE, &Now);
		if (ns == 0)
		{
			Debug_View(L"NtSetTimerResolution: Now=%d", Now);
			Now = 0;
			ns = fn(1, TRUE, &Now);
			Debug_View(L"set NtTimerRes 1 =%x, Accual=%d", ns, Now);
		}
		else
		{
			Debug_View(L"!NtSetTimerResolution %x", ns);
		}
	}
	else
	{
		Debug_View(L"no NtSetTimerResolution");
	}
}

Win10上测试能够实现0.5ms的精度,USSleep可以实现最小的Sleep间隔! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值