Skip to content

Commit 282bb2d

Browse files
committed
Modified and reimplemented FastLanes library in C
There is a background and intro document in src/fl/README.md The short version is: this change brings a C implementation of the FastLanes library to Timescale DB. The original idea is from 'The FastLanes Compression Layout: Decoding >100 Billion Integers per Second with Scalar Code' by Azim Afroozeh and Peter Boncz (https://doi.org/10.14778/3598581.3598587) Our version departs from the original ideas in a few ways: - instead of having a single 1024 bit virtual register, I introduce multiple smaller register widths, that I call 'tiered FL', the widths are 8, 16, 32, 64, 128 and 256 - we do not have 1024 bit virtual registers - our implementation relies on C macros, instead of C++ templates and code generation The src/fl/README.md file provides more details about the library.
1 parent 14583bd commit 282bb2d

30 files changed

Lines changed: 3244 additions & 3 deletions

coverage/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ else()
7272
--rc
7373
lcov_branch_coverage=1
7474
--rc
75-
"lcov_excl_br_line=Assert\\(|Ensure\\(|ereport\\("
75+
"lcov_excl_br_line=TestAssert(Int64Eq|True)\\(|Assert\\(|Ensure\\(|ereport\\("
7676
--no-external
7777
--base-directory
7878
${CMAKE_SOURCE_DIR}

tsl/src/compression/algorithms/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ set(SOURCES
88
${CMAKE_CURRENT_SOURCE_DIR}/null.c
99
${CMAKE_CURRENT_SOURCE_DIR}/uuid_compress.c)
1010
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})
11+
12+
add_subdirectory(fastlanes)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
set(SOURCES
2+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor.c
3+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_8.c
4+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_16.c
5+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_32.c
6+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_64.c
7+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_128.c
8+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor_256.c
9+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack.c
10+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_8.c
11+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_16.c
12+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_32.c
13+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_64.c
14+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_128.c
15+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack_256.c
16+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_sizing.c)
17+
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})

tsl/src/compression/algorithms/fastlanes/README.md

