Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
a7ac1ae
Add PFOR core algorithm and tests
sfc-gh-pgaur Apr 20, 2026
c8501f5
Integrate PFOR encoding into parquet encoder/decoder
sfc-gh-pgaur Apr 21, 2026
8ac3f28
Add PFOR encoding benchmark
sfc-gh-pgaur Apr 21, 2026
ff837d9
Use signed integer types consistently per Arrow style guide
sfc-gh-pgaur Jun 3, 2026
f8c70e8
Use arrow::util::span for buffer parameters in Store/Load/Decode/Seri…
sfc-gh-pgaur Jun 3, 2026
65b29f1
Return Result<T>/Status on decode paths instead of ARROW_DCHECK
sfc-gh-pgaur Jun 3, 2026
7c5e6e4
Add static_assert(ARROW_LITTLE_ENDIAN) and replace reinterpret_cast w…
sfc-gh-pgaur Jun 3, 2026
0c5ef3e
Use single-call unpack() instead of manual batch loop + BitReader rem…
sfc-gh-pgaur Jun 3, 2026
a99dee8
Add pragma GCC unroll/ivdep to decode loops for better vectorization
sfc-gh-pgaur Jun 3, 2026
c1a8eb5
Add PforEncodedVectorView for zero-copy decode path
sfc-gh-pgaur Jun 3, 2026
49ef1ce
Make vector_size configurable on encode path with default kPforVector…
sfc-gh-pgaur Jun 3, 2026
6f3a5bf
Fix pfor_test.cc: unwrap Result<> from PforVectorInfo::Load()
sfc-gh-pgaur Jun 3, 2026
cd2ed56
Convert PforWrapper::LoadHeader to return Result<PforHeader>
sfc-gh-pgaur Jun 14, 2026
b6aae63
Use SafeLoadAs/SafeStore for header and offset array in PforWrapper
sfc-gh-pgaur Jun 14, 2026
374eae6
Validate header fields in PforWrapper::LoadHeader
sfc-gh-pgaur Jun 14, 2026
87564b8
Use int64_t and global ::arrow:: prefix at PFOR encoder/decoder call …
sfc-gh-pgaur Jun 15, 2026
4f305b9
Use uint8_t* consistently in PforWrapper API instead of char*
sfc-gh-pgaur Jun 15, 2026
9d5e82c
Convert PforVectorInfo to class with SafeLoadAs/SafeStore and bit_wid…
sfc-gh-pgaur Jun 15, 2026
5583424
Convert PforEncodedVector and PforEncodedVectorView to classes
sfc-gh-pgaur Jun 15, 2026
d801fe4
Tighten PforVectorInfo encapsulation: validate num_exceptions in Load…
sfc-gh-pgaur Jun 15, 2026
9fabb7e
make
sfc-gh-pgaur Jun 25, 2026
384e89f
Use bit_util::IsPowerOf2/CeilDiv and add incremental encode/decode TO…
sfc-gh-pgaur Jun 26, 2026
9d5470c
Drop Snowflake attribution from PFOR header comments
sfc-gh-pgaur Jun 26, 2026
9bef47b
Fix IsPowerOf2(int32_t) ambiguity in pfor_wrapper.cc
sfc-gh-pgaur Jun 28, 2026
6ae3c9b
Add FastLanes auto-vectorized bit-packing library
sfc-gh-pgaur Jun 28, 2026
822f91b
Add FastLanes-FOR to parquet-pfor-comparison-benchmark
sfc-gh-pgaur Jun 28, 2026
ef3e746
FastLanes-FOR: add DecodeFlat (flat output) and benchmark it
sfc-gh-pgaur Jun 28, 2026
4037bb2
FastLanes: add toTransposed32; correct fromTransposed32 docstring
sfc-gh-pgaur Jun 28, 2026
26ea3c4
PFOR: add PackingMode (BitPack | FastLanes) per-vector flag
sfc-gh-pgaur Jun 28, 2026
51f2568
PFOR: add OutputOrder { Flat, Transposed } decode option
sfc-gh-pgaur Jun 28, 2026
d9ef31b
Local: keep -O3 from CMake Release defaults (drop -O2 downgrade)
sfc-gh-pgaur Jun 28, 2026
6468d91
PFOR: cover bit_width 0 and 32 paths in transposed-output tests
sfc-gh-pgaur Jul 20, 2026
3a66cb1
PFOR: adapt to updated Arrow util APIs
sfc-gh-pgaur Jul 20, 2026
a106ed5
PFOR: migrate to std::span
sfc-gh-pgaur Jul 20, 2026
20b90fb
PFOR: add FastLanesOrdered packing mode (interleave without FL_ORDER)
sfc-gh-pgaur Jul 20, 2026
98bdbf2
PFOR: decode BitPack into stack scratch (avoid per-vector heap alloc)
sfc-gh-pgaur Jul 24, 2026
c52b0ff
PFOR: vectorize the frame-of-reference add in BitPack decode
sfc-gh-pgaur Jul 24, 2026
be09337
PFOR: speed up encode (cost-model histogram + stack deltas)
sfc-gh-pgaur Jul 24, 2026
aa4e37b
Skip FOR-add pass in PFOR decode when frame-of-reference is 0
sfc-gh-pgaur Jul 24, 2026
4e8d557
Add TPC-H, more TPC-DS, and NYC-taxi numeric columns to PFOR benchmark
sfc-gh-pgaur Jul 24, 2026
11825c5
Remove FastLanes packing modes from PFOR (BitPack only)
sfc-gh-pgaur Jul 24, 2026
8b5453c
Add int64 (BIGINT) columns to the PFOR comparison benchmark
sfc-gh-pgaur Jul 25, 2026
db68831
Note the frame-of-reference bias fold as a TODO on the decode path
sfc-gh-pgaur Aug 21, 2026
5f12e15
Add a bias parameter to arrow::internal::unpack
sfc-gh-pgaur Aug 21, 2026
152e1d6
Keep unpack_full at memcpy speed when it carries a bias
sfc-gh-pgaur Aug 21, 2026
8cae5e6
Reflow an unpack call site to clang-format 18
sfc-gh-pgaur Aug 21, 2026
50fcf20
Store the PFOR bit width in 7 bits, not 6
sfc-gh-pgaur Aug 21, 2026
b0978e8
Fold the frame-of-reference add into the PFOR unpacker
sfc-gh-pgaur Aug 21, 2026
ba66ee7
Spell the restrict qualifier portably in unpack_full
sfc-gh-pgaur Aug 23, 2026
d995235
Fix two errors PFOR raises at the checkin warning level
sfc-gh-pgaur Aug 25, 2026
1d36687
List PFOR in SupportedEncodings for INT32 and INT64
sfc-gh-pgaur Aug 25, 2026
6a285b4
Use std::bit_width instead of __builtin_clz in PFOR
sfc-gh-pgaur Aug 25, 2026
aeffcb4
Apply clang-format 18 to the PFOR sources
sfc-gh-pgaur Aug 25, 2026
1a77108
Reject a PFOR page header that disagrees with its buffer
sfc-gh-pgaur Aug 25, 2026
db4f45f
Count PFOR exceptions in an unsigned field
sfc-gh-pgaur Aug 25, 2026
f54ffdf
Put the output buffer last in PforWrapper::Decode
sfc-gh-pgaur Aug 25, 2026
9d302f6
Return Status from PforWrapper::Encode
sfc-gh-pgaur Aug 25, 2026
f3e40a8
Decode PFOR pages with null slots through the Arrow path
sfc-gh-pgaur Aug 25, 2026
1c304ed
Note that incremental PFOR encode and decode come later
sfc-gh-pgaur Aug 25, 2026
1f3fe88
Correct the bit_width mask comments in PforVectorInfo
sfc-gh-pgaur Aug 26, 2026
6e82a32
Validate PFOR exception positions and counts before patching
sfc-gh-pgaur Aug 26, 2026
06df37f
Drop the cached PFOR page when the decoder is given a new one
sfc-gh-pgaur Aug 26, 2026
8412366
Validate PFOR wire metadata and enforce the output buffer size
sfc-gh-pgaur Aug 26, 2026
3ffe18f
Build PFOR into libarrow instead of recompiling it per target
sfc-gh-pgaur Aug 27, 2026
3deff2f
Stop PforDecoder from shadowing its base class page state
sfc-gh-pgaur Aug 27, 2026
6fd8480
Derive PFOR layout sizes from the fields they describe
sfc-gh-pgaur Aug 27, 2026
87aa140
Drop the unused PFOR vector view; harden SerializeVector
sfc-gh-pgaur Aug 27, 2026
ccd88a8
Note the missing PLAIN fallback in PforEncoder
sfc-gh-pgaur Aug 27, 2026
544af58
Type-parameterize the PFOR tests over int32 and int64
sfc-gh-pgaur Aug 27, 2026
7dd4a9c
Take the PFOR value count from the page's own header
sfc-gh-pgaur Sep 3, 2026
16be70a
Validate the PFOR element count and offset chain before decoding
sfc-gh-pgaur Sep 3, 2026
f150979
Encode an all-null PFOR page as a bare header
sfc-gh-pgaur Sep 3, 2026
239898f
Serialize PFOR little-endian instead of refusing to build
sfc-gh-pgaur Sep 3, 2026
9d57072
Build the PFOR sources under meson too
sfc-gh-pgaur Sep 3, 2026
3570bf1
Record what a batched PFOR read costs today
sfc-gh-pgaur Sep 3, 2026
87fdc1d
Add end-to-end PFOR tests through the Arrow reader and writer
sfc-gh-pgaur Sep 3, 2026
b5c850f
Name the PFOR headers _internal.h
sfc-gh-pgaur Sep 3, 2026
96c7a11
Give PFOR a delta mode and a frame it can search
sfc-gh-pgaur Sep 1, 2026
1d97580
Cut the frame search's cost, and lower its frame onto a real value
sfc-gh-pgaur Sep 1, 2026
56e2003
Add the delta-mode column shapes to the codec comparison benchmark
sfc-gh-pgaur Sep 1, 2026
10bd2d7
Decline the PFOR delta mode from a sampled estimate
sfc-gh-pgaur Sep 3, 2026
06cd8cb
Gate PFOR and its delta mode behind writer properties
sfc-gh-pgaur Sep 3, 2026
55cb7ee
Harden the PFOR delta flag against a corrupt page
sfc-gh-pgaur Sep 3, 2026
f2e3609
Note that the delta prefix sum could fold into the unpack kernel
sfc-gh-pgaur Sep 3, 2026
5c22ffe
Bound the sequential unpacker at the page, not at the vector
sfc-gh-pgaur Sep 5, 2026
4a297f5
Add a lane-interleaved bit-packing kernel for 32-bit blocks
sfc-gh-pgaur Sep 4, 2026
c817453
Let a PFOR page choose the lane-interleaved bit-packing layout
sfc-gh-pgaur Sep 4, 2026
e31900f
Test the interleaved bit-packing layout
sfc-gh-pgaur Sep 4, 2026
61b3795
Let a writer ask PFOR for the interleaved bit-packing layout
sfc-gh-pgaur Sep 4, 2026
ddff362
Test the interleaved layout through a written Parquet file
sfc-gh-pgaur Sep 4, 2026
901ee06
Benchmark the interleaved layout against the sequential one
sfc-gh-pgaur Sep 4, 2026
8f708eb
Benchmark a page-sized destination, not only powers of 1024
sfc-gh-pgaur Sep 5, 2026
845286e
Bound the sequential unpacker at the page, not at the vector
sfc-gh-pgaur Sep 5, 2026
fed2b63
Say what the interleaved layout is worth, and where it is not
sfc-gh-pgaur Sep 6, 2026
18cf2c6
Measure the FastLanes paper's lane assignment for DELTA
sfc-gh-pgaur Sep 5, 2026
ec59209
Fuse the transpose into the prefix sum so the repair costs nothing
sfc-gh-pgaur Sep 5, 2026
61f4427
Read the lane-parallel delta layout through a Parquet decoder
sfc-gh-pgaur Sep 8, 2026
eaba96e
Benchmark the lane-delta decoder against the one the library ships
sfc-gh-pgaur Sep 8, 2026
6782c34
Refuse a lane-delta page that does not describe itself
sfc-gh-pgaur Sep 8, 2026
9b57406
Stop the entry-point reader at the end of its stream
sfc-gh-pgaur Sep 9, 2026
6834bec
Frame the paper's lane assignment as a page
sfc-gh-pgaur Sep 9, 2026
abdb35d
Store the paper's lane assignment in a lane-delta page
sfc-gh-pgaur Sep 9, 2026
a2f2a3e
Add a three-arm hardware-counter kernel sweep driver
sfc-gh-pgaur Sep 9, 2026
684341f
Gate the interleaved-layout tests behind the PFOR preview flag
sfc-gh-pgaur Sep 10, 2026
f271fa7
Add an x86 register-width sweep for the PFOR layout comparison
sfc-gh-pgaur Sep 12, 2026
fef9686
Add plain-PFOR interleaved-layout decode benchmark on real datasets
sfc-gh-pgaur Sep 12, 2026
0346cd8
Separate the layout question from the delta decision in the PFOR sweep
sfc-gh-pgaur Sep 12, 2026
19b530f
Cap the bit-unpack SIMD dispatch at 256 bits
sfc-gh-pgaur Sep 13, 2026
4719504
Fuse the FL_ORDER transpose into the unpack kernels
sfc-gh-pgaur Sep 13, 2026
79df33a
Add the benchmark driver scripts used for the layout measurements
sfc-gh-pgaur Sep 13, 2026
12258fb
Add the fl5_corpus layout harness so aarch64 can reproduce it
sfc-gh-pgaur Sep 13, 2026
99c7809
Drop the 512-bit leg from the register-width sweep
sfc-gh-pgaur Sep 13, 2026
80ee8a0
Sweep the layout arms across five working-set sizes
sfc-gh-pgaur Sep 14, 2026
ef6d1ba
Extend the fl5 corpus harness to five footprints and price the store …
sfc-gh-pgaur Sep 14, 2026
f43b746
Stop the benchmark drivers from dividing across incompatible arms
sfc-gh-pgaur Sep 15, 2026
ad79359
Merge the two fl5 harness lines: page-scale ladder plus the store cei…
sfc-gh-pgaur Sep 15, 2026
2a11935
Judge the layout control per point instead of per column
sfc-gh-pgaur Sep 16, 2026
0d8f446
Dispatch the interleaved PFOR kernel on the runtime instruction set
sfc-gh-pgaur Sep 16, 2026
c03d939
Turn the layout harness into a single no-argument binary
sfc-gh-pgaur Sep 18, 2026
21e7835
Clean up the comments and naming in the layout research code
sfc-gh-pgaur Sep 19, 2026
50319e5
Normalize the two banner blocks the earlier comment pass missed
sfc-gh-pgaur Sep 19, 2026
6609085
Plain the last shouted words and the width-matrix header
sfc-gh-pgaur Sep 19, 2026
fcd340f
State the layout harness's requirements instead of its history
sfc-gh-pgaur Sep 19, 2026
656fa92
Measure the interleaved sweep arm out of line, as the other two are
sfc-gh-pgaur Sep 20, 2026
6b77752
Do the FL_ORDER permutation in registers on Arm too
sfc-gh-pgaur Sep 20, 2026
7f6034d
Add a width-resolved layout ladder
sfc-gh-pgaur Sep 20, 2026
2aed672
Resolve the layout comparison by output element width
sfc-gh-pgaur Sep 20, 2026
4e1bcb7
Correct the output-width note in the width ladder header
sfc-gh-pgaur Sep 20, 2026
4ddf2b2
Report both units in the width harnesses, add the corpus output-width…
sfc-gh-pgaur Sep 20, 2026
08d3307
Add the three output-element pairs the summary table needs
sfc-gh-pgaur Sep 20, 2026
e737aaf
Measure the layouts in the shape a decoder runs them in
sfc-gh-pgaur Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,6 @@ rat.txt

