เหตุใดการเปิดไฟล์จึงเร็วกว่าการอ่านเนื้อหาตัวแปร


36

ในbashสคริปต์ฉันต้องการค่าต่าง ๆ จาก/proc/ไฟล์ จนถึงตอนนี้ฉันมีหลายสิบบรรทัด grepping ไฟล์โดยตรงเช่นนั้น:

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

ในความพยายามที่จะทำให้มีประสิทธิภาพมากขึ้นฉันบันทึกเนื้อหาของไฟล์ไว้ในตัวแปรแล้วทำสิ่งต่อไปนี้:

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

แทนที่จะเปิดไฟล์หลายครั้งควรเปิดเพียงครั้งเดียวและ grep เนื้อหาตัวแปรซึ่งฉันคิดว่าน่าจะเร็วกว่า - แต่อันที่จริงมันช้ากว่า:

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

เช่นเดียวกับที่เป็นจริงสำหรับและdash zshฉันสงสัยว่าสถานะพิเศษของ/proc/ไฟล์เป็นเหตุผล แต่เมื่อฉันคัดลอกเนื้อหาของ/proc/meminfoไปยังไฟล์ปกติและใช้ว่าผลลัพธ์เหมือนกัน:

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

การใช้สตริงที่นี่เพื่อบันทึกไปป์ทำให้เร็วขึ้นเล็กน้อย แต่ก็ยังไม่เร็วเท่ากับไฟล์:

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

เหตุใดการเปิดไฟล์จึงเร็วกว่าการอ่านเนื้อหาเดียวกันจากตัวแปร


@ l0b0 ข้อสันนิษฐานนี้ไม่ผิดคำถามแสดงให้เห็นว่าฉันคิดอย่างไรและคำตอบจะอธิบายว่าทำไมถึงเป็นเช่นนั้น ขณะนี้การแก้ไขของคุณทำให้คำตอบไม่ตอบคำถามของชื่อเรื่องอีกต่อไปพวกเขาไม่ได้พูดว่าเป็นอย่างนั้นหรือไม่
ของหวาน

ตกลงชี้แจง เนื่องจากหัวเรื่องมีความผิดพลาดในกรณีส่วนใหญ่ไม่ใช่เพียงไฟล์หน่วยความจำบางไฟล์ที่ถูกแมป
l0b0

@ l0b0 ไม่นั่นคือสิ่งที่ฉันถามที่นี่: "ฉันสงสัยว่าสถานะของ/proc/ไฟล์พิเศษเป็นเหตุผล แต่เมื่อฉันคัดลอกเนื้อหาของ/proc/meminfoไปยังไฟล์ปกติและใช้ว่าผลลัพธ์เหมือนกัน:" มันไม่ได้พิเศษ/proc/ไฟล์การอ่านไฟล์ปกติก็เร็วขึ้นเช่นกัน!
ของหวาน

คำตอบ:


47

ที่นี่ไม่ได้เกี่ยวกับการเปิดไฟล์เมื่อเทียบกับการอ่านเนื้อหาของตัวแปรแต่เพิ่มเติมเกี่ยวกับการฟอร์กกระบวนการพิเศษหรือไม่

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfoส้อมกระบวนการที่ดำเนินการgrepที่เปิด/proc/meminfo(ไฟล์เสมือนในหน่วยความจำไม่มีดิสก์ I / O ที่เกี่ยวข้อง) อ่านมันและตรงกับ regexp

ส่วนที่แพงที่สุดคือการฟอร์กกระบวนการและโหลดยูทิลิตี้ grep และการพึ่งพาไลบรารีของมัน, ทำการลิงก์แบบไดนามิก, เปิดฐานข้อมูลโลแคล, ไฟล์หลายสิบไฟล์ที่อยู่บนดิสก์

ส่วนเกี่ยวกับการอ่าน/proc/meminfoไม่มีนัยสำคัญในการเปรียบเทียบเคอร์เนลต้องการเวลาน้อยในการสร้างข้อมูลในนั้นและgrepต้องการเวลาน้อยในการอ่าน

