Wieloprocesorowe programy do pakowania plików - serwery Ux

Obecnie niedrogie serwery z wieloma procesorami, czy rdzeniami, są czymś powszechnym. Jednak standardowe programy do kompresji i dekompresji są nadal jednowątkowe, chociaż dostępne są też programy pakujące potrafiące wykorzystać wieloprocesorowość. Dlatego przeprowadziliśmy testy w tym zakresie. Analizując uzyskane dane można wybrać odpowiedni kompresor, w zależności od typu przetwarzanych danych, wymaganego stopnia kompresji, czy też szybkości działania. W ten sposób mamy dane pozwalające wybrać, do prowadzonych projektów najbardziej, adekwatny program.
Do testów wybrałem serwer Intel XEON E5-2699V3 @ 2,3GHz (turbo: 3,6 GHz), 22 nm 18 rdzeni, 36 wątków, 45 MB SmartCache wraz ze 120 GB RAM. Ponieważ pracuję na na maszynie wirtualnej dostępne do testów będzie 16 rdzeni, a dzięki technologii Hyper-Threading system operacyjny będzie widział 32 rdzenie. Serwer w tej konfiguracji wybrałem z powodu tego, że potrzeba zastąpienia standardowych kompresorów zazwyczaj wystąpi przy przetwarzaniu dużych ilości danych, a taki stan łącze ze średnimi i dużymi systemami informatycznymi.
Do testów wybrałem najbardziej popularne programu, a przez to najbardziej sprawdzone. Oprócz programów obecnych w minimalnych dystrybucjach, jak: tar, gzip, bzip2 testom poddaliśmy doinstalowane programy: pigz, lbzip2, pbzip2, squashfs. Należy dokładnie zapoznać się z instrukcjami od poszczególnych programów decyzją o ich użyciu. Czasami np. dekompresja wykorzystuje tylko ograniczoną liczbę rdzeni.

Do testów przygotowaliśmy cztery zestawy danych:
1) Wygenerowane pliki logów. Cztery pliki o rozmiarze 1 GB (po 20 milionów inni w każdym z plików), oraz dwa pliki 100 MB (po 2 miliony inni w każdym z plików). Łącznie 2,3 GB danych zawierających 84 miliony linii. Dane w likach zawierają wiele wspólnych elementów, jak to zazwyczaj w logach, i powinny podlegać znacznej kompresji.
2) 2,3 GB skompresowanych danych w 16 plikach. Do tego testu przygotowaliśmy celowo 16 identycznych plików, by pokazać ciekawą funkcjonalność jednego z użytych programów. Ten test polega na tym, by wykazać zachowanie się programów kompresujących, gdy dane są już mocno spakowane - dlatego filmy są idealne do takiego testu.
3) Kolejny test obejmuje strukturę 107 650 katalogów zawierającymi 380 607 plików. Trzykrotnie rozpakowałem kolekcję FreeBSD Ports (ftp://ftp.freebsd.org/pub/FreeBSD/ports/ports/ports.tar.gz). Tym sposobem można sprawdzić zachowanie się programów przy dużych ilościach plików i katalogów, gdyż nie zawsze obsługa systemu plików jest ich najmocniejszą stroną.
4) Test ostatni polegać będzie na kompresowaniu jednego dużego pliku. Symuluję tutaj kompresowanie dump'a z bazy danych. Zazwyczaj dump'y z bazy danych kompresują się podobnie, jak pliki tekstowe, w stosunku od 1:10 do 1:20. Kompresowanie pojedynczego pliku pozwala sprawdzić zachowanie sie kompresorów wieloprocesorowych w takiej sytuacji. Użyty plik jest prawdziwym dump'em bazy, a jego rozmiar jest kompromisem, pomiędzy zrzutem z dużej bazy, a czasem wykonania testów.

Przygotowane dane do testów:

