perf(rpc): cut output formatting time on large list responses#68
Open
mike1o1 wants to merge 2 commits intoash-project:mainfrom
Open
perf(rpc): cut output formatting time on large list responses#68mike1o1 wants to merge 2 commits intoash-project:mainfrom
mike1o1 wants to merge 2 commits intoash-project:mainfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I started using Ash Typescript on an internal project which returns a fairly large number of resources (around 1500) and noticed that there is a decent amount of overhead when it comes to serializing the responses. After some investigating, there were two areas that can be improved where we try and figure out how to format the same field multiple times in a single response. This PR attempts to improve that hotspot by memoizing the format of the field name (per process) so that multiple records only try and figure out how to format a field once, and also adds a quick exit for known Ash types, instead of having to perform some costly introspection on each field for each resource.
Summary
Two independent, measured perf improvements to the RPC output formatting hot path. Combined they drop
OutputFormatter.format/4on a 1500-record list from ~70 ms to ~4.6 ms in the included benchmark (~15× faster, ~10× less allocated memory). No behavior change; no public API change.Added a guard clause to
ValueFormatter.format/5forAsh.Type.{String, UUID, UUIDv7, Boolean, Integer, Float, Decimal, Date, Time, DateTime, UtcDatetime, UtcDatetimeUsec, NaiveDatetime, Atom, Binary, CiString, Duration, Term}. These already land on thetrue -> valuepassthrough branch today, but only after runningIntrospection.unwrap_new_type/2(which callsSpark.implements_behaviour?/2) plus eightcondbranches. Values for these types are already JSON-normalized upstream inResultProcessor.normalize_primitive, so the dispatch work is pure overhead.Memoizes
format_field_name/2andformat_field_for_client/3on the process dictionary when inputs are(atom, module | nil, :camel_case | :snake_case | :pascal_case). A single list response hits the same(field, resource, formatter)triple ~once per record, so the cache resolves after the first call and stays warm for the duration of the request. Strings and custom{Mod, fun}formatters fall through unchanged - no unbounded cache growth and no assumptions about custom-formatter purity.Why the process dictionary
(fields × resources × formatters), typically a few hundred entries per process, regardless of record count.Numbers
Micro (
benchmarks/value_formatter.exs, per-call):scalar/stringscalar/utc_datetimescalar/uuidcomposite/embeddedcomposite/typed_structMicro (
benchmarks/field_formatter.exs, per-call):format_field_for_client/resource_unmappedformat_field_name/atomper_record_loop/todo_all_fields(21 fields)format_field_name/stringEnd-to-end (
benchmarks/output_formatter.exs, per call on 1500 records):mainTesting
mix test— 2219 tests, 0 failures, 1 skipped (unchanged from main).benchmarks/value_formatter.exsand by the existing test suite.{Mod, fun}formatters tested - they fall through to the uncached path as designed.Benchmarks
New
benchmarks/directory with three standalone scripts:Adds
:benchee ~> 1.3as a dev/test-only dependency.Contributor checklist
Leave anything that you believe does not apply unchecked.