Home

Awesome

<div align="center"> <img src="https://raw.githubusercontent.com/BrianPugh/tamp/main/assets/logo_300w.png"> </div> <div align="center">

Python compat PyPi GHA Status Coverage Documentation Status

</div>

Documentation: https://tamp.readthedocs.io/en/latest/

Source Code: https://github.com/BrianPugh/tamp


Tamp is a low-memory, DEFLATE-inspired lossless compression library intended for embedded targets.

Tamp delivers the highest data compression ratios, while using the least amount of RAM and firmware storage.

Features

Installation

Tamp contains 4 implementations:

  1. A reference desktop CPython implementation that is optimized for readability (and not speed).
  2. A Micropython Native Module implementation (fast).
  3. A Micropython Viper implementation (not recommended, please use Native Module).
  4. A C implementation (with python bindings) for accelerated desktop use and to be used in C projects (very fast).

This section instructs how to install each implementation.

Desktop Python

The Tamp library and CLI requires Python >=3.8 and can be installed via:

pip install tamp

MicroPython

MicroPython Native Module

Tamp provides pre-compiled [native modules]{.title-ref} that are easy to install, are small, and are incredibly fast.

Download the appropriate .mpy file from the release page.

Rename the file to tamp.mpy and transfer it to your board. If using Belay, tamp can be installed by adding the following to pyproject.toml.

[tool.belay.dependencies]
tamp = "https://github.com/BrianPugh/tamp/releases/download/v1.6.0/tamp-1.6.0-mpy1.23-armv6m.mpy"

MicroPython Viper

NOT RECOMMENDED, PLEASE USE NATIVE MODULE

For micropython use, there are 3 main files:

  1. tamp/__init__.py - Always required.
  2. tamp/decompressor_viper.py - Required for on-device decompression.
  3. tamp/compressor_viper.py - Required for on-device compression.

For example, if on-device decompression isn't used, then do not include decompressor_viper.py. If manually installing, just copy these files to your microcontroller's /lib/tamp folder.

If using mip, tamp can be installed by specifying the appropriate package-*.json file.

mip install github:brianpugh/tamp  # Defaults to package.json: Compressor & Decompressor
mip install github:brianpugh/tamp/package-compressor.json  # Compressor only
mip install github:brianpugh/tamp/package-decompressor.json  # Decompressor only

If using Belay, tamp can be installed by adding the following to pyproject.toml.

[tool.belay.dependencies]
tamp = [
   "https://github.com/BrianPugh/tamp/blob/main/tamp/__init__.py",
   "https://github.com/BrianPugh/tamp/blob/main/tamp/compressor_viper.py",
   "https://github.com/BrianPugh/tamp/blob/main/tamp/decompressor_viper.py",
]

C

Copy the tamp/_c_src/tamp folder into your project. For more information, see the documentation.

Usage

Tamp works on desktop python and micropython. On desktop, Tamp is bundled with the tamp command line tool for compressing and decompressing tamp files.

CLI

Compression

Use tamp compress to compress a file or stream. If no input file is specified, data from stdin will be read. If no output is specified, the compressed output stream will be written to stdout.

$ tamp compress --help
Usage: tamp compress [ARGS] [OPTIONS]

Compress an input file or stream.

╭─ Parameters ───────────────────────────────────────────────────────────────────────────────╮
│ INPUT,--input    -i  Input file to compress. Defaults to stdin.                            │
│ OUTPUT,--output  -o  Output compressed file. Defaults to stdout.                           │
│ --window         -w  Number of bits used to represent the dictionary window. [default: 10] │
│ --literal        -l  Number of bits used to represent a literal. [default: 8]              │
╰────────────────────────────────────────────────────────────────────────────────────────────╯

Example usage:

tamp compress enwik8 -o enwik8.tamp  # Compress a file
echo "hello world" | tamp compress | wc -c  # Compress a stream and print the compressed size.

The following options can impact compression ratios and memory usage:

Decompression

Use tamp decompress to decompress a file or stream. If no input file is specified, data from stdin will be read. If no output is specified, the compressed output stream will be written to stdout.

$ tamp decompress --help
Usage: tamp decompress [ARGS] [OPTIONS]

Decompress an input file or stream.

╭─ Parameters ───────────────────────────────────────────────────────────────────────────────╮
│ INPUT,--input    -i  Input file to decompress. Defaults to stdin.                          │
│ OUTPUT,--output  -o  Output decompressed file. Defaults to stdout.                         │
╰────────────────────────────────────────────────────────────────────────────────────────────╯

Example usage:

tamp decompress enwik8.tamp -o enwik8
echo "hello world" | tamp compress | tamp decompress

Python

The python library can perform one-shot compression, as well as operate on files/streams.

import tamp

