Learning Platform
Глоссарий Troubleshooting
Урок 09.03 · 35 мин
Продвинутый
Frame of ReferenceDelta EncodingBit-PackingDELTA_BINARY_PACKEDRLEv2 DeltaFastLanesInteger Compression

Целочисленные кодировки: FOR, Delta, Bit-Packing

В Модуле 01 мы видели delta encoding и bit-packing как отдельные приёмы. В Модуле 02 — как Parquet реализует DELTA_BINARY_PACKED. В Модуле 03 — как ORC RLEv2 Delta использует second-order дельты.

Теперь посмотрим глубже: три фундаментальных техники кодирования целых чисел — Frame-of-Reference (FOR), Delta и Bit-Packing — и как каждый формат комбинирует их в свои собственные конвейеры. Ключевое отличие от Модуля 01: мы разберём bit-level layout и увидим, почему порядок упаковки бит влияет на скорость декодирования в 10 раз.

Frame-of-Reference (FOR): base + offset

FOR — самая простая из трёх техник, но основа для всех остальных. Идея: если все значения в блоке лежат в узком диапазоне, вычтем base (минимум) и будем хранить только offsets:

Frame-of-Reference: вычитание base

Без FOR (raw int64)

Колонка price (центы)Цены товаров в центах. Диапазон 9990–10050. Каждое значение — 64 бита, хотя реально используются только ~14 бит из 64.
8 значений × 64 бит = 512 бит

С FOR

Base = 9990 (min)Минимальное значение в блоке. Хранится один раз как 64-bit integer. Все остальные значения — смещения от этой базы.
Offsets (7 бит каждый)Offsets = value - base. Максимальный offset: 10050 - 9990 = 60. ceil(log₂(61)) = 6 бит. С запасом — 7 бит на offset.
64 бит (base) + 8 × 7 бит = 120 бит (4.3x экономия)

FOR эффективен когда range ≪ max_value. Для цен 9990–10050: range = 60, max_value = 10050. Нужно 7 бит вместо 14 (для raw) или 64 (для int64). Экономия зависит от соотношения ceil(log₂(range)) / raw_bits.

Кто использует FOR

ФорматГде FORОсобенности
DuckDBОсновная кодировка для integerFOR + bit-packing, SIMD-оптимизированное декодирование
BtrBlocksОдин из 8 кандидатов cascadeFOR после sampling, может каскадироваться с другими
FastLanesFOR как часть expression encodingFOR + transposed bit-packing для автовекторизации
ORCPatched Base (RLEv2)FOR + patches для outliers — уникальная комбинация
Parquet прямого FORИспользует delta вместо FOR — другая философия
NOTE

Parquet не имеет отдельного FOR-кодирования. Вместо этого DELTA_BINARY_PACKED фактически включает FOR-подобное поведение: min_delta вычитается из всех дельт в блоке — это FOR, применённый к дельтам, а не к значениям.

Delta-варианты: три уровня глубины

Delta encoding хранит разности вместо абсолютных значений. Но есть три варианта, отличающихся глубиной:

Три уровня delta encoding

Simple Delta (Δ)

Уровень 1Хранит разности соседних значений: Δᵢ = Xᵢ - Xᵢ₋₁. Первое значение — base. Эффективно для монотонных данных с переменным шагом.

Block Delta + FOR

Уровень 2 (Parquet)Дельты группируются в блоки. Внутри блока вычитается min_delta (FOR на дельтах). Остаток — маленькие числа для bit-packing. Parquet DELTA_BINARY_PACKED.

Delta-of-Delta (ΔΔ)

Уровень 3 (ORC)Дельты дельт: ΔΔᵢ = Δᵢ - Δᵢ₋₁. Для данных с почти постоянным шагом (timestamps) — ΔΔ ≈ 0. ORC RLEv2 Delta использует этот подход.
Когда использовать какой уровеньКаждый уровень delta добавляет один шаг decode, но снижает энтропию. Для строго монотонных данных ΔΔ ≈ 0 → максимальная компрессия. Для хаотичных — ΔΔ не лучше Δ.

Кросс-форматное сравнение integer pipelines

Каждый формат строит свой конвейер из FOR, Delta и Bit-Packing. Вот как они обрабатывают одни и те же данные — отсортированные timestamps с шагом ~60 секунд:

