Skip to content

GH-48701: [C++][Parquet] Add ALPpd encoding - #48345

Open
prtkgaur wants to merge 165 commits into
apache:mainfrom
prtkgaur:gh540-alp-pseudoDecimal-encoding
Open

prtkgaur wants to merge 165 commits into
apache:mainfrom
prtkgaur:gh540-alp-pseudoDecimal-encoding

Conversation

@prtkgaur

@prtkgaur prtkgaur commented Dec 5, 2025 •

Copy link
Copy Markdown

Co-authored-by: Dhirhan Kanesalingam dhirhan17@gmail.com

With help from : @emkornfield, @wgtmac

Rationale for this change

Adaptive Lossless Floating-Point (ALP)
is designed for floating-point data that commonly represents decimal values.
For these workloads, ALP can provide better compression and faster decoding
than general-purpose compression or existing Parquet encodings.

This PR adds ALP support for Parquet FLOAT and DOUBLE columns in the Arrow
C++ implementation.

Specification

What changes are included in this PR?

This PR adds:

  • The core ALP compression and decompression implementation.
  • Sampling logic for selecting encoding parameters.
  • Page metadata serialization, validation, and decoding.
  • Parquet encoders and decoders for FLOAT and DOUBLE.
  • Incremental decoding of ALP pages one vector at a time.
  • CMake and Meson build integration.
  • C++ documentation describing how to use the encoding.

ALP is opt-in on the write path. It is used only when the writer explicitly
selects Encoding::ALP and dictionary encoding is disabled. Arrow does not
currently select ALP automatically based on the input data.

Are these changes tested?

Yes. Test coverage includes:

  • Interoperability with the ALP conformance file in parquet-testing.
  • FLOAT and DOUBLE round trips.
  • Decimal-like, constant, random, and exception-heavy inputs.
  • Empty and all-null pages.
  • Configurable vector sizes.
  • Truncated or malformed page metadata.
  • Invalid offsets, element counts, bit widths, and exception positions.
  • Batched and incremental decoding.

Are there any user-facing changes?

Yes. Arrow C++ can read Parquet pages encoded with ALP. Writers can explicitly
select ALP for supported floating-point columns.

ALP requires reader support and is not selected automatically, so existing
writer behavior remains unchanged unless users opt in.

@github-actions

github-actions Bot commented Dec 5, 2025

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose

Opening GitHub issues ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename the pull request title in the following format?

GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@prtkgaur
prtkgaur force-pushed the gh540-alp-pseudoDecimal-encoding branch 3 times, most recently from 1b78a5c to d563ce0 Compare December 7, 2025 15:46
Comment thread cpp/src/arrow/util/alp/data/floatingpoint_data.tar.gz Outdated
Comment thread cpp/src/parquet/types.h
@alamb

alamb commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Thanks @prtkgaur -- it is super exciting to see this movement.

Unfortunately, I am not familiar with the C/C++ codebase to give this a realistic review.

I started the CI checks on this PR and had some comments about the testing.

@prtkgaur prtkgaur changed the title [Gh540] Add ALPpd encoding to parquet [Gh539] Add ALPpd encoding to parquet Dec 8, 2025
Comment thread cpp/src/arrow/util/alp/data/floatingpoint_data.tar.gz Outdated
std::string tarball_path = std::string(__FILE__);
tarball_path = tarball_path.substr(0, tarball_path.find_last_of("/\\"));
tarball_path = tarball_path.substr(0, tarball_path.find_last_of("/\\"));
tarball_path += "/arrow/cpp/submodules/parquet-testing/data/floatingpoint_data.tar.gz";

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Reviewer the data sits in the parquet-testing submodule
apache/parquet-testing#100

Comment thread cpp/src/arrow/util/small_vector.h Outdated

// Unsafe resize without initialization - use only when you will immediately
// overwrite the memory (e.g., before memcpy). Only safe for POD types.
void UnsafeResize(size_t n) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this over resize gave us around 2-3% performance improvement