# One-shot compression
string = b"I scream, you scream, we all scream for ice cream."
compressed_data = tamp.compress(string)
reconstructed = tamp.decompress(compressed_data)
assert reconstructed == string

# Streaming compression
with tamp.open("output.tamp", "wb") as f:
    for _ in range(10):
        f.write(string)

# Streaming decompression
with tamp.open("output.tamp", "rb") as f:
    reconstructed = f.read()

Benchmark

In the following section, we compare Tamp against:

All of these are LZ-based compression algorithms, and tests were performed using a 1KB (10 bit) window. Since zlib already uses significantly more memory by default, the lowest memory level (memLevel=1) was used in these benchmarks. It should be noted that higher zlib memory levels will having greater compression ratios than Tamp. Currently, there is no micropython-compatible zlib or heatshrink compression implementation, so these numbers are provided simply as a reference.

Compression Ratio

The following table shows compression algorithm performance over a variety of input data sourced from the Silesia Corpus and Enwik8. This should give a general idea of how these algorithms perform over a variety of input data types.

datasetrawtampzlibheatshrink
enwik8100,000,00051,635,63356,205,16656,110,394
build/silesia/dickens10,192,4465,546,7616,049,1696,155,768
build/silesia/mozilla51,220,48025,121,38525,104,96625,435,908
build/silesia/mr9,970,5645,027,0324,864,7345,442,180
build/silesia/nci33,553,4458,643,6105,765,5218,247,487
build/silesia/ooffice6,152,1923,814,9384,077,2773,994,589
build/silesia/osdb10,085,6848,520,8358,625,1598,747,527
build/silesia/reymont6,627,2022,847,9812,897,6612,910,251
build/silesia/samba21,606,4009,102,5948,862,4239,223,827
build/silesia/sao7,251,9446,137,7556,506,4176,400,926
build/silesia/webster41,458,70318,694,17220,212,23519,942,817
build/silesia/x-ray8,474,2407,510,6067,351,7508,059,723
build/silesia/xml5,345,2801,681,6871,586,9851,665,179

Tamp usually out-performs heatshrink, and is generally very competitive with zlib. While trying to be an apples-to-apples comparison, zlib still uses significantly more memory during both compression and decompression (see next section). Tamp accomplishes competitive performance while using around 10x less memory.

Memory Usage

The following table shows approximately how much memory each algorithm uses during compression and decompression.

CompressionDecompression
Tamp(1 << windowBits)(1 << windowBits)
ZLib(1 << (windowBits + 2)) + 7KB(1 << windowBits) + 7KB
Heatshrink(1 << (windowBits + 1))(1 << (windowBits + 1))
Deflate (micropython)(1 << windowBits)(1 << windowBits)

All libraries have a few dozen bytes of overhead in addition to the primary window buffer, but are implementation-specific and ignored for clarity here. Tamp uses significantly less memory than ZLib, and half the memory of Heatshrink.

Runtime

As a rough benchmark, here is the performance (in seconds) of these different compression algorithms on the 100MB enwik8 dataset. These tests were performed on an M1 Macbook Air.

Compression (s)Decompression (s)
Tamp (Python Reference)109.576.0
Tamp (C)16.450.142
ZLib0.980.98
Heatshrink (with index)6.220.82
Heatshrink (without index)41.730.82

Heatshrink v0.4.1 was used in these benchmarks. When heathshrink uses an index, an additional (1 << (windowBits + 1)) bytes of memory are used, resulting in 4x more memory-usage than Tamp. Tamp could use a similar indexing to increase compression speed, but has chosen not to to focus on the primary goal of a low-memory compressor.

To give an idea of Tamp's speed on an embedded device, the following table shows compression/decompression in bytes/second of the first 100KB of enwik8 on a pi pico (rp2040) at the default 125MHz clock rate. The C benchmark does not use a filesystem nor dynamic memory allocation, so it represents the maximum speed Tamp can achieve. In all tests, a 1KB window (10 bit) was used.

Compression (bytes/s)Decompression (bytes/s)
Tamp (MicroPython Viper)4,30042,000
Tamp (Micropython Native Module)12,770644,000
Tamp (C)28,5001,042,524
Deflate (micropython builtin)6,715146,477

Tamp resulted in a 51637 byte archive, while Micropython's builtin deflate resulted in a larger, 59442 byte archive.

Binary Size

To give an idea on the resulting binary sizes, Tamp and other libraries were compiled for the Pi Pico (armv6m). All libraries were compiled with -O3. Numbers reported in bytes.

CompressorDecompressorCompressor + Decompressor
Tamp (MicroPython Viper)442942057554
Tamp (MicroPython Native)323230475505
Tamp (C)200819723864
Heatshrink (C)295638766832
uzlib (C)235539636318

Heatshrink doesn't include a high level API; in an apples-to-apples comparison the Tamp library would be even smaller.

Acknowledgement