Один поток timestamps — четыре конвейера
Входные данные1M unix-timestamps с шагом ~60 секунд ± jitter 0–5 секунд. Типичный event log. int64.

Parquet DELTA_BINARY_PACKED

Δ = Xᵢ - Xᵢ₋₁ → [62, 59, 62, 57, …]

Шаг 1: вычислить дельты. Δ = [62, 59, 62, 57, ...]. Диапазон дельт: 55–65.

Блоки по 128: min_Δ=55, offsets=[7,4,7,2,…]

Шаг 2: разбить на блоки по 128 значений. В каждом блоке: min_delta ≈ 55, вычесть из всех. Остатки: 0–10.

Bit-pack: 4 бит/offset → ~4 бит/значение

Шаг 3: bit-pack offsets. Range 0–10 → 4 бита. 128 offsets × 4 бит = 64 байта на блок. + header: 1 base + 1 min_delta + 4 bit-widths.

ORC RLEv2 Delta

base=1704067200, base_Δ=62

Шаг 1: base value = первое значение. Base delta = первая дельта. ΔΔᵢ = Δᵢ - Δᵢ₋₁.

ΔΔ = [-3, +3, -5, …] → zigzag

Шаг 2: вычислить дельты дельт (second-order). ΔΔ = [-3, +3, -5, ...]. Zigzag: [5, 6, 9, ...].

Bit-pack ΔΔ: 4 бит/значение

Шаг 3: bit-pack zigzag-encoded ΔΔ. Range ≈ ±5 → zigzag 0–10 → 4 бит. Аналогичная эффективность, но decode на один шаг дороже.

DuckDB FOR + BitPacking

Delta → Δ = [62, 59, 62, 57, …]

Шаг 1: DuckDB сначала применяет delta (если данные монотонные). Δ = [62, 59, 62, 57, ...].

FOR: base=55, offsets=[7, 4, 7, 2, …]

Шаг 2: FOR на дельтах. base = min(Δ) = 55. Offsets = [7, 4, 7, 2, ...]. Range 0–10.

SIMD bit-pack: 4 бит/значение, vectorized decode

Шаг 3: SIMD-optimized bit-packing. DuckDB выравнивает bit-width до степеней 2 (4 бит). Decode: одна SIMD-операция распаковывает 64 значения.

FastLanes FOR + Transposed BP

FOR на блоке 1024 значений

Шаг 1: FOR — вычесть base из блока 1024 значений. Дельты не нужны, если FOR достаточен.

Transposed Layout (1024-bit virtual SIMD)

Шаг 2: Transposed Layout — переупорядочить значения для SIMD параллелизма. Индекс '04261537' чередует значения из разных SIMD-лейнов.

Data-parallel bit-pack: > 100B int/sec decode

Шаг 3: Data-parallel bit-packing — каждый SIMD-лейн работает с непрерывным блоком бит. Автовекторизуется на любом CPU (SSE/AVX/NEON/WASM). >100B int/sec decode.

Все четыре конвейера достигают ~4 бит/значение на этих данных. Но скорость декодирования различается на порядок — из-за bit-packing layout.

Bit-Packing: scalar vs transposed layout

Bit-packing — финальный шаг в каждом конвейере. Он определяет, как маленькие числа (offsets, дельты) упаковываются в байты. И здесь layout — порядок упаковки бит — критически влияет на производительность:

Scalar vs Transposed bit-packing layout

Scalar layout (Parquet, ORC)

Последовательная упаковкаЗначения упакованы последовательно: биты значения 0, затем биты значения 1, затем значения 2... Простой, но не SIMD-friendly: значение может пересекать границы машинного слова.
Проблема: cross-word extractionПри 3-бит упаковке значение 2 (111|010) пересекает границу байтов 0 и 1. Распаковка требует: загрузить 2 байта, shift, mask. Не векторизуется без SIMD intrinsics.

Transposed layout (FastLanes)

Interleaved по SIMD-лейнамЗначения переупорядочены так, что bit 0 всех значений в одном машинном слове, bit 1 — в следующем. Каждое SIMD-слово содержит один битовый слой из N значений.
Параллельная распаковкаDecode: 3 SIMD loads (по одному на битовый слой) + 3 shifts + 3 ORs = все 8 значений за 9 операций. Scalar: 8 × (load + shift + mask) = 24 операции. Speedup: 2.5x минимум.