Poniżej zamieszczam screenshot'y z testów.
Dają istotne informacje o wykorzystaniu zasobów przez różne programy w czasie pracy - zarówno dla wielu plików, jak i dla pojedynczego pliku.

Wykorzystanie procesorów przez jednowątkowy program kompresujący bzip2:

Kompresowanie programem lbzip2:

Kompresowanie programem pbzip2:

Spakowane trzy typy danych, oprócz testu pliku dump.sql, z podziałem na programy kompresujące:

Kompresowanie programem squashfs wielu małych plików:

Kompresowanie programem squashfs dużego pojedynczego pliku:

System dysków nie był ograniczeniem dla tych testów:

Rozpakowanie programem pigz:

Rozpakowanie programem lbzip2:

Rozpakowanie programem squashfs wielu małych plików:

Rozpakowanie programem squashfs pojedynczego pliku:

Dane z testów:


*** Compress ***



Katalog: logRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do time mksquashfs log/ "$i"log.sq ; done117 31215,41314,97815,314
for i in {1..3}; do time tar --use-compress-program=lbzip2 -cf "$i"log.lbzip2 log/; done180 9274,8264,8424,911
for i in {1..3}; do time tar -cjf "$i"log.tar.gz2 log/; done181 439423,130421,484423,268
for i in {1..3}; do time tar --use-compress-program=pbzip2 -cf "$i"log.pbzip2 log/; done181 66216,60015,98716,376
for i in {1..3}; do tar -czf $1log.tar.gz log/; done282 86662,91162,52161,621
for i in {1..3}; do time tar --use-compress-program=pigz -cf "$i"log.tar.pigz log/; done283 1752,5752,7382,856
for i in {1..3}; do time tar -cf "$i"log.tar log/; done2 289 0001,9261,9251,911
Katalog: movieRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do time tar -cf "$i"movie.tar movie/; done2 337 0001,9491,9671,949
for i in {1..3}; do time tar --use-compress-program=pigz -cf "$i"movie.tar.pigz movie/; done2 335 0003,6983,6823,557
for i in {1..3}; do time tar --use-compress-program=lbzip2 -cf "$i"movie.lbzip2 movie/; done2 344 00011,17711,22011,180
for i in {1..3}; do time mksquashfs movie/ "$i"movie.sq ; done14914,79514,62214,516
for i in {1..3}; do time tar --use-compress-program=pbzip2 -cf "$i"movie.pbzip2 movie/; done2 344 00015,92416,12516,900
for i in {1..3}; do time tar -czf "$i"movie.tar.gz movie/; done2 334 00090,48091,46790,887
for i in {1..3}; do time tar -cjf "$i"movie.tar.gz2 movie/; done2 344 000394,723394,035394,080
Katalog: portsRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do time tar --use-compress-program=pigz -cf "$i"ports.tar.pigz ports/; done156 7493,4943,4223,499
for i in {1..3}; do time tar --use-compress-program=lbzip2 -cf "$i"ports.lbzip2 ports/; done123 9923,6683,7343,437
for i in {1..3}; do time tar -cf "$i"ports.tar ports/; done1 077 0003,8223,4473,471
for i in {1..3}; do time tar --use-compress-program=pbzip2 -cf "$i"ports.pbzip2 ports/; done124 0744,0664,4794,180
for i in {1..3}; do time mksquashfs ports/ "$i"ports.sq ; done52 47612,76812,14112,398
for i in {1..3}; do time tar -czf "$i"ports.tar.gz ports/; done157 12126,85925,23425,244
for i in {1..3}; do time tar -cjf "$i"ports.tar.gz2 ports/; done121 77192,58192,07092,169

Przed niektórymi poleceniami dałem komendy kasujące poprzednie archiwa, gdyż niektóre programy były na to "czułe".
Warto zwrócić uwagę na rozmiar archiwum katalogu "movie", gdy kompresorem był squashfs - przypomnę, że do tego testu przygotowaliśmy 16 kopii tego samego pliku. Jak widać program squashfs skorzystał z tego skwapliwie, podobnie jak potrafią to robić nowoczesne systemy plików (jak np.: ZFS).


