• Stars
    star
    410
  • Rank 105,468 (Top 3 %)
  • Language
    C
  • License
    Other
  • Created over 12 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Small & portable byte-aligned LZ77 compression

License Code style Address Sanitizer

Overview

FastLZ (MIT license) is an ANSI C/C90 implementation of Lempel-Ziv 77 algorithm (LZ77) of lossless data compression. It is suitable to compress series of text/paragraphs, sequences of raw pixel data, or any other blocks of data with lots of repetition. It is not intended to be used on images, videos, and other formats of data typically already in an optimal compressed form.

The focus for FastLZ is a very fast compression and decompression, doing that at the cost of the compression ratio. As an illustration, the comparison with zlib when compressing enwik8 (also in more details):

Ratio Compression Decompression
FastLZ 54.2% 159 MB/s 305 MB/s
zlib -1 42.3% 50 MB/s 184 MB/s
zlib -9 36.5% 11 MB/s 185 MB/s

FastLZ is used by many software products, from a number of games (such as Death Stranding) to various open-source projects (Godot Engine, Facebook HHVM, Apache Traffic Server, Calligra Office, OSv, Netty, etc). It even serves as the basis for other compression projects like BLOSC.

For other implementations of byte-aligned LZ77, take a look at LZ4, Snappy, Density, LZO, LZF, LZJB, LZRW, etc.

Usage

FastLZ can be used directly in any C/C++ applications. For other programming languages/environments, use the corresponding binding:

FastLZ consists of only two files: fastlz.h and fastlz.c. Just add these files to your project in order to use FastLZ. For the detailed information on the API to perform compression and decompression, see fastlz.h.

For Vcpkg users, FastLZ is already available: vcpkg install fastlz.

A simple file compressor called 6pack is included as an example on how to use FastLZ. The corresponding decompressor is 6unpack.

FastLZ supports any standard-conforming ANSI C/C90 compiler, including the popular ones such as GCC, Clang, Visual Studio, and even Tiny CC. FastLZ works well on a number of architectures (32-bit and 64-bit, big endian and little endian), from Intel/AMD, PowerPC, System z, ARM, MIPS, and RISC-V.

The continuous integration system runs an extensive set of compression-decompression round trips on the following systems:

For more details, check the corresponding GitHub Actions build logs.

amd64 Linux Windows macOS
GCC amd64_linux_gcc amd64_windows_gcc amd64_macos_gcc
Clang amd64_linux_clang amd64_windows_clang amd64_macos_clang
TinyCC amd64_linux_tcc amd64_windows_tcc
VS 2019 amd64_windows_vs2019
i686 Linux Windows macOS
GCC i686_linux_gcc
Clang i686_linux_clang
TinyCC i686_windows_tcc
VS 2019 i686_windows_vs2019
i586 Linux DOS
GCC i586_dos_gcc_cross
Linux
powerpc
GCC powerpc_linux_gcc
ppc64(le)
GCC ppc64_linux_gcc
GCC ppc64le_linux_gcc
s390x
GCC s390x_linux_gcc
armhf
GCC armhf_linux_gcc
arm64
GCC arm64_linux_gcc
mips(el)
GCC mipsel_linux_gcc
GCC mips_linux_gcc
mips64(el)
GCC mips64el_linux_gcc
GCC mips64_linux_gcc
riscv
GCC riscv_linux_gcc
riscv64
GCC riscv64_linux_gcc

Block Format

Let us assume that FastLZ compresses an array of bytes, called the uncompressed block, into another array of bytes, called the compressed block. To understand what will be stored in the compressed block, it is illustrative to demonstrate how FastLZ will decompress the block to retrieve the original uncompressed block.

The first 3-bit of the block, i.e. the 3 most-significant bits of the first byte, is the block tag. Currently the block tag determines the compression level used to produce the compressed block.

Block tag Compression level
0 Level 1
1 Level 2

The content of the block will vary depending on the compression level.

Block Format for Level 1

FastLZ Level 1 implements LZ77 compression algorithm with 8 KB sliding window and up to 264 bytes of match length.

The compressed block consists of one or more instructions. Each instruction starts with a 1-byte opcode, 2-byte opcode, or 3-byte opcode.

Instruction type Opcode[0] Opcode[1] Opcode[2]
Literal run 000, L₅-L₀ - -
Short match M₂-M₀, R₁₂-R₈ R₇-R₀ -
Long match 111, R₁₂-R₈ M₇-M₀ R₇-R₀

Note that the very first instruction in a compressed block is always a literal run.

Literal run instruction

For the literal run instruction, there is one or more bytes following the code. This is called the literal run.

The 5 least-significant bits of opcode[0], L, determines the number of literals following the opcode. The value of 0 indicates a 1-byte literal run, 1 indicates a 2-byte literal run, and so on. The minimum literal run is 1 and the maximum literal run is 32.