FastLanes transposed layout — главная инновация для скорости декодирования. Обычный (scalar) bit-packing достигает ~5–15 миллиардов int/sec на современном CPU. FastLanes transposed layout — >100 миллиардов int/sec на scalar коде, и ~140 миллиардов с AVX-512. Разница — в автовекторизации: компилятор видит простые побитовые операции над массивами и генерирует SIMD-код автоматически.

Patched Base: FOR с обработкой outliers

ORC предлагает уникальную комбинацию — Patched Base (RLEv2 подкодировка 10). Это FOR, который обрабатывает outliers отдельно, вместо того чтобы расширять bit-width для всех значений:

Patched Base: FOR + patches для outliers
Проблема: один outlier портит весь блокБез Patched Base: один outlier (999999 при остальных 100–110) заставляет использовать 20-бит width для всех значений. С Patched Base: 4-бит для 90% + patch для outlier.

base=100, reduced_width=3 бит

Шаг 1: вычислить base = min(values) = 100. Вычесть: [0, 2, 1, 3, 999899, 0, 4, 2]. Определить 90-й перцентиль bit-width: 3 бита (для 0–4).

Основной: [0,2,1,3, 0*,0,4,2] (3 бит)

Шаг 2: значения, не помещающиеся в reduced_width (999899 > 7), помечаются как patches. На их месте в основном массиве — 0 (placeholder). Patch: (position=4, value=999899).

Patches: [(pos=4, val=999899)]

Шаг 3: patch list хранит позиции и полные значения outliers отдельно. Bit-width patches — шире (20 бит), но их мало (1 из 8). Общий размер: 8×3 + 1×20 = 44 бит vs 8×20 = 160 бит.

Patched Base — уникальная подкодировка ORC, не имеющая аналога в Parquet. Она особенно эффективна для данных с редкими выбросами: sensor data с аномалиями, financial data с extreme values, log data с occasional spikes.

Zigzag: кодирование знаковых чисел

Delta encoding производит знаковые числа (дельты могут быть отрицательными). Стандартное two’s complement представление неэффективно для varint/bit-packing: -1 в int64 = 0xFFFFFFFFFFFFFFFF = 64 бита.

Zigzag-кодирование решает это, отображая маленькие signed числа на маленькие unsigned:

Zigzag: маппинг signed → unsigned для bit-packing
ФормулаZigzag(n) = (n << 1) ^ (n >> 63) для int64. Идея: чередовать положительные и отрицательные: 0, -1, 1, -2, 2, ... → 0, 1, 2, 3, 4, ... Маленькие signed → маленькие unsigned.
Маппинг0→0, -1→1, 1→2, -2→3, 2→4. Дельта ±5 → zigzag ≤ 10 → 4 бит. Без zigzag: ±5 в two's complement → 64 бит (отрицательные числа = все старшие биты установлены).
Где используетсяВезде, где delta может быть отрицательным: ORC RLEv2 Delta, Parquet DELTA_BINARY_PACKED, Protocol Buffers sint32/sint64, DuckDB delta encoding.

Сводная таблица: кто что использует