# for ODBC DLL
*.rc

# Local out-of-tree benchmark build dir
cpp/build-bench/
81 changes: 81 additions & 0 deletions ab_compare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
#
# A/B comparison protocol for pfor_comparison_benchmark.
#
# Why this exists
# This benchmark has two deterministic confounds, both larger than most
# effects being measured. Neither is noise, and neither shrinks with more
# repetitions. See INTEL_RESULTS.md section 10.
#
# (1) ARROW_USER_SIMD_LEVEL caps Arrow's runtime dispatch (bpacking.cc:29-45).
# It reaches BM_PforDecode only -- the FastLanes kernel has no dispatch.
# Capping to AVX2 makes Arrow's sequential decoder 3.21x faster than
# leaving it uncapped on Granite Rapids, because the AVX-512 family takes
# a second pass for the frame bias where the xsimd kernels fold it in.
# unset means the maximum, i.e. AVX512, so the default selects the slow path.
#
# (2) The interleaved decoder reads either ~36 or ~48 GiB/s (1.33x) at identical
# binary, cap, filter and reps. It is stable to 1.00-1.03x across
# consecutive processes but flips between batches minutes apart, and it
# also moves with how many columns the filter admits (more columns ->
# more corpora allocated before the hot buffer). fl_order is immune.
#
# The protocol
# Run every variant alternating inside one batch, several processes each, with
# the cap pinned. Ratios are then taken within a batch, where confound (2) is
# constant. Report the cross-process spread, because the within-process stddev
# is 0.2-1% here and hides both confounds completely.
#
# The benchmarks and their groups come from bench_groups.sh, which states
# which of them may be divided by which. This script used to name the production decoder against an
# exception-free standalone one and call the ratio a layout result.
#
# Usage
# ./ab_compare.sh <binA>:<cap> <binB>:<cap> [...] # binaries under bin/
# ROUNDS=3 REPS=3 CORE=2 ./ab_compare.sh bench_O2_256:AVX2 fix_O2_256:AVX2
# GROUPS='GROUP_LAYOUT GROUP_DEST' ./ab_compare.sh bench_O2_256:AVX2
#
set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
. ./bench_groups.sh

