Skip to content

perf: replace functools.partial with closure in _query callback (100-200ns improvement - 1.3-1.6x speedup) - #786

Draft
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/eliminate-partial-per-request
Draft

perf: replace functools.partial with closure in _query callback (100-200ns improvement - 1.3-1.6x speedup)#786
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/eliminate-partial-per-request

Conversation

@mykaul

@mykaul mykaul commented Apr 3, 2026

Copy link
Copy Markdown

Motivation

ResponseFuture._query() creates a functools.partial(self._set_result, host, connection, pool) on every single query (line 4623). This is the primary response callback, executed for every request. partial has non-trivial allocation overhead: it creates a new partial C object, allocates a tuple for positional args, and invocation goes through partial.__call__ indirection.

Summary

  • Replace partial(self._set_result, host, connection, pool) with a closure using default-argument capture
  • The closure avoids the partial object allocation and __call__ indirection
  • Default-argument capture stores the bound values directly in the function's __defaults__ tuple, making both creation and invocation cheaper

Before

cb = partial(self._set_result, host, connection, pool)

After

_set_result = self._set_result
def cb(response, _h=host, _c=connection, _p=pool, _sr=_set_result):
    _sr(_h, _c, _p, response)

Testing

  • All 30 unit tests in tests/unit/test_cluster.py pass
  • The callback signature is preserved: cb(response) calls self._set_result(host, connection, pool, response) identically
  • Other partial uses in cluster.py (connection factory, watchers, reprepare, timeout rescheduling) are left unchanged as they are on cold/rare paths

Benchmarks

Isolated benchmark measuring callback creation + 1 invocation (5M iterations):

Pattern Creation Invocation Total Speedup
functools.partial (before) 343.9 ns 91.8 ns 435.7 ns/call
Closure (default-arg) (after) 192.0 ns 76.3 ns 268.3 ns/call 1.62x
Closure (simple) 260.4 ns 69.8 ns 330.3 ns/call 1.32x

The default-argument closure was chosen as it has the best total cost (creation + invocation), since both happen exactly once per request.

Pre-review checklist

  • I have split my patch into logically separate commits.
  • All commit messages clearly explain what they change and why.
  • All commits compile, pass static checks and pass test.
  • PR description sums up the changes and reasons why they should be introduced.

@mykaul
mykaul force-pushed the perf/eliminate-partial-per-request branch from 8809c16 to 9c8f83f Compare April 3, 2026 14:38
@mykaul mykaul changed the title perf: replace functools.partial with closure in _query callback perf: replace functools.partial with closure in _query callback (100-200ns improvement - 1.3-1.6x speedup) Apr 7, 2026
Copilot AI review requested due to automatic review settings July 29, 2026 20:26
@mykaul
mykaul force-pushed the perf/eliminate-partial-per-request branch from 9c8f83f to c67ef30 Compare July 29, 2026 20:27
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR optimizes the hot-path response callback in ResponseFuture._query() by replacing a per-request functools.partial(...) allocation with a closure that captures the required arguments via default parameters.

Changes:

  • Replace partial(self._set_result, host, connection, pool) with a closure capturing host/connection/pool and _set_result.
  • Reduce callback creation + invocation overhead on every request (per PR benchmarks).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cassandra/cluster.py
Comment on lines +5043 to +5046
_set_result = self._set_result

def cb(response, _h=host, _c=connection, _p=pool, _sr=_set_result):
_sr(_h, _c, _p, response)
Signed-off-by: Yaniv Michael Kaul <yaniv.kaul@scylladb.com>
@mykaul
mykaul force-pushed the perf/eliminate-partial-per-request branch from c67ef30 to a442ae5 Compare September 11, 2026 10:50
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