Skip to documentation
SLOP

tiny.simd.ThreadPool

Reference tiny.simd ThreadPool

Defined in thread.pool.

API (47)

Actions

Public operations.

Types and contracts

Public types and contracts.

Fields and members

Public fields and members.

No direct callersNo direct callsthread.poolThreadPool
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/simd/src/thread/pool.zig:316

zig
pub const ThreadPool = struct {    pub const State = enum(u8) {        initialization,        steady,        teardown,    };    pub const Capacity = struct {        requested_threads: usize,        threads: u8,        workers: u8,        pub fn derive(requested_threads: usize) Capacity {            const supported = topology.haveThreadingSupport() and                sys.thread.threadsSupported();            const thread_count: usize = if (supported)                @min(requested_threads, max_threads)            else                0;            const worker_count: usize = thread_count + 1;            return .{                .requested_threads = requested_threads,                .threads = @intCast(thread_count),                .workers = @intCast(worker_count),            };        }        pub fn wasClamped(self: Capacity) bool {            return self.requested_threads != self.threads;        }    };    pub const StartError = sys.thread.SpawnError || error{        AlreadyDeinitialized,        PoolMoved,    };    pub const RunError = StartError || error{        Busy,        InvalidRange,        TooManyTasks,    };    state: State = .initialization,    capacity: Capacity,    mapping: PoolWorkerMapping,    address: usize = 0,    wait_mode: std.atomic.Value(u8) =        std.atomic.Value(u8).init(@backingInt(PoolWaitMode.block)),    config_bits: std.atomic.Value(u16) =        std.atomic.Value(u16).init((Config{ .wait_type = .block }).encode()),    epoch: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),    work_available: std.atomic.Value(bool) =        std.atomic.Value(bool).init(false),    exiting: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),    busy: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),    synchronization: sys.thread.Mutex = .{},    done: sys.thread.Condition = .{},    started_threads: usize = 0,    handles: [max_threads]sys.thread.JoinHandle = undefined,    workers: [max_workers]Worker align(64) = @splat(.{}),    task_context: *anyopaque = undefined,    task_callback: Callback = undefined,    current_begin: u64 = 0,    current_tasks: usize = 0,    stats: PoolStats = .{},    caller_stats: [max_callers]CallerStats = @splat(.{}),    block_tuner: AutoTuner = .{},    spin_tuner: AutoTuner = .{},    pub fn init(        requested_threads: usize,        mapping: PoolWorkerMapping,    ) ThreadPool {        var self = ThreadPool{            .capacity = Capacity.derive(requested_threads),            .mapping = mapping,        };        var candidates: [max_configs]Config = undefined;        self.block_tuner.setCandidates(            Config.candidates(.block, &candidates),        ) catch unreachable;        self.spin_tuner.setCandidates(            Config.candidates(.spin, &candidates),        ) catch unreachable;        self.config_bits.store(            self.block_tuner.nextConfig().encode(),            .monotonic,        );        self.configureWorkers();        return self;    }    pub fn maxThreads() usize {        if (!topology.haveThreadingSupport() or            !sys.thread.threadsSupported())        {            return 0;        }        var affinity = topology.LogicalProcessorSet{};        if (topology.getThreadAffinity(&affinity)) {            const count = affinity.count();            if (count != 0) return @min(count - 1, max_threads);        }        return @min(            topology.totalLogicalProcessors() -| 1,            max_threads,        );    }    pub fn numThreadsFromCores(        allocator: std.mem.Allocator,    ) std.mem.Allocator.Error!usize {        if (!topology.haveThreadingSupport() or            !sys.thread.threadsSupported())        {            return 0;        }        var detected = try topology.init(allocator);        defer detected.deinit(allocator);        if (detected.packages.len == 0) return maxThreads();        return @min(detected.packages[0].cores.len -| 1, max_threads);    }    pub fn start(self: *ThreadPool) StartError!void {        if (self.state == .teardown) return error.AlreadyDeinitialized;        if (self.state == .steady) {            try self.requireStableAddress();            return;        }        self.address = @intFromPtr(self);        if (self.capacity.threads == 0) {            self.state = .steady;            return;        }        var spawned: usize = 0;        while (spawned < self.capacity.threads) : (spawned += 1) {            self.handles[spawned] = sys.thread.spawn(workerMain, .{                WorkerContext{                    .pool = self,                    .worker_index = spawned + 1,                },            }) catch |err| {                self.stopSpawned(spawned);                return err;            };            nameWorker(self.handles[spawned], spawned);        }        self.synchronization.lock();        while (self.started_threads != self.capacity.threads) {            self.done.wait(&self.synchronization);        }        self.synchronization.unlock();        self.state = .steady;    }    pub fn deinit(self: *ThreadPool) void {        if (self.state == .teardown) return;        if (self.state == .initialization) {            self.state = .teardown;            self.address = 0;            return;        }        self.requireStableAddress() catch unreachable;        std.debug.assert(!self.busy.load(.acquire));        if (self.capacity.threads != 0) {            self.signalExit();            for (self.handles[0..self.capacity.threads]) |handle| {                handle.join();            }        }        self.state = .teardown;        self.address = 0;    }    pub fn numWorkers(self: *const ThreadPool) usize {        return self.capacity.workers;    }    pub fn wasClamped(self: *const ThreadPool) bool {        return self.capacity.wasClamped();    }    pub fn setWaitMode(        self: *ThreadPool,        mode: PoolWaitMode,    ) (error{Busy} || StartError)!void {        if (self.state == .teardown) return error.AlreadyDeinitialized;        if (self.busy.cmpxchgStrong(            false,            true,            .acquire,            .monotonic,        ) != null) return error.Busy;        defer self.busy.store(false, .release);        self.wait_mode.store(@backingInt(mode), .release);        const mode_tuner = self.tuner(mode);        const selected = mode_tuner.best() orelse mode_tuner.nextConfig();        self.config_bits.store(selected.encode(), .release);        if (self.state == .steady and self.capacity.threads != 0) {            try self.requireStableAddress();            const epoch = self.wakeWorkers(false, true);            self.waitForWorkers(epoch);        }    }    pub fn waitMode(self: *const ThreadPool) PoolWaitMode {        return @fromBackingInt(@intCast(self.wait_mode.load(.acquire)));    }    pub fn config(self: *const ThreadPool) Config {        return Config.decode(self.config_bits.load(.acquire));    }    pub fn autoTuneComplete(self: *const ThreadPool) bool {        return self.tunerConst(self.waitMode()).best() != null;    }    pub fn autoTuneCosts(self: *ThreadPool) []autotune.CostDistribution {        return self.tuner(self.waitMode()).costs();    }    pub fn statsSnapshot(self: *ThreadPool) PoolStats {        self.synchronization.lock();        defer self.synchronization.unlock();        return self.stats;    }    pub fn callerStats(        self: *ThreadPool,        caller: Caller,    ) ?CallerStats {        if (caller.index >= max_callers) return null;        self.synchronization.lock();        defer self.synchronization.unlock();        return self.caller_stats[caller.index];    }    pub fn resetStats(self: *ThreadPool) error{Busy}!void {        if (self.busy.cmpxchgStrong(            false,            true,            .acquire,            .monotonic,        ) != null) return error.Busy;        defer self.busy.store(false, .release);        self.synchronization.lock();        defer self.synchronization.unlock();        self.stats = .{};        self.caller_stats = @splat(.{});    }    pub fn globalWorkerIndex(        self: *const ThreadPool,        local_worker_index: usize,    ) error{WorkerOutOfBounds}!usize {        if (local_worker_index >= self.numWorkers()) {            return error.WorkerOutOfBounds;        }        return self.mapping.globalIndex(local_worker_index);    }    pub fn run(        self: *ThreadPool,        begin: u64,        end: u64,        context: anytype,        comptime body: fn (@TypeOf(context), u64, usize) void,    ) RunError!void {        return self.runWithCaller(begin, end, .{}, context, body);    }    pub fn runWithCaller(        self: *ThreadPool,        begin: u64,        end: u64,        caller: Caller,        context: anytype,        comptime body: fn (@TypeOf(context), u64, usize) void,    ) RunError!void {        if (begin > end) return error.InvalidRange;        const task_count_u64 = end - begin;        if (task_count_u64 > std.math.maxInt(usize)) {            return error.TooManyTasks;        }        const task_count: usize = @intCast(task_count_u64);        const Context = @TypeOf(context);        const Adapter = CallbackAdapter(Context, body);        var context_storage = context;        const started_ns = nowNanoseconds();        if (task_count <= 1 or self.numWorkers() == 1) {            var task = begin;            while (task < end) : (task += 1) body(context, task, 0);            self.recordRun(                caller,                task_count,                false,                0,                elapsedNanoseconds(started_ns),            );            return;        }        if (self.busy.cmpxchgStrong(            false,            true,            .acquire,            .monotonic,        ) != null) return error.Busy;        defer self.busy.store(false, .release);        try self.start();        try self.requireStableAddress();        self.task_context = @ptrCast(&context_storage);        self.task_callback = Adapter.call;        self.current_begin = begin;        self.current_tasks = task_count;        self.divideRange(begin, end);        for (self.workers[0..self.numWorkers()]) |*worker| {            worker.last_tasks = 0;            worker.last_stolen = 0;        }        const epoch = self.wakeWorkers(            true,            self.config().wait_type == .block,        );        self.runWorker(0);        self.waitForWorkers(epoch);        var stolen_tasks: usize = 0;        for (self.workers[0..self.numWorkers()]) |worker| {            stolen_tasks += worker.last_stolen;        }        const elapsed_ns = elapsedNanoseconds(started_ns);        self.recordRun(            caller,            task_count,            true,            stolen_tasks,            elapsed_ns,        );        self.notifyAutotune(elapsed_ns);    }    fn configureWorkers(self: *ThreadPool) void {        const worker_count: u32 = self.capacity.workers;        for (self.workers[0..worker_count], 0..) |*worker, worker_index| {            worker.* = .{};            worker.victim_count = @intCast(@min(max_victims, worker_count));            const coprime = ShuffledIota.findAnotherCoprime(                worker_count,                @intCast((worker_index + 1) * 257 + worker_index * 13),            );            const shuffled = ShuffledIota.init(coprime);            worker.victims[0] = @intCast(worker_index);            for (1..worker.victim_count) |victim_index| {                worker.victims[victim_index] = @intCast(shuffled.next(                    worker.victims[victim_index - 1],                    worker_count,                ));            }        }    }    fn divideRange(self: *ThreadPool, begin: u64, end: u64) void {        for (self.workers[0..self.numWorkers()], 0..) |*worker, index| {            const range = workerRange(                0,                end - begin,                self.numWorkers(),                index,            ) catch unreachable;            worker.begin.store(@intCast(range.begin), .monotonic);            worker.end = @intCast(range.end);        }    }    fn runWorker(self: *ThreadPool, worker_index: usize) void {        var task_count: usize = 0;        var stolen_count: usize = 0;        if (self.current_tasks <= self.numWorkers()) {            const offset = self.workers[0].begin.load(.monotonic) +                worker_index;            const end = self.workers[self.numWorkers() - 1].end;            if (offset < end) {                self.task_callback(                    self.task_context,                    self.current_begin + offset,                    worker_index,                );                task_count = 1;            }        } else {            const worker = &self.workers[worker_index];            for (worker.victims[0..worker.victim_count]) |victim_index| {                const victim = &self.workers[victim_index];                while (true) {                    const offset = victim.begin.fetchAdd(1, .monotonic);                    if (offset >= victim.end) break;                    self.task_callback(                        self.task_context,                        self.current_begin + offset,                        worker_index,                    );                    task_count += 1;                    stolen_count += @intFromBool(victim_index != worker_index);                }            }        }        self.workers[worker_index].last_tasks = task_count;        self.workers[worker_index].last_stolen = stolen_count;    }    fn wakeWorkers(        self: *ThreadPool,        work_available: bool,        wake_blocked: bool,    ) u32 {        self.work_available.store(work_available, .monotonic);        const epoch = self.epoch.load(.monotonic) +% 1;        self.epoch.store(epoch, .release);        for (self.workers[1..self.numWorkers()]) |*worker| {            worker.wait_epoch.store(epoch, .release);        }        if (wake_blocked and self.capacity.threads != 0) {            wait.wakeAll(&self.workers[1].wait_epoch);        }        return epoch;    }    fn waitForWorkers(self: *ThreadPool, epoch: u32) void {        spin.callWithSpin(            self.config().spin_type,            BarrierContext{ .pool = self, .epoch = epoch },            waitAtBarrier,        );    }    fn waitForWork(        self: *ThreadPool,        worker_index: usize,        observed_epoch: u32,    ) ?u32 {        while (true) {            if (self.exiting.load(.acquire)) return null;            const config_value = self.config();            switch (config_value.wait_type) {                .block => {                    const next_epoch = wait.blockUntilDifferent(                        observed_epoch,                        &self.workers[1].wait_epoch,                    );                    if (self.exiting.load(.acquire)) return null;                    return next_epoch;                },                .spin_shared, .spin_separate => {                    const watched = if (config_value.wait_type ==                        .spin_separate)                        &self.workers[worker_index].wait_epoch                    else                        &self.epoch;                    var next_epoch: u32 = observed_epoch;                    spin.callWithSpin(                        config_value.spin_type,                        SpinWaitContext{                            .previous = observed_epoch,                            .watched = watched,                            .next = &next_epoch,                        },                        waitUntilDifferent,                    );                    if (self.exiting.load(.acquire)) return null;                    return next_epoch;                },            }        }    }    fn workerStarted(self: *ThreadPool) void {        self.synchronization.lock();        self.started_threads += 1;        self.done.broadcast();        self.synchronization.unlock();    }    fn workerReached(self: *ThreadPool, worker_index: usize, epoch: u32) void {        self.workers[worker_index].barrier_epoch.store(epoch, .release);    }    fn stopSpawned(self: *ThreadPool, spawned: usize) void {        self.signalExit();        for (self.handles[0..spawned]) |handle| handle.join();        self.exiting.store(false, .monotonic);        self.epoch.store(0, .monotonic);        self.started_threads = 0;        self.address = 0;        self.work_available.store(false, .monotonic);        self.configureWorkers();    }    fn signalExit(self: *ThreadPool) void {        self.exiting.store(true, .release);        const epoch = self.epoch.load(.monotonic) +% 1;        self.epoch.store(epoch, .release);        for (self.workers[1..self.numWorkers()]) |*worker| {            worker.wait_epoch.store(epoch, .release);        }        if (self.capacity.threads != 0) {            wait.wakeAll(&self.workers[1].wait_epoch);        }    }    fn requireStableAddress(self: *const ThreadPool) error{PoolMoved}!void {        if (self.address != @intFromPtr(self)) return error.PoolMoved;    }    fn recordRun(        self: *ThreadPool,        caller: Caller,        tasks: usize,        threaded: bool,        stolen_tasks: usize,        elapsed_ns: u64,    ) void {        self.synchronization.lock();        defer self.synchronization.unlock();        self.stats.runs +|= 1;        self.stats.serial_runs +|= @intFromBool(!threaded);        self.stats.threaded_runs +|= @intFromBool(threaded);        self.stats.tasks +|= tasks;        self.stats.stolen_tasks +|= stolen_tasks;        self.stats.elapsed_ns +|= elapsed_ns;        const caller_index = if (caller.index < max_callers)            caller.index        else            0;        const stats = &self.caller_stats[caller_index];        stats.runs +|= 1;        stats.tasks +|= tasks;        stats.workers +|= if (threaded) self.numWorkers() else 1;        stats.elapsed_ns +|= elapsed_ns;    }    fn notifyAutotune(self: *ThreadPool, elapsed_ns: u64) void {        const mode = self.waitMode();        const selected = blk: {            const tuner_value = self.tuner(mode);            if (tuner_value.best()) |best| break :blk best.*;            tuner_value.notifyCost(@max(elapsed_ns, 1));            break :blk if (tuner_value.best()) |best|                best.*            else                tuner_value.nextConfig().*;        };        self.config_bits.store(selected.encode(), .release);    }    fn tuner(self: *ThreadPool, mode: PoolWaitMode) *AutoTuner {        return switch (mode) {            .block => &self.block_tuner,            .spin => &self.spin_tuner,        };    }    fn tunerConst(self: *const ThreadPool, mode: PoolWaitMode) *const AutoTuner {        return switch (mode) {            .block => &self.block_tuner,            .spin => &self.spin_tuner,        };    }};

