Skip to content

v3: Speed, Safety, Zig, and new C APIs

Choose a tag to compare

@ashvardanian ashvardanian released this 15 Jul 15:25

ForkUnion banner

ForkUnion v3 is a full redesign of the core C++ project, with rippling effects on the existing Rust and C bindings, as well as the new Zig interface. The project is now reorganized, uses token-based completion tracking — always-odd generation handles — across multiple pools, smarter NUMA-aware work-stealing from per-worker fetch_add cursors, more hardware-friendly assembly primitives for busy-waiting and cache-line promotions and demotions, and explicit "topology" and "fabric" interfaces to drive the design of your higher-level systems.

Highlights: ForkUnion is often ~3–6× faster than OpenMP and ~12–16× faster than Rayon and Taskflow. Full tables ↓

What's the same: still avoids heap allocations, syscalls, and CAS on the hot path. Still has no shared_ptr<future<T>> or std::function boxes. Still has no work-stealing deque. On the steal path this matters. A worker whose own slice runs dry drains its neighbors' private cursors in coprime order, so stealers fan out across cursors instead of dog-piling one — overshooting a slice by at most one task, and never issuing a CAS. Rayon, Taskflow, and oneTBB steal halves through a Chase-Lev CAS deque instead, and OpenMP's shared queue serializes under the same pressure. The README walks through the full claim-and-steal protocol.

What's new:

  • "Topology" interface to query the placement of compute and memory domains — their levels, per-core capacity, and private cache sizes.
  • "Fabric" interface to query the distance, bandwidth, and latency between any two domains — measured manually.
  • Supporting more features on Windows with MSVC intrinsics and WinAPI integrations — processor groups, VirtualAllocExNuma, large pages, and _umonitor/_umwait.
  • Supporting new operating systems: FreeBSD with domainset NUMA placement, iOS, and Android.
  • Productively leveraging hardware wake-ups, timed waits, and cache-line hints on x86, Arm, and RISC-V — UMONITOR/UMWAIT and TPAUSE, WFET, WRS.STO/WRS.NTO, and CLDEMOTE/DC CVAC. Yes, prefetching mostly hurts, but here it doesn't.
  • New Zig binding, with explicit init/deinit lifetimes — for the love of explicit memory management! Thanks to @steeve and @zml for convincing me to finally do this!
  • Improved safety guarantees and test suite hardening with help from @guillaumeriousat and the broader community 🤗

Hard things are still hard, but in my not so humble opinion, ForkUnion v3 is now one of the very few systems projects with advanced hardware awareness, and it should make designing hardware-friendly software a bit easier.

The numbers

Two benchmarks, the same runtimes on deliberately equal footing — identical -O3 and target-cpu=native, no LTO, one warmup, identical kernels. N-body isolates the dispatch path: every task costs the same, so only scheduling is left. Connected components by label propagation isolates the fork-join frequency, re-paying the dispatch-and-join tax across 84 bandwidth-bound rounds per pass. Reported static / dynamic, one fixed window per cell; ➕ marks C++, 🦀 marks Rust.

N-Body — dispatch latency, µs, lower is better

Machine ➕ ForkUnion 🦀 ForkUnion ➕ OpenMP 🦀 Rayon ➕ Taskflow
18× Apple M5 Pro, macOS 24 / 26 28 / 32 115 / 137 222 / 339 83 / 94
24× Intel RPL, Windows 148 / 143 112 / 91 360 / 627 151 / 151 812 / 1026
128× Intel SPR, Linux 54 / 86 40 / 67 115 / 226 483 / 739 666 / 694
192× AWS Graviton 5, Linux 47 / 136 42 / 137 266 / 216 490 / 580 602 / 639

Connected Components — fork-join frequency, GTEPS, higher is better

Machine ➕ ForkUnion 🦀 ForkUnion ➕ OpenMP 🦀 Rayon ➕ Taskflow
18× Apple M5 Pro, macOS 24.7 / 20.5 19.4 / 20.2 23.0 / 18.3 23.8 / 14.6 21.4 / 1.3
24× Intel RPL, Windows 10.0 / 7.2 5.2 / 5.5 11.4 / 0.1 9.4 / 5.2 6.6 / 0.7
128× Intel SPR, Linux 46.5 / 20.9 65.2 / 31.4 28.2 / 0.4 8.5 / 6.5 25.1 / 0.5
192× AWS Graviton 5, Linux 192.6 / 77.6 258.7 / 87.3 53.9 / 0.4 8.4 / 6.2 53.0 / 0.4

Both ForkUnion columns are the same pool over a bit-identical graph; the gap between them is only how GCC and LLVM vectorize the identical kernel. The others fall off in the dynamic column because their shared queues contend at scale — ForkUnion claims one vertex at a time from private fetch_add cursors and has no queue to contend on.

Major

  • Break: Measure the memory fabric in-process, replacing HMAT and kernel tiering (49290e7)
  • Break: DomainAllocator, symmetric containers, SDK benchmark alignment (06bf8fe)
  • Break: Explicit fu_topology_t handle across the C ABI, no singletons (6903711)
  • Break: One vocabulary, one canonical order, across every surface (fa78301)
  • Break: Remove every back-compatibility shim (818151a)
  • Break: Layer capability macros, split the umbrella header (ec013e8)
  • Break: Sound, allocation-free RoundRobinVec bulk operations (384bef5)
  • Break: Split topology into compute and memory domains (64d4bdf)
  • Break: Generation tokens for non-blocking multi-pool coordination (1545311)
  • Break: New project name & link (6da19ed)
  • Break: Name spacing/capitalization (0911619)

Minor

  • Add: Bind the measured fabric to Rust and Zig (35e439b)
  • Add: Enumerate per-domain cache sizes from sysfs and CPUID (c322c93)
  • Add: FreeBSD reschedule, topology, memory placement, and superpages (6a84509)
  • Add: Label-propagation benchmark replacing triangle counting (2604198)
  • Add: Cache-line demote & promote hints for x86, Arm, and RISC-V (4145073)
  • Add: Monitored waits, per-loop wait bounds, and Zawrs (d7774e5)
  • Add: Owning arrays, and rebuild the topology on them (586e138)
  • Add: Windows support for topology, NUMA pools, and pinning (2ae8150)
  • Add: Miri coverage of the pure-Rust unsafe (6c7f62d)
  • Add: Rust scope() API replacing the SafePtr smuggle (b71341f)
  • Add: Memory tiers, cpuless domains, HMAT metrics, and macOS harvest (c27faf8)
  • Add: Stoppable & Exception-handling try_for (cb6b257)
  • Add: Reductions in Rust (e7932c4)
  • Add: NUMA-aware allocator for Zig (bbb9e90)
  • Add: Bench against libXEV and Spice (bf998f9)
  • Add: Draft Zig binding (916e641)

