Skip to content

perf: replace functools.partial with closure in _query callback#786

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

perf: replace functools.partial with closure in _query callback#786
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/eliminate-partial-per-request

Conversation

@mykaul
Copy link
Copy Markdown

@mykaul mykaul commented Apr 3, 2026

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

1 participant