Skip to main content

Make multi-threaded concurrency backward- and forward-compatible for the free-threaded future of Python.

Project description

conditional-futures

Make multi-threaded concurrency backward- and forward-compatible for the free-threaded future of Python.

Multi-Threading In and Out of Free-Threading

The following is a table of performance results for the execution of a function across each row of NumPy array, with python3.14t and python3.14, and with and without using a ThreadPoolExecutor. Performance improves with python3.14t but degrades with python3.14.

Interpreter Executor Duration
python3.14t None 🟡 0.577
python3.14t ThreadPoolExecutor 🟢 0.34
python3.14 None 🟡 0.544
python3.14 ThreadPoolExecutor 🔴 2.231

ConditionalThreadPoolExecutor lets a single interface get the best result in both contexts.

Interpreter Executor Duration
python3.14t None 🟡 0.577
python3.14t ConditionalThreadPoolExecutor 🟢 0.339
python3.14 None 🟡 0.544
python3.14 ConditionalThreadPoolExecutor 🟡 0.532

Introduction

The new free-threaded version of Python (with the GIL disabled) offers extraordinary improvement in performance of CPU-bound processes. Upgrading your code to take advantage of this performance, however, is problematic. The same multi-threaded code, if run with the GIL enabled, can actually perform significantly worse than single-threaded execution. Worse, even when using a free-threaded interpreter, importing an incompatible C-extension will automatically re-enable the GIL.

For code that will run in multiple interpreters, we need interfaces that perform multi-threaded processing only when the GIL is disabled.

The conditional-futures package provides ConditionalThreadPoolExecutor, a drop-in replacement for ThreadPoolExecutor that adapts based on the runtime state of the GIL.

When running under free-threaded Python with the GIL disabled ConditionalThreadPoolExecutor behaves like a normal thread pool. When running under a GIL-enabled build, it falls back on single-threaded execution, potentially avoiding a significant degradation in performance. The same implementation thus offers optimal performance in all contexts.

Note that, even with the GIL enabled, multi-threading can perform well for I/O-bound processes. ConditionalThreadPoolExecutor is appropriate only for CPU-bound processes that perform worse with the GIL.

Example

Function application on the rows of a 2D NumPy array can prove the benefits of both free-threaded Python and the need for ConditionalThreadPoolExecutor.

First, using the free-threaded build of Python 3.14, we can create an array and apply a function to each row of that array. The ipython %time utility is used to measure duration.

$ python3.14t
>>> import numpy as np
>>> array = np.arange(100_000_000).reshape(100_000, 1_000)
>>> func = lambda row: (row[row % 2 == 0]**2).sum()
>>> %time _ = np.fromiter((func(row) for row in array), dtype=float, count=array.shape[0])
CPU times: user 580 ms, sys: 662 μs, total: 580 ms
Wall time: 581 ms

Using ConditionalThreadPoolExecutor with this GIL-disabled build of Python we can take advantage of multi-threaded performance on a CPU-bound process: the same routine is almost twice as fast:

>>> from conditional_futures import ConditionalThreadPoolExecutor
>>> with ConditionalThreadPoolExecutor(max_workers=4) as ex:
...     %time _ = np.fromiter(ex.map(func, array), dtype=float, count=array.shape[0])
...
CPU times: user 1.31 s, sys: 98 ms, total: 1.41 s
Wall time: 352 ms

Now, if using the standard Python 3.14 interpreter (with the GIL enabled), we can see detrimental performance using when using the standard ThreadPoolExecutor: the same operation takes six times as long!

$ python3.14
>>> from concurrent.futures import ThreadPoolExecutor
>>> array = np.arange(100_000_000).reshape(100_000, 1_000)
>>> func = lambda row: (row[row % 2 == 0] ** 2).sum()
>>> with ThreadPoolExecutor(max_workers=4) as ex:
...     %time _ = np.fromiter(ex.map(func, array), dtype=float, count=array.shape[0])
...
CPU times: user 1.9 s, sys: 2.21 s, total: 4.12 s
Wall time: 2.33 s

Using ConditionalThreadPoolExecutor we can have one implementation that performs optimally in both contexts. Running the same code with the GIL enabled, ConditionalThreadPoolExecutor does not perform as well as in python3.14t but provides the best option available, single-threaded performance.

>>> from conditional_futures import ConditionalThreadPoolExecutor
>>> with ConditionalThreadPoolExecutor(max_workers=4) as ex:
...     %time _ = np.fromiter(ex.map(func, array), dtype=float, count=array.shape[0])
...
CPU times: user 532 ms, sys: 773 μs, total: 533 ms
Wall time: 533 ms

Installation

pip install conditional-futures

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

conditional_futures-1.0.0.tar.gz (5.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

conditional_futures-1.0.0-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

File details

Details for the file conditional_futures-1.0.0.tar.gz.

File metadata

  • Download URL: conditional_futures-1.0.0.tar.gz
  • Upload date:
  • Size: 5.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for conditional_futures-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1fab8213d55c56a0899c34db18b235b96dea4235cba5f8fe3fd24c9a6c58187e
MD5 411c6731d8019a789dab251141cf4c17
BLAKE2b-256 6eefa3477f78179c0d0a3f9d9035e8ba221518e52111544040cbc8e1c2727e00

See more details on using hashes here.

File details

Details for the file conditional_futures-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for conditional_futures-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dba24d170faee88eddacf127a59e60654c7f771373c58d7ed595ecfeac26a8ca
MD5 51a1c01ce677b0cab4cc25a26cdab1e7
BLAKE2b-256 e485fd0f692460e79829d5cc51459440fe85a14ede663c8e39748e4f15a5dd4e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page