Hardware-Class Inference: How Render and Compute Timing Reveals Your Real Machine Tier
Imagine eight test profiles on one machine. Each is configured with a different canvas hash, a different WebGL renderer string and a different hardwareConcurrency value between 4 and 16. Each reports a different computer. Yet every one of them completes the same WebGL benchmark in almost exactly the same time: the time this machine's GPU takes.
That is not how eight different computers behave. All eight profiles are running on the same hardware, and a timing script sees it.
This is timing-based hardware fingerprinting. Reported values can say anything; how fast the machine actually runs is much harder to misstate. If you run a site, it is one of the signals fraud and bot-management tools can weigh. If you run separate profiles for QA, client work or privacy, it explains why a profile's reported hardware should describe the machine it runs on. And if you review traffic quality on your own campaigns with a tool like ClickzProtect, it helps to know which device signals can be cross-checked.
What We're Building
By the end of this tutorial, you'll understand how sites infer real hardware class through WebGL render timing and WASM compute benchmarks. You'll know which timing signals a site can measure, why a reported navigator.hardwareConcurrency that ignores the real machine produces an inconsistent identity, and how to measure your own machines so profiles describe them accurately.
Prerequisites
- Basic understanding of browser fingerprinting (if you're new, start with our multi-account browser myths post)
- A multi-account browser with hardware parameters you can set per profile — JustBrowser has a 7-day trial (card required)
- Chrome DevTools basics (we'll use the Console)
- A machine where you can run timing tests without background interference
Step 1: Understanding Timing-Based Hardware Inference
navigator.hardwareConcurrency tells websites your CPU core count. navigator.deviceMemory tells them your RAM. These are JavaScript properties, so their values are whatever the browser reports.
Timing is different. It reflects what the machine actually does, whatever the JavaScript properties say.
// Run a parallel compute benchmark
const iterations = 10000000;
const workers = navigator.hardwareConcurrency;
const start = performance.now();
// Spawn workers, run math operations, wait for completion
const elapsed = performance.now() - start;
console.log(`${workers} cores completed ${iterations} iterations in ${elapsed}ms`);
If a profile reports 4 cores but the benchmark completes at 16-core speed, the reported hardwareConcurrency says one thing and the actual compute throughput says another. A script that compares the two sees the mismatch.
The same goes for GPU timing. WebGL render work scales with GPU tier: an integrated laptop GPU, a mid-range discrete card and a high-end discrete card land in clearly different bands for the same shader and the same number of draw calls. The exact numbers depend on the driver, the browser and the test, so measure your own machines (Step 4) rather than trusting a published table.
If the WebGL renderer string says Intel UHD 620 but draw calls complete at RTX 4090 speeds, the two disagree. (We covered the WebGL renderer fundamentals in our WebGL fingerprinting tutorial — this post builds on those concepts.)
Step 2: Benchmarks a Site Can Run
Fingerprinting services don't just query hardwareConcurrency and trust it. They can validate it with micro-benchmarks.
WebGL Shader Compilation Time
const gl = canvas.getContext('webgl2');
const shaderSource = `
precision highp float;
// Complex shader with loops and texture samples
`;
const start = performance.now();
const shader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
const compileTime = performance.now() - start;
Shader compilation depends on the GPU and driver, and different GPUs compile at different speeds. A profile reporting an Intel integrated GPU but compiling shaders at high-end discrete speed is inconsistent.
WASM Compute Loops
const wasmModule = await WebAssembly.instantiateStreaming(fetch('benchmark.wasm'));
const benchmark = wasmModule.instance.exports;
const start = performance.now();
benchmark.runIntegerBenchmark(10000000);
const elapsed = performance.now() - start;
WebAssembly runs close to native speed, so execution time directly reflects CPU capability. A machine reporting 4GB RAM and 4 cores shouldn't execute a compute-intensive WASM module at server-grade speeds.
Canvas Rendering Speed
const ctx = canvas.getContext('2d');
const start = performance.now();
for (let i = 0; i < 10000; i++) {
ctx.fillRect(Math.random() * 100, Math.random() * 100, 10, 10);
ctx.arc(50, 50, 20, 0, Math.PI * 2);
ctx.fill();
}
const elapsed = performance.now() - start;
Canvas operations are CPU-bound on the 2D context and GPU-accelerated on WebGL. Either way, timing corresponds to hardware class: fast machine, fast rendering.
Parallel Worker Throughput
// Deliberately start more workers than the reported core count
const workerCount = navigator.hardwareConcurrency * 4;
const start = performance.now();
const workers = [];
for (let i = 0; i < workerCount; i++) {
workers.push(new Worker('worker.js'));
}
// Give every worker the same fixed task and time how long the batch takes
On a machine that really has 4 cores, total throughput stops improving at around four busy workers. If it keeps improving well past the reported count, the machine has more cores than hardwareConcurrency says, and the identity is inconsistent.
Step 3: Why Mismatched Hardware Parameters Create an Inconsistent Identity
Real machines have coherent hardware. CPU cores, RAM, GPU and performance characteristics all agree. A gaming PC with an RTX 4090 and 32GB RAM runs benchmarks at gaming-PC speeds. A $400 laptop with Intel UHD and 8GB RAM runs them at $400-laptop speeds. There's no contradiction.
A profile configured with $400-laptop specs on a gaming PC does contradict itself. That is a stronger signal than simply reporting the real specs, because it is the mismatch that stands out, not the hardware.
The gap is widest on server hardware. A cloud server usually has far more cores than a consumer laptop, so a profile there that reports an "Intel i5 laptop" finishes parallel benchmarks much faster than a real i5 laptop would.
Step 4: Testing Your Profile's Timing Coherence
Before relying on a profile, test whether the timing matches the configured hardware. Here's the process.
Step 4.1: Baseline Your Real Hardware
Open a regular browser (no profile overrides) and run these benchmarks:
// Save this as timing-test.html and open it
const results = {};
// Test 1: WASM-style compute
const start1 = performance.now();
let x = 0;
for (let i = 0; i < 50000000; i++) {
x += Math.sqrt(i) * Math.sin(i);
}
results.computeLoop = performance.now() - start1;
// Test 2: Canvas operations
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');
const start2 = performance.now();
for (let i = 0; i < 5000; i++) {
ctx.beginPath();
ctx.arc(Math.random() * 500, Math.random() * 500, 20, 0, Math.PI * 2);
ctx.fill();
}
results.canvasOps = performance.now() - start2;
console.table(results);
console.log('hardwareConcurrency:', navigator.hardwareConcurrency);
console.log('deviceMemory:', navigator.deviceMemory);
Record the results. This is your actual hardware performance.
Step 4.2: Run the Same Test in Your Profile
Now open the same test in the profile. Compare:
- Does the compute loop time match what you'd expect from the configured hardwareConcurrency?
- Does the canvas ops time match the configured GPU?
- If the profile reports a slower machine but the benchmark runs faster, you have a coherence problem.
Step 4.3: Allow for Timer Resolution
Browsers deliberately coarsen performance.now(), more so on pages that are not cross-origin isolated, so a single short measurement is noisy. Run each benchmark several times and compare medians rather than single readings.
Step 5: Keeping Profiles Consistent With the Machine
You have three options when your timing doesn't match a profile's configured hardware.
Option 1: Configure Hardware That Matches Your Real Machine
The simplest fix. If you're running on a Ryzen 9 with an RTX 4070, don't configure an Intel i3 with integrated graphics. Configure profiles at a similar tier. The timing naturally matches because the profile describes the machine it runs on.
This limits flexibility, but it's the most reliable approach.
Option 2: Use Hardware You're Actually Reporting
If you need profiles that represent budget consumer machines, for example to test how your site renders on one, run them on budget consumer machines. Enterprise QA and device-testing teams often maintain hardware fleets for exactly this reason.
Option 3: Use a Multi-Account Browser That Sets Hardware Parameters Natively
JustBrowser sets the hardware values a profile reports (WebGL vendor and renderer strings, hardwareConcurrency, deviceMemory) at the C++ level, so they agree with each other. It does not throttle render or compute timing, so pick values close to the machine you actually run on.
Extension-based or wrapper-based tools can override what the JavaScript APIs return, but nothing changes how fast the GPU actually renders. Timing diverges whenever the configured class sits far from the real one.
JustBrowser keeps the reported hardware parameters (hardwareConcurrency, deviceMemory, WebGL strings) consistent per profile at the engine level. Timing coherence is up to you: match the configured class to the hardware you run on. There's one plan — $9.99/month, or $99.99/year — with unlimited profiles. The 7-day trial (card required) lets you test the concept before you pay.
Common Measurement Problems
WebGL benchmarks complete far faster than expected for the configured GPU
The profile is configured with integrated graphics but the machine has a discrete GPU. Change the configured GPU to match your actual hardware class.
WASM compute benchmarks complete inconsistently
This usually means background processes are affecting timing. Close other applications, disable browser extensions, run the test multiple times. Real hardware produces consistent results.
Worker throughput keeps scaling past the configured core count
The profile reports 4 cores but the machine has 16, so throughput keeps rising well past four workers. Match the configured core count to your actual cores.
Each benchmark looks plausible on its own, but the overall picture doesn't
Timing analysis can combine many measurements: WebGL, WASM, canvas, network request parallelism, DOM manipulation speed. No single benchmark tells the whole story, which is one more reason to report the hardware you actually have rather than tune values test by test. If you're also dealing with click fraud issues, ClickzProtect can help monitor those patterns separately.
Why Server Hardware Looks Different
Server hardware doesn't time like consumer hardware. A cloud server runs Xeon or EPYC processors with many cores and sustained clocks. A $400 laptop runs a mobile chip that boosts briefly and then slows down as it heats up. These machines don't perform the same and don't time the same, and no navigator override changes that.
If you need consumer-grade timing signatures, for instance to check how your own site behaves on typical consumer devices, the straightforward way is to use consumer-grade hardware or a real device farm. Each platform's own rules on multiple accounts still apply to any account you hold.
Next Steps
Timing-based hardware inference is one piece of the fingerprint stack. For complete profile consistency, you also need:
- WebGL parameter matching — renderer string, texture limits, shader precision all need to match the configured GPU (covered in our WebGL fingerprinting guide)
- Audio fingerprint coherence — AudioContext timing also varies by hardware (covered in our audio fingerprinting post)
- Font and locale consistency — your font list should match the configured OS
- Proxy location — a proxy whose location matches the configured locale (our ISP proxies setup tutorial covers adding one to a profile)
If you run these benchmarks through automation, connect Playwright or Puppeteer to a profile through the local REST API and CDP. Teams doing ad verification or geo-specific testing can pair consistent profiles with ClickzProtect for click quality monitoring or JustAnalytics for cookieless analytics.
Test your current setup against the timing benchmarks above. If a profile reports one thing but performs like another, now you know why.
Frequently Asked Questions
How do websites infer hardware class from timing?
Sites run micro-benchmarks using WebGL render passes and WASM compute loops, then measure how long they take. A high-end discrete GPU finishes a WebGL test many times faster than an integrated laptop GPU. These timing differences reveal the actual hardware tier regardless of what navigator.hardwareConcurrency reports. If a profile is configured with 4 CPU cores but executes a parallel WASM benchmark at 16-core speeds, the two signals disagree.
Can throttling the CPU make a profile time like slower hardware?
Not reliably. Throttling would have to apply evenly to every operation, not just one benchmark, and uneven throttling (fast page loads, slow benchmarks) is an inconsistency of its own. For QA on slower devices, test on slower devices or use DevTools CPU throttling as a rough approximation; for everyday profiles, report a hardware class close to the machine you have.
Which timing benchmarks can a site run?
Common ones are WebGL shader compilation time, WebGL draw-call throughput, WASM integer and float loops, canvas rendering speed and parallel Worker throughput. Commercial fraud and bot-management vendors don't publish exactly which measurements they combine.
Does JustBrowser handle timing fingerprinting?
JustBrowser sets the hardware values a profile reports (WebGL vendor and renderer, hardwareConcurrency, deviceMemory) in the engine and keeps them consistent per profile. It does not change real render or compute speed, so choose a hardware class close to your actual machine.
Try JustBrowser
A multi-account browser for teams, built on a custom Chromium engine — not extension-based. Native C++ engine patches at the canvas / WebGL / audio / font / screen layer provide 40+ identity parameters, and each profile keeps its own cookies, storage and proxy. Local REST API + CDP for Playwright, Puppeteer, Selenium. Windows x64 and macOS (Apple Silicon). $9.99/month or $99.99/year. 7-day free trial, card required — cancel any time in the seven days and you are not charged. Unlimited profiles.
Get started → · How it differs from Multilogin / GoLogin / AdsPower
Ready to manage multiple accounts?
Seven days free, then $9.99/month — one plan, everything included.