Skip to content

(improvement) Add __slots__ to _Frame to eliminate per-instance __dict__ (30-40ns saving, 264 bytes saved per call) - #802

Open
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/frame-slots
Open

(improvement) Add __slots__ to _Frame to eliminate per-instance __dict__ (30-40ns saving, 264 bytes saved per call)#802
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/frame-slots

Conversation

@mykaul

@mykaul mykaul commented Apr 6, 2026

Copy link
Copy Markdown

Summary

Add __slots__ to the _Frame class in cassandra/connection.py. Eliminates per-instance __dict__ allocation.

Motivation

_Frame is instantiated for every response frame received from the server. It has exactly 6 fixed attributes (version, flags, stream, opcode, body_offset, end_pos) and is never monkey-patched or dynamically extended. Adding __slots__ removes the per-instance __dict__, reducing memory pressure on high-throughput workloads.

Benchmark (CPython 3.14, per-call)

Memory:

Size
Original (obj + __dict__) 48 + 296 = 344 bytes
Optimized (__slots__) 80 bytes
Savings per frame 264 bytes (76.7%)

Timing:

Operation Original Optimized Savings per call
Construction 146ns 118ns 28ns (19%)
Attribute access (4 attrs) 78ns 40ns 38ns (49%)

Bulk (10K frames):

  • 1,285,888 → 885,120 bytes = 400KB saved (31%)
  • At 1K concurrent in-flight frames: ~257KB saved, reducing GC pressure

Changes

  • cassandra/connection.py: Add __slots__ = ('version', 'flags', 'stream', 'opcode', 'body_offset', 'end_pos') to _Frame

Testing

Unit tests pass (28/28 in test_connection.py). Verified that _Frame instances no longer have __dict__.

@mykaul
mykaul marked this pull request as draft April 6, 2026 19:26
@mykaul

mykaul commented Apr 6, 2026

Copy link
Copy Markdown
Author

Benchmark results (CPython 3.14, 500k iterations)

Per-instance memory:

Size
Original (obj + __dict__) 48 + 296 = 344 bytes
Optimized (__slots__) 80 bytes
Savings per frame 264 bytes (76.7%)

Per-call timing:

Operation Original Optimized Δ per call
Construction 146ns 118ns -28ns
Attribute access (4 attrs) 78ns 40ns -38ns

Bulk allocation (10k frames):

  • Original: 1,285,888 bytes → Optimized: 885,120 bytes → 400KB saved (31%)
  • At 1K concurrent in-flight frames: ~257KB saved, reducing GC pressure

_Frame has exactly 6 fixed attributes and is never dynamically extended — textbook __slots__ candidate.

@mykaul mykaul changed the title (improvement) Add __slots__ to _Frame to eliminate per-instance __dict__ (improvement) Add __slots__ to _Frame to eliminate per-instance __dict__ (30-40ns saving, 264 bytes saved per call) Apr 7, 2026
Copilot AI review requested due to automatic review settings July 29, 2026 20:35
@mykaul
mykaul force-pushed the perf/frame-slots branch from 4028466 to 77f28de Compare July 29, 2026 20:35
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mykaul, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 5d84be1e-add3-4fed-b0ab-560e31ee393f

📥 Commits

Reviewing files that changed from the base of the PR and between e5f5d62 and 3d7f16d.

📒 Files selected for processing (1)
  • cassandra/connection.py

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

@mykaul

mykaul commented Jul 29, 2026

Copy link
Copy Markdown
Author

Rebased onto current `origin/master` (was based on an older commit; no conflicts).

Per a related discussion on PR #805/#806 about __slots__ risk, I re-verified that `_Frame`'s slot list is still complete after the rebase:

  • Grepped the whole repo for every `_Frame(...)` construction site and every attribute read/write touching a `_Frame` instance (cassandra/connection.py, tests, and the newly-added DRIVER-153 (SCYLLA_USE_METADATA_ID) commits that landed on master after this branch forked).
  • The only construction site is Connection._read_frame_header (cassandra/connection.py), passing exactly the 6 declared attributes (version, flags, stream, opcode, body_offset, end_pos).
  • Connection.process_msg and everywhere else only reads those same 6 attributes (header.stream, header.version, header.flags, header.opcode, frame.body_offset, frame.end_pos) — nothing assigns a new attribute post-construction.
  • No subclasses of _Frame exist anywhere in the codebase.
  • The DRIVER-153 changes (skip_meta/result_metadata_id handling) touch ExecuteMessage/_QueryMessage, PreparedStatement, and cluster.py — they don't touch _Frame or its construction/usage at all.

Conclusion: the __slots__ list is still complete and safe; no code changes were needed beyond the rebase.

Also ran tests/unit/test_connection.py, tests/unit/test_protocol.py, and the full tests/unit/ suite locally: 720 passed, 88 skipped (pre-existing skips, unrelated to this change), 0 failures.

Force-pushed the rebased commit (same single commit, no new commits added). Still a draft.

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.

Adds __slots__ to the internal _Frame class to avoid per-instance __dict__ allocation, reducing memory usage and improving hot-path performance when parsing response frames.

Changes:

  • Add __slots__ to _Frame with its fixed set of attributes.

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

Comment thread cassandra/connection.py


class _Frame(object):
__slots__ = ('version', 'flags', 'stream', 'opcode', 'body_offset', 'end_pos')
_Frame is instantiated for every response frame received from the server.
Adding __slots__ eliminates the per-instance __dict__ allocation (~104 bytes
on CPython), reducing memory pressure on high-throughput workloads.

_Frame only has 6 fixed attributes (version, flags, stream, opcode,
body_offset, end_pos) and is never monkey-patched or dynamically extended.

Signed-off-by: Yaniv Kaul <yaniv.kaul@scylladb.com>
@mykaul
mykaul marked this pull request as ready for review August 14, 2026 10:47
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