Single-column row comparator dispatches through interface, copying two Values

perfloop/parquet-go · INDIRECT DISPATCH HOT LOOP

https://perfloop.ai/t/oss/case_42cpb9gsx5

Verdict

VERIFIED · settled 2026-06-25 · merged as parquet-go/parquet-go#547

What happened: The hypothesis is successfully validated. By specializing single-column row comparison closures returned by compareRowsFuncOfIndexAscending and compareRowsFuncOfIndexDescending using a type switch on the column's Type, we bypass the need for interface dispatch on typ.Compare and the copying of two 24-byte Value structures per element comparison. On BenchmarkMergeRowBuffers (which sorts and merges row groups by a single int64 column), the specialized comparison logic yields a 39.86% reduction in timing (median execution time reduced from 316.3 ms/op to 190.2 ms/op). Similarly, BenchmarkSortRowBuffer (sorting by a 16-byte fixed array/uuid column) yields a timing reduction of more than 57% (3.57 ms/op to 1.53 ms/op). High-precision Time(Nanosecond) columns are also safely supported by correctly switching comparison logic on t.useInt32() to use 64-bit comparisons, resolving a truncation bug in the initial iteration that was highlighted by the reviewer feedback. A regression-prevention test has been added to ensure high-precision times comparing and sorting correctly.

Hypothesis

This hypothesis targets the source anchor compareRowsFuncOfIndexAscending in compare.go. On the Parquet Row Group Merging and Sorting Workload, the generic row comparator returned by compareRowsFuncOfIndexAscending performs typ.Compare(row1[i], row2[i]) for each row comparison. This interface dispatch on typ.Compare plus copying two 24-byte Value structures per comparison is executed O(N log N) times inside tight sorting and merging loops, where it is hypothesized to dominate the CPU execution profile. The concrete mechanism proposed is to resolve the concrete column Type once when the closure is constructed and instead compare primitives directly (such as raw int64, int32, or float fields read off the Row), bypassing interface dispatches and value copies entirely. The proof targets a case session would measure to verify this benefit are the CPU execution times and allocations per operation of SortRowBuffer and MergeRowBuffers benchmarks (e.g. BenchmarkSortRowBuffer and BenchmarkMergeRowBuffers) on single primitive sort columns, which are expected to show significant throughput improvements.

Change to test: In compare.go inside compareRowsFuncOfIndexAscending, check the column type at closure construction time. If it is a known primitive type, return a monomorphic comparison closure (such as comparing raw int64, int32, or float fields read directly off the Row) to perform the comparisons directly, avoiding copying two 24-byte Values and dispatching through typ.Compare.

Where it lives

perfloop/parquet-go · merge.go

Evidence

Timeline