หากคุณเรียกใช้strace -cสิ่งนั้นคุณจะเห็นการเรียกใช้ระบบopen()หนึ่งและหนึ่งที่read()ใช้ในการอ่าน/proc/meminfoคือถั่วลิสงเมื่อเปรียบเทียบกับทุกสิ่งที่grepทำเพื่อเริ่มต้น ( strace -cไม่นับการฟอร์ก)

ใน:

a=$(</proc/meminfo)

ในเชลล์ส่วนใหญ่ที่สนับสนุน$(<...)โอเปอเรเตอร์ ksh นั้นเชลล์จะเปิดไฟล์และอ่านเนื้อหาของมัน (และดึงอักขระขึ้นบรรทัดใหม่ที่ต่อท้าย) bashจะแตกต่างกันและมีประสิทธิภาพน้อยกว่ามากในการที่มันทำให้กระบวนการอ่านและส่งผ่านข้อมูลไปยังผู้ปกครองผ่านไปป์ แต่ที่นี่มันทำเพียงครั้งเดียวดังนั้นมันจึงไม่สำคัญ

ใน:

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

เชลล์ต้องการวางไข่สองกระบวนการซึ่งทำงานพร้อมกัน แต่มีปฏิสัมพันธ์ระหว่างกันผ่านไพพ์ การสร้างท่อนั้นการฉีกขาดและการเขียนและการอ่านจากมันมีค่าใช้จ่ายเล็กน้อย ค่าใช้จ่ายที่สูงกว่ามากคือการวางไข่ของกระบวนการพิเศษ การจัดตารางเวลาของกระบวนการมีผลกระทบเช่นกัน

คุณอาจพบว่าการใช้ตัว<<<ดำเนินการzsh ทำให้เร็วขึ้นเล็กน้อย:

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

ใน zsh และทุบตีที่ทำโดยการเขียนเนื้อหาของ$aในไฟล์ชั่วคราวที่มีน้อยราคาแพงกว่าวางไข่กระบวนการพิเศษ แต่อาจจะไม่ให้กำไรใด ๆ /proc/meminfoเมื่อเทียบกับการรับข้อมูลออกตรง ยังคงมีประสิทธิภาพน้อยกว่าวิธีการของคุณที่คัดลอก/proc/meminfoบนดิสก์เนื่องจากการเขียนไฟล์ temp เสร็จสิ้นในแต่ละรอบซ้ำ

dashไม่รองรับที่นี่ - สตริง แต่ heredocs ของมันจะถูกนำมาใช้กับท่อที่ไม่เกี่ยวข้องกับการวางไข่กระบวนการพิเศษ ใน:

 grep '^MemFree' << EOF
 $a
 EOF

เชลล์สร้างไพพ์เพื่อให้เกิดกระบวนการ เด็กดำเนินการgrepกับ stdin ของมันเป็นส่วนท้ายของการอ่านไปป์และผู้ปกครองเขียนเนื้อหาที่ปลายอีกด้านหนึ่งของไปป์

แต่การจัดการไปป์และการซิงโครไนซ์กระบวนการนั้นยังคงมีราคาแพงกว่าเพียงแค่ดึงข้อมูลออกมา/proc/meminfoทันที

เนื้อหา/proc/meminfoสั้นและใช้เวลาไม่นานในการผลิต หากคุณต้องการบันทึกรอบ CPU บางอย่างคุณต้องการลบส่วนที่มีราคาแพง: การฟอร์กกระบวนการและการเรียกใช้คำสั่งภายนอก

ชอบ:

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

หลีกเลี่ยงbashแม้ว่าการจับคู่รูปแบบจะไม่มีประสิทธิภาพมาก ด้วยzsh -o extendedglobคุณสามารถย่อให้เหลือเพียง:

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

โปรดทราบว่า^เป็นพิเศษในหลาย ๆ เชลล์ (บอร์น, ปลา, rc, es และ zsh พร้อมกับตัวเลือก Extendedglob อย่างน้อย) ฉันขอแนะนำให้อ้างถึง นอกจากนี้ทราบว่าechoไม่สามารถใช้ข้อมูลโดยพลการส่งออก (เพราะฉะนั้นการใช้งานของฉันprintfข้างต้น)