BIN_DIR=${BIN_DIR:-$HOME/Projects/pfor_x86_handoff/width_matrix_v2/bin}
OUT=${OUT:-/tmp/ab}
ROUNDS=${ROUNDS:-3}
REPS=${REPS:-3}
CORE=${CORE:-2}

# Which groups to time. Several in one process is fine and preferred, since
# benchmarks timed together share the machine state; dividing across groups is
# not -- see bench_groups.sh.
GROUPS=${GROUPS:-'GROUP_LAYOUT GROUP_ORDER'}
SETS=()
for NAME in $GROUPS; do SETS+=("${!NAME}"); done
FILTER=$(bench_filter "${SETS[@]}")

[ $# -ge 1 ] || { sed -n '2,40p' "$0"; exit 1; }

mkdir -p "$OUT"
LOAD=$(awk '{print int($1)}' /proc/loadavg)
if [ "$LOAD" -gt 2 ] && [ "${FORCE:-0}" != 1 ]; then
echo "!! loadavg is $LOAD -- too busy. Set FORCE=1 to override."; exit 1
fi

for i in $(seq 1 "$ROUNDS"); do
for SPEC in "$@"; do
IFS=: read -r B CAP <<<"$SPEC"
[ -x "$BIN_DIR/$B" ] || { echo "-- no binary $BIN_DIR/$B, skipping"; continue; }
# Cap pinned per variant. Stated in the output filename so the analysis
# cannot silently mix caps -- that is confound (1).
ARROW_USER_SIMD_LEVEL="${CAP:-MAX}" taskset -c "$CORE" "$BIN_DIR/$B" \
--benchmark_filter="$FILTER" \
--benchmark_repetitions="$REPS" \
--benchmark_report_aggregates_only=true \
--benchmark_out="$OUT/${B}__${CAP:-MAX}__$i.json" \
--benchmark_out_format=json >/dev/null 2>&1 \
&& echo " $B cap=${CAP:-MAX} round $i" \
|| echo " !! $B cap=${CAP:-MAX} round $i FAILED"
done
done
echo
echo "analyze with: python3 ~/Projects/pfor_x86_handoff/ab_analyze.py $OUT"
71 changes: 71 additions & 0 deletions bench_groups.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Shared benchmark groups and column set for pfor_comparison_benchmark drivers.
# Sourced, not executed: . "$(dirname "$0")/bench_groups.sh"
#
# Why this file exists
# Four drivers each carried their own copy of the column list and the
# benchmark regex, and all four copies named the same wrong pair: BM_PforDecode (the
# production decoder, which patches exceptions) against BM_InterleavedPforDecode
# and BM_InterleavedPforFlOrderDecode (a standalone path with no exception
# handling at all). A ratio between those is not a layout result -- the
# interleaved side is doing less work. The correct pair had already landed in
# the same benchmark file when those drivers were written.
#
# Keeping the sets here means the rule below is stated once and cannot drift
# between drivers.
#
# Which benchmarks may be compared with which
# Only within a group. Every benchmark in a group runs the same code with the same
# exception handling and the same destination policy, so the named variable is
# the only difference.
#
# GROUP_LAYOUT sequential vs interleaved container, both through the
# production encoder and decoder with delta declined on both
# sides. This is the pair the layout question is read from.
#
# GROUP_ORDER file order vs the paper's lane assignment inside the
# interleaved container. These run arrow/util/fastlanes/
# interleaved_pfor.h, which has no exception handling anywhere in
# it, so the file-order benchmark in this group is their only
# valid baseline. Never pair one of these against a production
# benchmark.
# The production format has no lane-assignment mode, which is why
# this group exists outside it.
#
# GROUP_DEST whole-output materialization vs a reused block-sized
# destination, at each layout. Answers what the output buffer
# costs, not what the layout costs.
#
# These benchmarks arrive with the output-buffer-reuse change
# and are absent from a tree without it; a filter naming them then
# matches nothing, which google-benchmark reports as no
# benchmarks to run.
#
# GROUP_SHIPPED the shipping default, planner free to difference a vector. Not
# a layout comparison: on a sorted or correlated column it deltas, and
# the decode then also pays a serial prefix sum.
#
# Column set
# Changing it changes the numbers, because the count of columns a filter
# admits decides how many corpora are allocated before the timed buffer. Hold
# it fixed across anything meant to be compared.

COLS=${COLS:-'TpcdsSoldDateSk|TpcdsStoreSk|TpcdsItemSk|TpcdsQuantity|EventDate|ClientIP|CounterID|SortedKeys|MonotoneRowId|RandomWalk'}

GROUP_LAYOUT='BM_(PforPlainSeqDecode|PforPlainInterleavedDecode)'
GROUP_ORDER='BM_(InterleavedPforDecode|InterleavedPforFlOrderRawDecode|InterleavedPforFlOrderDecode)'
GROUP_DEST='BM_Pfor(Whole|Reuse)(Seq|Interleaved)Decode'
GROUP_SHIPPED='BM_Pfor(64)?Decode'

# Build a google-benchmark filter from one or more groups.
# FILTER=$(bench_filter "$GROUP_LAYOUT" "$GROUP_ORDER")
# Groups are unioned so one process can time several of them -- that is fine and
# is in fact preferred, since benchmarks timed in one process share the machine
# state.
# What is not fine is dividing a number from one group by a number from another.
bench_filter() {
local joined="" a
for a in "$@"; do
joined="${joined:+$joined|}${a}"
done
printf '(%s)/(%s)/' "$joined" "$COLS"
}
71 changes: 71 additions & 0 deletions build_transpose_ab.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
#
# Build the AVX2 Transpose32x32 A/B points.
#
# Treatment: transposed_delta.h gained Transpose32x32Avx2 (sixteen in-register
# 8x8 transposes) behind #if defined(__AVX2__). Before this, x86 fell through to
# Transpose32x32Scalar -- 1024 four-byte loads at a 128-byte stride plus 1024
# scalar stores per block, because only NEON had a hand-written path. Isolated,
# the permutation goes 28.67 -> 73.56 GiB/s (2.57x).
#
# Only the kFlOrder decoder calls the transpose, so BM_InterleavedPforDecode (file
# order) and BM_PforDecode are controls this change cannot reach. If either
# moves by more than the cross-process spread, the comparison is contaminated --
# see INTEL_RESULTS.md section 10.
#
# Correctness is not left to inspection: pfor_comparison_benchmark.cc
# round-trips every column through encode+decode and ARROW_CHECKs the result
# before timing starts, for both orders. A wrong transpose aborts the binary.
# Transpose32x32 is also its own inverse on a square grid, so encode and decode
# share the path and a broken permutation cannot cancel itself out.
#
# Build only. Time with ab_compare.sh.
set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
export PATH="$HOME/.local/bin:$PATH" # cmake must be <4

BUILD=build-x86-sweep
OUT="$HOME/Projects/pfor_x86_handoff/width_matrix_v2"
mkdir -p "$OUT/bin"

# Matched to the existing fix_* binaries so the only difference is the transpose.
POINTS=(
"tr_O3_256:AVX2:-O3:256"
"tr_O2_256:AVX2:-O2:256"
)

for P in "${POINTS[@]}"; do
IFS=: read -r NAME LEVEL OPT PVW <<<"$P"
echo "== $NAME : ARROW_SIMD_LEVEL=$LEVEL $OPT -mprefer-vector-width=$PVW"
# Explicit every time: CMAKE_CXX_FLAGS_RELEASE is a CACHE variable and would
# otherwise inherit the previous point's value.
if ! cmake -S cpp -B "$BUILD" -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DARROW_SIMD_LEVEL="$LEVEL" \
-DCMAKE_CXX_FLAGS_RELEASE="$OPT -DNDEBUG -mprefer-vector-width=$PVW" \
-DCMAKE_C_FLAGS_RELEASE="$OPT -DNDEBUG -mprefer-vector-width=$PVW" \
-DARROW_PARQUET=ON -DARROW_BUILD_BENCHMARKS=ON \
-DARROW_WITH_ZSTD=ON -DARROW_WITH_LZ4=ON \
-DARROW_BUILD_TESTS=OFF > "$OUT/configure_$NAME.log" 2>&1; then
echo "!! configure FAILED -- $OUT/configure_$NAME.log"; continue
fi
if ! cmake --build "$BUILD" --target parquet-pfor-comparison-benchmark -j 6 \
> "$OUT/build_$NAME.log" 2>&1; then
echo "!! build FAILED -- tail:"; tail -30 "$OUT/build_$NAME.log"; continue
fi
cp "$BUILD/release/parquet-pfor-comparison-benchmark" "$OUT/bin/bench_$NAME"
echo " -> $OUT/bin/bench_$NAME"
# Prove the AVX2 transpose is actually in the kFlOrder kernel.
objdump -d --demangle "$OUT/bin/bench_$NAME" 2>/dev/null | awk '
/^[0-9a-f]+ <.*InterleavedPforDecode<.*>:$/ {
inside=1
name=($0 ~ /InterleavedPforOrder\)1/) ? "kFlOrder(1)" : "kFileOrder(0)"
n=0; y=0; z=0; perm=0; next
}
/^[0-9a-f]+ </ { if (inside) printf " %-14s insns=%-7d ymm=%-6d zmm=%-6d vperm2x128=%d\n", name,n,y,z,perm; inside=0; next }
inside { nf=split($0,p,"\t"); if (nf<3) next; n++
y+=gsub(/%ymm[0-9]+/,"&",p[3]); z+=gsub(/%zmm[0-9]+/,"&",p[3])
if (p[3] ~ /vperm2[fi]128/) perm++ }
END { if (inside) printf " %-14s insns=%-7d ymm=%-6d zmm=%-6d vperm2x128=%d\n", name,n,y,z,perm }'
done
echo "TRANSPOSE_BUILDS_DONE"
164 changes: 164 additions & 0 deletions build_width_matrix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env bash
#
# Register-width x optimizer matrix: one benchmark binary per point, each
# preserved under $OUT/bin/ so timing can be replayed without rebuilding and so
# builds and timing runs never overlap.
#
# The optimizer level is set explicitly at every point and never inherited.
# CMAKE_BUILD_TYPE=Release does not mean -O2 here: cpp/cmake_modules/
# SetupCxxFlags.cmake:637 carries a local override that keeps CMake's default
# -O3 -DNDEBUG for the pfor benchmark, so a point that lets Release supply its
# own flags comes out -O3 whatever it was meant to be. Width scaling read off a
# run that inherited its level mixes levels across the points and is not a width
# axis.
#
# CMAKE_CXX_FLAGS_RELEASE is a CACHE variable, so a value set at one point
# persists into the next configure of the same build dir. It is passed at every
# point here, so no point can inherit a neighbour's flags.
#
# -mprefer-vector-width is set at every point too. Arrow's
# ARROW_SIMD_LEVEL=AVX512 uses -march=skylake-avx512, and GCC defaults that
# target to -mprefer-vector-width=256, so a nominally-512 build emits zero zmm
# unless asked for.
#
# Width is verified inside the kernel function, not across the whole binary.
# Whole-binary counts are dominated by Arrow's own explicit-vector code and say
# nothing about what the autovectorizer did to the FastLanes kernel.
#
# No 512-bit leg
# Arrow's bit-unpack dispatch is capped at 256 bits, because the AVX-512
# kernels assemble their input register from scalar loads and measure 0.67x of
# the scalar kernel. Asking for the 512-bit level therefore hands the
# sequential decoder the 256-bit kernel back while the interleaved kernel really
# does widen, so such a point compares two widths on one side and one on the
# other. It comes back when a 512-bit unpack kernel exists that beats the
# 256-bit one.
#
# Build only. Time the binaries with ab_compare.sh, which alternates the
# variants inside one batch -- these numbers come from a shared virtualized
# container and a point timed minutes after its neighbour is not comparable to
# it.
#
set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
export PATH="$HOME/.local/bin:$PATH" # cmake must be <4; 3.31.10 lives here