@prtkgaur prtkgaur changed the title [Gh539] Add ALPpd encoding to parquet [Gh539][Encoding] Add ALPpd encoding to parquet Dec 8, 2025
@prtkgaur prtkgaur changed the title [Gh539][Encoding] Add ALPpd encoding to parquet [Gh-539][Encoding] Add ALPpd encoding to parquet Dec 8, 2025
@prtkgaur
prtkgaur force-pushed the gh540-alp-pseudoDecimal-encoding branch from 0c035b7 to 1cb0852 Compare December 8, 2025 23:48
@prtkgaur prtkgaur changed the title [Gh-539][Encoding] Add ALPpd encoding to parquet [Gh-539][ParquetEncoding][c++] Add ALPpd encoding to parquet Dec 9, 2025
@emkornfield

Copy link
Copy Markdown
Contributor

Talked offline and wanted to capture notes on high-level changes:

  1. For headers, lets try to reduce duplication with values already in the parquet header.
  2. For remaining items in headers, lets try to be parsimonious with values (i.e. 4 bytes is probably overkill for enums)
  3. Naming convention on files is off (use snake_case).
  4. Given description of ALP, we probably want a top level encoding enum value for the 2 different modes of ALP.

@prtkgaur
prtkgaur force-pushed the gh540-alp-pseudoDecimal-encoding branch from 35f1ad7 to 0908342 Compare December 15, 2025 21:28
@prtkgaur

Copy link
Copy Markdown
Author

Talked offline and wanted to capture notes on high-level changes:

  1. For headers, lets try to reduce duplication with values already in the parquet header.
  2. For remaining items in headers, lets try to be parsimonious with values (i.e. 4 bytes is probably overkill for enums)
  3. Naming convention on files is off (use snake_case).
  4. Given description of ALP, we probably want a top level encoding enum value for the 2 different modes of ALP.

Thanks for the feedback @emkornfield. We have addressed

  1. Reduce duplication of fields between page header and alp header
  2. Other fields have been updated to use 1 byte. Header is now just 8 bytes compared to 40 bytes earlier.
  3. Naming of files has been updated.
  4. We do have the top level enums describing the mode and layout structure.
    enum class AlpBitPackLayout { kNormal }; and enum class AlpMode { kAlp };

@prtkgaur prtkgaur changed the title [Gh-539][ParquetEncoding][c++] Add ALPpd encoding to parquet [Gh-48701][ParquetEncoding][c++] Add ALPpd encoding to parquet Dec 31, 2025
@kou kou changed the title [Gh-48701][ParquetEncoding][c++] Add ALPpd encoding to parquet GH-48701: [C++][Parquet] Add ALPpd encoding Jan 1, 2026
@github-actions

github-actions Bot commented Jan 1, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #48701 has been automatically assigned in GitHub to PR creator.

Comment thread cpp/src/parquet/decoder.cc Outdated

// Slow path: partial read - decode to intermediate buffer
// ALP Bit unpacker needs batches of 64
if (needs_decode_) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO(prateek) : check with Antoine and other reviewers if there is a way to relax this constraint. Though this has negligible impact on performance.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no action needed

Comment thread cpp/submodules/parquet-testing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check cpp/src/arrow/util/alp/ALP_Encoding_Specification_terse.md for a more terse spec of the encoding.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this file will be removed once the spec in parquet format repository is merged.


## 2. Data Layout

ALP encoding consists of a page-level header followed by one or more encoded vectors. Each vector contains up to 1024 elements.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 1024 with the constant specified in AlpConstant file.

