Windows 系统的特色功能,注册表,是一个十分方便有用的工具。它可以用来以一种统一和多线程安全的方式来永久性的保存数据。如果你将数据保存在 HKEY_CURRENT_USER 键下,则数据可以随着用户一起漫游,并且可以保护单个键值 (即使是在使用了 FAT 文件系统的操作系统也是如此)。
但这并不意味着这是一份免费的午餐。
据我所知,从打开注册表键开始,读取键值并关闭它,整个过程将花费大约 60000 到 100000 个 CPU 周期,这还是假定要查找的键值已经缓存在内存中的情况。如果你打开注册表键并保持打开状态,那么读取值的行为大约需要 15000 到 20000 个 CPU 周期(这些测算数据是 Windows XP 下的估计值,在真实情况下数据可能会有所不同)。
因此,我们不应在一个内部循环中读取注册表项。它不仅会在查询时花费 CPU 时间,而且注册表的不断查询意味着,注册表用于查找和存储键(包括注册表缓存中的条目)的数据结构将始终保存在系统工作集中。
另外,也不要在每次鼠标移动时读取注册表项,应该仅读取该值一次并缓存结果。
如果你担心用户在程序运行时修改了键值,则可以考虑建立一个规则,供人们在想要更改设置时遵循。
例如,Windows 使用诸如 SystemParametersInfo 之类的函数,在通常情况下,会首先读写缓存中的数据,而不是每次都从注册表中读取。调用 update 函数会更新注册表和内存中缓存。
如果无法建立协调设置更改的机制,则可以通过 RegNotifyChangeKeyValue 函数设置更改通知,以便在值更改时收到通知。一般原则是,应该尽可能针对常见情况进行优化,而不是针对罕见情况进行优化。常见情况是注册表值未更改。通过使用通知机制,可以将“但是如果值更改了怎么办?”的成本从内部循环中移出,并转移到大多数时间不执行的代码中。(请记住,最快的代码是永远不会运行的代码。)
当然,你不想在一个线程上等待多个通知事件。我的方法是:使用线程池。
RegisterWaitForSingleObject 函数可以用来告诉线程池,”嘿,当这个对象发出信号时,请通知我。” 然后,线程池会将其与要求等待的所有其他句柄组合成一个巨大的 WaitForMultipleObjects 调用。这样,一个线程可以处理多个等待对象。
需要注意的一个地方是,RegNotifyChangeKeyValue 函数所发出的通知具有线程亲缘性。
如果调用 RegNotifyChangeKeyValue 函数的线程退出,则会引发通知。这意味着你不应该从线程池线程调用该函数,因为当工作列表空闲并且不再需要它们存在时,系统将销毁线程池中的线程。
如果你不小心搞砸了并从线程池线程调用它,你会发现当线程池清理代码运行时,事件不断虚假触发,这可不是一件好事。
相反,你应该从持久线程(例如,实际关心值的线程) 创建等待,并在那里注册等待。当事件在线程池上触发时,处理更改,然后要求持久线程启动 RegNotifyChangeKeyValue 的新周期。这样,事件始终与持久线程相关联,而不是与暂时性线程池线程相关联。
总结
一般我们会将应用程序的设置数据保存到注册表,这很方便,但是记得读取的时候,尽量只读一次并缓存结果,而不是每次都从注册表里读取,这对运行时性能是有伤害的。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《The performance cost of reading a registry key》