mirror of
https://github.com/google/brotli.git
synced 2026-07-08 17:56:58 +00:00
q9 significantly faster than q6 for specific input #209
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @fasterthanlime on GitHub (Jan 9, 2018).
I've been in touch with Jyrki Alakuijala on twitter after I noticed compressing with q9 being faster than q6, and they mentioned q9 being faster "unusual".
The behavior was observed deep into a golang program using (non-official) cgo bindings, so I've been trying to reproduce it with vanilla brotli, and I was finally able to!
Exhibit
This is not a "benchmark done right", but I've been playing with various input files for dozens of runs for the last 48h and I've noticed this pattern quite a bit.
How I built brotli
I followed the instructions in
README.md- this was run on windows-amd64, compiled with cmake:CMake auto-detected and used
Visual Studio 14 2015to compile. Note that when I originally observed the performance difference, brotli's sources were built with gcc 7.2.0 (built by the MSYS2 project) via cgo.The version of brotli I used was
da254cffdb(HEAD of master at the time of this writing)The data
Here's a
none.br.zipfile, which is a 2.3MB zip archive (due to github issues limitations) containing a brotli-q9-compressed version of my sample input.📦 none.br.zip
The uncompressed input (
none) is a 2.2GB file. Its SHA-256 checksum is:352994da7a1b3f10e665e7eb4f477e9ebeaf5088742e4fddfacd7d4901d51df5It compresses particularly well (brotli performs splendidly in particular) because it's a patch file inspired by the original BSDiff paper.
In particular, the file:
It's supposed to compress well because bsdiff is basically:
00000029000000000000290000000000) - this is calleddiff dataextra dataOther differences from the original bsdiff:
diff dataextra datadiff dataandextra datadirectly after the relevant instruction - and limits an instruction's slice size. This lets us to streaming diff & apply with lower memory/disk requirements, with a slight compression penalty, I have a tweet thread about this for the curious.Hypotheses
Although I've played with compression formats a lot, I cannot claim to understand its inner workings, but I've noticed the following:
speed up RLE-ish data compressioncommit (and this input data might be RLE-ish? depending on the meaning of it), but afaict I was seeing the performance difference before that commit.Conclusion
At this point I'm tempted to use q=9 rather than q=6-8 (our diff process is much slower than brotli compression anyway, and we're running it on a machine with many cores, so it's not a bottleneck), but I figured it'd be interesting to brotli developers and I might learn a thing or two in the process :)
@fasterthanlime commented on GitHub (Jan 9, 2018):
Additional measurements
As part of my investigation, I'm comparing compressed size and compression speed of zstd and brotli, at various quality levels.
I'm posting here a few of the results, in CSV format. The
relative sizecolumn is the size of the compressed file relative to the uncompressed file. Thecompression speedcolumn is in MiB/s (that's uncompressed MiB per second, not compressed).Patch that compresses very well (manifold)
For the 2.2GB
nonefile mentioned in the original post, here are the results:Patch that doesn't compress well at all (perfect-heist)
For comparison, with another patch mostly made up of
extra data(almost none of theolddata is reusable - and what's more, the new data has high entropy afaict - it's probably already compressed).Uncompressed data size is around 260MiB.
As you can see, the compression speeds (30MiB/s for q6, 5MiB/s for q9) are a lot more in line with what one would expect.
Another patch that compresses well (fate)
This patch is 1.6GiB uncompressed, and it also compresses quite well:
Again we see q9 being faster (and compressing better).
@eustas commented on GitHub (Jan 11, 2018):
Thanks for the report, investigation and a sample file.
Going to analyse it soon.
@eustas commented on GitHub (Feb 27, 2018):
Hello.
I'm terribly sorry - I haven't had time to analyze the roots of the problem. So I will keep this issue open.
On the bright side - the last update have improved the situation. Here are the results of measurements, comparing brotli before and after the last commit.
0:35.52'550'5500:11.92'435'2110:39.72'405'8860:28.62'384'8854:30.02'215'9195:24.72'187'911Speed misbalance is fixed and compression ratio is improved =)
@mraszyk commented on GitHub (Sep 26, 2018):
As pointed out by @fasterthanlime, LGBLOCK is adjusted differently for quality >= 9, but in fact, it gets a larger value which results in faster and denser compression for this particular data.
LGBLOCK is the length of so-called input blocks (typically 64K to 256K) and for each input block at least one command used to be produced (at commit
da254cf). As a consequence, long chunks of zeros (spanning k full input blocks) resulted in a sequence of k equal commands (insert-length = 0, copy-length = 2^LGBLOCK, copy-distance = 1), although a single command would actually suffice (and the mandatory extra bits prevent Huffman coding from mitigating the issue). This is the reason why compression gets denser with longer input blocks on files with long chunks of zeros. In terms of compression time, with longer input blocks the hasher (which is invoked O(1) times per input block full of zeros) is invoked less times in total which results in faster compression.The described inefficiency has been fixed by introducing the method
ExtendLastCommand(at commit35e69fc) which checks if the last command of the previous input block can be extended into the current input block (before invoking the hasher). This results in faster and denser compression.Finally, there is still some room for improving the compression time for qualities <= 9 by optimizing out some calls to the hasher which cannot further improve the HasherSearchResult found so far.