Кросс-форматное сравнение integer encoding pipelines
Аспект кодирования целых чисел
ParquetApache Parquet: DELTA_BINARY_PACKED (enum 5)
ORCApache ORC: RLEv2 с 4 подкодировками
DuckDBDuckDB: Analyzer выбирает из Delta, FOR, BitPacking, Constant, RLE
FastLanesFastLanes: unified transposed layout для автовекторизации
BtrBlocksBtrBlocks: sampling-based cascading из пула 8 кодировок
FORFrame-of-Reference: вычитание base (минимума блока) и хранение offsets
Внутри deltamin_delta вычитается из дельт в каждом miniblock — это FOR на дельтах, не на значениях.
Patched Basebase value вычитается из всех значений. Outliers — в отдельный patch list.
ОтдельныйFOR как самостоятельная кодировка. base = min(segment), offsets bit-packed.
Core primitiveFOR — базовый примитив. Блок 1024 значений → base + transposed bit-packed offsets.
В каскадеFOR — один из кандидатов. Может быть первым шагом перед другой кодировкой в cascade.
DeltaDelta encoding: хранение разностей Δᵢ = Xᵢ - Xᵢ₋₁
DELTA_BINARY_PACKEDBlock delta: блоки по 128, min_delta, bit-packed offsets. First-order delta + FOR на дельтах.
RLEv2 Delta (11)Second-order: base + base_delta + delta-of-deltas (zigzag). Идеально для постоянного шага.
Delta сегментаПростой delta: первое значение + массив дельт. Может комбинироваться с FOR на дельтах.
Via expressionsFastLanes File Format (2025): expression encoding описывает цепочку преобразований (delta → FOR → BP) как выражение.
Нет отдельногоBtrBlocks не использует delta как отдельную кодировку. FOR + каскад покрывают аналогичные паттерны.
Bit-PackingУпаковка значений в минимальное количество бит
ScalarПоследовательная упаковка: значения идут друг за другом. Decode: shift + mask per value. ~5–15B int/sec.
ScalarПоследовательная упаковка внутри каждой подкодировки. Direct: bit-packed значения. Delta: bit-packed ΔΔ.
SIMD-alignedBit-width выровнен до 1/2/4/8/16/32. SIMD-intrinsics для распаковки. ~20–50B int/sec.
TransposedUnified Transposed Layout: биты переупорядочены для data-parallel decode. Автовекторизуется. >100B int/sec scalar.
SIMDBtrBlocks использует SIMD-оптимизированный bit-packing (AVX2). Платформо-специфичный (x86).
Outlier handlingКак обрабатываются выбросы (значения, не вписывающиеся в основной диапазон)
НетParquet расширяет bit-width для всех значений в блоке. Один outlier = широкий bit-width для всех 128 значений.
Patched BaseOutliers вынесены в patch list. Основная масса — узкий bit-width. Уникальная возможность ORC.
НетDuckDB расширяет bit-width. Но per-segment решение: если outliers только в одном сегменте — остальные не страдают.
Exception listFastLanes File Format поддерживает exceptions — аналог patches. Основные значения + список исключений.
Frequency enc.BtrBlocks Frequency encoding выносит редкие значения отдельно. Не patch, а отдельная кодировка.
Decode скоростьСкорость декодирования на современном CPU (int/sec)
~5–10B/sScalar decode: ~5–10 миллиардов int/sec. Достаточно для Parquet use case (disk I/O — bottleneck, не CPU).
~3–8B/sRLEv2 decode сложнее (4 подкодировки, zigzag). ~3–8B int/sec. Patched Base — самый дорогой.
~20–50B/sSIMD-aligned bit-packing. DuckDB использует intrinsics для AVX2/SSE. ~20–50B int/sec.
>100B/sTransposed layout автовекторизуется без intrinsics. >100B int/sec на scalar, ~140B/s с AVX-512.
~30–60B/sBtrBlocks SIMD (AVX2). Сопоставимо с DuckDB. Cascade decode может быть дороже из-за двух шагов.

Ключевые выводы

  1. FOR, Delta и Bit-Packing — три фундаментальных примитива, которые комбинируются в разном порядке: Parquet = Delta → FOR → scalar BP, ORC = ΔΔ → zigzag → scalar BP, DuckDB = Delta → FOR → SIMD BP, FastLanes = FOR → transposed BP.
  2. Bit-packing layout определяет скорость decode. Scalar (Parquet/ORC): ~5–10B int/sec. Transposed (FastLanes): >100B int/sec. Разница 10x — из-за автовекторизации.
  3. Patched Base (ORC) — единственный outlier-aware примитив среди файловых форматов. Один outlier не разрушает компрессию всего блока.
  4. Zigzag-кодирование обязательно для delta с signed дельтами: превращает -1 из 64-бит числа в 1-бит число. Используется в ORC, Parquet, DuckDB, Protocol Buffers.
  5. Delta-of-delta (ORC) vs Delta + FOR (Parquet) — два подхода к одной задаче. ΔΔ лучше для строго-регулярных данных (timestamps с постоянным шагом). Delta + FOR — для данных с переменным, но ограниченным диапазоном дельт.
  6. FastLanes Unified Transposed Layout — главная инновация в скорости: переупорядочивание бит позволяет компилятору автоматически генерировать SIMD-код без платформо-специфичных intrinsics.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Колонка timestamps: [1000, 1001, 1003, 1004, 1007]. Какой результат после FOR + delta + bit-packing?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6