hardbyte / hardbyte/python-common-expression-language

Release the GIL during evaluation, and decide on free-threaded (cp314t) support

オープン
#45 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る
enhancement
主要言語
Python
スター
43
フォーク
4
平均マージ
9時間 57分
マージ済み PR(30日)
14

説明

## Problem

`evaluate()` and `Program.execute()` hold the GIL for the entire Rust-side evaluation. A Python program that evaluates CEL from several threads therefore serialises on the interpreter even though the work is pure Rust once the context has been converted. This matters for the policy-engine style of use (many rules × many requests) that the README leads with.

## Proposal

1. **Release the GIL around `program.execute()`** with `py.detach(|| ...)` (PyO3 0.29's spelling of `allow_threads`). The pieces already have the right bounds: `cel::Program` is a plain AST, `cel::Context<'static>` is `Send + Sync` (its `Val`, `Function` and `VariableResolver` traits all require it), and the Python-callback wrappers already do their own `Python::attach`, so a callback simply re-acquires the GIL when it runs. Conversion of the result back to Python happens after re-attaching.
2. **Measure the fixed cost.** Detach/attach is on the order of tens of nanoseconds, but a trivial `x + y` executes in ~0.15 µs, so unconditional detaching could be a visible relative slowdown for tiny expressions while being a large absolute win for anything heavier or for multi-threaded callers. Options, in order of preference:
- detach unconditionally if the overhead measures under ~10% on the `compile_execute_benchmark.py` cases;
- otherwise detach only when the context has no Python functions and no resolver (that's when the evaluation cannot need the GIL), which is cheap to know from the `Context`;
- an explicit `execute(ctx, release_gil=...)` knob is a last resort.
3. **Free-threaded Python.** PyO3 0.29 supports the free-threaded build when the module opts in with `#[pymodule(gil_used = false)]`. Before doing that, audit: `Context` mutators are `&mut self` (PyO3's borrow checker turns concurrent mutation into an error rather than a data race), the shared stdlib `Env` is a `LazyLock`, and the per-`Context` cache proposed in the Context-reuse PR is behind a `Mutex`. Then add `cp314t` wheels to the CI matrix (maturin-action needs the interpreter listed explicitly; `--find-interpreter` won't pick it up).

## Non-goals

`Context` itself is documented as not thread-safe for concurrent *mutation*; that stays. Concurrent *evaluation* against a shared `Context` is already fine and is now pinned by tests.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Start at evaluate() and Program.execute(), then run compile_execute_benchmark.py to measure detach/attach overhead against the stated threshold. Audit Context, the shared stdlib Env, and the per-Context cache for free-threaded safety, and inspect the maturin-action CI matrix. Done means concurrent evaluation is covered by tests, the GIL-release choice is benchmarked, and cp314t wheels are included if supported.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python, rust
領域
backend, ci-cd, performance, testing
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。