ui: Bulk columnar decode for QueryResult - #7277
Open
stevegolton wants to merge 3 commits into
Open
Conversation
stevegolton
marked this pull request as draft
August 28, 2026 14:37
stevegolton
force-pushed
the
dev/sg/query-result-columnar-decode
branch
from
August 28, 2026 14:38
05e3223 to
ed62131
Compare
stevegolton
force-pushed
the
dev/sg/query-result-columnar-decode
branch
3 times, most recently
from
August 31, 2026 09:35
63d7551 to
4a4e43a
Compare
stevegolton
force-pushed
the
dev/sg/query-result-columnar-decode
branch
from
August 31, 2026 09:46
4a4e43a to
4314b80
Compare
stevegolton
marked this pull request as ready for review
August 31, 2026 10:01
stevegolton
force-pushed
the
dev/sg/query-result-columnar-decode
branch
from
August 31, 2026 11:41
7759661 to
3809763
Compare
The spec constants (NUM, LONG, STR, ...) previously doubled as the value
types they decode to (number, bigint, string, ...), with synthetic
brands layered on top (e.g. NUM = 0 as number & {__brand: 'NUM'}). This
let callers accidentally treat the spec as the row: reading a spec
value where a decoded value was expected type-checked because the brand
was erased onto a real number/string/bigint.
Replace the constants with plain marker objects ({__brand: 'NUM'} as
const). The spec side is now formally SpecValue/SpecType, mapped to the
decoded side via DecodeIterType/IterResultFor. iter(), firstRow(),
maybeFirstRow() and materializeRows() all return decoded row types
(RowIterator<T> = RowIteratorBase & IterResultFor<T>), so reading a row
gives number/bigint/string, never a marker.
This surfaces and fixes the type abuse in consumers:
- SliceTrack: the row passed to callbacks (colorizer, sliceName,
sliceSubtitle, tooltip, detailsPanel, fillRatio, slicePattern,
OnSlice*) and stored in Slice/Instant.row is the decoded
IterResultFor<T> rather than the spec type; RowSchema and the
data-driven plugins (GpuByProcess, StackSamples, TraceInfoPage) use
the spec constants in their schema types.
- SearchProvider.getSearchFilter() FilterExpression.columns is a schema
(SpecValue), not decoded values.
- ChromeScrollJank utils.rows() returns decoded IterResultFor<R>[].
- AndroidInputLifecycle: InputLifecycleSpec is a spec (SpecType) and its
fixed fields are read via it.get() casts like the dynamic stage
fields.
- Dynamic schema builders (AndroidLongBatteryTracing, Smaps) use
SpecValue; tests pass NUM instead of Number() to firstRow().
Add a decodeColumns() API to QueryResult that decodes whole columns at once into typed arrays (Float64Array, BigInt64Array, etc.) instead of row-by-row iteration, with the return type fully mapped from the spec (ColumnarResultFor<T>) so callers get correctly-typed columns without casts. Cell types are validated up-front per batch with the same checks and errors as iter(). Internals: - LONG columns are written straight into the BigInt64Array backing buffer via varint lo/hi int32 stores (readVarIntIntoInt32s), bit identical to the bigint path but skipping per-cell bigint allocation, with a runtime IS_LITTLE_ENDIAN fallback. - decodeInt64Varint() is refactored to take a cursor, matching readVarIntAsNumber() and avoiding a double skip past the varint. - Cell type validation is factored out of iter() into shared helpers (scanCellTypeMasks / isMaskCompatible / throwOnIncompatibleCell), one cheap byte scan per batch building per-column bitmasks with the compatibility check running once per column. throwOnIncompatibleCell computes the row from the within-batch offset, fixing a latent row-miscount in iter()'s error path for multi-batch results. NULL cells for non-nullable spec types now throw instead of decoding to NaN/0n, matching iter(). Includes a RUN_BENCH-gated micro-benchmark suite comparing iter() vs decodeColumns() across four representative workloads (instant/slice mixed, long-only, num-varint-only, num-float64-only; ~2.3-3.3x faster) and unit tests for the columnar decode, error cases and the varint cursor path.
… tracks Convert the track rendering hot paths from per-row iter() loop to the bulk decodeColumns() API: - SliceTrack (getInstantBuffers/getSliceBuffers): bulk-decode the fixed columns (id/ts/count/depth/start/end/incomplete) plus the dataset schema columns in one pass, and build the renderer buffers with native Float64 -> Float32 typed-array conversion instead of per-row copies. The schema columns are read out of the decoded arrays per row to build the object handed to the getTitle/getSubtitle/getColor/pattern callbacks (keeps the chunked task loop as rows are still built per-row). - CounterTrack: switch to decodeColumns() and drop the deferChunkedTask() machinery entirely, since the remaining min/max pass is a tight typed-array loop that no longer needs to yield. - CpuFreqTrack: decode both the freq and idle results columnarly and copy the plain data columns with direct typed-array conversions (BigInt64Array reuse for timestamps, Uint32/Int8 for the rest), leaving the step-area computation in the loop. - GroupSummaryTrack: decode the count/ts/dur/lane/utid columns into typed arrays and fill the derived buffer in a single pass. Also use direct copies of decoded columns where possible and trim the change-narration comments left over from the conversion.
stevegolton
force-pushed
the
dev/sg/query-result-columnar-decode
branch
from
August 31, 2026 12:00
3809763 to
7ca61ac
Compare
LalitMaganti
approved these changes
Aug 31, 2026
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.
Currently all queries must be iterated out row by row which involves mutating the row object for every iteration. On heavy queries with many rows (such as tracks) this overhead can add up. In addition, most tracks ultimately require the data in columnar TypedArrays in order to load into WebGL buffers, so round tripping to a row object is wasteful.
This patch introduces a new QueryResult API -
decodeColumns(spec)- which takes the same spec object as.iter(spec)but returns all rows in one go as a set of columnar basedTypedArrays. Building arrays directly allows certain shortcuts to be taken such as avoiding creating bigints and simply copying bytes.decodeColumns()runs around 2-4x faster thaniter(), depending on the row spec.This patch also migrates SliceTrack, CounterTrack, GroupSummaryTrack, and CpuFreqTrack over to
decodeColumns(). For compatibility, some row oriented work is still done in SliceTrack which leaves some performance on the table but changing this would involve changing track API, and this is out of scope of this PR.Added a benchmark utility to compare the relative performance of
iter()anddecodeColumns(), which can be run using the following command:RUN_BENCH=1 ui/run-unittests -f 'QueryResultBenchmark' -nNote: Changes in unrelated parts of the codebase are related to the fact that the type of the row spec has been decoupled from the type of the row itself. This was a neat trick but doesn't work for decodeColumns as we need to translate the spec to columnar TypedArrays - e.g. NUM -> Float64Array. Unfortunately Typescript cannot tell the difference between the NUM and NUM_NULL types when doing type based metaprogramming.
The spec types (NUM, STR, LONG, etc...) how now been changed to tagged types - so that each one is distinct and can be translated to the equivalent TypedArray properly.