Rekurencyjne zakładanie katalogów

Czasami przychodzi potrzeba założenia dużej ilości zagnieżdżonych katalogów, w celu przygotowania jakiejś struktury do przechowywania danych, można to zrobić na kilka sposobów, najlepiej w taki który jest dla nas najwygodniejszy. W przypadku kiedy takich struktur zakładamy wiele, albo są bardzo zagnieżdżone, warto pomyśleć również nad optymalizacją.

Jak myślicie, czy da się przyśpieszyć zakładanie katalogów poprzez użycie mkdir -p?

Posłużę się pewnym przykładem:

mamy 3 zagnieżdżone pętle i mkdir -p, skrypt tworzy taką strukturę katalogów:

0/0/0,
0/0/1,
... ,
f/f/e,
f/f/f,

założenie 4096 katalogów na moim sprzęcie trwa kilkanaście sekund, po przyjrzeniu się bliżej, z użyciem strace ;-)

mkdir("a", 0755) = 0
open("a", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_NOFOLLOW) = 3
fchdir(3) = 0
close(3) = 0
mkdir("a", 0755) = 0
open("a", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_NOFOLLOW) = 3
fchdir(3) = 0
close(3) = 0
mkdir("a/", 0755) = 0

widać, że podczas zakładania katalogów, mkdir -p za każdym razem wywołuje funkcje open(), fchdir() i dopiero mkdir(), co można analogicznie przełożyć na:

# mkdir a
# cd a
# mkdir a
# cd a
# mkdir a

znacznie szybciej byłoby to zrobić następująco:

# mkdir a
# mkdir a/a
# mkdir a/a/a

dodatkowo, najbardziej kosztownym czynnikiem jest to, że przy każdej iteracji pętli, mkdir musi zostać ponownie uruchomiony (dziękuję BartOwl za zwrócenie uwagi), czyli pozostają 2 możliwości, zmodyfikować powyższą pętle w bashu lub iść na łatwiznę i użyć Perla ;-)

który zachowuję się następująco:

mkdir("3", 0777) = 0
mkdir("3/0", 0777) = 0
mkdir("3/0/0", 0777) = 0
stat64("3/0/1", 0x81530c8) = -1 ENOENT (No such file or directory)
stat64("3/0", {st_mode=S_IFDIR|0755, st_size=72, ...}) = 0
mkdir("3/0/1", 0777) = 0
stat64("3/0/2", 0x81530c8) = -1 ENOENT (No such file or directory)
stat64("3/0", {st_mode=S_IFDIR|0755, st_size=96, ...}) = 0
mkdir("3/0/2", 0777) = 0

dla tej ilości katalogów operacja wykonuje się ewidentnie dużo szybciej, poniżej czasy wykonania dla skryptu bash'owego:

[email protected]:/home/jamzed/test# time ./createdir.sh
real 0m12.845s
user 0m7.133s
sys 0m5.692s

oraz dla skryptu napisanego w Perlu:

[email protected]:/home/jamzed/test# time ./createdir.pl
real 0m0.558s
user 0m0.332s
sys 0m0.226s

Wiem, że może być trudno sobie wyobrazić potrzebę zakładania takich struktur, ale tym wpisem chciałem pokazać, że czasami jedna bardzo mała zmiana może spowodować wzrost wydajności i spadek czasu wykonywania kilkadziesiąt razy. Poza tym, zawsze warto wiedzieć jak dokładnie działa jakieś narzędzie i ile można z niego jeszcze wycisnąć ;-)

A Wy które elementy systemów optymalizujecie najczęściej?

  • BartOwl

    Generalnie to nie do końca prawda, co wyżej napisane...
    Winny wolnego wykonania pętli w bashu jest fakt, że mkdir jest osobnym poleceniem i wiąże się z odpaleniem nowego procesu. Perl natomiast w ramach jednego procesu wywołuje syscall mkdir() co oczywiście trwa szybciej.

    W kwestii optymalizacji skryptów bash - na większości systemów zmiana pierwszej linii na #!/bin/sh przyśpiesza działanie skryptów, jako że sh często nie jest linkowane do basha tylko np. ksh, który jest mniejszy i szybciej się ładuje. Niestety nie obsługuje niektórych bashowych rozszerzeń, więc coś za coś...

  • Przyznaję Ci w pełni rację, mkdir musiał być wywoływany za każdym razem, przy każdej iteracji pętli, natomiast Perl wykonywał wszystko jako jeden proces. open, fchdir, close w porównaniu do uruchomienia mkdir jest znikomym czasem.

  • zen

    No dobra, a gdzie tu rekurencja? ;)

  • Ale po co od razu tak straszliwie krzyczeć... ;-) Rekurencja jest oczywiście w domyśle... ;-)

  • znik

    w bashu też może być niemal tak samo szybko, ale nie szybciej. mkdir akceptuje wiele argumentów. jak tego użyć? przez wywołanie xargs, ale nie wolno używać mkdir -p . z drugiej strony narzut na -p jest pomijalnie mały w porównaniu do narzutu jaki wnosi pętlenie i wielokrotne wywoływanie w bashu.

  • znik

    w bashu też może być niemal tak samo szybko, ale nie szybciej. mkdir akceptuje wiele argumentów. jak tego użyć? przez wywołanie xargs, ale nie wolno używać mkdir -p . z drugiej strony narzut na -p jest pomijalnie mały w porównaniu do narzutu jaki wnosi pętlenie i wielokrotne wywoływanie w bashu.