BUILD=build-x86-sweep # reused so bundled deps are not rebuilt
# Output directory keeps its name so the binaries and verification logs already
# on the x86 box are not orphaned.
OUT="$HOME/Projects/pfor_x86_handoff/width_matrix_v2"
mkdir -p "$OUT/bin"

# The autovectorized FastLanes kernel under test. Order 0 = plain interleaved,
# Order 1 = the paper's lane assignment. Order 0 is the one the width claim is
# about. This kernel has no exception handling, so its timings belong to the
# order group in bench_groups.sh and never against a production decoder.
KERNEL='arrow::util::fastlanes::InterleavedPforDecode<(arrow::util::fastlanes::InterleavedPforOrder)0>'

# name | ARROW_SIMD_LEVEL | optimizer | prefer-vector-width
POINTS=(
"O2_128:SSE4_2:-O2:128"
"O2_256:AVX2:-O2:256"
"O3_128:SSE4_2:-O3:128"
"O3_256:AVX2:-O3:256"
)

for P in "${POINTS[@]}"; do
IFS=: read -r NAME LEVEL OPT PVW <<<"$P"
echo "=================================================================="
echo "== $NAME : ARROW_SIMD_LEVEL=$LEVEL $OPT -mprefer-vector-width=$PVW"
echo "=================================================================="

