多線程 HashCache 需後台線程定期遍歷節點回寫髒數據。磁盤 I/O 耗時不可控,若長期持有大鎖會嚴重阻塞業務線程。目標:回寫線程工作 5ms → 休眠 15ms,20ms 周期內業務佔 75% 時間片。這要求毫秒級精度控制,且誤差極小。
舊庫 CDeltaTime 僅秒級精度;CCountSub 用 unsigned long 存毫秒會在 49 天溢出。首要任務:構建無溢出的 DWORD 差值計算器。
class CDWORDCount {
public:
DWORD GetX() { // 正確處理 GetTickCount() 環繞
if (m_dwEnd < m_dwBegin)
return m_dwEnd + (DWORD)abs(0 - m_dwBegin);
return m_dwEnd - m_dwBegin;
}
private:
DWORD m_dwBegin, m_dwEnd;
};
bool TimeIsUp0(DWORD dwMaxDelta) {
return dwMaxDelta <= GetDeltaMicroSeconds(); // 直接比較
}
Windows GetTickCount() 精度約 15.6ms,請求 1ms 實際等待 15ms,完全不可用。
引入 m_nFix 動態修正:每次觸發計算 nDeltaT - nMaxDelta,誤差為正則 m_nFix--(下次更易觸發),為負則 m_nFix++。但單次絕對誤差導致修正因子劇烈震盪,精度瓶頸在 7~8ms。
引入 CTonyMean 增量平均值模組,以歷史誤差均值驅動修正因子,抑制震盪:
class CTonyMean {
double Push(double v) { // 支持正負數混合、溢出自愈
m_dAllCount += v; ++m_nTimes;
if (OnlyOneTypeNumber() && DoubleIsZero(m_dAllCount)) ResetByOverflow();
if (!m_nTimes) ResetByOverflow(); // 計數器溢出保護
m_dMean = m_dAllCount / m_nTimes;
return m_dMean;
}
// ... 輔助函數 ...
};
bool TimeIsUp2(DWORD dwMaxDelta) {
int nDeltaT = GetDeltaMicroSeconds();
int nComp = dwMaxDelta + m_nFix;
bool bRet = nComp <= nDeltaT;
if (bRet) {
m_Mean.Push(nDeltaT - dwMaxDelta); // 累積誤差
if (m_Mean.GetMean() > 0) m_nFix--;
else if (m_Mean.GetMean() < 0) m_nFix++;
}
return bRet;
}
| 模式 | 目標 | 實測均值 | 穩定性 |
|---|---|---|---|
| TimeIsUp0 | 1 ms | 15.625 ms | 極差 |
| TimeIsUp1 | 1 ms | 7.8126 ms | 一般 |
| TimeIsUp2 | 1 ms | 1.0000 ms | 優秀(±0.003ms) |
測試邏輯:每模式 5 輪 × 1000 次,統計平均喚醒間隔。模式 2 首輪即達標,後續極其穩定。
提供 CTonyDeltaMicroSecondsWithLock,以 CMutexLock 保護所有介面。配套鎖調用宏簡化代碼:
#define TONY_DWORD_LOCK_CALL(func) \
{ DWORD dwRet; m_Lock.Lock(); dwRet = func; m_Lock.Unlock(); return dwRet; }
從「路徑跟蹤」遊戲 AI 靈感出發,經歷三代算法重構,最終以增量平均值驅動的自適應修正攻克系統定時器精度缺陷。此組件已應用於生產級 HashCache 回寫調度,長期運行穩定。代碼遵循《0bug》工程規範:內聯優化、跨平台宏、完整測試用例、資源鎖封裝。