How Does Our GPU Actually Work?

Every number on this page was measured on a Tesla P4 (sm_61), launched and profiled from Prolog, with hardware counters. No vendor whitepapers, no estimates — probe kernels and counted sectors. Provenance at the foot of the page.

1 · The Memory Floor

A noop-copy kernel — load every element, store it back, compute nothing — sets the floor under every elementwise op. The bars show measured read/write time at four working-set sizes:

l1_6k (N=6144)
3.34 + 3.27 μs · 100% memory
half_l2_125k (N=125000)
4.39 + 3.68 μs · 98% memory
full_l2_250k (N=250000)
6.26 + 5.05 μs · 98% memory
dram_4m (N=4000000)
108.3 + 108.1 μs · 100% memory
readwrite

Memory-bandwidth defines the no-op copy kernel. 100% of its runtime is spent moving bytes, as it performs no compute function. We use this kernel as a measurement reference, and to measure some basic parameters of our GPU’s performance.

2 · The Three Compute Regimes

To measure some basics about more realistic kernels, which perform computation, we study ReLU (Rectified Linear Units) because it is one of the simpler, element-wise computations. We do know that element-wise computations are typically limited by memory bandwidth. When you consider what kernel fusions to perform, where the kernel you are studying fits on this spectrum will decide which optimizations can make a difference:

single_kernel
98% / 2%

Standalone elementwise — ALU invisible behind memory

paired_kernels
85% / 15%

Back-to-back kernels — cache warm, ALU emerges

register_resident
0% / 100%

In-place loop — pure compute, 42% Kolmogorov delta

memorycompute

In the register-resident regime the instruction-count delta between strict and fast relu emission — invisible behind memory in regime one — becomes a measured 42% wall-time difference.

3 · In-Place vs Two-Buffer: Dataspace Fusion

An elementwise op can write its result back into its input buffer (halving cache footprint) or into a second buffer. Below the L2 boundary it makes no difference. AT the boundary (N=300K, ~2.4MB two-buffer footprint vs 2MB L2), the two-buffer version spills — and pays:

Nlayouttime μsL2 read missesDRAM read sectors
6144two_buffer4.4448103
6144inplace4.4348107
125000two_buffer4.69762167
125000inplace4.599762202
300000two_buffer15.8323365130
300000inplace6.1323365166

At the L2 boundary, in-place is 2.6× faster — its read-floor sits at ZERO because every read hits cache the writes just warmed.

Fair comparison: L2 flushed between measurements, both orderings tested, result order-independent.

4 · The Cache Hierarchy

L1: 48KB per SM. L2: 2MB shared across the chip. DRAM: 8GB. Timing a size-sweep under two cache configurations (30 runs each, fresh flush between):

N elements614412288245763276865536131072
PREFER_L1 μs6.56.76.756.977.428.43
PREFER_NONE μs7.187.297.57.577.768.56

Caveat, honestly held: the PREFER_NONE penalty (~9%) is driver decision overhead at launch, not cache behavior — the hardware cache allocation is the same.

L1 partially survives kernel boundaries: 64 cache lines persisted between two launches — 7% reuse, measured by read-miss deltas.

5 · What a Kernel Launch Actually Costs

The steady-state 7.5 μs launch+sync, decomposed by measurement:

prolog_fli
1.4 μs
pthread_mutex
0.3 μs
command_setup
0.5 μs
pcie_doorbell
0.1 μs
gpu_compute_6k
0.5 μs
sync_poll
4.7 μs

For a 6K-element kernel, ~98% of wall time is asking the GPU to do something. ~2% is the GPU doing it.

Caveat, honestly held: the 4.7 μs sync-poll term is a host-side polling artifact (the CPU waiting to notice completion), not GPU-intrinsic cost. First-launch costs 51 μs unconfigured, 23.9 μs with cache-config set; steady-state 7.5 μs.

6 · Coalescing: How Thread→Memory Mapping Decides Everything

Same bytes, same op — only the access pattern changes. L2 sector counts show WHY: stride-32 touches 8× the sectors for the same data:

patterndirectiontime μsL2 sectors
coalescedread6.04976
stride_2read5.77976
stride_32read15.457826
reversedread5.24976
coalescedwrite4.44976
stride_2write4.45986
stride_32write24.277820
reversedwrite4.48978

Stride-32 writes run 5.5× slower. Reversed order runs at coalesced speed — the hardware coalesces by address-set per warp, not by thread order.