वैरिएबल सामग्री पढ़ने से अधिक तेज़ी से फ़ाइल क्यों खोल रहा है?


36

एक bashस्क्रिप्ट में मुझे /proc/फाइलों से विभिन्न मूल्यों की आवश्यकता होती है । अब तक मेरे पास दर्जनों लाइनें हैं जो फाइलों को सीधे इस तरह से पकड़ती हैं:

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

उस अधिक कुशल बनाने के प्रयास में मैंने फ़ाइल की सामग्री को एक चर में सहेज लिया और उस पर कब्जा कर लिया:

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

फ़ाइल को कई बार खोलने के बजाय इसे बस एक बार खोलना चाहिए और परिवर्तनशील सामग्री को पकड़ना चाहिए, जिसे मैंने माना कि यह तेज होगा - लेकिन वास्तव में यह धीमा है:

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

यहाँ, इसके बारे में नहीं है एक फ़ाइल खोलने बनाम एक चर की सामग्री पढ़ने , लेकिन एक अतिरिक्त प्रक्रिया forking या नहीं के बारे में अधिक।

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 और bash में, यह $aएक अस्थायी फ़ाइल की सामग्री को लिखकर किया जाता है , जो एक अतिरिक्त प्रक्रिया को कम करने की तुलना में कम खर्चीला है, लेकिन संभवतः डेटा को सीधे प्राप्त करने की तुलना में आपको कोई लाभ नहीं देगा /proc/meminfo। यह अभी भी आपके दृष्टिकोण से कम कुशल है जो /proc/meminfoडिस्क पर कॉपी करता है, क्योंकि प्रत्येक फ़ाइल पर अस्थायी फ़ाइल का लेखन किया जाता है।

dashयहां स्ट्रिंग्स का समर्थन नहीं करता है, लेकिन इसके हेरेडकोस को एक पाइप के साथ लागू किया जाता है जिसमें एक अतिरिक्त प्रक्रिया पैदा नहीं होती है। में:

 grep '^MemFree' << EOF
 $a
 EOF

खोल एक पाइप बनाता है, एक प्रक्रिया की तलाश करता है। बच्चा grepअपने स्टड के साथ पाइप के रीडिंग एंड के रूप में निष्पादित करता है , और माता-पिता पाइप के दूसरे छोर पर सामग्री लिखते हैं।

लेकिन पाइप हैंडलिंग और प्रोसेस सिंक्रोनाइज़ेशन अभी भी डेटा को सीधे बंद करने की तुलना में अधिक महंगा होने की संभावना है /proc/meminfo

की सामग्री /proc/meminfoकम है और उत्पादन में ज्यादा समय नहीं लगता है। यदि आप कुछ सीपीयू चक्रों को बचाना चाहते हैं, तो आप महंगे भागों को निकालना चाहते हैं: प्रक्रियाओं को बनाना और बाहरी कमांड चलाना।

पसंद:

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

बचें bashजिसका पैटर्न मिलान बहुत ineficient है, हालांकि। इसके साथ zsh -o extendedglob, आप इसे छोटा कर सकते हैं:

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

ध्यान दें कि ^कई गोले में विशिष्ट है (बॉर्न, मछली, आरसी, एसई और ज़ेडश के साथ एक्सटेंडेडलॉग ऑप्शन कम से कम), मैं इसे उद्धृत करने की सलाह दूंगा। यह भी ध्यान दें कि echoमनमाने ढंग से डेटा का उत्पादन करने के लिए इस्तेमाल नहीं किया जा सकता है (इसलिए मेरा printfउपरोक्त उपयोग )।


4
इस मामले में printfआप कहते हैं कि शेल को दो प्रक्रियाओं को स्पॉन करने की आवश्यकता है, लेकिन printfशेल शेल नहीं है?
डेविड कॉनराड

6
@DavidConrad यह है, लेकिन अधिकांश गोले पाइप लाइन का विश्लेषण करने की कोशिश नहीं करते हैं कि यह वर्तमान प्रक्रिया में किन हिस्सों में चल सकता है। यह सिर्फ अपने आप को चाहता है और बच्चों को इसका पता लगाने देता है। इस मामले में, मूल प्रक्रिया दो बार कांटे; बाईं ओर का बच्चा तब एक अंतर्निहित देखता है और इसे निष्पादित करता है; दाईं ओर का बच्चा देखता grepऔर क्रियान्वित करता है।
शेपनर

1
@DavidConrad, पाइप एक IPC तंत्र है, इसलिए किसी भी स्थिति में दोनों पक्षों को अलग-अलग प्रक्रियाओं में चलना होगा। हालांकि A | B, AT & T ksh या zsh जैसे कुछ गोले हैं Bजो वर्तमान शेल प्रक्रिया में चलते हैं यदि यह एक अंतर्निहित या यौगिक या फ़ंक्शन कमांड है, तो मुझे Aवर्तमान प्रक्रिया में चलने वाले किसी भी का पता नहीं है। यदि कुछ भी करना है, तो उन्हें एक जटिल तरीके से SIGPIPE को संभालना होगा जैसे कि Aबच्चे की प्रक्रिया में चल रहा था और Bजल्दी बाहर निकलने पर व्यवहार के लिए खोल को समाप्त किए बिना खोल को समाप्त कर दिया । Bमूल प्रक्रिया में इसे चलाना बहुत आसान है ।
स्टीफन चेज़लस

बैश समर्थन करता है<<<
डी। बेन नोबल

1
@ D.BenKnoble, मेरा मतलब bashयह नहीं था कि समर्थन करने का मतलब यह नहीं था <<<कि ऑपरेटर ऑपरेटर की zshतरह $(<...)ksh से आया था।
स्टीफन चेजलस

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

जिसे grep कॉल के लिए पूरे 1 सेकंड के बजाय केवल 0.1 सेकंड का समय लगता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.