Patch

  • Make: Version the duplicated macros in types.hpp (6fc9312)
  • Docs: Rewrite the topology guide around the declared-versus-observed split (a6e9843)
  • Improve: Hoist the SplitMix64 avalanche into the shared types (3465c2d)
  • Docs: Refresh the intro and complete the v3 API coverage (45e4753)
  • Fix: Sync build-system references to place-* feature names (4e87654)
  • Fix: Build the timed-wait monitors under -march=native (0891489)
  • Make: Green the CI matrix - apt sources, tar, shellcheck, cross rows (1be2492)
  • Docs: Refresh the README against the v3 APIs and slim the benchmarks (e3396ec)
  • Make: Rewrite the pre-commit hook single-pass and Windows-proof (a52dfef)
  • Fix: Probe FreeBSD's memory placement at runtime, not just compile time (c2eb3a7)
  • Make: Restructure the pre-release CI around matrices and ctest (e3d7ff2)
  • Improve: Flatten test bodies into helpers and split bundled concerns (feeaddd)
  • Improve: Normalize the N-body kernels across languages (02bb703)
  • Improve: Deepen test coverage across schedulers, lifecycles, and the C ABI (6b77064)
  • Improve: Time-budgeted benchmarks, symmetric across C++, Rust, and Zig (45eb844)
  • Make: Install git in the FreeBSD CI VM for Taskflow fetch (198b04f)
  • Make: Align benchmark builds and add Taskflow as a baseline (ef68bca)
  • Fix: Hoist dead work and stale docs out of the pool internals (11aa660)
  • Improve: Flatten the distributed pool's colocations into one array (50d39e2)
  • Improve: Dispatch curated yield-and-hints pool variants at runtime (134b7f6)
  • Improve: Private cursors, read-probes, and demotions in dynamic stealing (c525460)
  • Improve: Pools carry a cache-hints policy beside the yield (ea5399a)
  • Make: Bump Zig to 0.16 for macOS 26 (4ce5b40)
  • Make: Refresh retired CI runners, fix FreeBSD linkage (e351fd1)
  • Improve: Steer Apple efficiency-core pools via QoS (c7be3fb)
  • Fix: Route platform checks through the FU_* vocabulary (f30ce60)
  • Fix: Robust NUMA placement under sandboxed and single-node hosts (239e3b0)
  • Improve: Locate and value-print test failures, backtrace on crash (c427d6c)
  • Make: Static-link the qemu-run cross-compile test binaries (ef3d11c)
  • Fix: Cross-platform CI test failures (315da41)
  • Make: Build the old-glibc floor on manylinux_2_28 (5f26aba)
  • Improve: Model Android and FreeBSD affinity and scheduling (b903e50)
  • Improve: Flatten the libc and hwprobe capability detection (7a4fd4e)
  • Fix: Assemble the RISC-V pause hint on stock toolchains (1da97c7)
  • Improve: Reach the CPU yield intrinsics on MSVC (a960fa3)
  • Fix: Harden pool-completion tests against scheduler starvation (e85d35f)
  • Make: Expand CI to validate cross-platform compatibility (d171746)
  • Fix: Build clean under -Wall -Wextra -Werror (3833a67)
  • Fix: Assemble the WFET capability probe on stock toolchains (05ed868)
  • Improve: Full backend matrix via one compile-time-policy dispatch per demo (12bab43)
  • Improve: Unified capability enumeration, pool_kind_t, leaner ABI (90546ed)
  • Improve: Cleaner feature controls, one set across the SDKs (c2e9669)
  • Fix: Restore Windows NUMA allocation and page-size probe (6d29828)
  • Fix: Size and pin pools from the cores we were given (33497ab)
  • Make: Expose each capability as an AUTO/ON/OFF tri-state (0020411)
  • Fix: Seed where a stealing thread starts probing, not just its stride (c5f30cd)
  • Improve: Private claim cursors for for_n_dynamic (5ea9298)
  • Improve: NUMA backends for the Rust N-body, and dual-socket results (8350177)
  • Improve: Compute-domain metadata across the bindings (95df7bc)
  • Fix: NUMA topology harvesting on multi-socket hosts (3ebeea5)
  • Docs: Reword stale topology phrasing to compute/memory domains (0272b39)
  • Make: Rename allocator numa_node to memory_domain (d8e170f)
  • Docs: Reword prong metadata for two-axis topology (f12d0ef)
  • Make: Bump CI runners and add Arm64 + sanitizer coverage (d230ab1)
  • Fix: Rust examples for by-reference for_threads (39b007c)
  • Make: Bump CI (9a08a62)
  • Fix: Over-aligned AllocationResult (3bf34e9)
  • Improve: Validate CacheAligned padding (46bb382)
  • Improve: RoundRobinVec code nesting (9982ad9)
  • Fix: Deprecate spice comparison (a707d55)
  • Make: Backport to Zig 0.15 (0167211)
  • Improve: Shared call_yield_ with & w/out thread (541842c)
  • Improve: Select worker-yield overloads with if constexpr (53e1b5d)
  • Fix: Route standard_worker_yield through micro_yield() (101a955)
  • Improve: Add worker_yield_t hook for idle backoff (18b6d63)
  • Make: Apple's libSystem contains Blocks runtime (1e9540f)
  • Make: Skip C11 tests on MSVC - lacking atomics (a21ac35)
  • Improve: Test C stable API (e8c6b48)
  • Improve: Static slicing for std.Thread.Pool (ca3d42b)
  • Make: Upgrade Zig to v0.16 (6d6283a)
  • Improve: Handle void contexts (0b9ed05)
  • Fix: Use aligned allocs for opaque_pool_t (fb063a7)
  • Improve: Thread name formatting (d9455c8)
  • Docs: Why no Miri or Loom? (0ec9ae9)
  • Fix: Safeguard try for std::thread (ddf8ea0)