Bash, argüman listelerini kullanırken performans sorunu yaşıyor mu?


11

Bash 5.0'da çözüldü

Arka fon

Arka plan (ve anlayış için (ve bu soruyu aşağı çekmekten kaçınmaya çalışıyorum) beni bu soruna götüren yolu açıklayacağım (iyi, iki ay sonra hatırlayabileceğim en iyisi).

Unicode karakterlerin listesi için bazı kabuk testleri yaptığınızı varsayın:

printf "$(printf '\\U%x ' {33..200})"

ve 1 milyondan fazla Unicode karakter olduğu için 20.000'i test etmek o kadar da görünmüyor.
Ayrıca karakterleri konum bağımsız değişkenleri olarak ayarladığınızı varsayın:

set -- $(printf "$(printf '\\U%x ' {33..20000})")

karakterleri farklı şekillerde işlemek için her bir işleve aktarmak amacıyla. Bu nedenle işlevler biçime test1 "$@"veya benzerine sahip olmalıdır . Şimdi bunun bash'ta ne kadar kötü bir fikir olduğunu anlıyorum.

Şimdi, hangisinin daha iyi olduğunu bulmak için her bir çözeltinin zamana (n = 1000) ihtiyaç olduğunu varsayın, bu koşullar altında aşağıdakine benzer bir yapı ile bitireceksiniz:

#!/bin/bash --
TIMEFORMAT='real: %R'  # '%R %U %S'

set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000

test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }

main1(){ time for i in $(seq $n); do test1 "$@"; done
         time for i in $(seq $n); do test2 "$@"; done
         time for i in $(seq $n); do test3 "$@"; done
       }

main1 "$@"

Fonksiyonlar test#burada sunulmak için çok basit hale getirilmiştir.
Orijinaller, büyük gecikmenin nerede olduğunu bulmak için aşamalı olarak kesildi.

Yukarıdaki komut dosyası çalışır, çalıştırabilir ve çok az şey yaparak birkaç saniye harcayabilirsiniz.

Gecikmenin tam olarak nerede olduğunu bulmak için basitleştirme sürecinde (ve birçok testten sonra her test fonksiyonunu neredeyse hiçbir şeye indirgemek aşırıdır), zamanın ne kadar iyileştiğini öğrenmek için argümanların her test fonksiyonuna geçişini kaldırmaya karar verdim, sadece 6 faktörü, fazla değil.

Kendinizi denemek için, tüm "$@"giriş işlevini kaldırın main1(veya bir kopyasını oluşturun) ve karşılaştırmak için tekrar test edin (veya her ikisini main1ve kopyasını main2(ile main2 "$@")). Bu, orijinal yazıda (OP) aşağıdaki temel yapıdır.

Ama merak ettim: kabuk neden "hiçbir şey yapmamak" için bu kadar uzun sürüyor? Evet, sadece "birkaç saniye", ama yine de neden?

Bu, beni sadece bash'ın bu sorunu yaşadığını keşfetmek için diğer kabuklarda test etmemi sağladı.
Deneyin ksh ./script(yukarıdakiyle aynı komut dosyasını).

Bu şu açıklamaya yol açar: test#bağımsız değişken olmadan bir function ( ) çağrılması parent ( main#) içindeki bağımsız değişkenler tarafından ertelenir . Bu, aşağıdaki açıklamadır ve aşağıdaki orijinal gönderidir (OP).

Orijinal gönderi.

Bir işlevi yapmak için bir işlevi çağırmak (Bash 4.4.12 (1) -çalışma), f1(){ :; }bin kat daha yavaştır, :ancak yalnızca üst arama işlevinde tanımlanmış bağımsız değişkenler varsa , Neden?

#!/bin/bash
TIMEFORMAT='real: %R'

f1   () { :; }

f2   () {
   echo "                     args = $#";
   printf '1 function no   args yes '; time for ((i=1;i<$n;i++)); do  :   ; done 
   printf '2 function yes  args yes '; time for ((i=1;i<$n;i++)); do  f1  ; done
   set --
   printf '3 function yes  args no  '; time for ((i=1;i<$n;i++)); do  f1  ; done
   echo
        }