The decompressor copies (L + 1) bytes of literal run, starting from the first one right after opcode.

Example: If the compressed block is a 4-byte array of [0x02, 0x41, 0x42, 0x43], then the opcode is 0x02 and that means a literal run of 3 bytes. The decompressor will then copy the subsequent 3 bytes, [0x41, 0x42, 0x43], to the output buffer. The output buffer now represents the (original) uncompressed block, [0x41, 0x42, 0x43].

Short match instruction

The 3 most-significant bits of opcode[0], M, determines the match length. The value of 1 indicates a 3-byte match, 2 indicates a 4-byte match and so on. The minimum match length is 3 and the maximum match length is 8.

The 5 least-significant bits of opcode[0] combined with the 8 bits of the opcode[1], R, determines the reference offset. Since the offset is encoded in 13 bits, the minimum is 0 and the maximum is 8191.

The following C code retrieves the match length and reference offset:

M = opcode[0] >> 5;
R = 256 * (opcode[0] << 5) + opcode[1];

The decompressor copies (M+2) bytes, starting from the location offsetted by R in the output buffer. Note that R is a back reference, i.e. the value of 0 corresponds the last byte in the output buffer, 1 is the second to last byte, and so forth.

Example 1: If the compressed block is a 7-byte array of [0x03, 0x41, 0x42, 0x43, 0x44, 0x20, 0x02], then there are two instructions in the there. The first instruction is the literal run of 4 bytes (due to L = 3). Thus, the decompressor copies 4 bytes to the output buffer, resulting in [0x41, 0x42, 0x43, 0x44]. The second instruction is the short match of 3 bytes (from M = 1, i.e 0x20 >> 5) and the offset of 2. Therefore, the compressor goes back 2 bytes from the last position, copies 3 bytes ([0x42, 0x43, 0x44]), and appends them to the output buffer. The output buffer now represents the complete uncompressed data, [0x41, 0x42, 0x43, 0x44, 0x42, 0x43, 0x44].

Example 2: If the compressed block is a 4-byte array of [0x00, 0x61, 0x40, 0x00], then there are two instructions in there. The first instruction is the literal run of just 1 byte (L = 0). Thus, the decompressor copies the byte (0x61) to the output buffer. The output buffer now becomes [0x61]. The second instruction is the short match of 4 bytes (from M = 2, i.e. 0x40 >> 5) and the offset of 0. Therefore, the decompressor copies 4 bytes starting using the back reference of 0 (i.e. the position of 0x61). The output buffer now represents the complete uncompressed data, [0x61, 0x61, 0x61, 0x61, 0x61].

Long match instruction

The value of opcode[1], M, determines the match length. The value of 0 indicates a 9-byte match, 1 indicates a 10-byte match and so on. The minimum match length is 9 and the maximum match length is 264.

The 5 least-significant bits of opcode[0] combined with the 8 bits of opcode[2], R, determines the reference offset. Since the offset is encoded in 13 bits, the minimum is 0 and the maximum is 8191.

The following C code retrieves the match length and reference offset:

M = opcode[1];
R = 256 * (opcode[0] << 5) + opcode[2];

The decompressor copies (M+9) bytes, starting from the location offsetted by R in the output buffer. Note that R is a back reference, i.e. the value of 0 corresponds to the last byte in the output buffer, 1 is for the second to last byte, and so forth.

Example: If the compressed block is a 4-byte array of [0x01, 0x44, 0x45, 0xE0, 0x01, 0x01], then there are two instructions in there. The first instruction is the literal run with the length of 2 (due to L = 1). Thus, the decompressor copies the 2-byte literal run ([0x44, 0x45]) to the output buffer. The second instruction is the long match with the match length of 10 (from M = 1) and the offset of 1. Therefore, the decompressor copies 10 bytes starting using the back reference of 1 (i.e. the position of 0x44). The output buffer now represents the complete uncompressed data, [0x44, 0x45, 0x44, 0x45, 0x44, 0x45, 0x44, 0x45, 0x44, 0x45, 0x44, 0x45].

Decompressor Reference Implementation

The following 40-line C function implements a fully-functional decompressor for the above block format. Note that it is intended to be educational, e.g. no bound check is implemented, and therefore it is absolutely unsafe for production.