@prtkgaur
prtkgaur force-pushed the gh540-alp-pseudoDecimal-encoding branch from 1b08599 to f5f5011 Compare January 12, 2026 16:00
@prtkgaur
prtkgaur marked this pull request as ready for review January 13, 2026 18:03
@prtkgaur
prtkgaur requested a review from wgtmac as a code owner January 13, 2026 18:03
sfc-gh-pgaur and others added 19 commits October 7, 2026 14:44
clang cannot attach a tparam to a member template declared in its class,
so the doc build rejects it.
The marker on a class does not reach a member template, so the Windows
link could not find the instantiations.
CompressVector takes int32_t, so passing a size_t narrows and clang
rejects it with -Wshorten-64-to-32 under -Werror.
Their definitions live in implementation files, so a separate test
executable cannot reach them across a shared library without the marker.
The index is 64-bit, so MSVC warns that a 32-bit shift may have been
meant as 64-bit, and the CI build treats that as an error.
arrow_reader_writer_test.cc has a DoRoundtrip with defaulted trailing
parameters, so a four-argument call matched both helpers once a unity
build compiled the two files together.
AlpEncodedVectorInfo is exported, so binding a reference to its static
constexpr kStoredSize needs an out-of-line definition the library does
not provide, and the Windows GCC link fails. GetStoredSize returns it by
value. The FOR info class is a template and is instantiated locally, so
it is unaffected.
An empty vector's data() may be null, and memcpy and memset must not be
passed a null pointer even for a zero length. A vector with no
exceptions, or with bit width zero, reached that case, which the
sanitizer build reports as fatal. The encoded form and decoded values
are unchanged.
The bounds admitted the largest float below 2^31 and the largest double
below 2^63. Fast rounding can carry a value up one ulp, to exactly 2^31
or 2^63, which the integer types cannot hold. Those two values now
become exceptions and still round-trip exactly.
The ALP conformance file is already on parquet-testing main, so Arrow
picks it up on any routine bump. The tests skip until the pin moves.
The pipeline flowchart in alp_internal.h becomes a paragraph, the page
layout is drawn once instead of three times, subscripts and arrows are
spelled in ASCII, and the test section headings use Arrow's single rule.
Comments only.
Some comments described what the code used to do rather than what the
test pins, such as the misaligned exception arrays that LoadView copies
into aligned storage. Others restated the code below them. Comments
only.
Split the ALP implementation into constants, metadata, compression,
sampler, and codec units. Simplify the internal APIs and update the
CMake/Meson build integration.

Remove the separate writer opt-in flag. ALP is selected explicitly with
Encoding::ALP while dictionary encoding is disabled.

Harden encoding and decoding:
- validate page headers, vector metadata, offset chains, bit widths,
  element counts, and exception positions;
- reject pages that leave ALP values unconsumed after all levels are read;
- reuse pool-backed scratch buffers and cache partially decoded vectors;
- use typed power-of-ten constants for exact decode semantics;
- emit a valid header-only page for all-null input.

Rework the ALP tests around the production APIs, remove redundant cases,
and add malformed-input, boundary, exception, and all-null coverage.
Move the end-to-end tests to arrow_encoding_test.cc and enable real
parquet-testing interoperability coverage. Update the submodule pin,
benchmarks, and C++ documentation.
The layout tables for AlpInfo, AlpForInfo and the serialized vector were
lost when alp_internal.h was split. They now sit next to the classes
they describe. The old ForInfo table gave 6 and 10 bytes; the real sizes
are 5 and 9, a frame of reference plus a bit width with no padding.
AlpEncoder's static_assert fires only when the template is instantiated,
and for an unsupported physical type the factories throw before that, so
test the throw for the six other types. Also restores the note about
falling back to PLAIN, since ALP expands a few of the paper's datasets
and the writer never declines it.
No write path selects ALP, so a column carries it only where encoding()
names it.
Move the decode target restriction from requires clauses to static
assertions, so an invalid target stays a compile-time error without the
constraint appearing in exported symbol names. GCC can then match the
explicit instantiations, and Clang shared builds resolve the same
symbols.
@prtkgaur
prtkgaur force-pushed the gh540-alp-pseudoDecimal-encoding branch from 619c39b to 2fb0c5f Compare October 7, 2026 16:03
@prtkgaur

prtkgaur commented Oct 7, 2026

Copy link
Copy Markdown
Author

Since this is now approved by @wgtmac are there any concerns if we merge it? If not, I propose we do so tomorrow @prtkgaur it looks like there is a conflict too

Done. The new failure looks unrelated to my change.

    @        0x110aafac3  absl::lts_20250127::log_internal::LogMessage::PrepareToDie()
    @        0x110aaf3e8  absl::lts_20250127::log_internal::LogMessage::SendToLog()
    @        0x110aaea47  absl::lts_20250127::log_internal::LogMessage::Flush()
    @        0x110aafd15  absl::lts_20250127::log_internal::LogMessageFatal::~LogMessageFatal()
    @        0x110aafd45  absl::lts_20250127::log_internal::LogMessageFatal::~LogMessageFatal()
    @        0x10f819561  grpc::internal::CallOpSet<>::ContinueFillOpsAfterInterception()
    @        0x10034e2be  grpc::internal::InterceptorBatchMethodsImpl::ProceedClient()

