Threading Benchmark
Monte Carlo Pi estimation benchmark demonstrating GIL release and OpenMP
parallelism. Declares free_threading: true for Python 3.14t.
Threading Bench
Interface
# Python dict format equivalent of mc_pi.c2py
{
"module": "mcpimod",
"source": ["mc_pi.c"],
"free_threading": True,
"functions": [
{
"py_sig": "mc_pi(n: int, seed: int = 0) -> int",
"gil_release": True,
"c_overloads": [
{
"sig": "mc_pi_serial(int n, int seed) -> int",
"map": {
"n": "n",
"seed": "seed",
},
},
],
},
{
"py_sig": "mc_pi_omp(n: int, seed: int = 0) -> int",
"c_overloads": [
{
"sig": "mc_pi_omp(int n, int seed) -> int",
"map": {
"n": "n",
"seed": "seed",
},
},
],
},
],
}
C Source
#include <stdint.h>
/* xorshift64 state */
typedef struct { uint64_t s; } xrs128_t;
static inline uint64_t xrs128_next(xrs128_t *st) {
uint64_t x = st->s;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
st->s = x;
return x;
}
static inline double xrs128_double(xrs128_t *st) {
return (double)(xrs128_next(st) >> 11) * 0x1.0p-53;
}
static void xrs128_seed(xrs128_t *st, unsigned int seed) {
st->s = (uint64_t)(seed + 1) * 0x9E3779B97F4A7C15ULL;
(void)xrs128_next(st);
}
int mc_pi_serial(int n, int seed) {
int inside = 0;
int i;
xrs128_t rng;
xrs128_seed(&rng, (unsigned int)seed);
for (i = 0; i < n; i++) {
double x = xrs128_double(&rng);
double y = xrs128_double(&rng);
if (x * x + y * y <= 1.0)
inside++;
}
return inside;
}
#ifdef _OPENMP
#include <omp.h>
int mc_pi_omp(int n, int seed) {
int inside = 0;
#pragma omp parallel reduction(+ : inside)
{
int tid = omp_get_thread_num();
xrs128_t rng;
xrs128_seed(&rng, (unsigned int)(seed + tid * 7919));
int i;
#pragma omp for
for (i = 0; i < n; i++) {
double x = xrs128_double(&rng);
double y = xrs128_double(&rng);
if (x * x + y * y <= 1.0)
inside++;
}
}
return inside;
}
int mc_pi_has_omp(void) {
return 1;
}
#else
int mc_pi_omp(int n, int seed) {
return mc_pi_serial(n, seed);
}
int mc_pi_has_omp(void) {
return 0;
}
#endif
Build
Compile:
$ cc -shared -fPIC c2py23/runtime/c2py_runtime.c mcpimod_wrapper.c mc_pi.c -I c2py23/runtime -o mcpimod.so -ldl -lm
See docs/building for cmake, meson, and setuptools options.
Run
$ python bench_mc_pi.py
ERROR: mcpimod.so not found. Build it first:
cd /home/runner/work/c2py23/c2py23/examples/threading_bench && make
How It Works
mc_pi releases the GIL (gil_release: true) during C computation,
allowing other Python threads to run. The C function generates random
points in a unit square and counts how many fall inside the unit circle.
mc_pi_omp uses OpenMP #pragma omp parallel for for multi-core scaling.
It is built with -fopenmp and linked against libgomp.
Both functions take an optional seed for deterministic runs (seed: int = 0).
free_threading: true at module level declares this module safe for
Python 3.14t true parallelism (no GIL re-enablement).