*** Decompres ***
Katalog: logRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do rm *.txt ; time unsquashfs -f -d . "$i"log.sq ; done---1,9031,7471,924
for i in {1..3}; do time tar -xf "$i"log.tar ; done---2,0222,3752,542
for i in {1..3}; do time tar --use-compress-program=pbzip2 -xf "$i"log.pbzip2 ; done---3,7624,1684,751
for i in {1..3}; do time tar --use-compress-program=lbzip2 -xf "$i"log.lbzip2 ; done---3,8443,7384,302
for i in {1..3}; do time tar --use-compress-program=pigz -xf "$i"log.tar.pigz ; done---4,7035,8965,416
for i in {1..3}; do time tar -xzf "$i"log.tar.gz ; done---14,96514,48514,353
for i in {1..3}; do time tar -xjf "$i"log.tar.gz2 ; done---71,02771,13571,540
Katalog: movieRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do rm *.mp4 ; time unsquashfs -f -d . "$i"movie.sq ; done---1,7391,6931,691
for i in {1..3}; do time tar -xf "$i"movie.tar ; done---2,0612,4473,211
for i in {1..3}; do time tar --use-compress-program=lbzip2 -xf "$i"movie.lbzip2 ; done---6,3036,1226,137
for i in {1..3}; do time tar --use-compress-program=pigz -xf "$i"movie.tar.pigz ; done---6,7117,1046,487
for i in {1..3}; do time tar --use-compress-program=pbzip2 -xf "$i"movie.pbzip2 ; done---7,0847,1577,251
for i in {1..3}; do time tar -xzf "$i"movie.tar.gz ; done---17,35017,44717,518
for i in {1..3}; do time tar -xjf "$i"movie.tar.gz2 ; done---197,856197,948199,170
Katalog: portsRozmiar (kB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do rm -r ports ; time tar --use-compress-program=pbzip2 -xf "$i"ports.pbzip2 ; done---21,85024,22925,791
for i in {1..3}; do rm -r ports-0? ; sync ;time unsquashfs -f -d . "$i"ports.sq ; done---22,52623,98022,605
for i in {1..3}; do rm -r ports ; time tar -xf "$i"ports.tar ; done---25,38225,48526,900
for i in {1..3}; do rm -r ports ; time tar --use-compress-program=lbzip2 -xf "$i"ports.lbzip2 ; done---25,94024,94426,902
for i in {1..3}; do rm -r ports ; time tar --use-compress-program=pigz -xf "$i"ports.tar.pigz ; done---27,69529,62724,790
for i in {1..3}; do rm -r ports ; time tar -xzf "$i"ports.tar.gz ; done---33,67832,63030,443
for i in {1..3}; do rm -r ports ; time tar -xjf "$i"ports.tar.gz2 ; done---52,31553,88754,021


*** SQL compress ***Rozmiar (MB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do time tar -cf "$i"sql.tar sql/; done10 1408,8649,04510,354
for i in {1..3}; do time tar --use-compress-program=pigz -cf "$i"sql.tar.pigz sql/; done1 52913,22713,38213,109
for i in {1..3}; do time tar --use-compress-program=lbzip2 -cf "$i"sql.lbzip2 sql/; done1 14622,38721,83922,362
for i in {1..3}; do time mksquashfs sql/ "$i"sql.sq ; done1 46733,97934,23233,757
for i in {1..3}; do time tar --use-compress-program=pbzip2 -cf "$i"sql.pbzip2 sql/; done1 15062,99363,20864,049
for i in {1..3}; do time tar -czf "$i"sql.tar.gz sql/; done1 532298,638294,853293,020
for i in {1..3}; do time tar -cjf "$i"sql.tar.gz2 sql/; done1 1491665,2051662,2651663,702
*** SQL decompres ***Rozmiar (MB)Czas (s)Czas (s)Czas (s)
for i in {1..3}; do rm -r sqldump.sql ; sync ; time unsquashfs -f -d . "$i"sql.sq ; done---7,517,5477,549
for i in {1..3}; do time tar -xf "$i"sql.tar ; done---9,31210,81710,924
for i in {1..3}; do rm -r sql/ ; time tar --use-compress-program=pbzip2 -xf "$i"sql.pbzip2 ; done---12,22812,33812,383
for i in {1..3}; do rm -r sql/ ; time tar --use-compress-program=lbzip2 -xf "$i"sql.lbzip2 ; done---12,92112,96912,871
for i in {1..3}; do rm -r sql/ ; time tar --use-compress-program=pigz -xf "$i"sql.tar.pigz ; done---27,58027,96228,936
for i in {1..3}; do time tar -xzf "$i"sql.tar.gz ; done---74,24776,32279,398
for i in {1..3}; do time tar -xjf "$i"sql.tar.gz2 ; done---273,099272,989272,595

Programy kompresujące:
Program gzip wykorzystuje algorytm kompresji znany jako "DEFLATE" - stosowany np.: w PNG, HTTP, SSH. Jego największa zaletą jest szybkość i niskie zużycie zasobów (choć wymaga czasami więcej pamięci).
Kompresor bzip2 powstał w 1996 r. Jego szczególną cecha jest wysoki stopień kompresji i powolne działania. Zaimplementowano w nie algorytmu Burrows-Wheeler, który jest dużo bardziej skomplikowany niż "DEFLATE" z gzip'a, ale nie wymaga więcej pamięci.
SquashFS w tym teście znalazł się, ponieważ ma bardzo dobre "osiągi". Nie jest to typowy program kompresujący i nie wszędzie znajdzie zastosowanie. Jest to skompresowany system plików (zamontowany jako system plików jest tylko do odczytu). Do kompresji używa programu gzip, ale potrafi też użyć: lzma, lzo, xz. Wykorzystany jest dystrybucje live CD, np.: Backtrack, Debian, Knoppix, OpenWrt. Przykłady użycia:
mksquashfs /some/dir dir.sqsh
mount dir.sqsh /mnt/dir -t squashfs -o loop
/etc/fstab:
/var/arch.sqsh  /var/arch   squashfs    ro,defaults 0 0


Przełączniki dla tar'a:
gzip:
tar czvf misc.tar.gz misc/
bzip2:
tar cjvf misc.tar.bz2 misc/
xz:
tar cJvf misc.tar.xz misc/


********

Więcej informacji:
Informatyka, FreeBSD, Debian


***

Inne wpisy:



Update: 2018.07.17
Create: 2018.07.17

Argumenty przekazywane do procesu (również bash)

Powłoka czyta polecenia jako jeden ciąg tekstowy. Następnie przeprowadza analizę wczytanych danych. O sposobie analizy decyduje:
a) Zmienna IFS, powodująca, że domyślnie argumenty oddzielone są spacjami.
b) Jednocześnie powłoka umożliwia obejmowanie części poleceń pojedynczymi lub podwójnymi cudzysłowami. Cudzysłowy powodują wyłączenie specjalnego interpretowania niektórych znaków, jak choćby spacje.
c) Interpretowane są znaki wieloznaczne (wildcards), jeżeli ich interpretacja jest włączona (wtedy następuje ich podmiana).


