This test compares the runtime of the freethreading Python version for a CPU centric task to calculate pi by a Monte Carlo simulation. This simulation makes strong use of the random-library for creating random numbers, also a CPU centric call.
The test runs with three different scenarios for using the random-library:
Scenario 1 takes a lot of memory by creating the random numbers first. Afterward freethreading runs smooth and the ipc penalty for processes and subinterpreters is clearly demonstrated.
Scenario 2 delegates the creation of random number to the already started concurrency tasks. It also takes a lot of memory for creating the random numbers in advance but reduces the ipc overhead, so subprocesses and subinterpreters are running faster. However, freethreading starts to degrade: increasing the concurrency level increases the runtime of the freethreading threads.
On scenario 3 subprocesses and subinterpreters are running as fast as expected. Theoretical freethreading should be even a bit more faster, because there is no overhead to delegate the program execution to processes or subinterpreters. Instead it turns out that this is the worst case for freethreading.
The scenarios are making different uses of the random library. In scenario 1 all random numbers are created in the main thread befor any concurrency starts. This changes on the other scenarios where the random-call happens on different concurrency levels in parallel, decreasing the runtime of threads.
This has also been checked with the freethreading Python versions 3.13 and 3.14 showing the same behaviour.
So it seems to be that (at least) the random library does not work well with the free threading versions.
To check whether this is a general behaviour, by a fourth script check_concurrency_pcr_math (based on check_concurrency_pcr.py) the math-library was introduced for also making C-calls via math.sqrt, but this turns out to be uncritical.
In the following there are the outputs from different simulation runs for concurrencies of 2, 4 and 8 with a constant value of random numbers (1e7 random number pairs).
% uv run -p3.15t check_concurrency_pcr.py -r 1e7 -t 2
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_pcr.py
Number of iterations : 1e+07
Concurrency level : 2
create random numbers ... duration: 0.74 sec
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.36628 1.00 3.1418
calculate_pi_by_threads 0.23201 0.63 3.1418
calculate_pi_by_processes 1.67842 4.58 3.1418
calculate_pi_by_subinterpreters 1.45123 3.96 3.1418
% uv run -p3.15t check_concurrency_ppcr.py -r 1e7 -t 2
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_ppcr.py
Number of iterations : 1e+07
Concurrency level : 2
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 1.23599 1.00 3.1414
calculate_pi_by_threads 0.95283 0.77 3.1418
calculate_pi_by_processes 0.81910 0.66 3.1417
calculate_pi_by_subinterpreters 0.82369 0.67 3.1414
% uv run -p3.15t check_concurrency_cr.py -r 1e7 -t 2
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_cr.py
Number of iterations : 1e+07
Concurrency level : 2
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.59177 1.00 3.1420
calculate_pi_by_threads 0.67061 1.13 3.1408
calculate_pi_by_processes 0.45307 0.77 3.1416
calculate_pi_by_subinterpreters 0.42753 0.72 3.1407
% uv run -p3.15t check_concurrency_pcr.py -r 1e7 -t 4
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_pcr.py
Number of iterations : 1e+07
Concurrency level : 4
create random numbers ... duration: 0.73 sec
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.36160 1.00 3.1410
calculate_pi_by_threads 0.13364 0.37 3.1410
calculate_pi_by_processes 1.32702 3.67 3.1410
calculate_pi_by_subinterpreters 1.62709 4.50 3.1410
% uv run -p3.15t check_concurrency_ppcr.py -r 1e7 -t 4
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_ppcr.py
Number of iterations : 1e+07
Concurrency level : 4
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 1.24565 1.00 3.1405
calculate_pi_by_threads 1.10420 0.89 3.1420
calculate_pi_by_processes 0.52941 0.43 3.1419
calculate_pi_by_subinterpreters 0.60540 0.49 3.1410
% uv run -p3.15t check_concurrency_cr.py -r 1e7 -t 4
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_cr.py
Number of iterations : 1e+07
Concurrency level : 4
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.59239 1.00 3.1420
calculate_pi_by_threads 0.86022 1.45 3.1411
calculate_pi_by_processes 0.31744 0.54 3.1417
calculate_pi_by_subinterpreters 0.33011 0.56 3.1426
% uv run -p3.15t check_concurrency_pcr.py -r 1e7 -t 8
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_pcr.py
Number of iterations : 1e+07
Concurrency level : 8
create random numbers ... duration: 0.73 sec
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.36373 1.00 3.1416
calculate_pi_by_threads 0.11375 0.31 3.1416
calculate_pi_by_processes 1.06109 2.92 3.1416
calculate_pi_by_subinterpreters 3.32374 9.14 3.1416
% uv run -p3.15t check_concurrency_ppcr.py -r 1e7 -t 8
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_ppcr.py
Number of iterations : 1e+07
Concurrency level : 8
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 1.23836 1.00 3.1413
calculate_pi_by_threads 1.79209 1.45 3.1401
calculate_pi_by_processes 0.41334 0.33 3.1412
calculate_pi_by_subinterpreters 0.68017 0.55 3.1409
% uv run -p3.15t check_concurrency_cr.py -r 1e7 -t 8
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_cr.py
Number of iterations : 1e+07
Concurrency level : 8
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.58894 1.00 3.1422
calculate_pi_by_threads 1.98227 3.37 3.1415
calculate_pi_by_processes 0.25313 0.43 3.1416
calculate_pi_by_subinterpreters 0.72912 1.24 3.1412
This is a short prove whether the math-library (also using C-calls) has the same behaviour like the random-library. By this check this is not the case.
% uv run -p3.15t check_concurrency_pcr_math.py -r 1e7 -t 8
Running version : 3.15.0b3 free-threading build (main, Jun 23 2026, 15:45:02) [Clang 22.1.3 ]
Running script : check_concurrency_pcr_math.py
Number of iterations : 1e+07
Concurrency level : 8
create random numbers ... duration: 0.74 sec
function name runtime factor result
-----------------------------------------------------------------
calculate_pi 0.47754 1.00 3.1416
calculate_pi_by_threads 0.13983 0.29 3.1416
calculate_pi_by_processes 1.09167 2.29 3.1416
calculate_pi_by_subinterpreters 3.38390 7.09 3.1416