W systemach Linux jest dostępnych wiele narzędzi do kompresji danych. Obok tak popularnych jak zip, gzip czy bzip2, spotkamy szereg mniej znanych komend. Jedną z nich jest brotli.
Brotli to algorytm i zestaw narzędzi stworzony przez Google do poprawy
wydajności przesyłu danych za pomocą protokołu HTTP. Później został
przystosowany do zastosowań ogólnych i jako polecenie brotli jest dostępny
w wielu dystrybucjach Linuksa.
System został stworzony jako lepszy zamiennik gzip i algorytmu Defalte również stosowanego kompresji strumieni danych w HTTP. Brotli podobnie jak Deflate bazuje na algorytmie LZ77 i kodowaniu Huffmana. Brotli jednak zawiera sporo różnic, m.in. predefiniowany słownik 13 tys popularnych słów i łańcuchów znaków opracowany na podstawie dużego zbioru danych tekstowych i HTML. Brotli stosuje także dynamiczne okno robocze: w Deflate są do 32 kB, w Brotli rozmiar okna może się zmieniać od 1 kB do 16MB. Pozwala to, w teorii, lepiej pakować większe pliki.
W naszym przeglądzie narzędzi do kompresji brotli wypadło zaskakująco słabo jeśli chodzi o czas kompresji, dlatego postanowiliśmy przyjrzeć się bliżej działania tego narzędzia. Przypomnijmy wyniki w zestawieniu z gzip:
| Program | Czas kompresji | Czas dekompresji | Stopień kompresji |
|---|---|---|---|
| brotli | 145.98 | 0.31 | 1.96 |
| gzip | 2.09 | 0.32 | 1.60 |
Nie wygląda to dobrze. Dla pewności powtórzyliśmy testy na tej samej
maszynie oraz na dwóch innych. Dla punktu odniesienia zrobiliśmy też pomiary
dla gzip.
Maszyny, system operacyjny i wersje brotli użyte w tym teście:
| "core-i5" | "xeon-platinum" | "xeon-gold" | |
|---|---|---|---|
| CPU | 6-Core Intel Core i5-10500 | 2 x 24-Core Intel Xeon Platinum 8268 | 1 x 24-Core INTEL XEON GOLD 6542Y |
| RAM | 32 GB | 186 GB | 2 GB |
| Kernel | 6.1.0-28-amd64 | 4.18.0-553.89.1.el8_10.x86_64 | 6.12.48+deb13-amd64 |
| Linux | MX-23.6 | Rocky Linux 8.10 (Green Obsidian) | Debian GNU/Linux 13 (trixie) |
| brotli | 1.0.9 | 1.0.6 | 1.1.0 |
Do testów wykorzystano zbiór danych z projektu Prague Corpus
Testy przeprowadzono w urządzeniu /dev/shm, aby wyeliminować maksymalnie
wpływ prędkości pamięci masowej na wyniki.
Użyto polecenia brotli (oraz gzip) bez włączania żadnych opcji.
Schemat testu był następujący:
cd /dev/shm/test;
cp $start_dir/PragueCorpus.tar .
isize=$(stat --printf="%s" "PragueCorpus.tar")
/usr/bin/time -f "$e" -f time_file brotli PragueCorpus.tar
osize=$(stat --printf="%s" "PragueCorpus.tar.br")
ctime=$(cat time_file)
/usr/bin/time -f "$e" -f time_file brotli -d PragueCorpus.tar.br
dtime=$(cat time_file)
echo brotli $ctime $dtime $isize $osize
Następnie osobno obliczono stopień kompresji dzieląc wartości isize przez osize
Wyniki kształtują się następująco:
| Program | Maszyna | Czas kompresji | Czas dekompresji | Stopień kompresji |
|---|---|---|---|---|
| brotli | core-i5 | 145.98 | 0.31 | 1.955108 |
| brotli | xeon-gold | 123.68 | 0.32 | 1.955108 |
| brotli | xeon-platinum | 204.64 | 0.39 | 1.955108 |
| gzip | core-i5 | 2.09 | 0.32 | 1.601789 |
| gzip | xeon-gold | 2.11 | 0.34 | 1.601789 |
| gzip | xeon-platinum | 2.73 | 0.42 | 1.601789 |
Jak widać, problem czasu kompresji najwyraźniej nie zależy od maszyny ani od
środowiska. Kompresja za pomocą domyślnych opcji brotli trwa zatrważająco
długo. Pocieszające jest, że dekompresja jest szybka i właściwie nie zależna
od maszyny i wersji programu. Również stopień kompresji nie zależy do
wersji, przynajmniej w takim zakresie jak to przetestowano.
W czym zatem tkwi problem? Aby to wyjaśnić przyjrzyjmy się jakie opcje i
przełączniki mają programy do kompresji. W przypadku brotli wyglada to tak:
$ brotli --help
Usage: brotli [OPTION]... [FILE]...
Options:
-# compression level (0-9)
-c, --stdout write on standard output
-d, --decompress decompress
-f, --force force output file overwrite
-h, --help display this help and exit
-j, --rm remove source file(s)
-k, --keep keep source file(s) (default)
-n, --no-copy-stat do not copy source file(s) attributes
-o FILE, --output=FILE output file (only if 1 input file)
-q NUM, --quality=NUM compression level (0-11)
-t, --test test compressed file integrity
-v, --verbose verbose mode
-w NUM, --lgwin=NUM set LZ77 window size (0, 10-24)
window size = 2**NUM - 16
0 lets compressor choose the optimal value
--large_window=NUM use incompatible large-window brotli
bitstream with window size (0, 10-30)
WARNING: this format is not compatible
with brotli RFC 7932 and may not be
decodable with regular brotli decoders
-S SUF, --suffix=SUF output file suffix (default:'.br')
-V, --version display version and exit
-Z, --best use best compression level (11) (default)
Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.
With no FILE, or when FILE is -, read standard input.
All arguments after '--' are treated as files.
Programy do kompresji takie jak brotli pozwalają regulować stopień kompresji za pomocą przełączników "-#", gdzie "#" oznacza liczbę z zakresu 0-9, przy czym "0" oznacza niski stopień kompresji, a 9 - wysoki. Podobną funkcję ma też gzip. Uzyskanie większego stopienia kompresji okupione jest dłuższym czasem pracy programu. W brotli stopień kompresji można też przełączać za pomocą opcji "-q NUM", z odpowiednimi liczbami. Tu okazuje się, że są dostępne jeszcze dwa poziomy: 10 i 11. Na koniec widzimy, że najwyższy stopień kompresji można włączyć także za pomocą "-Z", oraz, że to jest domyślny tryb działania aplikacji!
Sprawdźmy jak to wygląda w praktyce
| Program | Poziom kompresji | Czas kompresji | Czas dekompresji | Stopień kompresji |
|---|---|---|---|---|
| brotli | 1 | 0.19 | 0.23 | 1.531879 |
| brotli | 2 | 0.34 | 0.22 | 1.584773 |
| brotli | 3 | 0.42 | 0.21 | 1.587900 |
| brotli | 4 | 0.75 | 0.20 | 1.602303 |
| brotli | 5 | 1.59 | 0.21 | 1.669438 |
| brotli | 6 | 2.16 | 0.21 | 1.675939 |
| brotli | 7 | 3.99 | 0.21 | 1.685410 |
| brotli | 8 | 7.59 | 0.21 | 1.688666 |
| brotli | 9 | 26.24 | 0.21 | 1.699232 |
| brotli | 10 | 81.34 | 0.32 | 1.946666 |
| brotli | 11 | 142.42 | 0.31 | 1.955108 |
Zobaczmy zależność czasu kompresji od zadanego poziomu kompresji dla brotli na wykresie:

