淺談注冊表讀取所需要付出的性能代價
Windows 系統的特色功能,注冊表,是一個十分方便有用的工具。它可以用來以一種統一和多線程安全的方式來永久性的保存數據。如果你將數據保存在 HKEY_CURRENT_USER 鍵下,則數據可以隨著用戶一起漫游,并且可以保護單個鍵值 (即使是在使用了 FAT 文件系統的操作系統也是如此)。
但這并不意味著這是一份免費的午餐。
據我所知,從打開注冊表鍵開始,讀取鍵值并關閉它,整個過程將花費大約 60000 到 100000 個 CPU 周期,這還是假定要查找的鍵值已經緩存在內存中的情況。如果你打開注冊表鍵并保持打開狀態,那么讀取值的行為大約需要 15000 到 20000 個 CPU 周期(這些測算數據是 Windows XP 下的估計值,在真實情況下數據可能會有所不同)。
因此,我們不應在一個內部循環中讀取注冊表項。它不僅會在查詢時花費 CPU 時間,而且注冊表的不斷查詢意味著,注冊表用于查找和存儲鍵(包括注冊表緩存中的條目)的數據結構將始終保存在系統工作集中。
另外,也不要在每次鼠標移動時讀取注冊表項,應該僅讀取該值一次并緩存結果。
如果你擔心用戶在程序運行時修改了鍵值,則可以考慮建立一個規則,供人們在想要更改設置時遵循。
例如,Windows 使用諸如 SystemParametersInfo 之類的函數,在通常情況下,會首先讀寫緩存中的數據,而不是每次都從注冊表中讀取。調用 update 函數會更新注冊表和內存中緩存。
如果無法建立協調設置更改的機制,則可以通過 RegNotifyChangeKeyValue 函數設置更改通知,以便在值更改時收到通知。一般原則是,應該盡可能針對常見情況進行優化,而不是針對罕見情況進行優化。常見情況是注冊表值未更改。通過使用通知機制,可以將“但是如果值更改了怎么辦?”的成本從內部循環中移出,并轉移到大多數時間不執行的代碼中。(請記住,最快的代碼是永遠不會運行的代碼。)
當然,你不想在一個線程上等待多個通知事件。我的方法是:使用線程池。
RegisterWaitForSingleObject 函數可以用來告訴線程池,”嘿,當這個對象發出信號時,請通知我。” 然后,線程池會將其與要求等待的所有其他句柄組合成一個巨大的 WaitForMultipleObjects 調用。這樣,一個線程可以處理多個等待對象。
需要注意的一個地方是,RegNotifyChangeKeyValue 函數所發出的通知具有線程親緣性。
如果調用 RegNotifyChangeKeyValue 函數的線程退出,則會引發通知。這意味著你不應該從線程池線程調用該函數,因為當工作列表空閑并且不再需要它們存在時,系統將銷毀線程池中的線程。
如果你不小心搞砸了并從線程池線程調用它,你會發現當線程池清理代碼運行時,事件不斷虛假觸發,這可不是一件好事。
相反,你應該從持久線程(例如,實際關心值的線程) 創建等待,并在那里注冊等待。當事件在線程池上觸發時,處理更改,然后要求持久線程啟動 RegNotifyChangeKeyValue 的新周期。這樣,事件始終與持久線程相關聯,而不是與暫時性線程池線程相關聯。
總結
一般我們會將應用程序的設置數據保存到注冊表,這很方便,但是記得讀取的時候,盡量只讀一次并緩存結果,而不是每次都從注冊表里讀取,這對運行時性能是有傷害的。

















