Mengapa membuka file lebih cepat daripada membaca konten variabel?

36

Dalam bashskrip saya membutuhkan berbagai nilai dari /proc/file. Sampai sekarang saya memiliki lusinan baris yang melihat file secara langsung seperti itu:

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

Dalam upaya untuk menjadikannya lebih efisien, saya menyimpan konten file dalam sebuah variabel dan memahami bahwa:

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

Alih-alih membuka file beberapa kali ini hanya harus membukanya sekali dan mengambil konten variabel, yang saya anggap akan lebih cepat - tetapi sebenarnya lebih lambat:

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

Hal yang sama berlaku untuk dashdan zsh. Saya mencurigai keadaan khusus /proc/file sebagai alasan, tetapi ketika saya menyalin konten /proc/meminfoke file biasa dan menggunakannya hasilnya sama:

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

Menggunakan string di sini untuk menyimpan pipa membuatnya sedikit lebih cepat, tetapi masih tidak secepat file:

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

Mengapa membuka file lebih cepat daripada membaca konten yang sama dari suatu variabel?

pencuci mulut
sumber
@ l0b0 Asumsi ini tidak salah, pertanyaannya menunjukkan bagaimana saya memunculkannya dan jawabannya menjelaskan mengapa ini terjadi. Suntingan Anda sekarang membuat jawaban tidak lagi menjawab pertanyaan judul: Mereka tidak mengatakan apakah itu masalahnya.
hidangan penutup
OKE, diklarifikasi. Karena judulnya salah dalam sebagian besar kasus, hanya saja memori tertentu tidak memetakan file khusus.
l0b0
@ l0b0 Tidak, itu apa yang saya bertanya di sini: “Saya menduga keadaan khusus /proc/file sebagai alasan, tapi ketika saya menyalin isi dari /proc/meminfosebuah file biasa dan penggunaan yang hasilnya sama:” Hal ini tidak khusus untuk /proc/file, membaca file biasa juga lebih cepat!
hidangan penutup

Jawaban:

47

Di sini, ini bukan tentang membuka file versus membaca konten variabel tetapi lebih lanjut tentang forking proses tambahan atau tidak.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfoforks proses yang mengeksekusi grepyang membuka /proc/meminfo(file virtual, dalam memori, tidak ada disk I / O yang terlibat) membacanya dan cocok dengan regexp.

Bagian yang paling mahal di dalamnya adalah proses forking dan memuat utilitas grep dan dependensi pustaka, melakukan penautan dinamis, membuka basis data lokal, puluhan file yang ada di disk (tetapi kemungkinan di-cache dalam memori).

Bagian tentang membaca /proc/meminfotidak signifikan dibandingkan, kernel membutuhkan sedikit waktu untuk menghasilkan informasi di sana dan grepmembutuhkan sedikit waktu untuk membacanya.

Jika Anda menjalankannya strace -c, Anda akan melihat satu open()dan satu read()sistem panggilan yang digunakan untuk membaca /proc/meminfoadalah kacang dibandingkan dengan segala sesuatu yang lain grepuntuk memulai ( strace -ctidak termasuk forking).

Di:

a=$(</proc/meminfo)

Dalam kebanyakan shell yang mendukung $(<...)operator ksh itu, shell hanya membuka file dan membaca kontennya (dan menghapus karakter baris baru yang tertinggal). bashberbeda dan jauh lebih tidak efisien dalam hal garpu proses untuk melakukan itu membaca dan meneruskan data ke induk melalui pipa. Tapi di sini, dilakukan sekali jadi tidak masalah.

Di:

printf '%s\n' "$a" | grep '^MemFree'

Shell perlu menelurkan dua proses, yang berjalan bersamaan tetapi berinteraksi satu sama lain melalui pipa. Penciptaan pipa, penghancuran, dan menulis serta membaca darinya memiliki sedikit biaya. Biaya yang jauh lebih besar adalah pemijahan proses ekstra. Penjadwalan proses juga memiliki dampak.

Anda mungkin menemukan bahwa menggunakan <<<operator zsh membuatnya sedikit lebih cepat:

grep '^MemFree' <<< "$a"

Dalam zsh dan bash, itu dilakukan dengan menulis konten $adalam file sementara, yang lebih murah daripada menelurkan proses tambahan, tetapi mungkin tidak akan memberi Anda keuntungan dibandingkan dengan mendapatkan data langsung /proc/meminfo. Itu masih kurang efisien daripada pendekatan Anda yang menyalin /proc/meminfopada disk, karena penulisan file temp dilakukan pada setiap iterasi.