Widać, że dla wysokich poziomów 10-11 czas kompresji rośnie
kilkuset krotnie.
A więc mamy przyczynę problemu z brotli: program domyślnie stosuje
najwyższy poziom kompresji co jedna wymaga dużo czasu na pakowanie.
Dla niskich poziomów 1-6, ten czas jest całkiem krótki. Zwróćmy uwagę, że jest
porównywalny (2.16 s) z gzip z poprzednich testów (2.09 s).
Jednak czy długi czas kompresji rzeczywiście opłaca się pod względem uzyskanego stopnia kompresji?

W przypadków poziomów 10-11 kompresja jest znacząco wyższa niże przy niższych poziomach. Jednak jest to wzrost w stosunku 1.95 do 1.67 przy czasie kompresji rosnącym w stosunku 142.42 do 2.16.
Jeśli zależało nam będzie na szybkości działania warto użyć opcji przyspieszających pracę np.
$ brotli -6 plik
Jeśli chcemy uzyskać duży stopień kompresji chyba warto rozejrzeć się za innymi narzędziami, które oferują podobny stopień upakowania ale w krótszym czasie czasie. Oczywiście wiele zależy od rodzaju danych.
Podsumowanie
Poznaliśmy funkcję programu kompresji polegająca na regulacji stopnia kompresji, które wpływa zarówno na wielkość skompresowanych plików jak i na czas kompresji.
Nauczyliśmy się, że warto dokładnie prześledzić opcje programu, bo domyślne działanie może nie być tym czego byśmy oczekiwali, ale program ma jednak przydatne funkcje. Podobnie bywa z wieloma poleceniami w Linuksie.
Warto analizować działanie programów na konkretnych danych, zawłaszcza tych które wymagają pewnych nieco większych zasobów komputera, czy też czasu pracy procesora.
Pozostaje tylko pytanie: dlaczego autorzy brotli postawili na taki wariant domyślnego działania, gdzie czas kompresji jest bardzo niekorzystny, co może zniechęcać do użycia tego narzędzia? Na pewno priorytetem był stopień kompresji, ale czy warto, aż tak długo czekać?
Strona domowa: https://github.com/google/brotli
Licencja: MIT