Skip to content

Commit 4f3cb46

Browse files
committed
restructuring and renaming
1 parent 784829a commit 4f3cb46

24 files changed

Lines changed: 1681 additions & 7045 deletions

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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set(SOURCES
2+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_ffor.c
3+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_pack.c
4+
${CMAKE_CURRENT_SOURCE_DIR}/fastlanes_sizing.c)
5+
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})

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

Lines changed: 55 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
## What is this library
44

5-
This is a header only, C library that implements Fastlanes inspired bit-packing using C macros.
6-
The library only uses portable C and the main objective is to allow compilers generate efficient
7-
SIMD code from this. This is not a compression library. It is a building block for higher level
8-
compression algorithms.
5+
This is mostly a header only, C library that implements FastLanes inspired bit-packing using C
6+
macros and a very thin C wrapper over these. The library only uses portable C and the main
7+
objective is to allow compilers generate efficient SIMD code from this. This is not a compression
8+
library. It is a building block for higher level compression algorithms.
99

1010
## Credits and introduction
1111

1212
[The FastLanes Compression Layout:
1313
Decoding >100 Billion Integers per Second with Scalar Code](https://doi.org/10.14778/3598581.3598587)
1414

15-
That is the basis of the implementation here. There are other C++ and Rust implementations out there, like
16-
[cwida/fastlanes](https://github.com/cwida/fastlanes) and [spiraldb/fastlanes](https://github.com/spiraldb/fastlanes)
15+
That paper is the main inspiration basis of the implementation. There are other C++ and Rust
16+
implementations out there, like [cwida/fastlanes](https://github.com/cwida/fastlanes) and
17+
[spiraldb/fastlanes](https://github.com/spiraldb/fastlanes).
1718

1819
FastLanes is used as a building block for higher level compression algorithms, but not as a standalone
1920
compression algorithm. The main use of FastLanes is to pack integers. The callers need to do extra work
@@ -39,8 +40,8 @@ In case of odd bitwidths, for example 5 bits, and we want to pack them into a 64
3940
1) either we pack 12 integers and waste 4 bits, or
4041
2) we split the 13th integer and handle the overflow
4142

42-
Both options are not ideal. The beauty of the FastLanes approach is that we can pack odd width integers when we
43-
have enough of them. In the compression context we tend to have many integers so the numbers are not an issue.
43+
None of the options are ideal. The beauty of the FastLanes approach is that we can pack odd width integers when we
44+
have 'enough' of them. In the compression context we tend to have many integers so the numbers are not an issue.
4445

4546
The second advantage of the FastLanes approach is that it takes advantage of the 'enough' numbers to pack, and
4647
developed a set of instructions that allows to transpose the data with SIMD instructions. This is a huge
@@ -61,10 +62,10 @@ NULL values, in which case the underlying compression will only store the non-NU
6162
up with way less than 1000 non-NULL values. Finally, there are situations specific to TimescaleDB where we
6263
naturally have smaller batch sizes.
6364

64-
The problem with targeting a fixed size of 1024 elements when we often have much smaller batches is that it leads
65-
to a lot of wasted space which will erode the packing advantages of FastLanes. The natural pack boundary of FL1024
66-
is 128 bytes, so the smallest amount we can truncate from the output is 128 bytes. This causes too much waste
67-
for smaller batches.
65+
The problem with targeting a fixed size of 1024 elements (as in the original paper) when we often have much
66+
smaller batches is that it leads to a lot of wasted space which will erode the packing advantages of FastLanes.
67+
The natural pack boundary of FL1024 is 128 bytes, so the smallest amount we can truncate from the output is
68+
128 bytes. This causes too much waste for smaller batches.
6869

6970
### Supporting vectorized execution
7071

@@ -77,8 +78,8 @@ some level of cooperation from the caller. The three areas where it is required
7778
- the input buffer needs to have space for a certain number of elements, potentially more than the actual number of elements being packed
7879
- the packed buffer needs to be allocated with a certain size, potentially more than the actual size of the packed data, and the caller needs to use the returned truncated size to determine the result size
7980

80-
Imagine that we have 255 integers to pack. It is faster to pad it to 256 elements and use AVX2 instructions
81-
than use SSE2 to pack 128 and handle the remaining 127 separately. The requirements the library has are
81+
Imagine that we have 249 integers to pack. It is faster to pad it to 256 elements and use AVX2 instructions
82+
than use SSE2 to pack 128 and handle the remaining 121 separately. The requirements the library has are
8283
designed along these lines, so we maximize speed at the expense of some extra buffer capacity.
8384

8485
The library provides functions to inform the user about these requirements, and there is a section below, that
@@ -89,7 +90,8 @@ these three parameters:
8990
- the width of the input elements (T)
9091
- the actual bit-width of the packed elements (W)
9192

92-
Where W is <= T, and N is <= 256. These determine the actual packing method the library uses and the requirements for the buffers.
93+
Where W is <= T, and N is <= 256. These determine the actual packing method the library uses and the requirements
94+
for the buffers.
9395

9496
'N', 'W' and 'T' are the central concepts in the FastLanes library and they are often referred to by only these
9597
initials. The number of parallel lanes (S) is also a central concept but it is derived from T and the tier bits,
@@ -114,10 +116,10 @@ The tiered FL packing is used in many places in the compression algorithm, not o
114116
values but also for smaller integer arrays, for example the new dictionary compression has a smaller array of the
115117
dictionary entries, or in the PFOR compression the exceptions are stored as small integer arrays. In these cases
116118
we easily end up storing only a handful of elements and this is why the very small tiers are useful. The small
117-
tiers don't offer much SIMD performance but they save space significantly.
119+
tiers don't offer much SIMD performance but they save significant amount of space.
118120

119-
The library provides wrappers over the FL tiers in the `fl/fl.h` file and it is strongly encouraged to use
120-
these instead of calling the specific tiers directly.
121+
The library provides wrappers over the FL tiers in the `fastlanes/fastlanes.h` file and the internal
122+
tiers are not exposed.
121123

122124
## The C implementation
123125

@@ -146,36 +148,36 @@ truncated_size = ceil(rows * W / T) * tier_bytes
146148
## Allocation, alignment and input sizing
147149

148150
The SIMD operations mandate certain alignment and padding of the data. This includes both the input and the
149-
output buffers. In case of the input buffer the operations we use may over-read the input data if not properly
150-
sized. For the output data, the encoder may overwrite adjacent memory if not properly sized. Similarly, during
151-
unpacking we must carefully size the output buffer and ensure that the input buffer is aligned and padded.
151+
output buffers. For example, in case of the input buffer for the packing operation we may over-read the
152+
input data beyond the useful elementts and it needs to be properly sized. For the output data, the encoder
153+
may overwrite adjacent memory if not properly sized. Similarly, during unpacking we must carefully size the
154+
output buffer and ensure that the input buffer is aligned and padded by zeros.
152155

153156
The exact amount of alignment and padding depends on the FL tier. For example FL256 operates on 32 byte aligned
154157
data and it requires the tail padding to be 32 bytes as well. The other tiers have different (smaller)
155158
requirements. The natural truncation point of the output also depends on the tier.
156159

157-
The [fl/fl.h](./fl.h) header has functions to wrap the FL tiers and forward the sizing/packing/unpacking calls
158-
to the right tier based on the input parameters. This is for convenience. The users may choose to call
159-
a given tier directly as well.
160+
The [fastlanes/fastlanes.h](./fastlanes.h) header provides functions to size, pack, and unpack data.
161+
The right internal tier is automatically selected based on the input parameters.
160162

161-
The 'fl/fl.h' header has functions to determine the required alignment and allocation size based on the
162-
(N, W, T) triple. These functions are:
163+
The 'fastlanes/fastlanes.h' header has functions to determine the required alignment and allocation
164+
size based on the (N, W, T) triple. These functions are:
163165

164-
```C
166+
``` C
165167
/*
166168
* Tells how many bytes to allocate for the pack/unpack buffer based on the (N, W, T) triple.
167-
* This function selects the right FL tier based on the input parameters and returns the required
168-
* allocation size.
169+
* This function determines the right FL tier based on the input parameters and returns the required
170+
* allocation size based on the parameters.
169171
*/
170-
size_t fl_alloc_bytes(uint32_t n, uint8_t w, fl_elem_width_t t);
172+
size_t fl_required_bytes(uint32_t n, uint8_t w, fl_elem_width_t t);
171173

172174
/*
173-
* The truncated size allows to determine the result size in the output buffer. With
174-
* this function we can determine the part of the output buffer that is actually used. Remember,
175-
* this is needed because the FL tiers operate on a fixed number of elements which is larger or
176-
* equal to the actual number of elements we pack (for SIMD efficiency).
175+
* The function determines the result size in the output buffer. With this we can determine the
176+
* part of the output buffer that is actually used. Remember, this is needed because the FL tiers
177+
* operate on a fixed number of elements which is larger or equal to the actual number of elements
178+
* we pack (for SIMD efficiency).
177179
*/
178-
size_t fl_truncated_bytes(uint32_t n, uint8_t w, fl_elem_width_t t);
180+
size_t fl_result_bytes(uint32_t n, uint8_t w, fl_elem_width_t t);
179181

180182
/*
181183
* The fl_alignment function returns the required alignment for the buffers based on the
@@ -188,7 +190,7 @@ size_t fl_truncated_bytes(uint32_t n, uint8_t w, fl_elem_width_t t);
188190
size_t fl_alignment(uint32_t n, fl_elem_width_t t);
189191

190192
/*
191-
* The number of input elements of the `values` to be packed. The library expect
193+
* The number of input elements of the `values` to be packed. The library expects
192194
* this many elements and it is the caller's responsibility to provide it. As the
193195
* library uses SIMD instructions, it needs to operate on fixed sized element chunks.
194196
*/
@@ -204,12 +206,12 @@ size_t fl_input_bytes(uint32_t n, fl_elem_width_t t);
204206
| Buffer | Min size | Alignment |
205207
|----------------|---------------------------------|---------------------------------------------|
206208
| values (input) | `fl_input_count` elements | `fl_alignment` recommended, minimum natural |
207-
| packed | `fl_alloc_bytes` bytes | `fl_alignment` mandatory |
209+
| packed | `fl_required_bytes` bytes | `fl_alignment` mandatory |
208210
| unpack output | `fl_input_count` elements | `fl_alignment` recommended, minimum natural |
209211
210212
## Unpack tail padding
211213
212-
The input of the unpack functions must be padded with zeros up to `fl_alloc_bytes`. This
214+
The input of the unpack functions must be padded with zeros up to `fl_required_bytes`. This
213215
is because the SIMD instructions will read beyond the actual packed data, up to the allocated size.
214216
215217
## Interface contract
@@ -219,13 +221,10 @@ This is a concise recap of the previous sections.
219221
- the buffer for the input values MUST have at least `fl_input_count` elements (not bytes) and it SHOULD be aligned to `fl_alignment` bytes, and it MUST be aligned naturally
220222
- the packed buffer (pack output / unpack input) MUST be aligned to `fl_alignment` bytes
221223
- the output of the unpack function holding the unpacked values SHOULD be aligned to `fl_alignment` bytes, and MUST be aligned naturally
222-
- the size of the pack/unpack buffer must be `fl_alloc_bytes` bytes
223-
- the unpack input buffer must be padded with zeros up to `fl_alloc_bytes` bytes
224-
- `fl_pack` and `fl_pack_ffor` return the packed size in bytes, which is the same as `fl_truncated_bytes`
225-
- the input size `n` MUST NOT exceed 256, if using the fl/fl.h provided interface, and
226-
- if you use a specific tiered packer directly (in addition to the above):
227-
- `n` MUST NOT exceed the tier's limit
228-
- `w` MUST be <= `T`
224+
- the size of the pack/unpack buffer must be `fl_required_bytes` bytes
225+
- the unpack input buffer must be padded with zeros up to `fl_required_bytes` bytes
226+
- `fl_pack` and `fl_pack_ffor` return the packed size in bytes, which is the same as `fl_result_bytes`
227+
- the input size `n` MUST NOT exceed 256
229228
230229
## Simple packing vs FFOR packing
231230
@@ -258,10 +257,13 @@ caller's responsibility to store the `base` value, similar to `w` and `n`.
258257
259258
## Batch sizing
260259
261-
The various FL tiers have a maximum capacity. In case of FL256, it is 256 elements. It is the
262-
caller's responsibility to manage the inputs and potentially split it such that it fits into
263-
one of the available FL tiers. This is a deliberate decision, because in practice, using the
264-
same bitwidth for more than 256 elements is rarely economical because of the outliers.
260+
The maximum number of elements the FL tiers can handle is 256 elements (FL256 tier). It is the
261+
caller's responsibility to manage the inputs and potentially split it such that it fits. This
262+
is a deliberate decision, because in practice, using the same bitwidth for more than 256 elements
263+
is rarely economical because of the outliers.
264+
265+
When passing less than 256 elements, the library chooses the best tier based on performance
266+
and packing efficiency.
265267
266268
## Signed integers and input validation
267269
@@ -271,8 +273,7 @@ is chosen such that the subtracted values are non-negative. The library does not
271273
the inputs, so if the caller passes negative values, the behavior is undefined.
272274
273275
Similarly, the library doesn't validate the 'W' parameter to be <= T, or N <= 256, so
274-
it is the caller's responsibility to ensure that these conditions are met. If one
275-
calls a tiered FL function directly, the constraints for N can be tighter.
276+
it is the caller's responsibility to ensure that these conditions are met.
276277
277278
## The W=0 special case
278279
@@ -285,11 +286,11 @@ The pack functions in both cases will return 0, and they handle this special cas
285286
and with that, the functions ignore the buffers and passing NULLs as buffers is safe. The
286287
unpack functions need a valid result buffer. Also the below holds:
287288
288-
```C
289-
fl_alloc_bytes(n, 0, t) == fl_truncated_bytes(n, 0, t) == 0
289+
``` C
290+
fl_required_bytes(n, 0, t) == fl_result_bytes(n, 0, t) == 0
290291
```
291292

292293
## Test and usage examples
293294

294-
Example usage is provided in the `test_fl.c` file. The purpose of this file is to
295+
Example usage is provided in the `test_fastlanes.c` file. The purpose of this file is to
295296
demonstrate the usage of the library.
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+
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+
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+
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+
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+
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+
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+
size_t fl_pack(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t);
83+
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+
size_t fl_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t,
96+
uint64 base);
97+
void fl_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t,
98+
uint64 base);

tsl/src/compression/algorithms/fastlanes/fl_common.h renamed to tsl/src/compression/algorithms/fastlanes/fastlanes_common.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
/*
8-
* fl/fl_common.h -- shared macro vocabulary for the FL tier headers.
8+
* fastlanes/fastlanes_common.h -- shared macros for the FL tier headers.
99
*
1010
* Two pieces every tier needs:
1111
*
@@ -22,7 +22,7 @@
2222

2323
#pragma once
2424

25-
#include "fl_types.h"
25+
#include "fastlanes_types.h"
2626

2727
/*
2828
* Bit-position helpers -- compile-time when (row, W, T) are constants.

0 commit comments

Comments
 (0)