Share via

TotalProcessorTime differs between Windows 10 and Windows 11

Martin Dijkstra 20 Reputation points
Feb 22, 2023, 1:16 PM

For our applications we use the Process.GetCurrentProcess().TotalProcessorTime to determine how much CPU is used for actions calls.

When an action takes 1 second to execute and only performs cpu operations, we except 1 second of cpu time.

For Windows 10 this works as expected, for Windows 11 the cpu usage is always 30/50% lower.

This behavior applies to .net framework and .net 6 applications

Below is a test application to reproduce the problem.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

_ = Task.Run(() => ConsumeCPU());

while (true)
{
    await PrintCpuUsage();
}

static void ConsumeCPU()
{
    while (true) ;
}

static async Task PrintCpuUsage()
{
    var startTime = DateTime.UtcNow;
    var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime;

    await Task.Delay(1000);

    var endTime = DateTime.UtcNow;
    var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime;

    var cpuUsedMs = (int)(endCpuUsage - startCpuUsage).TotalMilliseconds;
    var totalMsPassed = (int)(endTime - startTime).TotalMilliseconds;
    Console.Clear();
    Console.WriteLine($"CPU:{cpuUsedMs}ms Watch:{totalMsPassed}ms");
}
Windows 10
Windows 10
A Microsoft operating system that runs on personal computers and tablets.
12,077 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,351 questions
Windows 11
Windows 11
A Microsoft operating system designed for productivity, creativity, and ease of use.
11,221 questions
{count} votes

Accepted answer
  1. Gary Nebbett 6,201 Reputation points
    Mar 2, 2023, 2:41 PM

    Hello Martin,

    I think that I am getting closer to understanding what is happening and that suggests to me that you will have to update your performance analysis techniques.

    Using ETW under Windows 10, one often sees sequences of events like this:

    User's image

    The first clock interrupt entry is the hardware clock interrupt; the lower bit of the final value for that entry indicates that this processor (0) is the clock owner. The next two clock interrupt entries occur as a result of an interprocessor interrupt (IPI) - this can be verified by examining the stacks for the three clock interrupt events. The clock interrupt is forwarded, via IPI, to all processors that are not "idle".

    This behaviour gives the results for CPU usage time that you have observed up to Windows 10.

    Under Windows 11, I see this:

    User's image

    User's image

    The differences are that a new bit (mask 0x10) is set on all clock interrupt events and clock interrupt events always occur just once per "tick" (i.e. no instances of clock interrupt events separated by tenths of microseconds on different processors).

    This could just be an artefact of ETW tracing under Windows 11, but it ties in nicely with the observed behaviour of the thread/process CPU usage counters. More work is needed...

    Gary


6 additional answers

Sort by: Most helpful
  1. red-ray 6 Reputation points
    Aug 25, 2023, 9:48 PM

    As I suspected this was a bug in Windows 11. I know this as it's been fixed in todays 23H2 Build 25936 and my test proves this as the two idle times are now much the same.

    RCR

    1 person found this answer helpful.
    0 comments No comments

  2. lostandfound 6 Reputation points
    Sep 5, 2023, 12:42 AM

    I know this as it's been fixed in todays 23H2 Build 25936

    @red-ray

    Nothing changed? Check the disassembly of those kernelbase functions, they're still using the exact same broken NtQuerySystemInformation behavior as previous versions of Windows.

    The SIV64X tool in your screenshot however was updated and their changelog mentions a workaround... They're now bypassing kernelbase.dll (NtQuerySystemInformation) and are instead calling NtQuerySystemInformationEx directly which is why you're seeing correct values from SIV64X - everything else using GetSystemTimes/GetSystemInfo from kernelbase (NtQuerySystemInformation) still has issues with Windows 11.

    1 person found this answer helpful.
    0 comments No comments

  3. Gary Nebbett 6,201 Reputation points
    Feb 24, 2023, 8:55 AM

    Hello Martin,

    As simple as your question might appear, it is fraught with imponderables. Here are some examples:

    The time, as reported by DateTime.UtcNow, is derived from the clock tick interrupt; between clock interrupts, the time value does not change; two measurements made just microseconds apart might differ by tens of milliseconds if a clock interrupt occurs between the measurements.

    The query of processor usage of threads and processes is obtained by reading values from kernel data structures. The processor usage is not "continuously" updated in this data structure while a thread is running; it might be updated on clock ticks, quantum ends, context switch out, etc. Without knowing implementation details, we can't be sure exactly what this value means for a running thread.

    The statement "When an action takes 1 second to execute and only performs cpu operations, we except 1 second of cpu time" is näive. The CPU frequency can and does often change and fewer instructions are executed in some 1 second intervals compared to other 1 second intervals. I think that processor cycles are used to measure consumed CPU time and that the value is normalised via the "nominal" speed of the processor to give a time in (micro)seconds.

    I stopped trying to think of other weaknesses in this type of test when I thought of three, so there might be more...

    Gary


  4. Gary Nebbett 6,201 Reputation points
    Mar 1, 2023, 1:58 PM

    Hello Martin,

    Thanks, the trace data is good. Below are some easy representations of the data.

    User's image

    The CPU usage of the "busy" thread is 14935.1855 milliseconds, measured with 0.1 microsecond accuracy of the time between switch-in and switch-out events. Some time was spent waiting, but probably during thread start-up and run-down.

    User's image

    User's image

    When the thread stopped, it had accumulated 573 CPU units in kernel mode and 165 units in user mode. (573 + 165) * 15.625 = 11531.25 milliseconds. Its "CycleTime" was 0x89CE0FBDA = 36991728602 units. The "nominal" "CPUSpeed" is 2496 MHz; 36991728602 / 2496 = 14820 milliseconds.

    User's image

    The "busy" thread mostly runs on processors 0, 1, 2 and 3. 36991728602 / 11531 = 3208 MHz. This is plausibly a mix of the frequencies of processors 0 to 3.

    More work is needed to investigate whether CPU frequency actually accounts for the numerical results and whether some system/CPU characteristics (such as whether RDTSC is a constant rate counter) are being incorrectly queried/read.

    Gary

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.