dashtidak mendukung string di sini, tetapi heredocs diimplementasikan dengan pipa yang tidak melibatkan pemijahan proses tambahan. Di:

 grep '^MemFree' << EOF
 $a
 EOF

Shell membuat pipa, mengarungi proses. Anak dieksekusi grepdengan stdin sebagai ujung pembacaan pipa, dan orang tua menulis konten di ujung pipa yang lain.

Tetapi penanganan pipa dan sinkronisasi proses masih cenderung lebih mahal daripada hanya mendapatkan data secara langsung /proc/meminfo.

Konten /proc/meminfopendek dan tidak membutuhkan banyak waktu untuk menghasilkan. Jika Anda ingin menyimpan beberapa siklus CPU, Anda ingin menghapus komponen mahal: proses forking dan menjalankan perintah eksternal.

Seperti:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

Hindari bashmeskipun pencocokan polanya sangat tidak efisien. Dengan zsh -o extendedglob, Anda dapat mempersingkatnya menjadi:

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

Catatan yang ^spesial di banyak shell (Bourne, fish, rc, es dan zsh dengan opsi extendedglob setidaknya), saya sarankan mengutipnya. Perhatikan juga bahwa echotidak dapat digunakan untuk menampilkan data yang sewenang-wenang (oleh karena itu saya menggunakan di printfatas).

Stéphane Chazelas
sumber
4
Dalam kasus dengan printfAnda mengatakan shell perlu menelurkan dua proses, tetapi bukankah printfshell builtin?
David Conrad
6
@ Davidvidon Yaitu, tetapi sebagian besar shell tidak mencoba untuk menganalisis pipa untuk bagian mana ia bisa berjalan dalam proses saat ini. Itu hanya memotong sendiri dan membiarkan anak-anak mencari tahu. Dalam hal ini, proses induk bercabang dua kali; anak untuk sisi kiri kemudian melihat built-in dan menjalankannya; anak untuk sisi kanan melihat grepdan mengeksekusi.
chepner
1
@ Davidvidon, pipa adalah mekanisme IPC, jadi bagaimanapun kedua belah pihak harus berjalan dalam proses yang berbeda. Sementara di A | B, ada beberapa shell seperti AT&T ksh atau zsh yang berjalan Bdalam proses shell saat ini jika itu adalah perintah builtin atau compound atau fungsi, saya tidak tahu ada yang berjalan Adalam proses saat ini. Jika ada, untuk melakukan itu, mereka harus menangani SIGPIPE dengan cara yang kompleks seolah-olah Asedang berjalan dalam proses anak dan tanpa menghentikan shell agar perilaku tidak terlalu mengejutkan ketika Bkeluar lebih awal. Jauh lebih mudah dijalankan Bdalam proses induk.
Stéphane Chazelas
Bash mendukung<<<
D. Ben Knoble
1
@ D.BenKnoble, saya tidak bermaksud menyiratkan bashtidak mendukung <<<, hanya saja operator datang dari zshseperti $(<...)berasal dari ksh.
Stéphane Chazelas
6

Dalam kasus pertama Anda, Anda hanya menggunakan utilitas grep dan menemukan sesuatu dari file /proc/meminfo, /procadalah sistem file virtual sehingga /proc/meminfofile ada di memori, dan itu membutuhkan sedikit waktu untuk mengambil kontennya.

Tetapi dalam kasus kedua, Anda membuat pipa, lalu meneruskan output perintah pertama ke perintah kedua menggunakan pipa ini, yang mahal.

Perbedaannya adalah karena /proc(karena ada di memori) dan pipa, lihat contoh di bawah ini:

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s
Prvt_Yadav
sumber
1

Anda memanggil perintah eksternal dalam kedua kasus (grep). Panggilan eksternal memerlukan subkulit. Mencungkil cangkang adalah penyebab mendasar dari penundaan ini. Kedua kasus serupa, dengan demikian: penundaan yang sama.

Jika Anda ingin membaca file eksternal hanya sekali dan menggunakannya (dari variabel) beberapa kali jangan keluar dari shell:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

Yang hanya memakan waktu sekitar 0,1 detik, bukannya 1 detik penuh untuk panggilan grep.

Ishak
sumber