perf: replace KeyError exception with dict.get() in BoundStatement.bind()#755
perf: replace KeyError exception with dict.get() in BoundStatement.bind()#755mykaul wants to merge 2 commits intoscylladb:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Improves the performance of dict-style parameter binding in BoundStatement.bind() by avoiding KeyError exception handling in the per-column binding loop, which is on a hot path during execute() for prepared statements.
Changes:
- Introduces a private sentinel (
_BIND_SENTINEL) to distinguish “missing key” from an explicitNone. - Replaces
try/except KeyErrorwithdict.get(..., sentinel)in the dict-binding loop to avoid exception overhead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review of Copilot's CommentsComment 1: Module-level
|
4b81afe to
68dbf6c
Compare
…nd() Replace try/except KeyError with dict.get() + sentinel pattern in the per-column binding loop of BoundStatement.bind(). This loop runs once per column per execute() call for dict-style bindings, making it a hot path. Using dict.get() avoids the overhead of raising and catching KeyError for every missing/optional column. The sentinel object (_BIND_SENTINEL) is necessary to distinguish a missing key from an explicit None value in the bound dict.
68dbf6c to
0b0dfaf
Compare
|
Closing in favor of PR #749, which replaces the entire Python bind loop with a Cython serializer fast path — making this Python-level dict.get() optimization largely moot. Benchmarks also showed a 1.5x regression in the most common case (all keys present), which further reduces the value of this change. |
Summary
Replace
try/except KeyErrorwithdict.get()+ sentinel pattern in the per-column binding loop ofBoundStatement.bind(). This loop runs once per column perexecute()call for dict-style bindings, making it a hot path. Usingdict.get()avoids the overhead of raising and catchingKeyErrorfor every missing/optional column.The sentinel object (
_BIND_SENTINEL) is necessary to distinguish a missing key from an explicitNonevalue in the bound dict.A
type(values_dict) is dictcheck gates the fast path so thatdictsubclasses with custom__missing__methods continue to work via the originaltry/exceptfallback.Benchmark results
Column-lookup loop (10 columns,
bind()hot path, protocol v4):try/except)dict.get())Note: When all keys are present (common case),
try/exceptis faster because no exceptions are raised and the overhead is minimal. Thedict.get()path adds overhead from the method call and sentinel comparison. The benefit materializes when columns are missing (partial binds with protocol v4+UNSET_VALUE), where avoiding exception creation/handling gives a significant speedup.Testing
test_dict_subclass_missing_valuetests for both v3 and v4 protocol versions to verify dict subclass__missing__semantics are preserved