Mengapa "gema" jauh lebih cepat daripada "sentuhan"?

116

Saya mencoba memperbarui stempel waktu ke waktu saat ini di semua file xml dalam direktori saya (secara rekursif). Saya menggunakan Mac OSX 10.8.5.

Pada sekitar 300.000 file, echoperintah berikut ini membutuhkan waktu 10 detik :

for file in `find . -name "*.xml"`; do echo >> $file; done

Namun, touchperintah berikut ini memakan waktu 10 menit ! :

for file in `find . -name "*.xml"`; do touch $file; done

Mengapa gema jauh lebih cepat daripada sentuhan di sini?

polym
sumber
20
Hanya komentar sisi: Anda tidak tahu bahwa kedua perintah tidak setara, bukan? Setidaknya untuk Unix / Linux, echo >> $fileakan ditambahkan baris baru $filedan karenanya memodifikasinya. Saya menganggap itu akan sama untuk OS / X. Jika Anda tidak menginginkannya, gunakan echo -n >> $file.
Dubu
2
Juga tidak touch `find . -name "*.xml"` akan lebih cepat dari kedua hal di atas?
Elmo
4
Atau anggap saja>>$file
gerrit
8
Bukan jawaban untuk pertanyaan eksplisit, tetapi mengapa memohon touchberkali-kali? find . -name '*.xml' -print0 | xargs -0 touchmeminta waktu yang touchjauh lebih sedikit (mungkin hanya sekali). Bekerja di Linux, harus bekerja pada OS X.
Mike Renfro
3
@elmo daftar argumen terlalu panjang (mudah, dengan 300.000 file ...)
Rmano

Jawaban:

161

Dalam bash, touchadalah biner eksternal, tetapi echomerupakan shell bawaan :

$ type echo
echo is a shell builtin
$ type touch
touch is /usr/bin/touch

Karena touchbiner eksternal, dan Anda memanggil touchsekali per file, shell harus membuat 300.000 instance touch, yang membutuhkan waktu lama.

echoNamun, adalah builtin shell, dan eksekusi shell builtin tidak memerlukan forking sama sekali. Sebaliknya, shell saat ini melakukan semua operasi dan tidak ada proses eksternal yang dibuat; inilah alasan mengapa jauh lebih cepat.

Berikut adalah dua profil operasi shell. Anda dapat melihat bahwa banyak waktu dihabiskan untuk mengkloning proses baru saat menggunakan touch. Menggunakan /bin/echobukannya builtin shell harus menunjukkan hasil yang jauh lebih sebanding.


Menggunakan sentuhan

$ strace -c -- bash -c 'for file in a{1..10000}; do touch "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 56.20    0.030925           2     20000     10000 wait4
 38.12    0.020972           2     10000           clone
  4.67    0.002569           0     80006           rt_sigprocmask
  0.71    0.000388           0     20008           rt_sigaction
  0.27    0.000150           0     10000           rt_sigreturn
[...]

Menggunakan gema

$ strace -c -- bash -c 'for file in b{1..10000}; do echo >> "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 34.32    0.000685           0     50000           fcntl
 22.14    0.000442           0     10000           write
 19.59    0.000391           0     10011           open
 14.58    0.000291           0     20000           dup2
  8.37    0.000167           0     20013           close
[...]
Chris Down
sumber
1
Apakah Anda mengkompilasi strace pada OS X atau menjalankan tes pada OS lain?
bmike
1
@ bmike Tes saya di Linux, tetapi prinsipnya identik.
Chris Down
Saya sangat setuju - lihat komentar saya pada pertanyaan utama tentang bagaimana / bin / echo lambat / bin / touch sehingga alasannya adalah suara. Saya hanya ingin mereproduksi waktu strace dan gagal menggunakan dtruss / dtrace dan sintaks bash -c tidak berfungsi seperti yang diharapkan pada OS X juga.
bmike
71

Seperti orang lain telah menjawab, menggunakan echoakan lebih cepat dari touchyang echoadalah perintah yang umum (meskipun tidak diperlukan untuk menjadi) built-in ke shell. Menggunakannya membuang-buang dengan overhead kernel terkait dengan menjalankan memulai proses baru untuk setiap file yang Anda dapatkan touch.

Namun, perhatikan bahwa cara tercepat untuk mencapai efek ini masih digunakan touch, tetapi daripada menjalankan program satu kali untuk setiap file, dimungkinkan untuk menggunakan -execopsi dengan finduntuk memastikan bahwa hanya dijalankan beberapa kali. Pendekatan ini biasanya akan lebih cepat karena menghindari overhead yang terkait dengan loop shell:

find . -name "*.xml" -exec touch {} +

Menggunakan +(sebagai lawan dari \;) dengan find ... -execmenjalankan perintah hanya sekali jika memungkinkan dengan setiap file sebagai argumen. Jika daftar argumen sangat panjang (seperti halnya dengan 300.000 file) beberapa jalan akan dibuat dengan daftar argumen yang memiliki panjang dekat dengan batas ( ARG_MAXpada kebanyakan sistem).

Keuntungan lain dari pendekatan ini adalah bahwa ia berperilaku kuat dengan nama file yang mengandung semua karakter spasi putih yang tidak terjadi pada loop asli.

Graeme
sumber
17
+1untuk menunjukkan +argumen find . Saya pikir banyak orang tidak menyadari hal ini (saya tidak).
gerrit
7
Tidak semua versi findmemiliki +argumen. Anda bisa mendapatkan efek yang serupa dengan memipipkan ke xargs.
Barmar
5
@Barmar, +bagian ini diperlukan oleh POSIX, jadi harus portabel. -print0bukan.
Graeme
1
Saya kadang-kadang masih mengalami implementasi yang tidak memilikinya. YMMV.
Barmar
1
@ ChrisDown, Sesuatu yang saya temukan adalah bahwa Busybox findmemiliki opsi yang tersedia tetapi hanya memperlakukannya seperti di ;bawah permukaan.
Graeme
29

echoadalah shell builtin. Di sisi lain, touchadalah biner eksternal.

$ type echo
echo is a shell builtin
$ type touch
touch is hashed (/usr/bin/touch)

Shell bawaan jauh lebih cepat karena tidak ada overhead yang terlibat dalam memuat program, yaitu tidak ada fork/ execterlibat. Dengan demikian, Anda akan mengamati perbedaan waktu yang signifikan ketika mengeksekusi perintah builtin vs eksternal beberapa kali.

Inilah alasan mengapa utilitas seperti timetersedia sebagai shell bawaan.

Anda bisa mendapatkan daftar lengkap shell builtins dengan mengatakan:

enable -p

Seperti disebutkan di atas, menggunakan utilitas yang bertentangan dengan hasil builtin dalam penurunan kinerja yang signifikan. Berikut ini adalah statistik waktu yang diperlukan untuk membuat ~ 9000 file menggunakan builtin echo dan utilitas echo :

# Using builtin
$ time bash -c 'for i in {1000..9999}; do echo > $i; done'

real    0m0.283s
user    0m0.100s
sys 0m0.184s

# Using utility /bin/echo
$ time bash -c 'for i in {1000..9999}; do /bin/echo > $i; done'

real    0m8.683s
user    0m0.360s
sys 0m1.428s
devnull
sumber
Dan saya pikir ada echobiner pada sebagian besar sistem (bagi saya itu /bin/echo), sehingga Anda dapat mencoba kembali tes pengaturan waktu menggunakan itu daripada built-in
Michael Mrozek
@MichaelMrozek Menambahkan tes timing untuk builtin dan biner.
devnull