GIL-Free Performance Optimization¶
cymongoose achieves C-level performance through the nogil optimization.
Overview¶
The nogil (no-GIL) optimization allows 21 performance-critical methods to release Python's Global Interpreter Lock (GIL) during execution, enabling true parallel execution and minimizing Python overhead.
Performance Impact: Achieves 60k+ req/sec (6-37x faster than pure Python frameworks)
How It Works¶
When USE_NOGIL=1 (default), critical operations release the GIL:
# With nogil optimization
manager.poll(100) # Releases GIL - other threads can execute
# Network operations release GIL
conn.send(data) # Releases GIL during C call
conn.reply(...) # Releases GIL during C call
Methods with nogil¶
The following 21 methods release the GIL for parallel execution:
Network Operations:
send()close()resolve()resolve_cancel()
WebSocket:
ws_send()ws_upgrade()
MQTT:
mqtt_pub()mqtt_sub()mqtt_ping()mqtt_pong()mqtt_disconnect()
HTTP:
reply()serve_dir()serve_file()http_chunk()http_sse()
TLS:
tls_init()tls_free()
Utilities:
sntp_request()http_basic_auth()error()
Properties:
local_addrremote_addr
Thread-safe:
Manager.wakeup()
Checking nogil Status¶
At startup, cymongoose prints:
Build Configuration¶
Enable/Disable¶
Rebuild After Changes¶
Performance Comparison¶
Benchmark results (Apple Silicon, wrk -t4 -c100 -d10s):
| Configuration | Req/sec | Performance |
|---|---|---|
| nogil enabled | 88,710 | 100% (baseline) |
| nogil disabled | ~35,000 | ~40% (slower) |
| Pure Python (aiohttp) | 42,452 | ~48% |
Thread Safety¶
Mongoose TLS Compatibility¶
nogil works safely with Mongoose's built-in TLS because:
- TLS operations are event-loop based (no background threads)
- No internal locks in Mongoose TLS implementation
- All TLS state is per-connection (no shared state)
# Safe: TLS + nogil
def handler(conn, ev, data):
if ev == MG_EV_ACCEPT:
opts = TlsOpts(cert=cert, key=key)
conn.tls_init(opts) # Releases GIL safely
Signal Handling¶
With nogil, KeyboardInterrupt may be delayed during poll():
# DON'T rely on try/except for Ctrl+C
try:
while True:
manager.poll(100) # GIL released - signals deferred
except KeyboardInterrupt:
pass # May not catch reliably
# DO use signal handlers
shutdown_requested = False
def signal_handler(sig, frame):
global shutdown_requested
shutdown_requested = True
signal.signal(signal.SIGINT, signal_handler)
while not shutdown_requested:
manager.poll(100)
Memory Lifetime¶
Python objects remain valid during nogil C calls:
# Safe: bytes object stays alive
data = b"Hello"
conn.send(data) # Pointer to data.buf is valid during nogil
Implementation Details¶
Cython Code¶
# With nogil
IF USE_NOGIL:
with nogil:
result = mg_send(conn, buf, length)
ELSE:
result = mg_send(conn, buf, length)
All Mongoose C functions must be declared with nogil in mongoose.pxd:
Best Practices¶
- Keep nogil enabled for production (default)
- Use signal handlers for Ctrl+C, not try/except
- Don't access Python objects from background threads without GIL
- Verify nogil at startup (check USE_NOGIL=1 message)
- Benchmark with nogil on/off to measure impact
Troubleshooting¶
nogil Not Working¶
Check startup message:
Rebuild if needed:
Performance Lower Than Expected¶
- Verify nogil is enabled (USE_NOGIL=1)
- Check poll timeout (use 100ms, not 5000ms)
- Ensure TLS is needed (disable if not: USE_TLS=0)
- Run benchmarks to compare