Przez dłuższy czas używałem, zsh, później z powodu problemów z UTFem wróciłem do basha, jednakże ostatnio wróciłem do zsh, jednakże nie z powodów o kŧórych będzie mowa poniżej.
Jakiś czas temu porównywałem różne shelle i postanowiłem się podzielić wynikami.
Wszystkie testy były wykonane w konsoli, starałem się by każdy shell miał podobne (jeśli chodzi o obciążenie maszyny) warunki, w związku z czym większość usług była wyłączona. Każdy test był wykonany 5 razy, wyniki kolejnych testów zamieściłem w tabelkach.
Każdy pojedyńczy test zawierał pętlę składającą się z 150*100*30 = 4500000 kroków, ktoś może powiedzieć, że nikt nie pisze takich skryptów, jednak wiele razy spotkałem się ze skryptami, które mają ponad 10000 iteracji.
Testowałem basha (chyba nadal najpopularniejszy shell), ksh (znajomy ostatnio się wypowiadał, więc byłem ciekaw jak wypadnie), oraz zsh, którego używam.
Test 1 - $() + seq
Najpierw przetestujemy zwykłe przypisanie wartości do zmiennej, do konstrukcji pętli użyjemy (trendi i jezzi ostatnio ;)) operatora $() oraz instrukcji seq
- z=0; s=`date +%s%N`;
- for i in $( seq 1400 1550 ); do
- for j in $( seq 1 100 ); do
- for k in $( seq 1 30 ); do
- z=0;
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 31.183 | 31.402 | 31.542 | 31.515 | 31.733 | 31.475 |
| ksh: | 22.724 | 22.737 | 22.755 | 22.766 | 22.737 | 22.744 |
| zsh: | 27.532 | 27.413 | 27.383 | 27.391 | 27.418 | 27.427 |
Test 2 - `` + seq
Podmieńmy operator $() na bardziej znany `` i zobaczmy co się stanie
- z=0; s=`date +%s%N`;
- for i in `seq 1400 1550`; do
- for j in `seq 1 100`; do
- for k in `seq 1 30`; do
- z=0;
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 31.260 | 31.327 | 31.382 | 31.314 | 31.804 | 31.418 |
| ksh: | 24.060 | 24.037 | 24.088 | 23.992 | 23.960 | 24.027 |
| zsh: | 29.162 | 29.242 | 29.208 | 29.292 | 29.275 | 29.236 |
Jak widać ksh ponownie ma najlepszy czas, co świadczyć może o tym, że ma najmniejszy narzut przy wykonywaniu komend, warto wziąć to pod uwagę, jeśli nasz skrypt korzysta z poleceń, które nie są wbudowanymi poleceniami powłoki. Wygląda też na to, że operator $() jest szybszy niż ``
Test3 - likwidujemy seq'a
Po dłuższym zastanowieniu dochodzimy do wniosku: "Zaraz zaraz, przecież przy takiej (trójwarstwowej) konstrukcji pętli, seq jest wywoływany 30*100 razy!", zamieńmy więc seq'a na wbudowaną konstrukcję powłoki {liczba..druga_liczba} (darmowy tip: zarówno bash jak i ksh pozwalają także na konstrukcję {literka..druga_literka})
- z=0; s=`date +%s%N`;
- for i in {1400..1550}; do
- for j in {1..100}; do
- for k in {1..30}; do
- z=0;
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 2.864 | 2.817 | 2.876 | 2.841 | 2.861 | 2.852 |
| ksh: | 1.152 | 1.250 | 1.266 | 1.199 | 1.161 | 1.206 |
| zsh: | 0.779 | 0.759 | 0.791 | 0.814 | 0.686 | 0.766 |
Jak widać, zysk jest conajmniej 10 krotny! (btw: bash vs. zsh 2.8/0.7 = 4.0)
Test 4 - wyrażenia arytmetyczne $(())
Nikt jednak nie robi w pętli (tylko) podstawienia, spróbujmy więc czegoś ciekawszego. Dawno dawno temu, za górami za lasami, mieszkała sobie komenda expr, jednak gdybyśmy chcieli jej użyć powodowałoby to 4500000 wywołań zewnętrznego polecenia... niezbyt dobry pomysł. Użyjemy więc operatora $(()), który pozwala na obliczenia stało i zmiennoprzecinkowe.
- s=`date +%s%N`;
- for i in {1400..1550}; do
- for j in {1..100}; do
- for k in {1..30}; do
- z=$(( $z + $j + $k - $i ));
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 15.916 | 16.038 | 16.147 | 16.389 | 16.299 | 16.158 |
| ksh: | 7.028 | 7.096 | 7.027 | 7.082 | 7.041 | 7.055 |
| zsh: | 2.876 | 2.831 | 2.987 | 2.967 | 2.856 | 2.903 |
Jak widać, w tym teście ksh jest ponad dwukrotnie szybszy od basha, na zsh ponad dwukrotnie szybszy od zsh i pięciokrotnie szybszy od basha
Test 5 - mała poprawka
Kiedy pisałem ten artykuł, zauważyłem jeszcze jedną rzecz. Ponieważ w dość specyficzny sposób skonstruowałem wyrażenie: z = z +j + k - i możemy je zamienić na z += j + k - i i użyć operatora (()).
- s=`date +%s%N`;
- for i in {1400..1550}; do
- for j in {1..100}; do
- for k in {1..30}; do
- (( z += $j + $k - $i ));
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 9.524 | 9.540 | 9.595 | 9.647 | 9.634 | 9.588 |
| ksh: | 4.929 | 4.901 | 5.025 | 4.944 | 4.999 | 4.960 |
| zsh: | 1.938 | 1.894 | 1.945 | 2.002 | 1.976 | 1.951 |
Jak widać, każdy shell przyśpieszył mniej więcej o 1/3.
Test 5b - sugestia peresa for ((a;b;c))
peres zasugerował w komentarzach, żeby spróbować jeszcze for'a C-podobnego, jednak wypada gorzej niż poprzednik, ergo for ((i=start; i<=stop; i++)) działa wolniej niż for i in {start..stop}
- s=`date +%s%N`;
- for ((i=1400; i<=1550; i++)); do
- for ((j=1; j<=100; j++)); do
- for ((k=1; k<=30; k++)); do
- (( z += $j + $k - $i ));
- done; done; done;
- echo $z; k=`date +%s%N`; echo $(( $k - $s ))
| shell | #1 | #2 | #3 | #4 | #5 | AVG |
|---|---|---|---|---|---|---|
| bash: | 13.149 | 13.210 | 13.221 | 13.269 | 13.292 | 13.228 |
| ksh: | 5.549 | 5.552 | 5.515 | 5.544 | 5.652 | 5.562 |
| zsh: | 2.319 | 2.330 | 2.394 | 2.395 | 2.397 | 2.367 |
Wnioski?
Ogólnie widać, że bash, w porównaniu z dwoma pozostałymi wypada kiepsko (zawsze twierdziłem, że to kobyła ;)). Oczywiście ciężko tu generalizować, gdyż to zaledwie kilka testów i testujących jedynie kilka rzeczy.
Ponadto głównym czynnikiem, który wpływa na czas wykonania się skryptów shellowych jest zazwyczaj czas w jakim wykonują się zewnętrzne programy, jednak jak widać zawsze można coś usprawnić. Tyle ode mnie.
gim.org.pl is down






