Diselesaikan di bash 5.0
Latar Belakang
Untuk latar belakang (dan pemahaman (dan mencoba untuk menghindari downvotes pertanyaan ini tampaknya menarik)) Saya akan menjelaskan jalan yang membawa saya ke masalah ini (well, yang terbaik yang bisa saya ingat dua bulan kemudian).
Asumsikan Anda sedang melakukan beberapa tes shell untuk daftar karakter Unicode:
printf "$(printf '\\U%x ' {33..200})"
dan ada lebih dari 1 juta karakter Unicode, pengujian 20.000 dari mereka tampaknya tidak banyak.
Juga asumsikan bahwa Anda menetapkan karakter sebagai argumen posisi:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
dengan maksud meneruskan karakter ke setiap fungsi untuk memprosesnya dengan cara yang berbeda. Jadi fungsinya harus berbentuk test1 "$@"
atau mirip. Sekarang saya menyadari betapa buruknya ide ini di bash.
Sekarang, asumsikan bahwa ada waktu (n = 1000) setiap solusi untuk mencari tahu mana yang lebih baik, dalam kondisi seperti itu Anda akan berakhir dengan struktur yang mirip dengan:
#!/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 "$@"
Fungsinya test#
dibuat sangat sederhana hanya untuk disajikan di sini.
Dokumen aslinya dipangkas secara progresif untuk menemukan di mana penundaan yang sangat besar.
Script di atas berfungsi, Anda dapat menjalankannya dan menghabiskan beberapa detik dengan sangat sedikit.
Dalam proses penyederhanaan untuk menemukan dengan tepat di mana penundaan itu (dan mengurangi setiap fungsi tes menjadi hampir tidak ada yang ekstrim setelah banyak uji coba) saya memutuskan untuk menghapus berlalunya argumen ke setiap fungsi tes untuk mengetahui berapa banyak waktu meningkat, hanya saja faktor 6, tidak banyak.
Untuk mencoba sendiri, hapus semua "$@"
fungsi dalam main1
(atau buat salinan) dan uji lagi (atau keduanya main1
dan salin main2
(dengan main2 "$@"
)) untuk membandingkan. Ini adalah struktur dasar di bawah di pos asli (OP).
Tetapi saya bertanya-tanya: mengapa cangkang itu membutuhkan waktu lama untuk "tidak melakukan apa-apa"? Ya, hanya "beberapa detik", tetapi tetap saja, mengapa?
Ini membuat saya menguji di shell lain untuk menemukan bahwa hanya bash yang memiliki masalah ini.
Coba ksh ./script
(skrip yang sama seperti di atas).
Ini mengarah ke deskripsi ini: memanggil fungsi ( test#
) tanpa argumen akan ditunda oleh argumen di induk ( main#
). Ini adalah deskripsi yang mengikuti dan merupakan pos asli (OP) di bawah ini.
Pos asli.
Memanggil fungsi (dalam Bash 4.4.12 (1) -release) untuk tidak melakukan apa-apa f1(){ :; }
adalah seribu kali lebih lambat daripada :
tetapi hanya jika ada argumen yang didefinisikan dalam fungsi panggilan induk , Mengapa?
#!/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
Hasil dari 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
Tidak ada argumen atau input atau output yang digunakan dalam fungsi f1
, penundaan faktor seribu (1000) tidak terduga. 1
Memperluas tes ke beberapa cangkang, hasilnya konsisten, sebagian besar cangkang tidak memiliki masalah atau menderita keterlambatan (n dan m yang sama digunakan):
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
Hasil:
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 ......
Batalkan komentar dua tes lainnya untuk mengonfirmasi bahwa baik seq
atau memproses daftar argumen adalah sumber untuk keterlambatan.
1 Halini diketahui bahwa lewat hasil dengan argumen akan meningkatkan waktu eksekusi. Terima kasih@slm
Jawaban:
Disalin dari: Mengapa penundaan dalam loop? atas permintaan Anda:
Anda dapat mempersingkat test case ke:
Itu memanggil fungsi sementara
$@
besar yang tampaknya memicu itu.Dugaan saya adalah bahwa waktu dihabiskan menabung
$@
ke tumpukan dan mengembalikannya sesudahnya. Mungkinbash
melakukannya dengan sangat tidak efisien dengan menduplikasi semua nilai atau sesuatu seperti itu. Waktu tampaknya dalam o (n²).Anda mendapatkan jenis waktu yang sama di kulit lain untuk:
Di situlah Anda melewati daftar argumen ke fungsi, dan kali ini shell perlu menyalin nilai-nilai (
bash
akhirnya menjadi 5 kali lebih lambat untuk yang itu).(Awalnya saya pikir itu lebih buruk di bash 5 (saat ini dalam alpha), tapi itu ke debug malloc diaktifkan dalam versi pengembangan seperti dicatat oleh @egmont; juga memeriksa bagaimana distribusi Anda membangun
bash
jika Anda ingin membandingkan bangunan Anda sendiri dengan satu sistem. Misalnya, Ubuntu menggunakan--without-bash-malloc
)sumber
RELSTATUS=alpha
keRELSTATUS=release
dalamconfigure
skrip.--without-bash-malloc
danRELSTATUS=release
ke hasil pertanyaan. Itu masih menunjukkan masalah dengan panggilan ke f.:
dan meningkatkan sedikit pada panggilanf
. Lihatlah timing test2 dalam pertanyaan.