void fastlz_level1_decompress(const uint8_t* input, int length, uint8_t* output) {
  int src = 0;
  int dest = 0;
  while (src < length) {
    int type = input[src] >> 5;
    if (type == 0) {
      /* literal run */
      int run = 1 + input[src];
      src = src + 1;
      while (run > 0) {
        output[dest] = input[src];
        src = src + 1;
        dest = dest + 1;
        run = run - 1;
      }
    } else if (type < 7) {
      /* short match */
      int ofs = 256 * (input[src] & 31) + input[src + 1];
      int len = 2 + (input[src] >> 5);
      src = src + 2;
      int ref = dest - ofs - 1;
      while (len > 0) {
        output[dest] = output[ref];
        ref = ref + 1;
        dest = dest + 1;
        len = len - 1;
      }
    } else {
      /* long match */
      int ofs = 256 * (input[src] & 31) + input[src + 2];
      int len = 9 + input[src + 1];
      src = src + 3;
      int ref = dest - ofs - 1;
      while (len > 0) {
        output[dest] = output[ref];
        ref = ref + 1;
        dest = dest + 1;
        len = len - 1;
      }
    }
  }
}

Block Format for Level 2

(To be written)

More Repositories

1

phantomjs

Scriptable Headless Browser
C++
29,475
star
2

kinetic

Kinetic Scrolling with JavaScript
HTML
811
star
3

kabarvirus

KabarVirus.com: cepat (PageSpeed 100), ringan (10 KB)
JavaScript
118
star
4

grunt-jsvalidate

Grunt task to validate JavaScript source
JavaScript
103
star
5

esrefactor

JavaScript
95
star
6

unmix

Undo the so-called mix lingo, a code-mixing style (Bahasa Jaksel)
JavaScript
88
star
7

dekontaminasi

DIY static API server for COVID-19 data in Indonesia
JavaScript
65
star
8

esmorph

ECMAScript source modification tool
JavaScript
64
star
9

X2

Mirror of http://gitorious.org/ofi-labs/x2
JavaScript
60
star
10

pico-jarvis

JavaScript
56
star
11

eightpack

Collection of select JavaScript-based command-line tools
C++
42
star
12

screenie

Fancy screenshot composer
C++
33
star
13

berkala

Run scheduled tasks
JavaScript
31
star
14

ama

Ask me anything!
29
star
15

tinker-chat

Clojure
28
star
16

ask-llm

Interact with an LLM service
JavaScript
25
star
17

coverage-mocha-istanbul-karma

Mocha code coverage example via Istanbul and Karma
JavaScript
24
star
18

coverage-jasmine-istanbul-karma

Jasmine code coverage example via Istanbul and Karma
JavaScript
23
star
19

hello-react-native

Hello world for React Native
Objective-C
22
star
20

uainfer

Infer the user agent from its User Agent string
JavaScript
22
star
21

ioquake3

Unofficial git mirror of ioquake3
C
21
star
22

ariya.github.com

Test cases of various web technologies (HTML5, CSS3, JavaScript)
JavaScript
20
star
23

awesome-bosque

A curated list of awesome Bosque related stuff
20
star
24

hello-c90

Hello world in C90 (ANSI C) built for Intel/AMD, PowerPC, System z, ARM, MIPS, RISC-V
Makefile
18
star
25

pictureflow

Cover Flow clone at a Qt widget
C++
18
star
26

tapdigit

Simple JavaScript-based math evaluator
JavaScript
16
star
27

penjabarberita

Extract the article list from its raw news HTML
JavaScript
16
star
28

es6-minify

Simplistic ES6 minifier
JavaScript
15
star
29

from-zero-to-hero

"From Zero to Hero" Example
JavaScript
13
star
30

ant-javascript-validate

Example Ant task to validate JavaScript using Esprima
JavaScript
11
star
31

evergreen-browser-tests

Running tests on web browsers on various CI systems
JavaScript
10
star
32

tebakmasa

Infer the date and time from the general description in Bahasa Indonesia
JavaScript
9
star
33

karma-appveyor

Karma AppVeyor integration demo
JavaScript
9
star
34

bosque-by-examples

Simple examples of Bosque language features
9
star
35

calculator.clj

Learning Clojure by writing a lexer, parser, evaluator
Clojure
8
star
36

metabase-tests

Experiment with testing Metabase
Clojure
8
star
37

nano-jarvis

Interact with any LLM service
HTML
6
star
38

coverage-qunit-istanbul-karma

QUnit code coverage example via Istanbul and Karma
JavaScript
4
star
39

hello-android

Hello world for Android
Java
4
star
40

gamal

Research tool leveraging LLM for answers
JavaScript
3
star
41

hello-ios

Hello world for iOS
Swift
2
star
42

query-llm

Query LLM with Chain-of-Tought
JavaScript
1
star
43

ariya

whoami
1
star
44

hello-firebase-experiment

Firebase experiment: TDD for Serverless and NoSQL
JavaScript
1
star
45

ghcr-test

1
star
46

timeline2sqlite

Convert location history to a database
Clojure
1
star
47

vscode-wsl-gcc

GCC/GDB on WSL with VS Code
C++
1
star