为什么 Stopwatch 值会随着 Thread.Sleep 变化 10 的数量级?

问题描述 投票:0回答:2

我在我的代码中看到我无法解释的

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
问题。

感谢您的观看

c# multithreading performance stopwatch
2个回答
3
投票

正如您所提到的

Sleep(0)
将只允许调度程序运行其他东西,在大多数情况下,您将有可用的核心,并且程序将继续运行。

另一方面,

Sleep(1)
实际上会挂起线程,可能会挂起整整 16 毫秒,如果没有其他要运行的东西,CPU 将处于空闲状态。但是你不是在测量睡眠,只是测量
DoStuff()
的时间,所以这有什么关系呢?

一个可能的解释是缓存。如果您的线程暂停了相当于 CPU 时间的永恒,那么 CPU 缓存中的任何数据很可能会被丢弃和替换。因此,当线程最终再次运行时,它必须从主内存中获取内容,因为主内存访问需要大约 100ns,一些缓存未命中可以很容易地解释时间差异。

另一个可能的解释是频率和功率。如果 CPU 空闲,它会降低频率和功率,因为它开始工作需要一些时间才能再次加速。细节相当复杂,并且会因处理器而异。

但在任何情况下,您都可能测量实际 CPU 时间的差异。如果你真的想深入了解 CPU 正在做什么的细节,你可以使用 intel VTune


-1
投票

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);

注意:真的不建议乱用这个,因为它是系统范围的设置,所以更改它会影响所有应用程序。

© www.soinside.com 2019 - 2024. All rights reserved.