OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / core / proc_parallel.h

74 lines · c ref:
#ifndef PROC_PARALLEL_H
#define PROC_PARALLEL_H

#include <algorithm>
#include <thread>
#include <vector>

namespace godot {

// ── PARALLEL ELEMENT LOOPS ───────────────────────────────────────────────────
//
// proc-nodes evaluated every field on ONE core. Measured 2026-09-08 on the
// bridged voronoi mountain, 2,582,018 vertices: the value cone behind Set
// Position took 14.9 s, and `grep -rln "std::thread\|parallel_for" src/` found
// exactly one file in the whole extension -- the SDF grid fill.
//
// Blender parallelises every field and multi-function evaluation:
//     FN_multi_function.hh:111        int64_t min_grain_size = 10000;
//     intern/multi_function.cc:136    threading::parallel_for_aligned(...)
//     intern/field.cc:477             threading::parallel_for(mask.index_range(), 2048, ...)
//
// so on a 16-core machine that difference alone is most of a 16x.
//
// std::thread rather than a job system: proc-nodes has no scheduler, node
// execution is a straight topological walk, and these loops are pure per-element
// writes into pre-sized arrays with no shared state. Adding a scheduler to reach
// the same place would be a much larger change for the same number.
//
// The grain exists because a thread costs more to start than a short loop costs
// to run; below it the serial path is taken and nothing is spawned.
template <typename F>
inline void proc_parallel_for(int p_count, F &&p_fn, int p_min_grain = 4096) {
	if (p_count <= 0) {
		return;
	}
	if (p_count < p_min_grain) {
		for (int i = 0; i < p_count; i++) {
			p_fn(i);
		}
		return;
	}
	unsigned hw = std::thread::hardware_concurrency();
	int nthreads = (int)(hw > 0 ? hw : 1u);
	nthreads = std::min(nthreads, (p_count + p_min_grain - 1) / p_min_grain);
	if (nthreads < 2) {
		for (int i = 0; i < p_count; i++) {
			p_fn(i);
		}
		return;
	}
	const int per = (p_count + nthreads - 1) / nthreads;
	std::vector<std::thread> pool;
	pool.reserve((size_t)nthreads);
	for (int t = 0; t < nthreads; t++) {
		const int b = t * per;
		const int e = std::min(b + per, p_count);
		if (b >= e) {
			break;
		}
		pool.emplace_back([b, e, &p_fn]() {
			for (int i = b; i < e; i++) {
				p_fn(i);
			}
		});
	}
	for (std::thread &th : pool) {
		th.join();
	}
}

} // namespace godot

#endif // PROC_PARALLEL_H