我在我的代码中看到我无法解释的
Stopwatch
对象的这种非常奇怪的行为。我编写了这个非常简单的应用程序来重现该行为,但它仍然存在。简而言之,如果我在循环中添加一个Thread.Sleep(1)
,但是在计时部分的outside,它会将循环的时间更改为 10.
这里是完整的代码。
public static void DoStuff()
{
long ticks;
double seconds;
ticks = Stopwatch.GetTimestamp();
if (ticks % 2 == 0)
{
ticks = ticks / Stopwatch.Frequency;
}
else
{
seconds = ticks / Stopwatch.Frequency;
}
}
static void Main(string[] args)
{
const int loopCount = 1000;
List<double> times;
Console.WriteLine("Loops = " + loopCount);
// No sleep
times = new List<double>();
for (int i = 0; i < loopCount; i++)
{
Stopwatch sw = Stopwatch.StartNew();
DoStuff();
sw.Stop();
times.Add(sw.Elapsed.TotalSeconds);
// Thread.Sleep(0);
}
Console.WriteLine();
Console.WriteLine("--- No sleep ---");
Console.WriteLine("Average = " + times.Average() + " s");
Console.WriteLine("Average = " + (times.Average() * 1000000000).ToString("N2") + " ns");
// Sleep
times = new List<double>();
for (int i = 0; i < loopCount; i++)
{
Stopwatch sw = Stopwatch.StartNew();
DoStuff();
sw.Stop();
times.Add(sw.Elapsed.TotalSeconds);
Thread.Sleep(1);
}
Console.WriteLine();
Console.WriteLine("--- Sleep ---");
Console.WriteLine("Average = " + times.Average() + " s");
Console.WriteLine("Average = " + (times.Average() * 1000000000).ToString("N2") + " ns");
Console.WriteLine();
Console.Write("Press any key to continue...");
Console.ReadKey();
}
下面是那段代码的运行结果,是一致的。我可以运行它几十次,如果我有一个
Sleep(1)
,那么秒表计时就会完全改变。我不知道该相信哪个数字。 Sleep(0)
实际上并没有让线程休眠,只是重新排队。但是,如果我执行 Sleep(0)
或者如果我只是注释掉那行代码,就会发生相同的行为。我试过只实例化
Stopwatch
对象一次并调用 Restart()
、Reset()
和 Start()
,但这也没有什么不同。我不知道这是线程问题还是Stopwatch
问题。
感谢您的观看
正如您所提到的
Sleep(0)
将只允许调度程序运行其他东西,在大多数情况下,您将有可用的核心,并且程序将继续运行。
另一方面,Sleep(1)
实际上会挂起线程,可能会挂起整整 16 毫秒,如果没有其他要运行的东西,CPU 将处于空闲状态。但是你不是在测量睡眠,只是测量DoStuff()
的时间,所以这有什么关系呢?
一个可能的解释是缓存。如果您的线程暂停了相当于 CPU 时间的永恒,那么 CPU 缓存中的任何数据很可能会被丢弃和替换。因此,当线程最终再次运行时,它必须从主内存中获取内容,因为主内存访问需要大约 100ns,一些缓存未命中可以很容易地解释时间差异。
另一个可能的解释是频率和功率。如果 CPU 空闲,它会降低频率和功率,因为它开始工作需要一些时间才能再次加速。细节相当复杂,并且会因处理器而异。
但在任何情况下,您都可能测量实际 CPU 时间的差异。如果你真的想深入了解 CPU 正在做什么的细节,你可以使用 intel VTune。
Windows 上的默认计时器分辨率 是 15.6 毫秒,因此默认情况下,您所有的睡眠时间都将约为 15.6 毫秒的倍数。
可以通过调用Windows API来增加计时器的分辨率
timeBeginPeriod()
您需要致电
timeendperiod()
才能恢复正常分辨率。
您可以使用以下 P/Invokes 从 C# 调用这些:
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("winmm.dll", EntryPoint="timeBeginPeriod", SetLastError=true)]
public static extern uint TimeBeginPeriod(uint uMilliseconds);
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("winmm.dll", EntryPoint="timeEndPeriod", SetLastError=true)]
public static extern uint TimeEndPeriod(uint uMilliseconds);
注意:真的不建议乱用这个,因为它是系统范围的设置,所以更改它会影响所有应用程序。