Skip to content

Commit 4ed57f8

Browse files
committed
Add process-bound Python environments
Each Erlang process gets its own Python globals/locals dict stored as a NIF resource in the process dictionary. When the process exits, the resource destructor frees the Python dicts. Works in both worker and subinterpreter modes. - Add py_env_resource_t and create_local_env NIF - Add _with_env variants of context_exec/eval/call - Add get_local_env/0 to py.erl using process dictionary - Update py_context to pass EnvRef to NIFs - Add tests for isolation, __main__ access, persistence, cleanup
1 parent 7576e5b commit 4ed57f8

File tree

13 files changed

+1479
-14
lines changed

13 files changed

+1479
-14
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
========================================================
3+
Reactor Modes vs Channel API Benchmark
4+
========================================================
5+
6+
System Information
7+
------------------
8+
Erlang/OTP: 28
9+
Schedulers: 14
10+
Python: 3.14.3 (main, Feb 7 2026, 05:34:13) [Clang 17.0.0 (clang-1700.6.3.2)]
11+
Subinterp: true
12+
13+
14+
--- Channel API ---
15+
A) Messages on persistent channel (1000 msgs):
16+
Size 64: 0.2 us/op, 5988024 ops/sec
17+
Size 1024: 0.3 us/op, 3508772 ops/sec
18+
Size 16384: 0.9 us/op, 1169591 ops/sec
19+
20+
B) Full lifecycle per op (create+send+recv+close, 200 ops):
21+
Size 64: 0.3 us/op, 3174603 ops/sec
22+
Size 1024: 0.4 us/op, 2631579 ops/sec
23+
Size 16384: 1.1 us/op, 877193 ops/sec
24+
25+
--- Reactor (Worker Mode) ---
26+
A) Messages on persistent connection (500 msgs):
27+
Size 64: 66.2 us/op, 15110 ops/sec
28+
Size 1024: 66.8 us/op, 14972 ops/sec
29+
Size 16384: 73.1 us/op, 13688 ops/sec
30+
31+
B) Full lifecycle per op (connect+handoff+echo+close, 100 ops):
32+
Size 64: 232.3 us/op, 4305 ops/sec
33+
Size 1024: 230.3 us/op, 4343 ops/sec
34+
Size 16384: 240.2 us/op, 4164 ops/sec
35+
36+
--- Reactor (Subinterpreter Mode) ---
37+
A) Messages on persistent connection (500 msgs):
38+
Size 64: 65.9 us/op, 15172 ops/sec
39+
Size 1024: 66.9 us/op, 14947 ops/sec
40+
Size 16384: 73.7 us/op, 13573 ops/sec
41+
42+
B) Full lifecycle per op (connect+handoff+echo+close, 100 ops):
43+
Size 64: 216.6 us/op, 4618 ops/sec
44+
Size 1024: 247.0 us/op, 4049 ops/sec
45+
Size 16384: 213.0 us/op, 4695 ops/sec
46+
47+
========================================================
48+
COMPARISON SUMMARY
49+
========================================================
50+
51+
A) PERSISTENT CONNECTION (messages on existing connection)
52+
-----------------------------------------------------------
53+
Size | Channel | Reactor/W | Reactor/S
54+
----------------------------------------------------
55+
64 | 5988024 | 15110 | 15172
56+
1024 | 3508772 | 14972 | 14947
57+
16384 | 1169591 | 13688 | 13573
58+
59+
B) FULL LIFECYCLE (create + send/recv + close per op)
60+
-----------------------------------------------------------
61+
Size | Channel | Reactor/W | Reactor/S
62+
----------------------------------------------------
63+
64 | 3174603 | 4305 | 4618
64+
1024 | 2631579 | 4343 | 4049
65+
16384 | 877193 | 4164 | 4695
66+
67+
Legend:
68+
Channel = py_channel API
69+
Reactor/W = erlang.reactor with worker mode
70+
Reactor/S = erlang.reactor with subinterpreter (SHARED_GIL)
71+
72+
Notes:
73+
- A) measures throughput on persistent connection (best case)
74+
- B) measures full lifecycle including setup/teardown
75+
- Channel is queue-based, Reactor goes through TCP stack
76+

0 commit comments

Comments
 (0)