Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

syc

Streaming archiver + compressor with its own LZ codecs, written from scratch in Free Pascal (x86-64 asm hot paths). Tuned for modest hardware. Alpha.

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

syc

A streaming archiver and compressor with its own LZ codecs, written from scratch in Free Pascal, with C and assembler for the hottest loops. Tuned for modest hardware.

Component Status
CI [![CI][ciBadge]][ciLink]
Latest release [![Release][relBadge]][relLink]
Language Free Pascal 3.2.2, with C and assembler beside a Pascal reference
Formats .syc 2 and .sylz 4 frozen; encrypted .syc 3 and .sylz 5 provisional

[ciBadge]: https://github.com/YadeWira/syc/actions/workflows/ci.yml/badge.svg "Continuous Integration" [ciLink]: https://github.com/YadeWira/syc/actions/workflows/ci.yml [relBadge]: https://img.shields.io/github/v/release/YadeWira/syc?include_prereleases "Latest release" [relLink]: https://github.com/YadeWira/syc/releases

OS Architectures State
Linux x86-64, arm64, x86 (32-bit) CI and release binaries
Windows x86-64, x86 (32-bit) CI and release binaries; also tested on Windows 10
macOS arm64 (Apple Silicon) CI and release binaries

CI builds and tests every platform above and checks they all compress to the same bytes, also with the Pascal-only build (-dSYLZ_PUREPASCAL).

Status: v0.5.x, pre-release. Every release stays a pre-release until v1.0.0. syc v0.1.x was a Rust archiver; its sources are archived in legacy/rust/. v0.2 restarted the project in Free Pascal.

Goals

"Modest hardware" is taken literally:

  • bounded memory, small match-finder tables, reusable contexts;
  • good on 1–4 cores rather than needing 16;
  • no AVX2 requirement: x86-64 baseline (SSE2) is enough;
  • 32-bit targets supported (the Pascal code paths run anywhere FPC does); on 32-bit x86 the decoder picks its SSE2 version at run time (cpuid) where the CPU has SSE2, its i686 one elsewhere.

Languages: Free Pascal first; C and x86-64 assembler only where Free Pascal's code falls short, each next to a Pascal version that stays the reference (the tests check they give the same bytes). C never becomes the main language.

sylz

The first codec: a fast LZ with separate control and literal streams, so the decoder finds the next sequence at a fixed distance whatever the literal length, and a token table derived from measured parses, so that few sequences need a length extension (a branch the CPU can't predict). Format v3 (-ec, extra compression, experimental) adds Huffman-coded literals, in 64 KiB chunks with four interleaved streams; a v2 block is a valid v3 block. Eleven levels, -0 to -10 (default -1): -0 and -1 are greedy single-hash levels (-1 with the bigger table); -2 and -3 use two hash tables, keyed on 6 and 4 bytes (-2 greedy, -3 lazy); -4 to -8 hash chains with lz4hc's parse (matches may start inside the previous one and extend backwards); -9 and -10 an optimal parser over a binary-tree match finder, whose prices include decode time, so it picks cheaper sequences. The decoder is safe on corrupt input. Hot loops have x86-64 assembler versions (Linux and Win64) next to the Pascal ones, the optimal parser's DP a C one, and the fuzzer checks that every path produces the same bytes.

silesia.tar, in memory, one block, one core (Xeon E5-2697A v4, 2.6 GHz), everything in one session (2026-10-23; the machine was moderately busy, load ~4, so speeds vary by ~10%). Fast and middle levels:

codec compress decompress ratio
lz4 1.10.0 507 MB/s 3132 MB/s 47.59%
misa77 0.6.0 --1 265 MB/s 4069 MB/s 46.74%
zxc 0.15.0 -3 155 MB/s 3457 MB/s 46.65%
sylz -0 480 MB/s 3070 MB/s 46.51%
misa77 0.6.0 -0 166 MB/s 3961 MB/s 44.60%
sylz -1 442 MB/s 3136 MB/s 44.55%
zxc 0.15.0 -4 114 MB/s 3442 MB/s 43.43%
lz4hc 1.10.0 -2 224 MB/s 2805 MB/s 42.05%
sylz -2 235 MB/s 2978 MB/s 40.89%
zxc 0.15.0 -5 68.8 MB/s 3431 MB/s 40.83%
lizard 2.1 -12 115 MB/s 2791 MB/s 40.68%
sylz -3 187 MB/s 2958 MB/s 39.99%
sylz -4 85.9 MB/s 2976 MB/s 38.80%
lz4hc 1.10.0 -3 67.9 MB/s 2823 MB/s 38.38%
sylz -5 61.9 MB/s 3029 MB/s 37.78%
misa77 0.6.0 -3 10.3 MB/s 3874 MB/s 37.76%
lz4hc 1.10.0 -4 57.6 MB/s 2878 MB/s 37.65%
sylz -6 46.2 MB/s 3061 MB/s 37.13%
lz4hc 1.10.0 -6 42.4 MB/s 3064 MB/s 36.98%

High ratios:

codec compress decompress ratio
lz4hc 1.10.0 -9 27.0 MB/s 3050 MB/s 36.75%
sylz -7 35.7 MB/s 3113 MB/s 36.73%
lizard 2.1 -19 3.78 MB/s 2879 MB/s 36.52%
lz4hc 1.10.0 -12 9.39 MB/s 3200 MB/s 36.45%
sylz -8 22.0 MB/s 3135 MB/s 36.38%
zxc 0.15.0 -6 9.31 MB/s 2891 MB/s 36.28%
sylz -9 7.65 MB/s 3449 MB/s 36.04%
sylz -10 6.45 MB/s 3393 MB/s 36.03%
zstd 1.5.7 -1 350 MB/s 1135 MB/s 34.53%

On very redundant data -10 searches deeper than -9 and pays for it: zpaq-std's mixed sample 17.73% (-9) -> 17.59% (-10).

32-bit Windows and arm64 have no assembler: there the C paths run. Windows 7 32-bit (a VM, mozilla): compress -1 282 MB/s, -4 66.6 MB/s, -8 18.6 MB/s, decode 1640-2370 MB/s; the Pascal paths made 177, 39.8 and

  1. MB/s, decode 850-1150 MB/s. Benchmarks.

Format v3 (-ec, Huffman-coded literals, experimental), every level, in another session (load ~4): -ec -0 .. -8 parse as -2 .. -10 do (v2's -0 and -1 leave so many literals that, Huffman-coded, they decode slower and compress worse than -2's parse); -ec -9 / -10 run the optimal parser with each literal priced by its Huffman code (two parses).

codec compress decompress ratio
sylz -ec -0 179 MB/s 2121 MB/s 38.80%
sylz -ec -2 75.5 MB/s 1979 MB/s 36.72%
sylz -ec -4 44.0 MB/s 2140 MB/s 35.18%
zstd 1.5.7 -1 329 MB/s 1061 MB/s 34.53%
sylz -ec -6 20.8 MB/s 2164 MB/s 34.47%
sylz -ec -8 6.35 MB/s 2282 MB/s 33.98%
sylz -ec -9 3.75 MB/s 2126 MB/s 33.61%
sylz -ec -10 3.13 MB/s 2107 MB/s 33.60%
zxc 0.15.0 -7 6.04 MB/s 1905 MB/s 33.09%
zstd 1.5.7 -2 230 MB/s 793 MB/s 32.70%
zstd 1.5.7 -3 157 MB/s 693 MB/s 31.20%

Same session, v2 -10: 36.03% at 3498 MB/s decode. With one thread v3 decodes at 60-65% of v2's speed on silesia.tar, at twice zstd -1's; on mixed data zstd -1 is still smaller.

Competitors measured with lzbench 2.4.1 (GCC 14.2, no -march=native) on the same core. More, and how: Benchmarks. The format: sylz format.

Build

Free Pascal 3.2.2 and a C compiler for the target (gcc; gcc -m32 for Linux x86; clang on macOS; mingw-w64 for Windows; aarch64-linux-gnu-gcc for arm64; CC overrides), which build.sh picks from the compiler's target:

bash
./build.sh                    # out/syc and out/sylzbench
./build.sh -Twin64            # Windows x86-64 (FPC's win64 units;
                              #   with FPC's Windows installer: -Px86_64 -Twin64)
./build.sh -dSYLZ_PUREPASCAL  # no assembler, no C: Pascal paths only
FPC=ppcross386 ./build.sh -Twin32                  # Windows 32-bit
FPC=ppcrossa64 ./build.sh -XPaarch64-linux-gnu-    # Linux arm64
scripts/fpc-i386.sh /opt/fpc-i386                  # Linux x86: a 32-bit FPC
FPC=/opt/fpc-i386/fpc ./build.sh                   #   (needs gcc-multilib)

On macOS (brew install fpc) ./build.sh builds as on Linux.

Release binaries: Linux x86-64, arm64 and x86, Windows x86-64 and 32-bit, macOS arm64. The macOS ones are not signed: after downloading, run xattr -d com.apple.quarantine syc (or allow it in System Settings › Privacy & Security). Setting up the cross compilers: Building.

Use

Every command and option: Usage (and syc h CMD). The archive (.syc, format 2) and stream (.sylz, version 4) formats are frozen since v0.4.0: syc archive format, syc stream format.

bash
out/syc a backup.syc docs/ photos/   # a new archive: deduplicated, solid blocks
out/syc a backup.syc docs/ photos/   # again: a new version with what changed
out/syc l -l backup.syc              # list (filters: paths, directories, globs)
out/syc i backup.syc                 # versions, sizes, deduplication
out/syc t backup.syc                 # check everything, write nothing
out/syc x backup.syc -o restore/     # extract (-f replaces, --skip leaves existing files)
out/syc x -u 1 backup.syc -o old/    # as of version 1 (or a date: -u 2026-10-24)
out/syc l --all backup.syc docs/     # every version of each file
out/syc a -p secret.syc docs/        # encrypted (asks for the password; or --password-file)
out/syc k secret.syc                 # a new password, in place (no recompression)

out/syc c -1 file          # -> file.sylz   (-0 .. -10, -ec, -b MIB, -t N threads)
out/syc d file.sylz        # -> file        (keeps the input; --rm deletes it)
out/syc t file.sylz        # check it, write nothing
tar c dir | out/syc c > dir.tar.sylz       # no FILE: stdin -> stdout
out/syc h a                # every option of a command
out/sylzbench -l 1,9 FILE  # in-memory benchmark, lzbench-style

An existing output is not overwritten without -f. Exit status: 0 ok, 1 an input skipped, 2 usage, 3 corrupt data, 4 I/O error.

Each block carries a CRC32 of its data, checked on decompression.

zpaq-std plugins

plugin/ holds one plugin file for zpaq-std (plugin ABI 1) with two codecs: sylz (-ma:sylz, format v2) and sylzec (-ma:sylzec, format v3: -ec's), levels 0..10, default 1. It exports zpaqstd_codec_v1 (sylz, what zpaq-std 1.7 and earlier load) and zpaqstd_codecs_v1(i) (0 sylz, 1 sylzec, then NULL). Before v0.4.2 sylzec was a file of its own.

bash
plugin/build.sh linux-x86_64          # out/plugin/linux-x86_64/sylz.so
plugin/build.sh win64                 # .../win64/sylz.dll
FPC=ppcross386 plugin/build.sh win32 -Fu<rtl>/units/i386-win32
FPC=ppcrossa64 plugin/build.sh linux-arm64 -Fu<rtl>/units/aarch64-linux \
  -Fl/usr/lib/gcc-cross/aarch64-linux-gnu/14 -Fl/usr/aarch64-linux-gnu/lib

plugin/test_plugin.c loads the plugin the way zpaq-std does (--codec N picks a codec of the list) and checks the contract: exported entry, ABI fields, roundtrips at every level, compress never past cap (also when cap is too small), corrupt and truncated streams, 8 threads at once, and that loading the library leaves the process's floating-point state (MXCSR, x87 control word) alone. It passes on Linux x86-64 and arm64 (qemu-user), Windows 10 x64 and Windows 7 (32-bit plugin).

sylz writes format v2 as frozen at tag v0.2.2, sylzec format v3 as frozen at tag v0.2.14 (a v3 block, or plain v2 when that is smaller); a later format would be a new codec name in zpaq-std. For decoders written elsewhere (zpaq-std's ZPAQL ones): tools/sylz_ref.py, a reference decoder in Python written from the format description, and tests/sylzvectors.pas, which writes test vectors (released as sylz-vectors.zip).

Test

bash
fpc -O3 -Fusrc -FUout/units -FEout tests/sylzfuzz.pas
out/sylzfuzz 3000 SAMPLE-FILES...

Roundtrips random, structured and real data at every level, decodes corrupted and truncated blocks against guard-page buffers (any read or write out of bounds faults), alternates the assembler and Pascal paths, and checks the two compressors agree byte for byte. Unix only (uses mmap). .github/test.sh runs every test the way CI does.

Layout

pascal
src/           sylz codec (sylz.pas, and sylzc.c: its C part), its Huffman
               coder (sylzhuf.pas), the syc CLI (syc.pas)
bench/         sylzbench, sylzstats (sequence histograms of a block)
tests/         sylzfuzz, sylzedge, sylzvectors (test vectors for other decoders)
tools/         sylz_ref.py: a reference decoder in Python
plugin/        zpaq-std codec plugin and its test
build.sh       (and build-c.sh, which compiles the C part for the target)
NOTES.md       development log: measurements, FPC codegen findings
legacy/rust/   syc v0.1.x, the Rust archiver: README, NOTES and its sources
               archived in syc-rust-v0.1.29.zip

Documentation

The wiki: format, benchmarks, building, Free Pascal codegen notes, versioning, and the legacy Rust version.

License

MIT, see LICENSE.

The legacy Rust v0.1.x was published as MIT OR Apache-2.0; its archive in legacy/rust/ also holds third-party code (packJPG, packPNG, ppmd-rust) under those projects' own licenses.