4
ในกรณีที่printfคุณบอกว่าเชลล์ต้องการที่จะวางไข่สองกระบวนการ แต่ไม่ใช่printfเชลล์ในตัว?
David Conrad

6
@DavidConrad มันคือ แต่เชลล์ส่วนใหญ่ไม่ได้พยายามวิเคราะห์ไปป์ไลน์ว่าส่วนใดที่มันสามารถทำงานได้ในกระบวนการปัจจุบัน มันแค่แยกตัวเองและปล่อยให้เด็กคิดออก ในกรณีนี้กระบวนการพาเรนต์จะส้อมสองครั้ง เด็กทางด้านซ้ายจะเห็นตัวในและดำเนินการนั้น เด็กทางด้านขวาเห็นgrepและผู้บริหาร
chepner

1
@DavidConrad ไปป์เป็นกลไก IPC ดังนั้นในกรณีใด ๆ ทั้งสองฝ่ายจะต้องทำงานในกระบวนการที่แตกต่างกัน ในขณะที่A | Bมีบางเชลล์เช่น AT&T ksh หรือ zsh ที่ทำงานBในกระบวนการเชลล์ปัจจุบันถ้ามันเป็นคำสั่ง builtin หรือคำสั่งผสมหรือฟังก์ชั่นฉันไม่ทราบว่ามีการทำงานAในกระบวนการปัจจุบัน หากมีสิ่งใดที่ต้องทำเช่นนั้นพวกเขาจะต้องจัดการ SIGPIPE ด้วยวิธีที่ซับซ้อนราวกับว่าAกำลังทำงานในกระบวนการลูกและโดยไม่ยุติเชลล์สำหรับพฤติกรรมที่ไม่น่าแปลกใจเมื่อBออกจากต้น การเรียกใช้Bในกระบวนการหลักนั้นง่ายกว่ามาก
Stéphane Chazelas

Bash รองรับ<<<
D. Ben Knoble

1
@ D.BenKnoble ฉันไม่ได้ตั้งใจจะบอกเป็นนัย ๆbashว่าไม่ได้รับการสนับสนุน<<<เพียงว่าโอเปอเรเตอร์นั้นมาจากzshอย่าง$(<...)นั้นมาจาก ksh
Stéphane Chazelas

6

ในกรณีแรกของคุณคุณเป็นเพียงการใช้ยูทิลิตี้ grep และหาบางสิ่งบางอย่างจากแฟ้ม/proc/meminfo, /procเป็นระบบไฟล์เสมือนดังนั้น/proc/meminfoไฟล์ในหน่วยความจำและมันต้องใช้เวลาน้อยมากที่จะดึงข้อมูลเนื้อหา

แต่ในกรณีที่สองคุณกำลังสร้างไพพ์จากนั้นส่งผ่านเอาต์พุตของคำสั่งแรกไปยังคำสั่งที่สองโดยใช้ไพพ์นี้ซึ่งมีค่าใช้จ่ายสูง

ความแตกต่างเป็นเพราะ/proc(เพราะมันอยู่ในหน่วยความจำ) และไพพ์ดูตัวอย่างด้านล่าง:

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

1

คุณกำลังเรียกคำสั่งภายนอกทั้งสองกรณี (grep) การโทรภายนอกจำเป็นต้องมีการแบ่งย่อย การแยกเปลือกนั้นเป็นสาเหตุพื้นฐานของความล่าช้า ทั้งสองกรณีมีความคล้ายคลึงกันดังนั้น: ความล่าช้าที่คล้ายกัน

หากคุณต้องการอ่านไฟล์ภายนอกเพียงครั้งเดียวและใช้งาน (จากตัวแปร) หลาย ๆ ครั้งอย่าออกจากเชลล์:

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

ซึ่งใช้เวลาเพียงประมาณ 0.1 วินาทีแทนที่จะใช้เวลา 1 วินาทีเต็มสำหรับการโทร grep

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.