| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| zpaqfranz_solaris | < 7 hours ago | 4.6 MB | |
| zpaqfranz_opensuse | < 7 hours ago | 4.5 MB | |
| zpaqfranz_nas_x86_64 | < 7 hours ago | 5.0 MB | |
| zpaqfranz_nas_i686 | < 7 hours ago | 5.8 MB | |
| zpaqfranz_macos | < 7 hours ago | 8.9 MB | |
| zpaqfranz_esx | < 7 hours ago | 4.7 MB | |
| zpaqfranz_cortexa72 | < 7 hours ago | 4.5 MB | |
| zpaqfranz_cortexa57 | < 7 hours ago | 4.5 MB | |
| zpaqfranz_cortexa55 | < 7 hours ago | 4.5 MB | |
| zpaqfranz_cortexa53 | < 7 hours ago | 4.5 MB | |
| zpaqfranz_armv8 | < 7 hours ago | 4.5 MB | |
| zpaqfranz_armv7_cortexa15 | < 7 hours ago | 4.6 MB | |
| zpaqfranz_armv7_cortexa9 | < 7 hours ago | 4.6 MB | |
| zpaqfranz_armv7 | < 7 hours ago | 4.6 MB | |
| zpaqfranz_armv5 | < 7 hours ago | 4.6 MB | |
| zpaqfranzxp.exe | < 7 hours ago | 5.4 MB | |
| zpaqfranz-open.exe | < 7 hours ago | 4.5 MB | |
| zpaqfranzhw.exe | < 7 hours ago | 5.3 MB | |
| zpaqfranz-full.exe | < 7 hours ago | 11.1 MB | |
| zpaqfranz32.exe | < 7 hours ago | 5.6 MB | |
| zpaqfranz.exe | < 7 hours ago | 5.4 MB | |
| zpaqfranz.cpp | < 7 hours ago | 6.9 MB | |
| Just about everything source code.tar.gz | 2026-09-25 | 2.4 MB | |
| Just about everything source code.zip | 2026-09-25 | 2.4 MB | |
| README.md | 2026-09-25 | 18.9 kB | |
| Totals: 25 Items | 123.5 MB | 0 | |
zpaqfranz 65.5
This is marked as pre-release, because there is a lot of new code to debug
BTW a even bigger 65.6 is coming 😄
Finally: the wiki is (more or less) aligned
Four headlines:
-m8: zstd inside zpaq. Fast as the fast methods, smaller than-m1, and still extractable by any zpaq, even 7.15.-m6is now the "4.5": the-m4you know, plus three cheap contexts that take about 40% of the gain of-m5for a quarter more time. LZ4 and LZAV (the old-m6and-m7) are gone.a -appendrewritten: two passes, the archive is written only forward (never a seek back), and-append -stdoutsends the new version to a pipe as a delta you can glue after the archive.autotest -all -quick: a four-step self test for the slow machines (NAS), and the NAS builds use all their cores on ARM.
This document has three parts:
- Part one: what is new, in short, for everybody
- Part two: the same things in more detail, for power users
- Part three: "lo spiegone", how it works inside, and why, for developers
Part one, what is new, for the user
-m8: zstd-m6: the "4.5"a -append: only forward, and to stdout- <[inline_block>0](#4
- NAS: all the cores
- Smaller things
1. -m8: zstd
zpaqfranz a z:\backup.zpaq c:\data -m8
zpaqfranz a z:\backup.zpaq c:\data -m8 -turbo
zstd 1.5.7 is built into zpaqfranz. On the test machine (Ryzen 7950X3D,
RamDisk, Windows), with -turbo:
-m1 |
-m8 |
|
|---|---|---|
| VM, 11.7 GB, time | 15.4 s | 6.5 s |
| VM, archive | 5,641 MB | 5,440 MB |
| 116,703 files, 6.5 GB, archive | 1,407 MB | 1,317 MB |
Smaller than -m1, more than twice as fast on a big file. The archive is a
normal zpaq archive: zpaqfranz decompresses it with native zstd, any other
zpaq (7.15 included) with a zstd decoder stored inside the archive itself.
-m8h19 is stronger (zstd level 19), -m86 uses 64 MB blocks.
2. -m6: the "4.5"
zpaqfranz a z:\backup.zpaq c:\data -m6
Many asked for something between -m4 and -m5. -m6 is now exactly
-m4, with three more "sparse" contexts in its CM path: on the same two
test sets it is 1.2% (a VM) and 4.3% (many small files) smaller than -m4,
for 24% more time. -m5 goes further (2.9% / 10.7%) but needs 3 to 5 times
the time, to compress and to extract.
The old -m6 (LZ4) and -m7 (LZAV) are gone: -m8 beats both in size at
the same speed. Archives made with them are still extracted (the decoder is
inside the archive). -m7 has no method of its own any more: today it
goes through the same code as -m5.
3. a -append: only forward, and to stdout
zpaqfranz a z:\backup.zpaq c:\data -append
zpaqfranz a /backup/data.zpaq /data -append -stdout >/tmp/delta.zpaq
-append (the "anti-ransomware" mode, for files that can only grow, as
chflags sappend on BSD) now really writes the archive only forward: it
reads the data twice, the first time only to know the sizes of the header.
The two passes must see the same files, folders and bytes, otherwise
nothing is kept and the exit code is 2.
With -stdout the archive on the command line is only read. If it does not
exist, stdout gets a complete archive; if it exists, stdout gets only the
new version, to be glued after it:
cat /backup/data.zpaq /tmp/delta.zpaq > /backup/new.zpaq
copy /b z:\data.zpaq+z:\delta.zpaq z:\new.zpaq
Always check the exit code: a stream with an exit code other than 0 is not valid.
4. autotest -all -quick
zpaqfranz autotest -all -quick -to /tmp/qt
sh /tmp/qt/quicktest.sh
The full autotest -all needs 18 GB and hours on a NAS. The quick suite
does exactly four things: extracts the built-in sha256.zpaq, compresses the
data with -m1, then with -m8, then tests both archives. Its final check
fails if one of the four is missing, or if anything else was run.
5. NAS: all the cores
The NAS builds (and every ARM Linux build) used 1 thread whatever the CPU: now a 4 core ARMv8 NAS uses 4 threads (32 bit builds: 2, as always for 32 bit).
6. Smaller things
-mx...and-ms...are the short form of-method(-mx6.0ci1am). Before they were "Unknown option ignored", and the archive was made, in silence, with-m1.mounton Windows as Administrator: the drive letter is created for the whole system (Mount Manager), so Explorer sees it.- The Makefile has
ENABLE_ZPAQMOUNT=yesto buildmount(FUSE found withpkg-config, with fallbacks for OpenBSD, macOS, the BSDs). mountin a build without it (no-DZPAQMOUNT) now says "This zpaqfranz has NO mount command" with exit code 2, instead of the generic help with exit code 0. A build with mount shows+Mafter the JIT in the banner.monitor(Win64) works also in the builds without SFTP (it was wrongly inside#ifdef SFTP: the command was accepted and not run).- The help lists
zipandpakkaon every platform (they always worked everywhere, but the help showed them only on Windows); the help ofksays that-tois mandatory. - A batch of fixes of issues reported on GitHub (see part three).
Part two, the details, for power users
-m8in practice-m6: what exactly changed- <[inline_block>0](#3
- <[inline_block>0](#4
- <[inline_block>0](#5
- The NAS builds
1. -m8 in practice
| switch | meaning |
|---|---|
-m8 |
zstd level 3, 16 MB blocks (like -m1) |
-m8hN |
zstd level N (1..22) |
-m8aN |
zstd "fast" level -N |
-m86... |
64 MB blocks (the digit after the 8 is the block size, as in the other levels) |
- The whole block is one zstd frame, the window is the block: no checksum (zpaq has its own SHA-1 for every fragment).
-m8goes well with-turbo: on a big file the limit is the fragmenter, not the compressor, and with-turbo-m8reaches the speed of-m0.autotestchecks at start that the ZPAQL decoder built into zpaqfranz is the frozen one (a SHA-1 is compared): a build where it changed says so and must not be released.- Not in the builds for old compilers (
-DANCIENTwithout-DNAS,-DESX): there-m8is not available. The NAS builds have it.
2. -m6: what exactly changed
-m6 is level 4, with one difference in the CM path chosen by the analysis
of the block:
m4 x6,<e8>ci1,1,1,1,2a[w]m
m6 x6,<e8>ci1,1,1,1,2ac0,2,0,255c0,3,0,0,255c0,4,0,0,0,255[w]m
Everything else is the same as -m4: the pre-check of compressibility, the
choice between store, LZ77, BWT and CM, E8E9 when there is x86 code, the
word model on text, 64 MB blocks. Standard zpaq components: any zpaq
extracts it, no new decoder.
Measured (a CM on a Ryzen 7950X3D, 16 cores):
| VM 11.7 GB | vs -m4 |
time | 116,703 files | vs -m4 |
time | |
|---|---|---|---|---|---|---|
-m4 |
4,888,062 KB | 409 s | 885,432 KB | 244 s | ||
-m6 |
4,827,398 KB | -1.2% | 509 s | 847,422 KB | -4.3% | 302 s |
-m5 |
4,744,893 KB | -2.9% | 1921 s | 790,770 KB | -10.7% | 810 s |
Extraction costs as compression (a CM must redo the same work).
3. a -append
- Pass one: the normal
add(), the same scan, the same fragmenter, the same compression, but nothing is written and nothing is opened for writing. It gives the sizes of the header and three counters: files added, folders added, bytes read from the files. - Pass two: the header is written at once with its final sizes, then the data. No seek back.
- The check: same counters and same sizes, else the new version is removed (the archive is truncated back, or deleted if it was new) and the exit code is 2. It is a check by quantity: a file replaced, between the two passes, by another one of the same size is not seen.
-stdinis refused (it cannot be read twice).- A problem in pass one (a file that cannot be read, a damaged archive) stops everything before writing: exit code of pass one.
- BSD and macOS: the archive is opened in append mode (
ab+), so achflags sappendfile can be updated (the normalacannot). Something after the last good version (an incomplete transaction) is refused there: trim it first. - With the same data and the same
-timestamp,aanda -appendgive the same archive, to the byte (checked with-m1and-m8, new and existing archives, on Linux, FreeBSD, OpenBSD, macOS, Windows).
4. a -append -stdout
- The archive on the command line is never created, written or truncated: if it exists it is only read.
- Not there: stdout gets a complete archive (the same bytes
awould write). - There: stdout gets only the new version, a delta.
cat archive deltagives the same archive of a normalaon a copy, to the byte. -key: the delta is encrypted with the same key and the same salt of the archive (it has only one), and does not start with a new salt. A wrong key, or no key (there is no prompt: stdout is the archive), is refused before sending a byte.- A damaged or truncated archive is refused before sending a byte.
- Only archive bytes on stdout (messages on stderr or silent). On Windows stdout is binary.
- Not with: multipart archives,
-chunk,-index,-franzen,-fasttxt. -stdoutwithout-appendis unchanged.
5. autotest -all -quick
| step | what |
|---|---|
| 1 | x sha256.zpaq and sum -rename (the 256 files) |
| 2 | a quick_m1.zpaq te -m1 |
| 3 | a quick_m8.zpaq te -m8 |
| 4 | t quick_m1.zpaq, t quick_m8.zpaq |
The script quicktest.sh (quicktest.bat on Windows) ends with
autotest -all -quick -checktxt: every step writes its number in
quickops.txt, and the check wants 1, 2, 3, 4 and the expected outputs, and
nothing else (another number, an output of the full suite: failure).
-quick keeps its general meaning everywhere else. The quick suite does not
need the 18 GB of the full one.
6. The NAS builds
-DNASis for modern machines with little RAM,-DANCIENTfor really old ones: the limits of NAS (buffers, no SFTP, no mount, no HW SHA...) are the same as before.- The number of threads on Linux came from the "cpu cores" line of
/proc/cpuinfo, which exists only on x86: every ARM got 1 thread. Now, without that line, every "processor" line is a core. Measured in qemu: Cortex-A53 build 4 threads (was 1), ARMv7 build 2 (was 1; 32 bit builds stay at 2 at most). A real-DANCIENTbuild is unchanged.
Part three, lo spiegone, for developers
- zstd and
-m8: how, and at what price - The "4.5": how it was found
-append: the double pass-append -stdout: a stream that only goes forward- The quick suite
- Fixes of GitHub issues
1. zstd and -m8: how, and at what price
How
- zstd 1.5.7, the official amalgamation (
combine.py), without multithreading and dictBuilder, compiled as C++ insidenamespace zzstd(zpaqfranz has its ownU32,U64, xxhash...). 759 macros are saved and restored around it (push_macro/pop_macro) so they do not leak into the rest of the source. - A block is stored with a PCOMP: the ZPAQL program that decodes a zstd frame (about 910 lines of ZPAQL, generated by a script). zpaq 7.15, or any zpaq, runs it and gets the data back. zpaqfranz recognizes that exact PCOMP (byte by byte) and calls native zstd instead.
- The encoder: window = the whole block, no checksum, content size in the
frame. The native decoder:
ZSTD_decompressStream.
The good
- Size: smaller than
-m1(-3.6% on a VM, -6.4% on many files), much smaller than LZ4 (-14% / -36%) and LZAV (-9% / -23%). - Speed: with
-turbothe fast methods all reach the speed of-m0on a big file (the fragmenter is the limit):-m86.5 s against 15.4 s of-m1. - Extraction: native zstd, the fastest of all.
- Compatibility: the archive is still a zpaq archive, every zpaq extracts it.
The bad
- The source got big.
zpaqfranz.cppwent from 148,378 lines (5.2 MB, 65.4m) to 188,125 lines (6.9 MB, 65.5g): about 47,600 lines are zstd. It compiles slower, and a "single file you can read" is less readable. - The PCOMP is forever. Once archives exist, the ZPAQL decoder inside
them cannot change: a bug there stays in those archives. And zpaqfranz
never runs it in normal use (it uses native zstd): a bug would be seen only
by zpaq 7.15 (or
p -verify). This is why it was tested hard (every zstd level, frames of every kind, about 30,000 frames made bydecodecorpus, 20,000 damaged streams, extraction with zpaq 7.15), and whyautotestchecks its SHA-1. A different decoder, one day, will be a new PCOMP next to this one, never a change of this one. - ZPAQL is slow: when zpaq 7.15 runs the PCOMP, 80-95 MB/s with the JIT. Usable, not fast.
- Old compilers: gcc 3.4 (ESXi) has no
push_macro:-m8is not in the-DESXand-DANCIENTbuilds.
2. The "4.5": how it was found
The obvious ideas were wrong. Adding the "clever" parts of -m5 at the end
of -m4 (SSE, MIX2, a 16 bit MIX) gave almost nothing: they work on the
output of the models, and if the models do not see a structure, mixing their
guesses does not create it.
The method: on a sample (16 slices of 64 MB along a VM disk), start from
-m4 and add one piece of -m5 at a time; start from -m5 and remove one
piece at a time. Both said the same thing: the piece that matters is the
three sparse contexts c0,2,0,255, c0,3,0,0,255, c0,4,0,0,0,255
(removing them from -m5 cost 8.5 MB, everything else together less).
A sparse context looks at the byte 2, 3 or 4 positions back, and at the position modulo 2, 3 or 4: the high byte of a 32 bit integer is predicted by the high byte of the previous integer, not by the byte just before it (the low one, almost random). Binary data are full of that: integers, pointers, tables, structures, RGB pixels.
Why they cost so little: in -m5 each of them is followed by an ISSE of
order 1, tables of 2^19..2^20 rows, 32-64 MB each, a random access for every
bit (cache misses). Without the ISSE they are ICMs of 2^13..2^14 rows,
0.5-1 MB: they stay in cache. That keeps 8.5 of the 9.6 MB of their gain,
at the speed of -m4.
The candidate was then tested with a build where the CM path of level 4 had
them, so the pre-check of -m4 stays (a hand written x... method skips it,
and works hard on data already compressed). That candidate is now -m6: it
does not reach -m5, and it is not meant to; it makes -m4 a bit better
for a small price.
3. -append: the double pass
append()runsadd()twice. Pass one hasg_fakewrite: theOutputArchiveopens nothing and only moves a logical offset. At the end it stores the compressed size and the fragment table size.- Pass two (
g_appendpass2) writes the jidac header with those sizes and skips the final "seek to the header and write it again". - The counters: files added (
files_added + files_updated), folders written in the index (counted inbuildindexchunks), bytes read (total_done). They, and the sizes, must be the same in both passes: a header with the wrong size would make the archive unreadable. - On a mismatch, after the close: the archive is truncated back to the start
of the new version (or deleted if it was new; the chunks of this run
deleted with
-chunk), exit code 2. - The date: both passes use the same one (it is taken once at start, or
from
-timestamp), so the two passes compress the same bytes. - Checked by tracing the descriptor of the archive (strace, truss, ktrace,
dtruss): the normal
aseeks back to the header after the data,a -appendnever does.
4. -append -stdout: a stream that only goes forward
OutputArchivegets a stream mode:fpis stdout (the handle on Windows, so no text translation),offis the logical position inside the archive;seekaccepts only the current position (or, before the first byte, the start of the new data),tellreturnsoff + ptr.- Encryption: the AES counter is the logical position, so a delta that
starts at the end of an existing archive decrypts after
cat. The salt comes from the existing archive; a new archive starts with a new one. - Nothing after the close touches the archive on the command line (no trim,
no date, no
-fasttxt); control-C has nothing to roll back. - A tail after the last good version (the archive size is not the end of the last transaction) is refused: the delta would be glued after garbage.
5. The quick suite
One table (g_autotestquick) makes both the script and the final check:
each entry has its commands, its -out files and what they must contain.
The script writes echo N >>quickops.txt after each operation. The check
reads the numbers (1..4, in order, nothing more), greps the outputs, and
fails on any quickNN.txt not in the table or any outNN.txt of the full
suite. Removing the -m8 operation from the script, adding a fifth number,
or dropping an out03.txt in the folder: each one fails the check.
6. Fixes of GitHub issues
A batch of fixes of issues reported on GitHub, one by one reproduced (on Linux and on Windows), fixed, tested:
292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313.
Not everything was changed as asked in the issues: some requests were declined on purpose, and one (the SIGINT handler of 308, which is not async-signal-safe) needs a redesign and is not done yet.