Skip to content

perf: replace KeyError exception with dict.get() in BoundStatement.bi…#755

Draft
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/bind-dict-get
Draft

perf: replace KeyError exception with dict.get() in BoundStatement.bi…#755
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/bind-dict-get

Conversation

@mykaul
Copy link

@mykaul mykaul commented Mar 16, 2026

…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.

Pre-review checklist

  • I have split my patch into logically separate commits.
  • All commit messages clearly explain what they change and why.
  • I added relevant tests for new features and bug fixes.
  • All commits compile, pass static checks and pass test.
  • PR description sums up the changes and reasons why they should be introduced.
  • I have provided docstrings for the public items that I want to introduce.
  • I have adjusted the documentation in ./docs/source/.
  • I added appropriate Fixes: annotations to PR description.

…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.
@mykaul mykaul marked this pull request as draft March 16, 2026 16:10
@mykaul mykaul requested a review from Copilot March 16, 2026 17:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 explicit None.
  • Replaces try/except KeyError with dict.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.

@mykaul
Copy link
Author

mykaul commented Mar 16, 2026

Review of Copilot's Comments

Comment 1: Module-level _BIND_SENTINEL exposure

Copilot's concern: The sentinel could be accidentally used by callers.

Assessment: Low severity, mostly noise. The _ prefix is the standard Python convention for private objects. Moving it inside bind() would create a new object() on every call to a hot path — counterproductive for a performance PR. The suggestion to use UNSET_VALUE instead would complicate the logic with separate v3/v4 code paths for marginal benefit.

Comment 2: dict.get() doesn't call __missing__

Copilot's concern: Switching from values_dict[col.name] (which triggers __missing__ for subclasses like defaultdict) to values_dict.get() is a behavior change.

Assessment: Technically correct, but negligible practical impact. Using a defaultdict for binding parameters would be unusual and arguably a misuse — it would silently provide defaults for missing columns instead of raising errors. No evidence in the codebase or tests that __missing__-implementing subclasses are used here.

Additional Observations

  1. The optimization is sound — avoiding try/except KeyError on a hot path is a reasonable micro-optimization.
  2. No bugs introduced — the new logic correctly handles all three cases (key present, key missing on v4+, key missing on v3). Existing unit tests cover these paths.
  3. _BIND_SENTINEL export is a non-issue — no __all__ in the module, and the _ prefix excludes it from wildcard imports.
  4. Missing benchmark data — the PR would be strengthened by including before/after timings, though this isn't blocking.

@mykaul mykaul self-assigned this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants