Skip to content

perf: Optimize unstructure and validation for large arrays#1196

Merged
nathan-stender merged 1 commit into
mainfrom
perf/optimize-unstructure-and-validation
Apr 29, 2026
Merged

perf: Optimize unstructure and validation for large arrays#1196
nathan-stender merged 1 commit into
mainfrom
perf/optimize-unstructure-and-validation

Conversation

@nathan-stender

Copy link
Copy Markdown
Collaborator

Summary

  • unstructure() fast-path — Lists whose first element is a primitive (int, float, str, bool, None) are returned directly without per-element recursion. For data cube float arrays with millions of elements, this eliminates ~35M unnecessary function calls. (13s → 0.2s)
  • FastDraft202012Validator — Custom jsonschema validator that bulk-checks array items via isinstance for simple type-only schemas (e.g. {"type": "number"}, {"anyOf": [{"type": "number"}, {"type": "null"}]}), falling back to the original per-element validation for complex schemas. (147s → 12.8s)

Total pipeline for a 135MB Biacore T200 Evaluation .bme file (917 measurements, 35M data points, 548MB JSON output): ~176s → ~38s (4.7x speedup).

Test plan

  • Full test suite passes (1,119 tests)
  • Lint + mypy clean
  • Verified fast validator correctly rejects invalid data (wrong types in arrays)
  • Verified fast validator falls back to original for complex schemas
  • Benchmarked against 135MB .bme file end-to-end

🤖 Generated with Claude Code

…arrays

Data cube arrays (e.g., Biacore T200 sensorgram data) can contain millions of
floats. Two bottlenecks made serialization of such files impractical:

1. unstructure() recursed into every list element individually, making ~35M
   function calls (each with 5 isinstance checks) just to return primitives
   unchanged. A fast-path now detects lists of primitives by checking the first
   element and returns the list directly. (13s → 0.2s)

2. jsonschema's items validator called validator.descend() per array element for
   simple type schemas like {"type": "number"} and
   {"anyOf": [{"type": "number"}, {"type": "null"}]}. A custom
   FastDraft202012Validator bulk-checks via isinstance, falling back to the
   original validator for complex schemas. (147s → 12.8s)

Total pipeline for a 135MB .bme file: ~176s → ~38s (4.7x speedup).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nathan-stender
nathan-stender requested a review from a team as a code owner April 29, 2026 18:19
@nathan-stender
nathan-stender merged commit 8009b8b into main Apr 29, 2026
9 checks passed
@nathan-stender
nathan-stender deleted the perf/optimize-unstructure-and-validation branch April 29, 2026 21:09
nathan-stender added a commit that referenced this pull request Apr 30, 2026
## Summary

Follow-up to #1196 targeting the remaining validation bottlenecks,
identified via cProfile on a 135MB Biacore T200 `.bme` file (917
measurements, 35M data points).

- **Fast `oneOf` for typed arrays** — `tDimensionArray` and
`tMeasureArray` are `oneOf` over 3 typed array refs
(number/boolean/string). The default `oneOf` validator validates against
the first matching branch, then re-validates against ALL remaining
branches to confirm uniqueness. We short-circuit by inspecting the first
element's type to select the correct branch directly. **(~6s saved)**
- **Fast `oneOf` for array-vs-object** — `oneOf[tDimensionArray,
tFunction]` patterns: if the instance is a list, it must be the array
branch (tFunction is an object). Skip the redundant validation. **(917
calls saved)**
- **Schema + validator caching** — Cache schema JSON, `RefResolver`, and
`FastDraft202012Validator` instances per schema path to avoid redundant
disk I/O and object construction on repeated validations. **(~1.7s saved
on first call)**
- **Eliminate redundant `fields()` call** — `unstructure()` called
`fields()` twice per dataclass (once for iteration, once to build a set
for custom_information_document check). Reuse the tuple.

### Benchmark (135MB .bme file)

| Phase | Before #1196 | After #1196 | After this PR |
|---|---|---|---|
| unstructure | 13s | 0.2s | 0.2s |
| validate | 147s | 12.8s | **5.0s** |
| **Total** | **~176s** | **~38s** | **~25s** |

## Test plan

- [x] Full test suite passes (1,119 tests)
- [x] Lint + mypy clean
- [x] Verified oneOf fast validator correctly handles:
number/string/boolean arrays, nullable arrays, mixed-type rejection,
non-array oneOf fallback, array-vs-object disambiguation
- [x] Benchmarked end-to-end against 135MB `.bme` file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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