A simple C library for compressing lists of integers using binary packing
See the codeA simple C library for compressing lists of integers using binary packing and SIMD instructions. The assumption is either that you have a list of 32-bit integers where most of them are small, or a list of 32-bit integers where differences between successive integers are small. No software is able to reliably compress an array of 32-bit random numbers.
This library can decode at least 4 billions of compressed integers per second on most desktop or laptop processors. That is, it can decompress data at a rate of 15 GB/s. This is significantly faster than generic codecs like gzip, LZO, Snappy or LZ4.
On a Skylake Intel processor, it can decode integers at a rate 0.3 cycles per integer, which can easily translate into more than 8 decoded billions integers per second.
It runs on both x86/x64 (SSE/AVX) and 64-bit ARM (NEON, e.g. Apple Silicon). See Platforms below.
This library is part of the Awesome C list of C resources.
Contributors: Daniel Lemire, Nathan Kurz, Christoph Rupp, Anatol Belski, Nick White and others
This is a low-level library for fast integer compression. By design it does not define a compressed format. It is up to the (sophisticated) user to create a compressed format.
It is used by:
include/neon128.h); no third-party translation library is pulled in.For a plain C version that does not use SIMD instructions, see https://github.com/lemire/LittleIntPacker
The library supports two SIMD backends behind the same API:
-march=native on a capable host).include/neon128.h). This is our own code
written directly against <arm_neon.h>; no third-party translation layer
(such as sse2neon) is pulled in. The wider AVX2/AVX-512 paths are x86-only and
are simply inactive on ARM.The public API is identical on both: it is selected automatically at compile
time, so the same source (including the __m128i-based entry points) builds on
either architecture.
Compression works over blocks of 128 integers.
For a complete working example, see example/example.c (after building, run it with "./build/example").
const uint32_t b = maxbits(datain);// computes bit width
simdpackwithoutmask(datain, buffer, b);//compressed to buffer, compressing 128 32-bit integers down to b*32 bytes
simdunpack(buffer, backbuffer, b);//uncompressed to backbuffer
While 128 32-bit integers are read, only b 128-bit words are written. Thus, the compression ratio is 32/b.
We used differential coding: we store the difference between successive integers. For this purpose, we need an initial value (called offset).
uint32_t offset = 0;
uint32_t b1 = simdmaxbitsd1(offset,datain); // bit width
simdpackwithoutmaskd1(offset, datain, buffer, b1);//compressing 128 32-bit integers down to b1*32 bytes
simdunpackd1(offset, buffer, backbuffer, b1);//uncompressed
General example for arrays of arbitrary length:
int compress_decompress_demo() {
size_t k, N = 9999;
__m128i * endofbuf;
uint32_t * datain = malloc(N * sizeof(uint32_t));
uint8_t * buffer;
uint32_t * backbuffer = malloc(N * sizeof(uint32_t));
uint32_t b;
for (k = 0; k < N; ++k){ /* start with k=0, not k=1! */
datain[k] = k;
}
b = maxbits_length(datain, N);
buffer = malloc(simdpack_compressedbytes(N,b)); // allocate just enough memory
endofbuf = simdpack_length(datain, N, (__m128i *)buffer, b);
/* compressed data is stored between buffer and endofbuf using (endofbuf-buffer)*sizeof(__m128i) bytes */
/* would be safe to do : buffer = realloc(buffer,(endofbuf-(__m128i *)buffer)*sizeof(__m128i)); */
simdunpack_length((const __m128i *)buffer, N, backbuffer, b);
for (k = 0; k < N; ++k){
if(datain[k] != backbuffer[k]) {
printf("bug\n");
return -1;
}
}
return 0;
}
We also have frame-of-reference (FOR) functions (see simdfor.h header). They work like the bit packing routines, but do not use differential coding so they allow faster search in some cases, at the expense of compression.
The library builds with CMake:
cmake -B build
cmake --build build
ctest --test-dir build # run the unit tests
To install the library, headers, and a CMake package configuration:
cmake --install build --prefix /your/install/prefix
Once installed, another CMake project can use it with:
find_package(simdcomp REQUIRED)
target_link_libraries(myapp PRIVATE simdcomp::simdcomp)
Or vendor it directly via FetchContent (no install needed):
include(FetchContent)
FetchContent_Declare(simdcomp
GIT_REPOSITORY https://github.com/lemire/simdcomp.git
GIT_TAG master)
FetchContent_MakeAvailable(simdcomp)
target_link_libraries(myapp PRIVATE simdcomp::simdcomp)
Useful options: -DSIMDCOMP_BUILD_BENCHMARKS=OFF (the benchmarks pull in
lemire/counters for cycle-accurate timing
and require C++17), -DSIMDCOMP_BUILD_TESTS=OFF, and -DSIMDCOMP_NATIVE=OFF
(disable -march=native).
The benchmark reports CPU cycles per integer using hardware performance counters when they are available (otherwise it falls back to wall-clock time and throughput):
./build/bitpackingbenchmark # on Apple Silicon/Linux, run with sudo for cycle counts
If you are a go user, there is a "go" folder where you will find a simple demo.
1,430 followers · starred Aug 2023
163 followers · starred Jul 2021
421 followers · starred May 2015
713 followers · starred Oct 2025
C
99.0%
A simple C library for compressing lists of integers using binary packing
See the codeA simple C library for compressing lists of integers using binary packing and SIMD instructions. The assumption is either that you have a list of 32-bit integers where most of them are small, or a list of 32-bit integers where differences between successive integers are small. No software is able to reliably compress an array of 32-bit random numbers.
This library can decode at least 4 billions of compressed integers per second on most desktop or laptop processors. That is, it can decompress data at a rate of 15 GB/s. This is significantly faster than generic codecs like gzip, LZO, Snappy or LZ4.
On a Skylake Intel processor, it can decode integers at a rate 0.3 cycles per integer, which can easily translate into more than 8 decoded billions integers per second.
It runs on both x86/x64 (SSE/AVX) and 64-bit ARM (NEON, e.g. Apple Silicon). See Platforms below.
This library is part of the Awesome C list of C resources.
Contributors: Daniel Lemire, Nathan Kurz, Christoph Rupp, Anatol Belski, Nick White and others
This is a low-level library for fast integer compression. By design it does not define a compressed format. It is up to the (sophisticated) user to create a compressed format.
It is used by:
include/neon128.h); no third-party translation library is pulled in.For a plain C version that does not use SIMD instructions, see https://github.com/lemire/LittleIntPacker
The library supports two SIMD backends behind the same API:
-march=native on a capable host).include/neon128.h). This is our own code
written directly against <arm_neon.h>; no third-party translation layer
(such as sse2neon) is pulled in. The wider AVX2/AVX-512 paths are x86-only and
are simply inactive on ARM.The public API is identical on both: it is selected automatically at compile
time, so the same source (including the __m128i-based entry points) builds on
either architecture.
Compression works over blocks of 128 integers.
For a complete working example, see example/example.c (after building, run it with "./build/example").
const uint32_t b = maxbits(datain);// computes bit width
simdpackwithoutmask(datain, buffer, b);//compressed to buffer, compressing 128 32-bit integers down to b*32 bytes
simdunpack(buffer, backbuffer, b);//uncompressed to backbuffer
While 128 32-bit integers are read, only b 128-bit words are written. Thus, the compression ratio is 32/b.
We used differential coding: we store the difference between successive integers. For this purpose, we need an initial value (called offset).
uint32_t offset = 0;
uint32_t b1 = simdmaxbitsd1(offset,datain); // bit width
simdpackwithoutmaskd1(offset, datain, buffer, b1);//compressing 128 32-bit integers down to b1*32 bytes
simdunpackd1(offset, buffer, backbuffer, b1);//uncompressed
General example for arrays of arbitrary length:
int compress_decompress_demo() {
size_t k, N = 9999;
__m128i * endofbuf;
uint32_t * datain = malloc(N * sizeof(uint32_t));
uint8_t * buffer;
uint32_t * backbuffer = malloc(N * sizeof(uint32_t));
uint32_t b;
for (k = 0; k < N; ++k){ /* start with k=0, not k=1! */
datain[k] = k;
}
b = maxbits_length(datain, N);
buffer = malloc(simdpack_compressedbytes(N,b)); // allocate just enough memory
endofbuf = simdpack_length(datain, N, (__m128i *)buffer, b);
/* compressed data is stored between buffer and endofbuf using (endofbuf-buffer)*sizeof(__m128i) bytes */
/* would be safe to do : buffer = realloc(buffer,(endofbuf-(__m128i *)buffer)*sizeof(__m128i)); */
simdunpack_length((const __m128i *)buffer, N, backbuffer, b);
for (k = 0; k < N; ++k){
if(datain[k] != backbuffer[k]) {
printf("bug\n");
return -1;
}
}
return 0;
}
We also have frame-of-reference (FOR) functions (see simdfor.h header). They work like the bit packing routines, but do not use differential coding so they allow faster search in some cases, at the expense of compression.
The library builds with CMake:
cmake -B build
cmake --build build
ctest --test-dir build # run the unit tests
To install the library, headers, and a CMake package configuration:
cmake --install build --prefix /your/install/prefix
Once installed, another CMake project can use it with:
find_package(simdcomp REQUIRED)
target_link_libraries(myapp PRIVATE simdcomp::simdcomp)
Or vendor it directly via FetchContent (no install needed):
include(FetchContent)
FetchContent_Declare(simdcomp
GIT_REPOSITORY https://github.com/lemire/simdcomp.git
GIT_TAG master)
FetchContent_MakeAvailable(simdcomp)
target_link_libraries(myapp PRIVATE simdcomp::simdcomp)
Useful options: -DSIMDCOMP_BUILD_BENCHMARKS=OFF (the benchmarks pull in
lemire/counters for cycle-accurate timing
and require C++17), -DSIMDCOMP_BUILD_TESTS=OFF, and -DSIMDCOMP_NATIVE=OFF
(disable -march=native).
The benchmark reports CPU cycles per integer using hardware performance counters when they are available (otherwise it falls back to wall-clock time and throughput):
./build/bitpackingbenchmark # on Apple Silicon/Linux, run with sudo for cycle counts
If you are a go user, there is a "go" folder where you will find a simple demo.
1,430 followers · starred Aug 2023
163 followers · starred Jul 2021
421 followers · starred May 2015
713 followers · starred Oct 2025
C
99.0%