You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/scalability.md
+210-3Lines changed: 210 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,14 +30,150 @@ When running on a free-threaded Python build (compiled with `--disable-gil`), er
30
30
31
31
### Sub-interpreter Mode (Python 3.12+)
32
32
33
-
Uses Python's sub-interpreter feature with per-interpreter GIL. Each sub-interpreter has its own GIL, allowing true parallel execution across interpreters.
33
+
Uses Python's sub-interpreter feature with per-interpreter GIL (`Py_GIL_OWN`). Each sub-interpreter runs in its own dedicated thread with its own GIL, enabling true parallel execution across interpreters.
34
+
35
+
**Architecture:**
36
+
- Thread pool manages N subinterpreters (default: number of schedulers)
37
+
- Each subinterpreter has its own thread, GIL, and Python state
38
+
- Requests are routed to subinterpreters via `py_context_router`
39
+
- 25-30% faster cast operations compared to worker mode
34
40
35
41
**Note:** Each sub-interpreter has isolated state. Use the [Shared State](#shared-state) API to share data between workers.
36
42
43
+
**Explicit Context Selection:**
44
+
```erlang
45
+
%% Get a specific context by index (1-based)
46
+
Ctx=py:context(1),
47
+
{ok, Result} =py:call(Ctx, math, sqrt, [16]).
48
+
49
+
%% Or use automatic scheduler-affinity routing
50
+
{ok, Result} =py:call(math, sqrt, [16]).
51
+
```
52
+
37
53
### Multi-Executor Mode (Python < 3.12)
38
54
39
55
Runs N executor threads that share the GIL. Requests are distributed round-robin across executors. Good for I/O-bound workloads where Python releases the GIL during I/O operations.
**py_context_router**: Routes requests to context processes based on scheduler affinity or explicit binding.
157
+
158
+
**py_context_process**: Gen_server that owns a Python context reference and handles call/eval/exec operations.
159
+
160
+
**Subinterpreter Thread Pool (C)**: Manages N threads, each with its own Python subinterpreter created with `Py_NewInterpreterFromConfig()` and `Py_GIL_OWN`.
161
+
162
+
### Request Flow
163
+
164
+
1. Erlang process calls `py:call(Module, Func, Args)`
165
+
2.`py_context_router` selects context based on scheduler ID
166
+
3. Request sent to `py_context_process` gen_server
167
+
4. Gen_server calls NIF which executes on subinterpreter's thread
168
+
5. Result returned through gen_server to caller
169
+
170
+
### Thread Safety
171
+
172
+
- Each subinterpreter has its own GIL (no cross-interpreter contention)
173
+
- NIF calls are serialized per-context via gen_server
174
+
- Erlang message passing provides synchronization
175
+
- C code uses atomics for cross-thread state (`thread_running` flag)
176
+
41
177
## Rate Limiting
42
178
43
179
All Python calls pass through an ETS-based counting semaphore that prevents overload:
@@ -106,12 +242,83 @@ This allows your application to implement backpressure or shed load gracefully.
106
242
107
243
## Parallel Execution with Sub-interpreters
108
244
109
-
For CPU-bound workloads on Python 3.12+, use explicit parallel execution:
245
+
For CPU-bound workloads on Python 3.12+, erlang_python provides true parallelism via OWN_GIL subinterpreters.
246
+
247
+
### Check Support
110
248
111
249
```erlang
112
-
%% Check if supported
250
+
%% Check if subinterpreters are supported (Python 3.12+)
113
251
true=py:subinterp_supported().
114
252
253
+
%% Check current execution mode
254
+
subinterp=py:execution_mode().
255
+
```
256
+
257
+
### Using the Context Router
258
+
259
+
The context router automatically distributes calls across subinterpreters:
260
+
261
+
```erlang
262
+
%% Start contexts (usually done by application startup)
263
+
{ok, _} =py:start_contexts().
264
+
265
+
%% Calls are automatically routed to subinterpreters
266
+
{ok, 4.0} =py:call(math, sqrt, [16]).
267
+
{ok, 6} =py:eval(<<"2 + 4">>).
268
+
ok=py:exec(<<"x = 42">>).
269
+
```
270
+
271
+
### Explicit Context Selection
272
+
273
+
For fine-grained control, use explicit context selection:
274
+
275
+
```erlang
276
+
%% Get a specific context by index (1-based)
277
+
Ctx=py:context(1),
278
+
279
+
%% All operations on this context share state
280
+
ok=py:exec(Ctx, <<"my_var = 'hello'">>),
281
+
{ok, <<"hello">>} =py:eval(Ctx, <<"my_var">>),
282
+
{ok, 4.0} =py:call(Ctx, math, sqrt, [16]).
283
+
284
+
%% Different context has isolated state
285
+
Ctx2=py:context(2),
286
+
{error, _} =py:eval(Ctx2, <<"my_var">>). %% Not defined in Ctx2
287
+
```
288
+
289
+
### Context Router API
290
+
291
+
```erlang
292
+
%% Start router with default number of contexts (scheduler count)
0 commit comments