Lines changed: 296 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* This file and its contents are licensed under the Timescale License.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-TIMESCALE for a copy of the license.
5+
*/
6+
7+
/*
8+
* fastlanes/fastlanes.h -- public interface for the FastLanes pack/unpack
9+
* layer.
10+
*
11+
* This is the ONLY header callers should include. All functions are
12+
* declared here; their definitions live in fastlanes_sizing.c
13+
* (tier selection + sizing API), fastlanes_pack.c (plain pack and
14+
* unpack), and fastlanes_ffor.c (FFOR pack and unpack).
15+
*
16+
* Conventions:
17+
*
18+
* - 'N' is the number of values to pack (0..256).
19+
* - 'T' is the element bit width (8, 16, 32, or 64), expressed
20+
* via fl_elem_width_t.
21+
* - 'W' is the actual bit width of the packed elements (0..T).
22+
*
23+
* See README.md for the full caller contract: alignment and
24+
* sizing rules.
25+
*/
26+
#pragma once
27+
28+
#include <postgres.h>
29+
30+
#include "fastlanes_types.h" /* fl_elem_width_t, fl_tier_width_t */
31+
32+
/*
33+
* Tier selection.
34+
*
35+
* | N range | T = 8 | T = 16 | T = 32 | T = 64 |
36+
* | 0..8 | FL8 | FL16 | FL32 | FL128 |
37+
* | 9..16 | FL16 | FL16 | FL32 | FL128 |
38+
* | 17..32 | FL32 | FL32 | FL32 | FL128 |
39+
* | 33..64 | FL64 | FL64 | FL64 | FL128 |
40+
* | 65..128 | FL128 | FL128 | FL128 | FL128 |
41+
* | 129..256 | FL256 | FL256 | FL256 | FL256 |
42+
*/
43+
extern fl_tier_width_t fl_tier_select(uint32 n, fl_elem_width_t t);
44+
45+
/* Required size for the tier (selected by fl_tier_select(N, T)).
46+
* Determines the sizes of pack output and unpack input buffers exactly.
47+
*/
48+
extern size_t fl_required_bytes(uint32 n, uint8 w, fl_elem_width_t t);
49+
50+
/* Bytes the encoded output carries for N elements (<= fl_required_bytes).
51+
* Matches the return of fl_pack / fl_pack_ffor; useful for sizing the
52+
* truncated prefix without running the encoder. */
53+
extern size_t fl_result_bytes(uint32 n, uint8 w, fl_elem_width_t t);
54+
55+
/* Required alignment for the _packed_ input and output buffers.
56+
* Recommended alignment for the input and output _values_.
57+
*/
58+
extern size_t fl_alignment(uint32 n, fl_elem_width_t t);
59+
60+
/* Number of input ELEMENTS the kernel reads -- callers must provide
61+
* at least this many readable elements at `values` (positions past N
62+
* are read but only the [0..N) outputs are meaningful). */
63+
extern uint32 fl_input_count(uint32 n, fl_elem_width_t t);
64+
65+
/* Byte size of the input buffer (= fl_input_count() * t / 8). */
66+
extern size_t fl_input_bytes(uint32 n, fl_elem_width_t t);
67+
68+
/*
69+
* Plain pack / unpack.
70+
*
71+
* fl_pack returns the truncated byte count (<= fl_required_bytes) --
72+
* the size of the `packed` data in bytes. The kernel reads
73+
* fl_input_count() elements from `values` and writes fl_required_bytes
74+
* to `packed`.
75+
*
76+
* fl_unpack reverses the operation. The caller MUST pre-zero
77+
* packed[truncated_bytes..alloc_bytes) before calling unpack.
78+
*
79+
* W = 0 (constant block) is a special case: fl_pack returns 0 and
80+
* writes nothing; fl_unpack fills outputs [0..N) with 0.
81+
*/
82+
extern size_t fl_pack(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t);
83+
extern void fl_unpack(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t);
84+
85+
/*
86+
* FFOR (Frame Of Reference) variants.
87+
*
88+
* `base` is subtracted from each value before packing and added back
89+
* after unpacking. Equivalent to packing `values - base` with the
90+
* plain functions, but combined into the same loop.
91+
*
92+
* W = 0: fl_pack_ffor returns 0 and writes nothing; fl_unpack_ffor
93+
* fills outputs [0..N) with `base`.
94+
*/
95+
extern size_t fl_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
96+
uint64 base);
97+
extern void fl_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
98+
uint64 base);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* This file and its contents are licensed under the Timescale License.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-TIMESCALE for a copy of the license.
5+
*/
6+
7+
/*
8+
* fastlanes/fastlanes_common.h -- shared macros for the FL tier headers.
9+
*
10+
* Two pieces every tier needs:
11+
*
12+
* 1. Bit-position helpers (FL_SHIFT, FL_WORD, FL_NWORD, FL_SPLITS,
13+
* FL_REM, FL_CUR, FL_CUR_SAFE) -- pure (row, W, T) arithmetic.
14+
*
15+
* 2. Cascading row-enumeration macros (FL_P2..FL_P64, FL_U2..FL_U64,
16+
* FL_FP2..FL_FP64, FL_FU2..FL_FU64) and their typed "ALL"
17+
* wrappers. Each tier binds FL_P1 / FL_U1 / FL_FP1 / FL_FU1 to
18+
* its own row body before invoking the per-W generator; C
19+
* macros are late-bound so the same cascade expands tier-
20+
* specific code under each binding.
21+
*/
22+
23+
#pragma once
24+
25+
#include "fastlanes_types.h"
26+
27+
/*
28+
* Bit-position helpers -- compile-time when (row, W, T) are constants.
29+
*
30+
* SHIFT bit offset of this row's first bit within its word
31+
* WORD word index containing this row's first bit
32+
* NWORD word index containing the next row's first bit
33+
* SPLITS this row crosses a word boundary
34+
* REM bits spilling into the next word
35+
* CUR bits remaining in the current word
36+
*/
37+
38+
#define FL_SHIFT(row, W, T) (((row) * (W)) % (T))
39+
#define FL_WORD(row, W, T) (((row) * (W)) / (T))
40+
#define FL_NWORD(row, W, T) ((((row) + 1) * (W)) / (T))
41+
#define FL_SPLITS(row, W, T) (FL_NWORD(row, W, T) > FL_WORD(row, W, T))
42+
#define FL_REM(row, W, T) ((((row) + 1) * (W)) % (T))
43+
#define FL_CUR(row, W, T) ((W) -FL_REM(row, W, T))
44+
45+
/* MSVC C4293: keep the intermediate non-negative so the warning checker
46+
* (which runs before constant folding through the mask) sees a small
47+
* positive value. Equivalent to ((W - FL_REM) mod T). */
48+
#define FL_CUR_SAFE(row, W, T) (((W) + (T) -FL_REM(row, W, T)) & ((unsigned) (T) -1u))
49+
50+
/* Doubling macro expansion cascades. */
51+
#define FL_P2(T, S, W, b) FL_P1(T, S, W, (b)) FL_P1(T, S, W, (b) + 1)
52+
#define FL_P4(T, S, W, b) FL_P2(T, S, W, (b)) FL_P2(T, S, W, (b) + 2)
53+
#define FL_P8(T, S, W, b) FL_P4(T, S, W, (b)) FL_P4(T, S, W, (b) + 4)
54+
#define FL_P16(T, S, W, b) FL_P8(T, S, W, (b)) FL_P8(T, S, W, (b) + 8)
55+
#define FL_P32(T, S, W, b) FL_P16(T, S, W, (b)) FL_P16(T, S, W, (b) + 16)
56+
#define FL_P64(T, S, W, b) FL_P32(T, S, W, (b)) FL_P32(T, S, W, (b) + 32)
57+
58+
#define FL_PACK_ALL_8(S, W) FL_P8(8, S, W, 0)
59+
#define FL_PACK_ALL_16(S, W) FL_P16(16, S, W, 0)
60+
#define FL_PACK_ALL_32(S, W) FL_P32(32, S, W, 0)
61+
#define FL_PACK_ALL_64(S, W) FL_P64(64, S, W, 0)
62+
63+
/* Doubling macro expansion cascades. */
64+
#define FL_U2(TYPE, T, S, W, b) FL_U1(TYPE, T, S, W, (b)) FL_U1(TYPE, T, S, W, (b) + 1)
65+
#define FL_U4(TYPE, T, S, W, b) FL_U2(TYPE, T, S, W, (b)) FL_U2(TYPE, T, S, W, (b) + 2)
66+
#define FL_U8(TYPE, T, S, W, b) FL_U4(TYPE, T, S, W, (b)) FL_U4(TYPE, T, S, W, (b) + 4)
67+
#define FL_U16(TYPE, T, S, W, b) FL_U8(TYPE, T, S, W, (b)) FL_U8(TYPE, T, S, W, (b) + 8)
68+
#define FL_U32(TYPE, T, S, W, b) FL_U16(TYPE, T, S, W, (b)) FL_U16(TYPE, T, S, W, (b) + 16)
69+
#define FL_U64(TYPE, T, S, W, b) FL_U32(TYPE, T, S, W, (b)) FL_U32(TYPE, T, S, W, (b) + 32)
70+
71+
#define FL_UNPACK_ALL_8(TYPE, S, W) FL_U8(TYPE, 8, S, W, 0)
72+
#define FL_UNPACK_ALL_16(TYPE, S, W) FL_U16(TYPE, 16, S, W, 0)
73+
#define FL_UNPACK_ALL_32(TYPE, S, W) FL_U32(TYPE, 32, S, W, 0)
74+
#define FL_UNPACK_ALL_64(TYPE, S, W) FL_U64(TYPE, 64, S, W, 0)
75+
76+
/* Doubling macro expansion cascades. */
77+
#define FL_FP2(T, S, W, b) FL_FP1(T, S, W, (b)) FL_FP1(T, S, W, (b) + 1)
78+
#define FL_FP4(T, S, W, b) FL_FP2(T, S, W, (b)) FL_FP2(T, S, W, (b) + 2)
79+
#define FL_FP8(T, S, W, b) FL_FP4(T, S, W, (b)) FL_FP4(T, S, W, (b) + 4)
80+
#define FL_FP16(T, S, W, b) FL_FP8(T, S, W, (b)) FL_FP8(T, S, W, (b) + 8)
81+
#define FL_FP32(T, S, W, b) FL_FP16(T, S, W, (b)) FL_FP16(T, S, W, (b) + 16)
82+
#define FL_FP64(T, S, W, b) FL_FP32(T, S, W, (b)) FL_FP32(T, S, W, (b) + 32)
83+
84+
#define FL_FFOR_PACK_ALL_8(S, W) FL_FP8(8, S, W, 0)
85+
#define FL_FFOR_PACK_ALL_16(S, W) FL_FP16(16, S, W, 0)
86+
#define FL_FFOR_PACK_ALL_32(S, W) FL_FP32(32, S, W, 0)
87+
#define FL_FFOR_PACK_ALL_64(S, W) FL_FP64(64, S, W, 0)
88+
89+
/* Doubling macro expansion cascades. */
90+
#define FL_FU2(TYPE, T, S, W, b) FL_FU1(TYPE, T, S, W, (b)) FL_FU1(TYPE, T, S, W, (b) + 1)
91+
#define FL_FU4(TYPE, T, S, W, b) FL_FU2(TYPE, T, S, W, (b)) FL_FU2(TYPE, T, S, W, (b) + 2)
92+
#define FL_FU8(TYPE, T, S, W, b) FL_FU4(TYPE, T, S, W, (b)) FL_FU4(TYPE, T, S, W, (b) + 4)
93+
#define FL_FU16(TYPE, T, S, W, b) FL_FU8(TYPE, T, S, W, (b)) FL_FU8(TYPE, T, S, W, (b) + 8)
94+
#define FL_FU32(TYPE, T, S, W, b) FL_FU16(TYPE, T, S, W, (b)) FL_FU16(TYPE, T, S, W, (b) + 16)
95+
#define FL_FU64(TYPE, T, S, W, b) FL_FU32(TYPE, T, S, W, (b)) FL_FU32(TYPE, T, S, W, (b) + 32)
96+
97+
#define FL_FFOR_UNPACK_ALL_8(TYPE, S, W) FL_FU8(TYPE, 8, S, W, 0)
98+
#define FL_FFOR_UNPACK_ALL_16(TYPE, S, W) FL_FU16(TYPE, 16, S, W, 0)
99+
#define FL_FFOR_UNPACK_ALL_32(TYPE, S, W) FL_FU32(TYPE, 32, S, W, 0)
100+
#define FL_FFOR_UNPACK_ALL_64(TYPE, S, W) FL_FU64(TYPE, 64, S, W, 0)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* This file and its contents are licensed under the Timescale License.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-TIMESCALE for a copy of the license.
5+
*/
6+
7+
/*
8+
* fastlanes/fastlanes_ffor.c -- public FFOR (Frame Of Reference)
9+
* pack/unpack entry points.
10+
*
11+
* Defines fl_pack_ffor and fl_unpack_ffor as switches
12+
* on fl_tier_select that forward to the matching per-tier static.
13+
*/
14+
15+
#include <postgres.h>
16+
17+
#include "fastlanes.h"
18+
#include "fastlanes_common.h"
19+
20+
/*
21+
* These extern declarations are needed because MSVC runs out of the heap space if all FFOR
22+
* macro expansion happens in the same C file. These functions are intentionally
23+
* not placed in a header file, to discourage direct use.
24+
*/
25+
extern size_t fl8_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
26+
uint64 base);
27+
extern size_t fl16_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
28+
uint64 base);
29+
extern size_t fl32_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
30+
uint64 base);
31+
extern size_t fl64_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
32+
uint64 base);
33+
extern size_t fl128_pack_ffor(const void *values, void *packed, uint32 n, uint8 w,
34+
fl_elem_width_t t, uint64 base);
35+
extern size_t fl256_pack_ffor(const void *values, void *packed, uint32 n, uint8 w,
36+
fl_elem_width_t t, uint64 base);
37+
extern void fl8_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
38+
uint64 base);
39+
extern void fl16_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
40+
uint64 base);
41+
extern void fl32_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
42+
uint64 base);
43+
extern void fl64_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
44+
uint64 base);
45+
extern void fl128_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w,
46+
fl_elem_width_t t, uint64 base);
47+
extern void fl256_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w,
48+
fl_elem_width_t t, uint64 base);
49+
50+
/* Public FFOR pack/unpack
51+
*
52+
* Each per-tier fl{T}_pack_ffor already computes the truncated byte
53+
* count internally, so the dispatcher just forwards the result.
54+
*/
55+
56+
size_t
57+
fl_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t, uint64 base)
58+
{
59+
switch (fl_tier_select(n, t))
60+
{
61+
case FL_TIER_W8:
62+
return fl8_pack_ffor(values, packed, n, w, t, base);
63+
case FL_TIER_W16:
64+
return fl16_pack_ffor(values, packed, n, w, t, base);
65+
case FL_TIER_W32:
66+
return fl32_pack_ffor(values, packed, n, w, t, base);
67+
case FL_TIER_W64:
68+
return fl64_pack_ffor(values, packed, n, w, t, base);
69+
case FL_TIER_W128:
70+
return fl128_pack_ffor(values, packed, n, w, t, base);
71+
case FL_TIER_W256:
72+
return fl256_pack_ffor(values, packed, n, w, t, base);
73+
}
74+
Assert(false);
75+
return 0;
76+
}
77+
78+
void
79+
fl_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t, uint64 base)
80+
{
81+
switch (fl_tier_select(n, t))
82+
{
83+
case FL_TIER_W8:
84+
fl8_unpack_ffor(packed, values, n, w, t, base);
85+
break;
86+
case FL_TIER_W16:
87+
fl16_unpack_ffor(packed, values, n, w, t, base);
88+
break;
89+
case FL_TIER_W32:
90+
fl32_unpack_ffor(packed, values, n, w, t, base);
91+
break;
92+
case FL_TIER_W64:
93+
fl64_unpack_ffor(packed, values, n, w, t, base);
94+
break;
95+
case FL_TIER_W128:
96+
fl128_unpack_ffor(packed, values, n, w, t, base);
97+
break;
98+
case FL_TIER_W256:
99+
fl256_unpack_ffor(packed, values, n, w, t, base);
100+
break;
101+
}
102+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This file and its contents are licensed under the Timescale License.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-TIMESCALE for a copy of the license.
5+
*/
6+
7+
#include <postgres.h>
8+
9+
#include "fastlanes.h"
10+
#include "fastlanes_common.h"
11+
12+
/*
13+
* These extern declarations are needed because MSVC runs out of the heap space if all FFOR
14+
* macro expansion happens in the same C file. The functions are being called from
15+
* `fastlanes_ffor.c`, which dispatches between the tiers. These functions are intentionally
16+
* not placed in a header file, to discourage direct use.
17+
*/
18+
extern size_t fl128_pack_ffor(const void *values, void *packed, uint32 n, uint8 w,
19+
fl_elem_width_t t, uint64 base);
20+
extern void fl128_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w,
21+
fl_elem_width_t t, uint64 base);
22+
23+
/* clang format would reorder the headers which is not desired here */
24+
/* clang-format off */
25+
26+
#define FL_TIER 128
27+
#include "fastlanes_tier_sizing.h"
28+
#include "fastlanes_tier_ffor_impl.h"
29+
#undef FL_TIER
30+
31+
/* clang-format on */
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This file and its contents are licensed under the Timescale License.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-TIMESCALE for a copy of the license.
5+
*/
6+
7+
#include <postgres.h>
8+
9+
#include "fastlanes.h"
10+
#include "fastlanes_common.h"
11+
12+
/*
13+
* These extern declarations are needed because MSVC runs out of the heap space if all FFOR
14+
* macro expansion happens in the same C file. The functions are being called from
15+
* `fastlanes_ffor.c`, which dispatches between the tiers. These functions are intentionally
16+
* not placed in a header file, to discourage direct use.
17+
*/
18+
extern size_t fl16_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
19+
uint64 base);
20+
extern void fl16_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
21+
uint64 base);
22+
23+
/* clang format would reorder the headers which is not desired here */
24+
/* clang-format off */
25+
26+
#define FL_TIER 16
27+
#include "fastlanes_tier_sizing.h"
28+
#include "fastlanes_tier_ffor_impl.h"
29+
#undef FL_TIER
30+
31+
/* clang-format on */

0 commit comments

Comments
 (0)