有人问了我这样一个问题:


我需要在我的程序中需要生成一些独一无二的标识符,当然,我可以使用 GUID 来实现,但是 GUID 对我的实际使用场景来说实在是太大了,我做了一些功课,发现有 QueryPerformanceCounter 这个函数:
[
System.Runtime.InteropServices.DllImport(“Kernel32.dll”)]
private static extern int
QueryPerformanceFrequency(ref System.Int64 frequency);

[System.Runtime.InteropServices.DllImport(“Kernel32.dll”)]
private static extern int
QueryPerformanceCounter(ref System.Int64 performanceCount);

public static long GenerateUniqueId()
{
System.Int64 id = 0;
QueryPerformanceFrequency(ref id);
QueryPerformanceCounter(ref id);
return id;
}
上面这段代码可以生成一个 64 位的唯一值(我希望它确实是唯一的),这个唯一性是基于进程级别的。
也就是说,两个不同的经常可能会生成相同的数字,但是对于单实例进程来说,它应该始终返回一个唯一值。
至于多线程环境下对 GenerateUniqueId 的调用是否返回唯一值,我没有仔细研究。

以上代码中,QueryPerformanceCounter 函数来获取高精度性能计数器的当前值,但是,系统并不保证每次调用它的时候,它都会返回一个不同的值。
高精度性能计数器的频率由 HAL 确定。你可能会说,RDTSC 指令应该更加适合这个场景,因为它返回 CPU 时钟周期数,这个数字会以高速增长。

但是 RDTSC 会有很多问题,举例来说,可变速的处理器意味着 CPU 时钟经过的速率随时间而变化。当计算机使用壁式电源运行时,一百万个时钟滴答可能需要一毫秒,但当使用电池电源运行时,可能需要两毫秒。

如果 HAL 不能使用 RDTSC,它会使用什么?好吧,正如我所说,这取决于 HAL 是否找到合适的东西。较旧的主板必须使用可编程间隔计时器,该计时器以每秒 1,193,182 个滴答(每个滴答约 0.8 微秒)的速度运行。较新的主板可以使用 ACPI 计时器,该计时器以每秒 3,579,545 个刻度(每个刻度约为 0.3 微秒)运行。

我办公室中的一台计算机使用 ACPI 计时器作为其高分辨率性能计数器,因此我编写了一个快速程序,以查看通过快速连续调用 QueryPerformanceCounter 来跟踪 ACPI 计时器有多接近。使用 1.80GHz 处理器时,计算机设法足够快地调用 QueryPerformanceCounter,以至于在连续调用之间仅经过 ACPI 计时器的四个刻度。

我们正在进入能够调用 QueryPerformanceCounter 两次并从 ACPI 计时器获取相同值的范围。

换句话说,你今天可能很幸运,因为你的 CPU 不够快,无法调用 QueryPerformanceCounter 两次并返回相同的值,但看起来我们确实威胁要很快到达那里。

话又说回来,所有这些粗略的计算都是多余的。

您所需要的只是一台具有多个处理器的机器。让两个处理器同时调用 QueryPerformanceCounter (或几乎如此),它们将获得相同的计时器值。

如果要生成唯一的 64 位值,则只需使用 InterlockedIncrement64。

总结

所以,又一条成功避坑条款:不要使用 QueryPerformanceCounter 来生成唯一值。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《QueryPerformanceCounter is not a source for unique identifiers》

打开网易新闻 查看更多图片