# Explicit every time. Nothing inherited, nothing assumed about Release.
if ! cmake -S cpp -B "$BUILD" -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DARROW_SIMD_LEVEL="$LEVEL" \
-DCMAKE_CXX_FLAGS_RELEASE="$OPT -DNDEBUG -mprefer-vector-width=$PVW" \
-DCMAKE_C_FLAGS_RELEASE="$OPT -DNDEBUG -mprefer-vector-width=$PVW" \
-DARROW_PARQUET=ON -DARROW_BUILD_BENCHMARKS=ON \
-DARROW_WITH_ZSTD=ON -DARROW_WITH_LZ4=ON \
-DARROW_BUILD_TESTS=OFF > "$OUT/configure_$NAME.log" 2>&1; then
echo "!! configure FAILED -- $OUT/configure_$NAME.log"; continue
fi
# Record the flags cmake actually resolved, so the log proves the level.
grep -E "CMAKE_CXX_FLAGS(_RELEASE)?:" "$OUT/configure_$NAME.log" | sed 's/^/ /'

if ! cmake --build "$BUILD" --target parquet-pfor-comparison-benchmark -j 6 \
> "$OUT/build_$NAME.log" 2>&1; then
echo "!! build FAILED -- $OUT/build_$NAME.log"; continue
fi

