← All reports

Zig — Randomized Quicksort Report (HW3)

Language / toolchain

Algorithm / RNG

Implements CLRS's RANDOMIZED-QUICKSORT / RANDOMIZED-PARTITION directly:

RNG: a single std.Random.DefaultPrng (Zig's default PRNG, xoshiro256++), seeded once at program start from 8 bytes pulled out of std.crypto.random (the OS CSPRNG), is used for both purposes required by the harness:

  1. generating each iteration's array of random test data (rand.intRangeLessThan(i32, 0, 1_000_000)), and
  2. choosing the random pivot index at every partition step (rand.intRangeAtMost(usize, lo, hi)).

Data generation happens before the timer starts and is excluded from the measured time, per the harness spec.

External code

No external code used — original implementation based on CLRS RANDOMIZED-QUICKSORT/RANDOMIZED-PARTITION pseudocode (Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, Ch. 5). No third-party libraries; only the Zig standard library (std.Random, std.crypto.random, std.time.Timer, std.heap.page_allocator, std.fs, std.io, std.fmt) was used.

Results

Full results (also in HW3/results/zig.csv), n vs. time in seconds:

n time_seconds
1,000 0.000049
2,000 0.000085
4,000 0.000223
8,000 0.000368
16,000 0.000786
32,000 0.001660
64,000 0.003569
128,000 0.006941
256,000 0.014789
512,000 0.030438
1,024,000 0.063931
2,048,000 0.130468
4,096,000 0.273824
8,192,000 0.582978
16,384,000 1.208293
32,768,000 2.616146
65,536,000 5.589182

The run stopped after 17 iterations because the n = 65,536,000 iteration took 5.589 s, exceeding the 4.0 s cutoff (the 25-iteration and 200,000,000-n caps were not reached). Total wall-clock time for zig build-exe -O ReleaseFast plus the full harness run was ~11 seconds.

Growth analysis

The measured times track O(n log n) closely. Doubling n each iteration should roughly double the time, scaled up slightly by the growing log factor (2·log₂(2n)/log₂(n)); at these sizes (n ≈ 8M–65M, log₂n ≈ 23–26) that predicted ratio is about 2.08–2.09. The observed ratios between the last four consecutive doublings are 2.13, 2.07, 2.17, and 2.14 — right in that neighborhood, with the small excess plausibly explained by cache/TLB effects as the working set (up to ~256 MB for the n = 65,536,000 array of i32) outgrows L2/L3 cache at the largest sizes, plus ordinary timer-resolution and OS-scheduling noise at the very small end of the table (n = 1,000–8,000, where times are single-digit microseconds and dominated by call/loop overhead rather than the sort itself). No warmup, GC, or JIT effects are relevant here — Zig is ahead-of-time compiled to native code with no runtime or garbage collector, so there is no first-call interpretation or compilation cost to amortize; the very first iteration's time is already representative of steady-state performance.

As expected, Zig's results land essentially in the same performance class as the other AOT-compiled, manually-memory-managed systems languages in this comparison (C, C++, Rust): all four reach tens of millions of elements before hitting the 4 s cutoff, and their per-n timings are within a small constant factor of each other, consistent with all four compiling randomized quicksort down to similar native machine code with no managed-runtime overhead (no garbage collector, no bytecode interpretation, no bounds-checked-by-default array indexing in ReleaseFast mode). This is worth calling out explicitly since it's the expected outcome for a language occupying the same "compiled, manual memory management" niche as C/C++/Rust, in contrast to the managed/interpreted languages (Python, Java, C#, JavaScript, etc.) elsewhere in this comparison.