Tablica argc[]:
a) argc[0] - nazwa polecenia
b) argv[argc-1] - ciąg odpowiadający ostatniemu argumentowi
c) argv[argc] - null odpowiadający końcowi tablicy. Czyli przekazując wskaźnik argv+1 faktycznie przekazujemy listy argumentów bez nazwy polecenia.


Pełna deklaracja funkcji main():
int main(int argc, char const *argv[], char const *envp[]) {
Uproszczona deklaracja funkcji main()
int main(int argc,char *argv[], char *envp[]) {

int main(
    int argc,      /* licznik argumentów
    char *argv[]   /* tablica argumentów
)


Wniosek:
Każdy napis argumentu może zawierać cokolwiek, byle był zakończany null. Jest problem z przekazywaniem binarnej wartości "zero".


Warto zobaczyć:
https://github.com/torvalds/linux/blob/master/lib/argv_split.c
Oraz:
- getopt()
- argp_parse()


Tablica envp[]:
- ostatni element to null
- nie ma licznika (takiego jak w argv[] stanowi argc)
- Dostęp do środowiska uzyskiwany przez
extern char **environ;
Jest równoznaczny z environ[x] odpowiadającemu envp[x]


Aby przetworzyć listę dowolnego rodzaju:
mydata=("$somedata"*.txt)
program "${mydata[@]}"


Nazwami plików zaczynające się od -? (kreska lub minus):
Większość poleceń zinterpretuje jako oznaczenie opcji. Można użyć oznaczeni końca opcji:
set -- "$filelist"*.txt
program -- "$@"
Możesz też upewnić się, że nazwy plików zaczynają się od znaku innego niż - (kreska lub minus) Można dodać "/" lub "./"
case "$f" in -*) "f=./$f";; esac


Pozostaje jeszcze interpretacja jako standardowego wejścia lub standardowe wyjście.  Sprawdź różnicę:
"du -sh *"
a,
"du -sh ./*"?


Przypisanie polecenia do zmiennej - przykłady:
command_path="$1"
"$command_path" --option --message="test"

cmd=(/bin/program --option --message="test" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

set -- /bin/program --option --message="test" --
set -- "$@" "$file1" "$file2"
"$@"

code='/bin/program --option --message="test" -- /var/local/file1 | grep "test"'
eval "$code"

Jeszcze lepszym rozwiązaniem jest użycie funkcji.


Polecenia xargs / find - podpowiedzi:
xargs -L1
xargs -l
xargs -0
find cos -exec program parm1 parm2 parm3 {} +

find cos -exec sh -c '
  for x do
    program "$x"
  done
' find-sh {} +


Maksymalna długość (wielość) przekazywanych argumentów:
1) Silnie zależny od systemu.
2) Wypróbuj:
- getconf ARG_MAX (od wersji 4.4BSD i System V)
- sysconf(_SC_ARG_MAX)
- sys/limits.h  ARG_MAX
- xargs --show-limits
3) Test:
MAXA=`perl -e 'print "a"x150000'`
bash test.sh $MAXA

Patrząc na ARG_MAX/NCARGS, musisz wziąć pod uwagę konsumpcję przestrzeni zarówno przez argv[] i envp[] (argumenty i otoczenie).
Niektóre powłoki umożliwiają eksportowanie funkcji do środowiska. Powyższa sytuacja lekko się komplikuje, ponieważ ich definicje zawierają nowe znaki, które zmogą zostać błędnie zinterpretowane jako nowe envp[].  To samo dotyczy sytuacji, gdy wartości zmiennych zawierają znaki nowej linii.

Od 2.6.23 jest inaczej, niż w UNIX'ach:
- Dodano: AX_ARG_STRLEN i MAX_ARG_STRINGS.
- ARG_MAX zazwyczaj będzie o wielkości 1/4 stosu (RLIMIT_STACK).
- Dla systemów wbudowanych bez MMU limit nie ma zastosowania.

Dokładnie jest to sprawdzane w:
- "do_execve()" w fs/exec.c  ( PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *) / sizeof(void *) )
- linux/binfmts.h


Można próbować używać poleceń typu:
expr `getconf ARG_MAX` - `env|wc -c` - `env|egrep '^[^ ]+='|wc -l` \* 4 - 2048
W teorii powinny dać jakieś poglądowe wartości, nawet uwzględniając istnienie funkcji powłoki w tym obszarze pamięci, ale faktycznie zazwyczaj zawyżają wartości. Używając jednak:
getconf ARG_MAX
I wiedząc, że:
- Długość tablicy wskaźników do tych ciągów to zwykle 8 bajtów dla x86_64
- Używając również: limit stacksize lub ulimit -a
Otrzymujemy: jeden milion pustych argumentów utworzy listę wskaźników o wielkości 8 MB, a taki jest rozmiar stosu na moim 64 bitowym Debianie.
Lecz trzeba pamiętać, że w Linuksie maksymalny rozmiar pojedynczego argumentu to 128 kiB.
1000 = kB kilobyte
1024 = KiB kibibyte

Jak uniknąć ograniczeń przekazywanych argumentów?
for i in *; do command "$i"; done  #powolne
printf '%s\0' *|xargs -0 command
find . -exec command {} \;     #powolne
find . -exec command {} +
find . -print0|xargs -0 command
find . -print|xargs command   #bez spacji
"find . ! -name . -prune [...]"
cd /directory/with/long/path; command *
command [a-e]*; command [f-m]*; ...
Nie używać argumentów do przekazywania dużych ilości danych.


********

Więcej informacji:
Informatyka, FreeBSD, Debian


***

Inne wpisy:



Update: 2018.06.12
Create: 2018.06.12

Pizza

Domowa pizza. Palce lizać!




Dlaczego wspominam o tak banalnym daniu jak płaskie ciasto z dodatkami? W przyszłe wakacje stawiam prawdziwy piec do pizzy. Takie plany...

***


Więcej na:



Update: 2018.02.16
Create: 2018.02.16

Biblioteczka informatyczna

Niestety, nie mam miejsca, by trzymać wszystkie przeczytane książki. W miarę starzenia się technologii i ich wycofywania z użycia wyrzucałem zbędne pozycje. Po co komuś teraz specyfikacja przerwań DOS, czy opis BIOS... Tak samo różne odmiany UNIX'ów, starych Winzgroz.


2018.07.20
















**********************************
ARCHIWUM

Zdjecia z 2018.02.15













********

Więcej informacji:
Informatyka, FreeBSD, Debian


***

Inne wpisy:



Update: 2018.07.20
Create: 2018.02.15

Paliwa zimowe

Olej napędowy (ropa, diesel)
Olej napędowy zimowy, do -20°C.
Od 16 listopada do końca lutego.

Olej napędowy przejściowy, do -10°C.
Od 1 marca do 15 kwietnia.
Od 1 października do 15 listopada.

Olej napędowy letni, do 0°C.
Od 16 kwietnia do 30 września.
(Jest jeszcze olej arktyczny, np.: IZ-40 z (LOTOS). Gwarantuje temperaturę blokady zimnego filtra (CFPP) do -32°C oraz temperaturę mętnienia do -22°C)


Benzyna
Okres zimowy, prężność par 60-90.
Od 1 listopada do końca lutego.

Okres przejściowy, prężność par 45-60.
Od 1 marca do 30 kwietnia.
Od 1 do 31 października.


Okres letni 0°C prężność par 45-90.
Od 1 maja do 30 września.


LPG
LPG zimowy.
Od 1 listopada do do 31 marca.
Większa zawartość propanu.

LPG letni.
Od 1 kwietnia do 31 października.
Niższa zawartość propanu, wyższa butanu.


***

W okresach przejściowych staram się tankować jak najmniejsze ilości paliwa - tak by w okresie zimowym mieć większa powinność, że w zbiornikach stacji paliw, oraz w zbiornikach moich samochodów, jest tylko paliwo zimowe.


***


Więcej na:
Tigra / samochód

***

Update: 2017.11.03
Create: 2017.11.03