SRC="$BUILD/release/parquet-pfor-comparison-benchmark"
BIN="$OUT/bin/bench_$NAME"
cp "$SRC" "$BIN"

# ---- verify the width the autovectorizer gave this kernel ----------------
# Disassemble only the kernel's address range: from its symbol to the next.
ADDR=$(nm -C "$BIN" | grep -F "$KERNEL" | awk '{print $1}' | head -1)
{
echo "point=$NAME level=$LEVEL opt=$OPT prefer=$PVW"
echo "sha256: $(sha256sum "$BIN" | cut -d' ' -f1)"
echo "kernel: $KERNEL"
echo "kernel addr: 0x$ADDR"
if [ -n "$ADDR" ]; then
# Boundaries come from objdump's own '<addr> <symbol>:' labels, not from
# sorting nm addresses. The nm approach silently produced an empty range
# for O2_256 -- kernel_O2_256.asm came out 0 bytes and the point then
# reported "zmm: 0 ymm: 0", which reads as a failed vectorization rather
# than a failed extraction. nm lists symbols from the symbol table in an
# order that need not bracket the function body (aliases, local symbols
# and ifunc resolvers all land in between), so "next address after this
# one" is not the function's end. objdump cannot disagree with itself.
objdump -dC --no-show-raw-insn "$BIN" 2>/dev/null \
| awk -v s="$KERNEL" '
/^[0-9a-f]+ </ { inside = index($0, s) > 0 }
inside' > "$OUT/kernel_$NAME.asm"
echo "kernel insns: $(grep -cE '^\s+[0-9a-f]+:' "$OUT/kernel_$NAME.asm")"
echo "IN-KERNEL vector register usage:"
for R in xmm ymm zmm; do
echo " $R: $(grep -c "%$R" "$OUT/kernel_$NAME.asm")"
done
echo "IN-KERNEL notable ops:"
# v?-prefixed: a 128-bit SSE build emits non-VEX psrld/movdqa, so
# VEX-only patterns reported zero shifts and zero stack traffic for every
# 128-bit point -- which made the narrow builds look spill-free when
# O3_128 in fact had the most stack traffic in the matrix.
for OP in 'v?psrld' 'v?pslld' 'vpsrlvd' 'vpsllvd' 'v?pand' 'v?por' \
'v?movdqu' 'v?movdqa' 'vpgatherdd' 'vpermd' 'v?pshufb' \
'vpbroadcastd' 'vzeroupper'; do
C=$(grep -cE "^\s+[0-9a-f]+:\s+$OP" "$OUT/kernel_$NAME.asm")
[ "$C" -gt 0 ] && echo " $OP: $C"
done
# Vector moves with a stack-relative operand. Reported as traffic, not
# spills: a genuine stack scratch buffer matches this too, which is
# exactly what the pre-fix kFileOrder path was (a 4 KiB grid, ~1984
# write-only stores with zero matching reloads).
echo " stack vector traffic: $(grep -cE '^\s+[0-9a-f]+:\s+v?mov(dqu|dqa|ups|aps).*\((%rsp|%rbp)' "$OUT/kernel_$NAME.asm")"
else
echo "!! kernel symbol not found -- cannot verify width"
fi
} > "$OUT/verify_$NAME.txt" 2>&1
cat "$OUT/verify_$NAME.txt"

# A width that did not materialize is reported, never silently averaged in.
# Only the 256-bit points can miss: they are built at AVX2, where the compiler
# may still choose xmm. The 128-bit points are built at SSE4_2, where __AVX2__
# is undefined and ymm cannot be emitted at all.
case "$PVW" in
256) grep -qE '^ ymm: [1-9]' "$OUT/verify_$NAME.txt" \
|| echo "!! WARNING $NAME: no ymm IN KERNEL -- this is NOT a 256-bit point" ;;
esac
echo
done

echo "=== builds complete. binaries in $OUT/bin/ ==="
sha256sum "$OUT"/bin/* 2>/dev/null
echo
echo "Distinct binaries (if two points share a sha256, they are the SAME build"
echo "and any difference between their timings is noise):"
sha256sum "$OUT"/bin/* 2>/dev/null | awk '{print $1}' | sort -u | wc -l
echo
echo "Now time them, when the box is quiet:"
echo " BIN_DIR=$OUT/bin ./ab_compare.sh \\"
echo " bench_O2_128:SSE4_2 bench_O2_256:AVX2 \\"
echo " bench_O3_128:SSE4_2 bench_O3_256:AVX2"
Loading
Loading