The performance cost of reading a registry key - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20060222-11/?p=32193
Raymond Chen 2006年2月22日
读取注册表键的性能成本
注册表是一个方便的场所,它以统一且多线程安全的方式记录了跨进程的持久数据。如果将数据存储在 HKEY_CURRENT_USER
下,它还可以随用户漫游,并且即使在不支持安全性的FAT文件系统上,也可以对单个键进行安全设置。
但这并不意味着它是零成本的。打开一个键、读取一个值、然后关闭它的操作大约需要60,000到100,000个CPU周期(据我了解)。这是在假设你访问的键已经在缓存中的情况下。如果你保持键打开的状态,那么读取值的操作成本会降低到大约15,000到20,000个CPU周期。(这些数字是基于Windows XP的估算;实际的性能可能有所不同。)
因此,你不应该在程序的核心循环中读取注册表键。这不仅会在查询时消耗CPU时间,而且频繁地访问注册表意味着用于定位和存储你的键的数据结构(包括注册表缓存中的条目)会被保留在系统的活动工作集中。不要在每次鼠标移动事件中都读取注册表键;你应该一次性读取值并将其缓存起来。如果你担心程序运行期间有人更改了该值,你可以建立一个协议,让人们在想要更改设置时遵循。例如,Windows使用如 SystemParametersInfo
这样的函数来操作那些通常被缓存而不是每次需要时都直接从注册表中读取的设置。调用更新函数可以同时更新注册表和内存中的缓存。
在可能的情况下,应该针对常见情况进行优化,而不是罕见情况。常见情况是注册表的值没有变化。通过使用通知机制,你可以将“如果值改变了怎么办?”的成本从核心循环中转移出去,转移到大多数时候不会执行的代码中。(记住,最快的代码是没有运行的代码。)
当然,你不会希望让一个线程一直等待通知事件。我建议使用线程池。RegisterWaitForSingleObject
函数允许你向线程池发出请求:“嘿,当这个对象被触发时,请呼叫我,好吗?”线程池随后会将这个请求与其他所有它被要求等待的句柄结合起来,统一进行 WaitForMultipleObjects
调用。这样,一个线程就可以同时处理多个等待事件。
使用 RegNotifyChangeKeyValue
函数时需要注意的一点是,通知具有线程亲和性!如果调用 RegNotifyChangeKeyValue
函数的线程终止,通知将被激发。这意味着你不应该从线程池中的线程调用此函数,因为系统会在线程池的工作列表空闲且不再需要时销毁线程。如果你错误地从线程池线程调用它,你会发现事件会不断地错误触发,因为线程池的清理代码在运行,这可能使问题变得更糟!相反,你应该从一个持久的线程(比如,真正关心该值的线程)创建等待,并在那里注册等待。当线程池中的事件触发时,处理更改,然后让你的持久线程启动 RegNotifyChangeKeyValue
的新周期。这样一来,事件总是与你持久的线程相关联,而不是与临时的线程池线程相关联。