Skip to content

⚡ perf: Optimize type reflection during dataclass decoding#339

Open
abn wants to merge 2 commits intomainfrom
perf/lru-cache-get-type-hints-16241414663598073856
Open

⚡ perf: Optimize type reflection during dataclass decoding#339
abn wants to merge 2 commits intomainfrom
perf/lru-cache-get-type-hints-16241414663598073856

Conversation

@abn
Copy link
Copy Markdown
Owner

@abn abn commented Mar 28, 2026

💡 What: Added an LRU-cached wrapper function _get_type_hints_cached around get_type_hints(target_type) inside src/aiographql/client/codec.py.

🎯 Why: The codebase relied on calling get_type_hints(target_type) for every element it decoded into a dataclass. This caused an excessive amount of reflection overhead, particularly when iterating and deserializing arrays of objects.

📊 Measured Improvement:

  • Baseline: 2.4898 seconds (Time taken to decode 100,000 objects using the previous codec).
  • Improved: 0.9904 seconds (Time taken to decode the same 100,000 objects with the cache patch applied).
  • Result: ~60% faster decoding speeds on identical payloads.

PR created automatically by Jules for task 16241414663598073856 started by @abn

The `typing.get_type_hints()` function can be extremely slow to run continuously in a loop when decoding repetitive API responses back to dataclass targets. Adding a `@functools.lru_cache()` drastically reduces the overhead since the classes generally remain static per execution.

Co-authored-by: abn <165325+abn@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 28, 2026
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Cache get_type_hints to optimize dataclass decoding performance

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Added LRU cache wrapper for get_type_hints() calls
• Reduces reflection overhead during dataclass decoding
• Achieves ~60% performance improvement on repeated decoding
• Includes benchmark script demonstrating performance gains
Diagram
flowchart LR
  A["Dataclass Decoding"] --> B["get_type_hints Call"]
  B --> C["LRU Cache Check"]
  C -->|Cache Hit| D["Return Cached Types"]
  C -->|Cache Miss| E["Compute Type Hints"]
  E --> F["Store in Cache"]
  F --> D
  D --> G["Decode Fields"]
Loading

Grey Divider

File Changes

1. src/aiographql/client/codec.py ✨ Enhancement +7/-1

Add LRU cache wrapper for type hints lookup

• Added functools import for LRU cache decorator
• Created _get_type_hints_cached() function with @functools.lru_cache() decorator
• Replaced direct get_type_hints(target_type) call with cached wrapper in decode() method
• Maintains identical functionality while reducing reflection overhead

src/aiographql/client/codec.py


2. benchmark_decode.py 🧪 Tests +30/-0

Add performance benchmark for codec decoding

• New benchmark script to measure decoding performance
• Creates sample User dataclass with multiple fields
• Performs 100,000 decode iterations with warmup phase
• Measures and reports total execution time

benchmark_decode.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 28, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Remediation recommended

1. Benchmark fails Ruff hooks 🐞 Bug ⚙ Maintainability
Description
The newly added benchmark_decode.py violates the repository’s Ruff configuration (missing required
from __future__ import annotations and using print()), which will cause pre-commit Ruff hooks
to fail on this file. This creates immediate contributor workflow breakage when running the
configured hooks.
Code

benchmark_decode.py[R1-30]

+import time
+import dataclasses
+from aiographql.client.codec import DefaultGraphQLCodec
+
+@dataclasses.dataclass
+class User:
+    id: int
+    name: str
+    is_active: bool
+    email: str
+
+codec = DefaultGraphQLCodec()
+
+data = {
+    "id": 1,
+    "name": "Alice",
+    "is_active": True,
+    "email": "alice@example.com"
+}
+
+# Warmup
+for _ in range(10):
+    codec.decode(data, User)
+
+start = time.perf_counter()
+for _ in range(100000):
+    codec.decode(data, User)
+end = time.perf_counter()
+
+print(f"Time taken: {end - start:.4f} seconds")
Evidence
benchmark_decode.py contains a top-level print() and does not include the required `from
__future__ import annotations`. The repo’s Ruff config explicitly enables flake8-print (T20) and
requires the future-annotations import, and pre-commit is configured to run Ruff on committed files.

benchmark_decode.py[1-30]
pyproject.toml[59-104]
.pre-commit-config.yaml[1-13]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`benchmark_decode.py` violates repo Ruff rules: it is missing the required `from __future__ import annotations` import and uses `print()`, which is flagged by `T20` (flake8-print). This will fail local `pre-commit` runs.

## Issue Context
- Ruff config enables `T20` and requires the future import.
- Pre-commit runs `ruff` and `ruff-format`.

## Fix Focus Areas
Choose one of these approaches:
1) **Exclude benchmark files from Ruff** (recommended for ad-hoc benchmarks): add `benchmark_decode.py` (or a `benchmarks/` directory) to Ruff `extend-exclude`.
2) **Make the benchmark conform**: add `from __future__ import annotations`, wrap execution in `if __name__ == "__main__":`, and either replace `print()` with logging or add an explicit file-level suppression (e.g., `# ruff: noqa: T20`).

### References
- benchmark_decode.py[1-30]
- pyproject.toml[59-104]
- .pre-commit-config.yaml[1-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@dosubot dosubot Bot added the enhancement New feature or request label Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant