perf: typed columns for pine-go/pine-java ColumnFrame (issue #156) - #162
Merged
Conversation
Port pine-cpp's Column hierarchy (include/pine/column.hpp) to the other two engines: fixed-width typed columns (float64/double, string, bool) with a validity bitmap, plus a JSON fallback for mixed/composite/ present-null data. Cashes in the batch access API from PR #155 — with the per-element interface tax gone, boxing was the next bottleneck. pine-go (internal/dataframe/column.go + column_frame.go rewrite): - column interface + typedColumn[T] generics + jsonColumn fallback - construction-time type inference (one pass per field, mirrors pine-cpp make_column); present-nil disqualifies typed storage - runtime promotion on type-mismatched / nil writes (toJSON + retry), matching pine-cpp write_item_field_locked semantics - deliberate divergence from pine-cpp, documented in column.go: typed dispatch is by EXACT runtime type (float64 only for numerics) — Go's `any` preserves int vs float64 and downstream contracts observe it (%T error messages, type-prefixed dedup keys); JSON-sourced data is always float64 so the common case still gets the typed path - types.Float64ColumnReader optional interface + OperatorInput.ItemColumnFloat64: zero-copy raw []float64 window, ok only when fully present (defaults can never fire); normalize and sort hot loops use it, skipping boxing + type assertions entirely - removals/reorder move to bitmap-compaction / cycle-following inside each column (scratch shared across columns) pine-java (Column.java + ColumnFrame.java rewrite): same model — Column.build inference, DoubleColumn/StringColumn/BoolColumn/JsonColumn, promotion on set/append failure, Frame.itemColumnDoubleView default method + OperatorInput.itemColumnDouble, TransformNormalize/ReorderSort fast paths. Benchmarks (Apple M5 Pro, high ambient load — same-run A/B): - pine-go New/column: 33→40 allocs but bytes 177KB→94KB (typed arrays) - Removals/column: 88KB→1KB per op, 21→1 allocs (bitmap compaction) - e2e transform-heavy 1000: column ~0.89ms vs row ~1.37ms (~35% faster, bytes -51%); 5000: column ~3.6ms vs row ~5.6ms Tests: typed inference / promotion / present-nil semantics / fast-path gating / row-vs-column result parity on both engines (Go typed_column_test.go, Java TypedColumnTest.java); full suites green (go test -race + golangci-lint 0 issues; mvn 256 tests + checkstyle); cross-validate sections 3/4/5 green (95/95 column-store parity); 120-round three-engine differential fuzz 0 divergence.
Contributor
🔍 PR 审查
整体质量很高:typed column 分层清晰、与 pine-cpp 语义对齐、promotion / present-null / bitmap 压缩 / cycle-following 置换都实现正确,
|
Both verified real: - pine-java ReorderSort: remove the dead `List<int[]> entries` — a translation leftover from pine-go's entry struct that allocated n small arrays per sort without ever being read, directly contradicting this PR's allocation-reduction goal. - pine-go/pine-java additions Pass 2: pre-grow the promoted JsonColumn to newCap so the remaining appends in a batch-addition-with-promotion path don't realloc element by element.
Contributor
🔍 PR 增量审查
上一轮提出的两个问题均已正确修复,增量改动干净、无回归。
|
Per the new convention (llmdoc updates land inside the PR, before merge, once CI is green and review items are resolved): - perf-evolution-roadmap: step 1 marked implemented — typed columns landed on pine-go/pine-java (pine-cpp was the template); records the deliberate exact-type-dispatch divergence rationale and the remaining string-arena follow-up - column-vs-row reflection: phase-2 implementation record (results, fast-path gating) + two new lessons (template alignment is not line-by-line copying; translation-leftover dead code is a high-risk defect class in porting PRs) - index.md: sync both entries
Contributor
🔍 PR 增量审查
本轮增量仅一个 commit(
前两轮代码审查提出的问题均已修复(死代码 |
Liam0205
added a commit
that referenced
this pull request
Jul 7, 2026
Both verified real: - pine-java ReorderSort: remove the dead `List<int[]> entries` — a translation leftover from pine-go's entry struct that allocated n small arrays per sort without ever being read, directly contradicting this PR's allocation-reduction goal. - pine-go/pine-java additions Pass 2: pre-grow the promoted JsonColumn to newCap so the remaining appends in a batch-addition-with-promotion path don't realloc element by element.
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.
Closes #156.
Summary
Ports pine-cpp's Column hierarchy (
include/pine/column.hpp) to pine-go and pine-java: fixed-width typed columns (float64/double, string, bool) with a validity bitmap, plus a JSON fallback for mixed/composite/present-null data. This cashes in the batch access API from PR #155 — with the per-element interface tax gone, boxing was the next bottleneck (investigation: typed scan 257ns ≈ ideal boxed scan 262ns, so typed columns only pay off with the batch API in place; both prerequisites now hold).Design (mirrors pine-cpp, one documented divergence)
makeColumn/Column.build): one pass per field over the items, picking float64/string/bool columns for homogeneous fields; present-null or mixed types fall to the JSON column. Mirrors pine-cppmake_column.write_item_field_locked.float64/Doublefor numerics), unlike pine-cpp which folds all numerics to DoubleColumn. Rationale (in column.go's header): Go'sany/ Java's boxing preserve int-vs-float distinctions that downstream contracts observe (%Terror messages, type-prefixed dedup keys). JSON-sourced data always arrives as float64/Double, so the common case still gets the typed path.OperatorInput.ItemColumnFloat64(Go) /itemColumnDouble(Java) return the raw primitive array zero-copy when the column is typed AND fully present (so item-defaults can never fire). normalize/sort hot loops use it, skipping both boxing and per-element type assertions. Falls back to the boxedItemColumnotherwise.pine-cpp is untouched (it already is the template).
Benchmarks (Apple M5 Pro, same-run A/B)
Verification
-racegreen, golangci-lint 0 issues; new typed_column_test.go (inference / promotion / present-nil / fast-path gating / row-vs-column result parity)