# freethreading concurrency log

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:

1. **check_concurrency_pcr.py**: calculates the random numbers before the simulation starts. The ipc penalty for processes and subinterpreters is clearly demonstrated.

2. **check_concurrency_ppcr.py**: calculates the random numbers before the simulation starts, but inside the threads, processes or subinterpreters. So processes and subinterpreters don't have the ipc-penalty.

3. **check_concurrency_cr.py**: calculates the random numbers on demand during the simulation. This is the most straight forward approach.

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).


## concurrency: 2

    % 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


## concurrency: 4


    % 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


## concurrency: 8

    % 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

## check for math library

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