main1() { set -- $(seq $m)
          f2  ""
          f2 "$@"
        }

n=1000; m=20000; main1

Sonuçları test1:

                     args = 1
1 function no   args yes real:  0.013
2 function yes  args yes real:  0.024
3 function yes  args no  real:  0.020

                     args = 20000
1 function no   args yes real:  0.010
2 function yes  args yes real: 20.326
3 function yes  args no  real:  0.019

İşlevde kullanılan herhangi bir argüman veya girdi veya çıktı yoktur f1, bin faktörün gecikmesi (1000) beklenmediktir. 1


Testleri birkaç mermiye genişletmek, sonuçlar tutarlıdır, çoğu mermide sorun yoktur veya gecikme yaşanmaz (aynı n ve m kullanılır):

test2(){
          for sh in dash mksh ksh zsh bash b50sh
      do
          echo "$sh" >&2
#         \time -f '\t%E' seq "$m" >/dev/null
#         \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
      done
}

test2

Sonuçlar:

dash
        0:00.01
        0:00.01
mksh
        0:00.01
        0:00.02
ksh
        0:00.01
        0:00.02
zsh
        0:00.02
        0:00.04
bash
        0:10.71
        0:30.03
b55sh             # --without-bash-malloc
        0:00.04
        0:17.11
b56sh             # RELSTATUS=release
        0:00.03
        0:15.47
b50sh             # Debug enabled (RELSTATUS=alpha)
        0:04.62
        xxxxxxx    More than a day ......

seqBağımsız değişken listesinin ya da işlenmesinin gecikmenin kaynağı olmadığını doğrulamak için diğer iki testi de kaldırın.

1. argümanlar sonuçları geçen uygulama süresini artacağı bilinmektedir. Teşekkürler@slm


3
Meta efekt tarafından kaydedildi. unix.meta.stackexchange.com/q/5021/3562
Joshua

Yanıtlar:


9

Kopyalanan: Neden döngüdeki gecikme? isteğin üzerine:

Test senaryosunu şu şekilde kısaltabilirsiniz:

time bash -c 'f(){ :;};for i do f; done' {0..10000}

Büyük bir işlevi $@tetikliyor gibi görünen bir işlevi çağırıyor .

Benim tahminim, zamanın $@bir yığına kaydetme ve daha sonra geri yükleme zamanıdır . Muhtemelen bashtüm değerleri veya bunun gibi bir şeyi çoğaltarak çok verimsiz yapar. Zaman o (n²) cinsindendir.

Diğer kabuklarda aynı zamana sahip olursunuz:

time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}

Bu, argüman listesini işlevlere ilettiğiniz yerdir ve bu kez kabuğun değerleri kopyalaması gerekir ( bashbunun için 5 kat daha yavaş olur).

(Başlangıçta bash 5'te (şu anda alfa'da) daha kötü olduğunu düşündüm, ancak bu, @egmont tarafından belirtildiği gibi geliştirme sürümlerinde etkinleştirilen malloc hata ayıklamasına bağlıydı; ayrıca bashkendi derlemenizi Örneğin, Ubuntu kullanır --without-bash-malloc)


Hata ayıklama nasıl kaldırılır?
Isaac

@isaac, ben değiştirerek yaptım RELSTATUS=alphaetmek RELSTATUS=releasede configuresenaryo.
Stéphane Chazelas

Her ikisi için test sonuçlarını eklendi --without-bash-mallocve RELSTATUS=releasesoru sonuçlarına. Bu hala f çağrısı ile ilgili bir sorun göstermektedir.
Isaac

@Isaac, evet, daha önce bash5'te daha kötü olduğunu söylerken yanlış olduğunu söylemiştim. Daha kötü değil, aynı kötü.
Stéphane Chazelas

Hayır, o kadar da kötü değil . Bash5 arama ile ilgili sorunu çözer ve aramada :biraz iyileşir f. Sorudaki test2 zamanlamalarına bakın.
Isaac
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.