Source: lib/simd/src/root.zig:136

zig
pub const ThreadPool = thread.ThreadPool;
Called byCallsNo direct callsThreadPoolinitThreadPool.Capacityderive
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsThreadPoolwasClampedThreadPool.CapacitywasClamped
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...private sourcelib.simd.src.thread.pool.ThreadPooltunerConstThreadPoolwaitModeThreadPoolautoTuneComplete
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...private sourcelib.simd.src.thread.pool.ThreadPooltunerThreadPoolwaitModeThreadPoolautoTuneCosts
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...ThreadPoolcallerStats
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsThreadPoolrunWithCallerprivate sourcelib.simd.src.thread.pool.ThreadPoolwaitForWorkprivate sourcelib.simd.src.thread.pool.ThreadPoolwaitForWorkerstest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...private sourcelib.simd.src.thread.pool.ConfigdecodeThreadPoolconfig
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate sourcelib.http.src.profiling.pooldispatchBoundedtiny.httpServerdeinittiny.httpServerinitprivate sourcelib.simd.src.thread.poolouterTasktest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...+8 moreprivate sourcelib.simd.src.thread.pool.ThreadPoolrequireStableAddressprivate sourcelib.simd.src.thread.pool.ThreadPoolsignalExitThreadPooldeinit
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallstest sourcelib.simd.src.thread.pooltest: Highway pool worker mapping pre...PoolWorkerMappingglobalIndexThreadPoolnumWorkersThreadPoolglobalWorkerIndex
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.http.src.profiling.pooldispatchBoundedtiny.httpServerinitprivate sourcelib.simd.src.thread.poolouterTasktest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...test sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...+7 morePoolConfigcandidatesThreadPool.CapacityderiveThreadPoolinit
Static calls · unresolved targets: 2 · external targets: 2.
Called byCallsNo direct callsThreadPoolnumThreadsFromCorestest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...test sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...test sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...test sourcelib.simd.src.thread.pooltest: Highway pools reuse shifted ran...+3 moreThreadPoolmaxThreads
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsNo direct callersThreadPoolmaxThreadsThreadPoolnumThreadsFromCores
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate sourcelib.simd.src.thread.pool.ThreadPooldivideRangeThreadPoolglobalWorkerIndexprivate sourcelib.simd.src.thread.pool.ThreadPoolrecordRunThreadPoolrunWithCallerprivate sourcelib.simd.src.thread.pool.ThreadPoolrunWorker+4 moreThreadPoolnumWorkers
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.simd.src.thread.pooltest: Highway pool clamps capacity an...test sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...ThreadPoolresetStats
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsprivate sourcelib.simd.src.thread.poolouterTasktest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...test sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...test sourcelib.simd.src.thread.pooltest: Highway pool clamps capacity an...test sourcelib.simd.src.thread.pooltest: Highway pools reuse shifted ran...+3 moreThreadPoolrunWithCallerThreadPoolrun
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsThreadPoolruntest sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...private sourcelib.simd.src.thread.poolCallbackAdapterThreadPoolconfigprivate sourcelib.simd.src.thread.pool.ThreadPooldivideRangeprivate sourcelib.simd.src.thread.pool.ThreadPoolnotifyAutotuneThreadPoolnumWorkers+8 moreThreadPoolrunWithCaller
Static calls · unresolved targets: 2 · external targets: 2.
Called byCallstest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...test sourcelib.simd.src.thread.pooltest: Highway pool autotuning visits ...test sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...test sourcelib.simd.src.thread.pooltest: Highway pools reuse shifted ran...test sourcelib.simd.src.thread.pooltest: Highway thread pool runs every ...private sourcelib.simd.src.thread.pool.ThreadPoolrequireStableAddressprivate sourcelib.simd.src.thread.pool.ThreadPooltunerprivate sourcelib.simd.src.thread.pool.ThreadPoolwaitForWorkersprivate sourcelib.simd.src.thread.pool.ThreadPoolwakeWorkersThreadPoolsetWaitMode
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsThreadPoolrunWithCallerprivate sourcelib.simd.src.thread.pool.ThreadPoolrequireStableAddressprivate sourcelib.simd.src.thread.pool.ThreadPoolstopSpawnedprivate sourcelib.simd.src.thread.poolnameWorkerThreadPoolstart
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callstest sourcelib.simd.src.thread.pooltest: Highway pool clamps capacity an...test sourcelib.simd.src.thread.pooltest: Highway pool wait modes preserv...ThreadPoolstatsSnapshot
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsThreadPoolautoTuneCompleteThreadPoolautoTuneCostsprivate sourcelib.simd.src.thread.pool.ThreadPoolnotifyAutotunetest sourcelib.simd.src.thread.pooltest: Highway live pools switch wait ...ThreadPoolwaitMode
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.simd.src.thread.pooltest: Highway pool clamps capacity an...ThreadPool.CapacitywasClampedThreadPoolwasClamped
Static calls · unresolved targets: 0 · external targets: 0.

Also reachable as

thread.ThreadPool.

Complete caller list for ThreadPool.deinit

13 direct callers.

Complete caller list for ThreadPool.init

12 direct callers.

Complete caller list for ThreadPool.maxThreads

8 direct callers.

Complete caller list for ThreadPool.numWorkers

9 direct callers.

Complete caller list for ThreadPool.run

8 direct callers.

Complete call list for ThreadPool.runWithCaller

13 direct calls.

Audit

Definitions25
Public names75
Members29
Version26.7.0
Revisiondaab053ee433