Skip to content

Snappy

Snappy is a fast compression algorithm developed by Google, optimized for speed rather than compression ratio. compressionz provides a pure Zig implementation.

PropertyValue
DeveloperGoogle
First Release2011
ImplementationPure Zig with SIMD
LicenseApache 2.0 (compressionz implementation)
MetricValue
Compress31.6 GB/s
Decompress9.2 GB/s
Ratio95.3%
  • Self-describing format
  • Auto-detection (magic bytes)
  • Zero-copy operations
  • Pure Zig implementation
  • No streaming
  • No dictionary
  • No checksum

Snappy has the simplest API — no options needed:

const cz = @import("compressionz");
// Compress (no options)
const compressed = try cz.snappy.compress(data, allocator);
defer allocator.free(compressed);
// Decompress (no options)
const decompressed = try cz.snappy.decompress(compressed, allocator);
defer allocator.free(decompressed);

Best for:

  • Real-time applications
  • Message queues and streaming
  • Caching systems
  • Log aggregation
  • When speed matters more than ratio

Not ideal for:

  • Storage (use Zstd for better ratio)
  • Network bandwidth constraints
  • Web content (use Gzip/Brotli)

Snappy supports zero-copy for allocation-free hot paths:

const cz = @import("compressionz");
var compress_buf: [65536]u8 = undefined;
var decompress_buf: [65536]u8 = undefined;
// Compress into buffer
const compressed = try cz.snappy.compressInto(data, &compress_buf);
// Decompress into buffer
const decompressed = try cz.snappy.decompressInto(compressed, &decompress_buf);
// Calculate maximum compressed size
const max_size = cz.snappy.maxCompressedSize(data.len);

For security, you can limit the decompressed size:

const decompressed = try cz.snappy.decompressWithLimit(compressed, allocator, max_size);

Snappy stores the uncompressed length in the header:

const size = try cz.snappy.getUncompressedLength(compressed);

Snappy uses a simple framing format:

+-----------------------------------------------------+
| Stream Identifier (10 bytes) |
| - Chunk type: 0xff |
| - Length: 6 (little-endian, 3 bytes) |
| - "sNaPpY" |
+-----------------------------------------------------+
| Compressed Data Chunk |
| - Chunk type: 0x00 |
| - Length (3 bytes) |
| - CRC32C masked (4 bytes) |
| - Compressed data |
+-----------------------------------------------------+
| ... more chunks ... |
+-----------------------------------------------------+

Within each chunk:

+-----------------------------------------------------+
| Uncompressed length (varint) |
+-----------------------------------------------------+
| Elements (sequence of): |
| |
| Literal: |
| - Tag byte: 00xxxxxx (length-1 in bits) |
| - [Extended length bytes] |
| - Literal data |
| |
| Copy with 1-byte offset: |
| - Tag byte: 01xxxxxx xxxxxxxx |
| - (length-4 in high bits, offset in low) |
| |
| Copy with 2-byte offset: |
| - Tag byte: 10xxxxxx + 2 offset bytes |
| |
| Copy with 4-byte offset: |
| - Tag byte: 11xxxxxx + 4 offset bytes |
+-----------------------------------------------------+
// Snappy stream identifier
const SNAPPY_MAGIC = "sNaPpY";
// Detection
if (data.len >= 6 and std.mem.eql(u8, data[0..6], "sNaPpY")) {
// It's Snappy
}

Snappy is a byte-oriented LZ77 variant:

  1. Hash table — 4-byte sequences hashed for matching
  2. Greedy parsing — Takes first sufficient match
  3. Variable-length encoding — For offsets and lengths
  4. No entropy coding — Raw bytes only
  • Optimized for 64-bit processors
  • Cache-efficient memory access
  • Minimal branching
  • Simple tag format
  • SIMD-optimized operations

Our implementation uses explicit SIMD:

// 16-byte vectorized match finding
const v1: @Vector(16, u8) = src[pos..][0..16].*;
const v2: @Vector(16, u8) = src[match_pos..][0..16].*;
const eq = v1 == v2;
// 8-byte fast copy
const chunk: @Vector(8, u8) = src[..8].*;
dest[0..8].* = @as([8]u8, chunk);

Both are speed-focused LZ77 variants. Comparison:

AspectSnappyLZ4 BlockLZ4 Frame
Compress31.6 GB/s36.6 GB/s4.8 GB/s
Decompress9.2 GB/s8.1 GB/s3.8 GB/s
Ratio95.3%99.5%99.3%
Self-describingYesNoYes
ChecksumNoNoYes
StreamingNoNoYes

Choose Snappy when:

  • You need a self-describing format without checksums
  • Decompression speed is critical
  • Working with Snappy-native systems

Choose LZ4 when:

  • Maximum compression speed needed
  • Better compression ratio desired
  • Checksum verification needed (frame)
pub fn publishMessage(queue: *Queue, message: []const u8) !void {
var buf: [65536]u8 = undefined;
const compressed = try cz.snappy.compressInto(message, &buf);
try queue.publish(compressed);
}
pub fn consumeMessage(allocator: Allocator, queue: *Queue) ![]u8 {
const compressed = try queue.consume();
return cz.snappy.decompress(compressed, allocator);
}
pub fn cacheGet(cache: *Cache, key: []const u8, allocator: Allocator) !?[]u8 {
const compressed = cache.get(key) orelse return null;
return cz.snappy.decompress(compressed, allocator);
}
pub fn cacheSet(cache: *Cache, key: []const u8, value: []const u8, allocator: Allocator) !void {
const compressed = try cz.snappy.compress(value, allocator);
defer allocator.free(compressed);
try cache.set(key, compressed);
}
const result = cz.snappy.decompress(data, allocator) catch |err| switch (err) {
error.InvalidData => {
// Not valid Snappy data
return error.CorruptedMessage;
},
error.OutputTooLarge => {
// Exceeds max_output_size
return error.MessageTooLarge;
},
else => return err,
};
MetricSnappyZstd
Compress31.6 GB/s12 GB/s
Decompress9.2 GB/s11.6 GB/s
Ratio95.3%99.9%
DictionaryNoYes
StreamingNoYes
ChecksumNoYes

Summary: Snappy is ~2.6x faster at compression but achieves worse ratios and lacks features. Use Snappy when compression speed is paramount.