Skip to content

Commit 1498677

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 1498677

14 files changed

Lines changed: 8049 additions & 0 deletions

File tree

src/fl/README.md

Lines changed: 296 additions & 0 deletions
Large diffs are not rendered by default.

src/fl/fl.h

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/*
2+
* This file and its contents are licensed under the Apache License 2.0.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-APACHE for a copy of the license.
5+
*/
6+
7+
/*
8+
* fl/fl.h -- dispatcher for the FastLanes pack/unpack layer.
9+
*
10+
* Forwards over the six tier headers (fl8..fl256). `fl_select(N, T)`
11+
* is a deterministic function of (N, T) so encoder and decoder
12+
* can both rederive the same tier independently.
13+
*/
14+
#pragma once
15+
16+
#include <postgres.h>
17+
18+
#include "fl128.h"
19+
#include "fl16.h"
20+
#include "fl256.h"
21+
#include "fl32.h"
22+
#include "fl64.h"
23+
#include "fl8.h"
24+
#include "fl_types.h"
25+
26+
/*
27+
* 'T' is the width of the input vector in bits.
28+
* 'N' is the number of values to pack / unpack.
29+
*
30+
* FL8 : T in {8} == {FL_ELEM_W8}
31+
* FL16 : T in {8, 16} == {FL_ELEM_W8, FL_ELEM_W16}
32+
* FL32 : T in {8, 16, 32} == {FL_ELEM_W8, FL_ELEM_W16, FL_ELEM_W32}
33+
* FL64 : T in {8, 16, 32} == {FL_ELEM_W8, FL_ELEM_W16, FL_ELEM_W32} (T=64 routes to FL128+)
34+
* FL128 : T in {8, 16, 32, 64} == {...}
35+
* FL256 : T in {8, 16, 32, 64} == {...}
36+
*
37+
* | N range | T = 8 | T = 16 | T = 32 | T = 64 |
38+
* | 0..8 | FL8 | FL16 | FL32 | FL128 |
39+
* | 9..16 | FL16 | FL16 | FL32 | FL128 |
40+
* | 17..32 | FL32 | FL32 | FL32 | FL128 |
41+
* | 33..64 | FL64 | FL64 | FL64 | FL128 |
42+
* | 65..128 | FL128 | FL128 | FL128 | FL128 |
43+
* | 129..256 | FL256 | FL256 | FL256 | FL256 |
44+
*/
45+
static inline fl_tier_width_t
46+
fl_select(uint32 n, fl_elem_width_t t)
47+
{
48+
if (t == FL_ELEM_W64)
49+
{
50+
/*
51+
* 64 bit values could be packed with FL64 as well,
52+
* but I prefer FL128/256 for better SIMD utilisation
53+
*/
54+
return (n <= 128) ? FL_TIER_W128 : FL_TIER_W256;
55+
}
56+
if (n <= 8 && t == FL_ELEM_W8)
57+
{
58+
/* the FL8 packer can only work with T=8 */
59+
return FL_TIER_W8;
60+
}
61+
if (n <= 16 && (t == FL_ELEM_W8 || t == FL_ELEM_W16))
62+
{
63+
/* the FL16 packer can only work with T=8 or T=16 */
64+
return FL_TIER_W16;
65+
}
66+
67+
if (n <= 32)
68+
{
69+
return FL_TIER_W32;
70+
}
71+
if (n <= 64)
72+
{
73+
return FL_TIER_W64;
74+
}
75+
if (n <= 128)
76+
{
77+
return FL_TIER_W128;
78+
}
79+
return FL_TIER_W256;
80+
}
81+
82+
/*
83+
* alloc_size for the tier fl_select(N, W, T) picks. Use this to size
84+
* pack output and decode input buffers exactly.
85+
*/
86+
static inline size_t
87+
fl_alloc_bytes(uint32 n, uint8 w, fl_elem_width_t t)
88+
{
89+
switch (fl_select(n, t))
90+
{
91+
case FL_TIER_W8:
92+
return fl8_alloc_bytes(w, t);
93+
case FL_TIER_W16:
94+
return fl16_alloc_bytes(w, t);
95+
case FL_TIER_W32:
96+
return fl32_alloc_bytes(w, t);
97+
case FL_TIER_W64:
98+
return fl64_alloc_bytes(w, t);
99+
case FL_TIER_W128:
100+
return fl128_alloc_bytes(w, t);
101+
case FL_TIER_W256:
102+
return fl256_alloc_bytes(w, t);
103+
}
104+
return 0;
105+
}
106+
107+
/* Bytes the encoded output carries for N elements (<= alloc_size).
108+
*
109+
* `fl_pack` returns the same value. This function exists so a
110+
* higher-level compressor can compute the truncated size during
111+
* costing decisions without actually packing.
112+
*/
113+
static inline size_t
114+
fl_truncated_bytes(uint32 n, uint8 w, fl_elem_width_t t)
115+
{
116+
switch (fl_select(n, t))
117+
{
118+
case FL_TIER_W8:
119+
return fl8_truncated_bytes(n, w, t);
120+
case FL_TIER_W16:
121+
return fl16_truncated_bytes(n, w, t);
122+
case FL_TIER_W32:
123+
return fl32_truncated_bytes(n, w, t);
124+
case FL_TIER_W64:
125+
return fl64_truncated_bytes(n, w, t);
126+
case FL_TIER_W128:
127+
return fl128_truncated_bytes(n, w, t);
128+
case FL_TIER_W256:
129+
return fl256_truncated_bytes(n, w, t);
130+
}
131+
return 0;
132+
}
133+
134+
/*
135+
* Required alignment and of the buffers including the 'values'
136+
* input, the 'packed' input and 'output' and the 'packed' input
137+
* and output.
138+
*/
139+
static inline size_t
140+
fl_alignment(uint32 n, fl_elem_width_t t)
141+
{
142+
switch (fl_select(n, t))
143+
{
144+
case FL_TIER_W8:
145+
return fl8_alignment();
146+
case FL_TIER_W16:
147+
return fl16_alignment();
148+
case FL_TIER_W32:
149+
return fl32_alignment();
150+
case FL_TIER_W64:
151+
return fl64_alignment();
152+
case FL_TIER_W128:
153+
return fl128_alignment();
154+
case FL_TIER_W256:
155+
return fl256_alignment();
156+
}
157+
return 0;
158+
}
159+
160+
/*
161+
* Number of input elements the kernel reads. The caller must provide
162+
* this many readable elements at `values`; content past index N is
163+
* irrelevant.
164+
*
165+
* Important: the return value is expressed as elements(!), not bytes.
166+
*/
167+
static inline uint32
168+
fl_input_count(uint32 n, fl_elem_width_t t)
169+
{
170+
switch (fl_select(n, t))
171+
{
172+
case FL_TIER_W8:
173+
return fl8_input_count();
174+
case FL_TIER_W16:
175+
return fl16_input_count();
176+
case FL_TIER_W32:
177+
return fl32_input_count();
178+
case FL_TIER_W64:
179+
return fl64_input_count();
180+
case FL_TIER_W128:
181+
return fl128_input_count();
182+
case FL_TIER_W256:
183+
return fl256_input_count();
184+
}
185+
return 0;
186+
}
187+
188+
/* Byte size of the input buffer. */
189+
static inline size_t
190+
fl_input_bytes(uint32 n, fl_elem_width_t t)
191+
{
192+
switch (fl_select(n, t))
193+
{
194+
case FL_TIER_W8:
195+
return fl8_input_bytes(t);
196+
case FL_TIER_W16:
197+
return fl16_input_bytes(t);
198+
case FL_TIER_W32:
199+
return fl32_input_bytes(t);
200+
case FL_TIER_W64:
201+
return fl64_input_bytes(t);
202+
case FL_TIER_W128:
203+
return fl128_input_bytes(t);
204+
case FL_TIER_W256:
205+
return fl256_input_bytes(t);
206+
}
207+
return 0;
208+
}
209+
210+
/* Pack / unpack. When (N, W, T) are compile-time constants the switch
211+
* collapses to a direct tier call. */
212+
213+
/*
214+
* Pack `values` into `packed`. Caller guarantees `values` has `fl_input_count`
215+
* readable elements. `packed` has fl_alloc_bytes(W, T) writable bytes and
216+
* is aligned to fl_alignment(N, T).
217+
*
218+
* `t` is the bit width of the input vector. The function returns the number of
219+
* resulting bytes, which is less than or equal to fl_alloc_bytes(W, T).
220+
*/
221+
static inline size_t
222+
fl_pack(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t)
223+
{
224+
switch (fl_select(n, t))
225+
{
226+
case FL_TIER_W8:
227+
return fl8_pack(values, packed, n, w, t);
228+
case FL_TIER_W16:
229+
return fl16_pack(values, packed, n, w, t);
230+
case FL_TIER_W32:
231+
return fl32_pack(values, packed, n, w, t);
232+
case FL_TIER_W64:
233+
return fl64_pack(values, packed, n, w, t);
234+
case FL_TIER_W128:
235+
return fl128_pack(values, packed, n, w, t);
236+
case FL_TIER_W256:
237+
return fl256_pack(values, packed, n, w, t);
238+
}
239+
return 0;
240+
}
241+
242+
static inline size_t
243+
fl_pack_ffor(const void *values, void *packed, uint32 n, uint8 w, fl_elem_width_t t, uint64 base)
244+
{
245+
switch (fl_select(n, t))
246+
{
247+
case FL_TIER_W8:
248+
return fl8_pack_ffor(values, packed, n, w, t, base);
249+
case FL_TIER_W16:
250+
return fl16_pack_ffor(values, packed, n, w, t, base);
251+
case FL_TIER_W32:
252+
return fl32_pack_ffor(values, packed, n, w, t, base);
253+
case FL_TIER_W64:
254+
return fl64_pack_ffor(values, packed, n, w, t, base);
255+
case FL_TIER_W128:
256+
return fl128_pack_ffor(values, packed, n, w, t, base);
257+
case FL_TIER_W256:
258+
return fl256_pack_ffor(values, packed, n, w, t, base);
259+
}
260+
return 0;
261+
}
262+
263+
static inline void
264+
fl_unpack(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t)
265+
{
266+
switch (fl_select(n, t))
267+
{
268+
case FL_TIER_W8:
269+
fl8_unpack(packed, values, n, w, t);
270+
break;
271+
case FL_TIER_W16:
272+
fl16_unpack(packed, values, n, w, t);
273+
break;
274+
case FL_TIER_W32:
275+
fl32_unpack(packed, values, n, w, t);
276+
break;
277+
case FL_TIER_W64:
278+
fl64_unpack(packed, values, n, w, t);
279+
break;
280+
case FL_TIER_W128:
281+
fl128_unpack(packed, values, n, w, t);
282+
break;
283+
case FL_TIER_W256:
284+
fl256_unpack(packed, values, n, w, t);
285+
break;
286+
}
287+
}
288+
289+
static inline void
290+
fl_unpack_ffor(const void *packed, void *values, uint32 n, uint8 w, fl_elem_width_t t, uint64 base)
291+
{
292+
switch (fl_select(n, t))
293+
{
294+
case FL_TIER_W8:
295+
fl8_unpack_ffor(packed, values, n, w, t, base);
296+
break;
297+
case FL_TIER_W16:
298+
fl16_unpack_ffor(packed, values, n, w, t, base);
299+
break;
300+
case FL_TIER_W32:
301+
fl32_unpack_ffor(packed, values, n, w, t, base);
302+
break;
303+
case FL_TIER_W64:
304+
fl64_unpack_ffor(packed, values, n, w, t, base);
305+
break;
306+
case FL_TIER_W128:
307+
fl128_unpack_ffor(packed, values, n, w, t, base);
308+
break;
309+
case FL_TIER_W256:
310+
fl256_unpack_ffor(packed, values, n, w, t, base);
311+
break;
312+
}
313+
}

0 commit comments

Comments
 (0)