# Windows CPU % under-counts partial load

Published 2026-10-20 · Windows, CPU, performance, Task Manager · https://home.tigran.manukyan.dev/notes/windows-cpu-percent-lies.html

**TL;DR:** Windows time-based CPU % (Task Manager, GetThreadTimes, PDH % Processor Time) under-reported a partially loaded 8-core/16-thread desktop CPU by up to about 45% compared with QueryThreadCycleTime. To judge a CPU bottleneck, use GPU utilization or PresentMon GPU Busy instead.

Games never seemed to push my 8-core CPU past 30% in Task Manager, even when they were clearly CPU-bound. So I wrote a small load generator and compared what Windows reports with what the CPU actually did.

## The test

Start N threads that spin without ever blocking. For each thread, compare two numbers every second:

- the time Windows credits it with (`GetThreadTimes`, the same source as Task Manager's "Processor Time"), and
- the cycles it actually ran (`QueryThreadCycleTime`), divided by the clock.

## What came out

- 1 busy thread: time accounting credited about 88% of the real run time.
- 8 to 12 busy threads: it dropped to about 55%.
- All 16 hardware threads busy: both said 100%.
- The cycle counter said 98 to 99% the whole time, and throughput agreed: 8 threads did 8.2× the work of 1.

With 8 threads pegged, the system-wide "time" number showed 28.5% while about half the machine was really busy. The other counter, "% Processor Utility", went the other way and read 80 to 85%, because it scales by boost clock against the base frequency. It also showed about 13% at idle.

## What I use instead

For "am I CPU-bound?" the CPU % is close to useless. GPU usage that sits below its cap, or PresentMon's GPU Busy compared to frame time, answers the question directly. I checked one third-party task manager too: it reads the same time-based counters, so it inherits the same under-count.