@wgtmac

wgtmac commented Oct 8, 2026

Copy link
Copy Markdown
Member

@github-actions crossbow submit -g cpp

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

Revision: 2fb0c5f

Submitted crossbow builds: ursacomputing/crossbow @ actions-1ee1f44348

Task Status
example-cpp-minimal-build-static GitHub Actions
example-cpp-minimal-build-static-system-dependency GitHub Actions
example-cpp-tutorial GitHub Actions
test-build-cpp-fuzz GitHub Actions
test-conda-cpp GitHub Actions
test-conda-cpp-valgrind GitHub Actions
test-debian-13-cpp-amd64 GitHub Actions
test-debian-13-cpp-i386 GitHub Actions
test-debian-experimental-cpp-gcc-15 GitHub Actions
test-fedora-42-cpp GitHub Actions
test-ubuntu-22.04-cpp GitHub Actions
test-ubuntu-22.04-cpp-bundled GitHub Actions
test-ubuntu-22.04-cpp-emscripten GitHub Actions
test-ubuntu-22.04-cpp-no-threading GitHub Actions
test-ubuntu-24.04-cpp GitHub Actions
test-ubuntu-24.04-cpp-gcc-13-bundled GitHub Actions
test-ubuntu-24.04-cpp-gcc-14 GitHub Actions
test-ubuntu-24.04-cpp-minimal-with-formats GitHub Actions
test-ubuntu-24.04-cpp-thread-sanitizer GitHub Actions

@wgtmac

wgtmac commented Oct 8, 2026

Copy link
Copy Markdown
Member

The two failed crossbow builds are related. Could you please fix them @prtkgaur?

kStoredSize is int64_t, so subtracting it from a size_t yields int64_t
wherever size_t is 32 bits. Inside the span's braced initializer that
is a narrowing conversion, which broke the i386 and Emscripten builds.
@prtkgaur

prtkgaur commented Oct 8, 2026

Copy link
Copy Markdown
Author

The two failed crossbow builds are related. Could you please fix them @prtkgaur?

Hi @wgtmac took a stab at it. Quick question, should I trigger this myself "@github-actions crossbow submit -g cpp"

@emkornfield

Copy link
Copy Markdown
Contributor

@github-actions crossbow submit -g cpp

@emkornfield

emkornfield commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

@prtkgaur I tried totrigger it but if it still comes up with failures might be worth a try to see if you have permissions.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

Revision: e2275a1

Submitted crossbow builds: ursacomputing/crossbow @ actions-f3bf07b14a

Task Status
example-cpp-minimal-build-static GitHub Actions
example-cpp-minimal-build-static-system-dependency GitHub Actions
example-cpp-tutorial GitHub Actions
test-build-cpp-fuzz GitHub Actions
test-conda-cpp GitHub Actions
test-conda-cpp-valgrind GitHub Actions
test-debian-13-cpp-amd64 GitHub Actions
test-debian-13-cpp-i386 GitHub Actions
test-debian-experimental-cpp-gcc-15 GitHub Actions
test-fedora-42-cpp GitHub Actions
test-ubuntu-22.04-cpp GitHub Actions
test-ubuntu-22.04-cpp-bundled GitHub Actions
test-ubuntu-22.04-cpp-emscripten GitHub Actions
test-ubuntu-22.04-cpp-no-threading GitHub Actions
test-ubuntu-24.04-cpp GitHub Actions
test-ubuntu-24.04-cpp-gcc-13-bundled GitHub Actions
test-ubuntu-24.04-cpp-gcc-14 GitHub Actions
test-ubuntu-24.04-cpp-minimal-with-formats GitHub Actions
test-ubuntu-24.04-cpp-thread-sanitizer GitHub Actions

@prtkgaur

prtkgaur commented Oct 8, 2026

Copy link
Copy Markdown
Author

@prtkgaur I tried totrigger it but if it still comes up with failures might be worth a try to see if you have permissions.

Thanks @emkornfield. Don't see that failure in the latest report.

The only failing thing now

110/110 Test  #81: arrow-s3fs-test ..............................***Timeout 300.04 sec

Is unrelated to this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants