Skip to content

Improve ALP compression ratios for Float32 data#111627

Open
rienath wants to merge 9 commits into
masterfrom
alp-float32-promoted-arithmetic
Open

Improve ALP compression ratios for Float32 data#111627
rienath wants to merge 9 commits into
masterfrom
alp-float32-promoted-arithmetic

Conversation

@rienath

@rienath rienath commented Jul 23, 2026

Copy link
Copy Markdown
Member

ALP(STD) compresses a floating-point column by turning each value into an integer. Value n is scaled as d = round(n * 10^e * 10^-f), for some whole numbers e and f s.t. f ≤ e. If scaling back as d * 10^f * 10^-e reproduces the exact bits of n, the integer is stored bit-packed. Otherwise n is stored verbatim as an exception.

In native Float32 arithmetic from ALP reference implementation, the scaling constant fl32(10^-e) is inexact enough that many decimal values (e.g. 0.05f) fail the bit-exact round-trip verification and are stored as expensive exceptions (example). Promoting the scaling arithmetic to Float64 makes every Float32 nearest to a decimal of up to 9 fractional digits round-trip bit-exactly. This doesn't have a noticeable performance impact, but quite a noticeable compression ratio change (i.e. 2x smaller l_discount on TPC-H SF1).

Also simplifies the code now that the Float32 constant set is gone.

Columns written by earlier versions may decode values 1 ulp away. This is intentional, the codec is experimental.

Changelog category (leave one):

  • Backward Incompatible Change

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

The experimental ALP codec now performs Float32 scaling arithmetic in Float64, which eliminates exceptions on decimal data and significantly improves compression ratios.

@clickhouse-gh

clickhouse-gh Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [47ee9f7]


AI Review

Summary

This PR switches ALP(STD) Float32 scaling arithmetic to Float64 and adds a stateless compression-ratio regression test. The compression-ratio improvement looks plausible, but the current implementation changes how existing version-1 ALP(STD) payloads are decoded, so an upgrade can silently return different Float32 bits for data that was already written.

Findings

❌ Blockers

  • [src/Compression/CompressionCodecALP.cpp:302] The Float32 decode path now reconstructs values with Float64 arithmetic, but the stream header is still emitted and accepted as version 1. That breaks the codec's lossless/backward-read contract for existing parts: values that were encodable under the old Float32 math can decode 1 ULP away under the new code. A concrete example is -2000000.0f, which old ALP(STD) can store with (e=7, f=4, d=-1999999872) and read back exactly, while the new decoder reconstructs -1999999.875f from the same payload.
    Suggested fix: bump ALP_CODEC_VERSION for the new Float32 arithmetic, keep the legacy Float32 decode path for version 1, and only use the promoted Float64 path for newly written streams.
Tests
  • ⚠️ Add a focused compatibility fixture for a pre-change version-1 ALP(STD) Float32 payload once the version split is in place, so the decoder proves that old parts still round-trip to the original bits.
Final Verdict
  • Status: ⚠️ Request changes

@clickhouse-gh clickhouse-gh Bot added the pr-backward-incompatible Pull request with backwards incompatible changes label Jul 23, 2026
T value_dec = value_float * ALPFloatTraits<T>::EXPONENTS[fraction] * ALPFloatTraits<T>::FRACTIONS[exponent];
return value_dec;
/// It's important to keep two multiplication steps as float multiplication is not associative.
Float64 value_dec = static_cast<Float64>(value) * EXPONENTS[fraction] * FRACTIONS[exponent];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This changes the Float32 ALP(STD) decode arithmetic for the existing version-1 bitstream, but the stream header still stays at ALP_CODEC_VERSION = 1. That means already-written parts can silently decode to different bits after upgrade: for example, under the old math -2000000.0f is a valid encoded value with (e=7, f=4, d=-1999999872) and round-trips exactly, while this decoder reconstructs -1999999.875f from the same payload. We need a new codec version (or another explicit format flag) so doDecompressData can keep the legacy Float32 path for old data and use the promoted Float64 path only for newly written streams.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No need to keep legacy path on experimental feature

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

Labels

pr-backward-incompatible Pull request with backwards incompatible changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant