इतनी जल्दी फाइल करने के लिए `Yes` कैसे लिखता है?


58

मुझे एक उदाहरण देने दें:

$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1

$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2

यहाँ आप उस आदेश को देख सकते हैं yesलिखते हैं 11504640एक दूसरे में लाइनों, जबकि मैं केवल लिख सकते हैं 1953bash का उपयोग 5 सेकंड में लाइनों forऔर echo

जैसा कि टिप्पणियों में सुझाव दिया गया है, इसे और अधिक कुशल बनाने के लिए विभिन्न तरकीबें हैं लेकिन कोई भी इसकी गति के मिलान के करीब नहीं है yes:

$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3

$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4

ये एक सेकंड में 20 हजार लाइन तक लिख सकते हैं। और उन्हें और बेहतर बनाया जा सकता है:

$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5' 
$ wc -l file5
34517 file5

$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6

ये हमें एक सेकंड में 40 हजार लाइन तक मिलती हैं। बेहतर है, लेकिन अभी भी एक बहुत रोना है yesजिसमें से एक सेकंड में लगभग 11 मिलियन लाइनें लिख सकते हैं!

तो, इतनी जल्दी फाइल कैसे yesलिखता है?



9
दूसरे उदाहरण में, आपके पास लूप के प्रत्येक पुनरावृत्ति के लिए दो बाहरी कमांड इनवोकेशन हैं, और dateकुछ भारी-भारी है, साथ ही शेल को echoप्रत्येक लूप पुनरावृत्ति के लिए आउटपुट स्ट्रीम को फिर से खोलना है । पहले उदाहरण में, एकल आउटपुट पुनर्निर्देशन के साथ केवल एक ही कमांड मंगलाचरण है, और कमांड बेहद हल्का है। दोनों किसी भी तरह से तुलनीय नहीं हैं।
बजे एक सीवीएन

@ माइकलकॉर्जलिंग तुम सही dateहो भारी हो सकता है, मेरे सवाल को संपादित देखें।
पंड्या

1
timeout 1 $(while true; do echo "GNU">>file2; done;)कमांड प्रतिस्थापन के समाप्त होने के timeout बाद से ही timeoutकमांड का उपयोग करने का गलत तरीका है। का उपयोग करें timeout 1 sh -c 'while true; do echo "GNU">>file2; done'
मुरु

1
उत्तर का सारांश: write(2)सिस्टम कॉल पर केवल CPU समय खर्च करके , अन्य syscalls, शेल ओवरहेड के बोटलोड पर नहीं, या यहां तक ​​कि अपने पहले उदाहरण में निर्माण की प्रक्रिया करें (जो dateफ़ाइल में छपी प्रत्येक पंक्ति के लिए चलाता है और इंतजार करता है )। लेखन का एक सेकंड बमुश्किल इसके लिए डिस्क आई / ओ (सीपीयू / मेमोरी के बजाय) पर बहुत सारे रैम के साथ एक आधुनिक सिस्टम पर टोंटी के लिए पर्याप्त है। यदि अधिक समय तक चलने दिया जाए, तो अंतर छोटा होगा। (आपके द्वारा उपयोग किए जाने वाले बैश कार्यान्वयन कितना बुरा है, और सीपीयू और डिस्क की सापेक्ष गति के आधार पर, आप बैश के साथ डिस्क I / O को संतृप्त भी नहीं कर सकते हैं)।
पीटर कॉर्डेस

जवाबों:


65

संक्षेप:

yesअधिकांश अन्य मानक उपयोगिताओं जो आम तौर पर करने के लिए इसी तरह के व्यवहार दिखाता बारे में एक करने के लिए फ़ाइल स्ट्रीम उत्पादन के साथ के माध्यम से libc द्वारा बफ़र stdio । ये केवल write()हर 4kb (16kb या 64kb) या जो भी आउटपुट ब्लॉक BUFSIZ है , को syscall करते हैं । echoएक है write()प्रति GNU। यह बहुत सारे मोड-स्विचिंग है (जो कि, जाहिरा तौर पर, संदर्भ-स्विच के रूप में महंगा नहीं है )

और यह उल्लेख करने के लिए बिल्कुल भी नहीं है कि इसके प्रारंभिक अनुकूलन लूप के अलावा, yesएक बहुत ही सरल, छोटा, संकलित सी लूप है और आपका शेल लूप किसी भी तरह से एक कंपाइलर अनुकूलित प्रोग्राम के बराबर नहीं है।


पर मैं गलत था:

जब मैंने कहा कि इससे पहले कि yesइस्तेमाल किया stdio, मैं केवल यह माना जाता है क्योंकि यह ऐसा व्यवहार करता है जो ऐसा करते हैं। यह सही नहीं था - यह केवल इस तरह से उनके व्यवहार का अनुकरण करता है। यह वास्तव में ऐसा करता है जो कि मैंने शेल के साथ नीचे की बात के एक एनालॉग की तरह है: यह पहली बार अपने तर्कों (या yयदि कोई नहीं) को जब्त करने के लिए लूप करता है, जब तक कि वे बिना अधिक से अधिक नहीं बढ़ सकते BUFSIZ

स्रोत से एक टिप्पणी तुरंत संबंधित forपाश राज्यों से पहले:

/* Buffer data locally once, rather than having the
large overhead of stdio buffering each item.  */

yesइसके write()बाद अपना खुद का करता है ।


विषयांतर:

(जैसा कि मूल रूप से प्रश्न में शामिल है और संभवतया यहां पहले से लिखे गए जानकारीपूर्ण स्पष्टीकरण के संदर्भ में बनाए रखा गया है) :

मैंने कोशिश की है timeout 1 $(while true; do echo "GNU">>file2; done;)लेकिन पाश को रोकने में असमर्थ है।

timeoutकमांड प्रतिस्थापन के साथ आपके पास जो समस्या है - मुझे लगता है कि मैं इसे अभी प्राप्त करता हूं, और बता सकता हूं कि यह क्यों नहीं रुकता है। timeoutशुरू नहीं होता है क्योंकि इसकी कमांड-लाइन कभी नहीं चलती है। आपका खोल एक बच्चे के खोल की तलाश करता है, इसके स्टडआउट पर एक पाइप खोलता है, और इसे पढ़ता है। जब बच्चा पढ़ता है तो यह पढ़ना बंद कर देगा, और फिर यह सभी बच्चे की व्याख्या करेगा जो कि $IFSमैन्बलिंग और ग्लोब विस्तार के लिए लिखा है , और परिणामों के साथ यह सब कुछ $(मिलान से बदल देगा )

लेकिन अगर बच्चा एक अंतहीन लूप है जो कभी पाइप को नहीं लिखता है, तो बच्चा कभी भी लूपिंग नहीं रोकता है, और timeoutकमांड कमांड-लाइन पहले कभी पूरी नहीं होती है (जैसा कि मुझे लगता है) आप CTRL-Cबच्चे के लूप को मारते हैं और मारते हैं। तो कभी भी उस लूप timeoutको नहीं मार सकते हैं जिसे शुरू करने से पहले पूरा करने की जरूरत है।


अन्य timeoutएस:

... अपने प्रदर्शन के मुद्दों के लिए बस उतना ही प्रासंगिक नहीं है जितना कि आपके शेल प्रोग्राम को आउटपुट को संभालने के लिए उपयोगकर्ता- और कर्नेल-मोड के बीच स्विच करना चाहिए। timeout, हालांकि, इस उद्देश्य के लिए एक शेल के रूप में लचीला नहीं है: जहां गोले एक्सेल तर्कों को मसलने और अन्य प्रक्रियाओं का प्रबंधन करने की उनकी क्षमता में है।

जैसा कि कहीं और उल्लेख किया गया है, बस [fd-num] >> named_fileलूप के आउटपुट टारगेट पर अपने रीडायरेक्शन को स्थानांतरित करने के बजाय केवल निर्देशन आउटपुट के लिए वहाँ पर लूप कमांड के लिए काफी हद तक प्रदर्शन में सुधार कर सकते हैं क्योंकि उस तरह से कम से कम open()syscall की जरूरत केवल एक बार ही होती है। यह |आंतरिक छोरों के आउटपुट के रूप में लक्षित पाइप के साथ भी नीचे किया गया है।


प्रत्यक्ष तुलना:

आप ऐसा कर सकते हैं:

for cmd in  exec\ yes 'while echo y; do :; done'
do      set +m
        sh  -c '{ sleep 1; kill "$$"; }&'"$cmd" | wc -l
        set -m
done

256659456
505401

कौन सा है प्रकार की कमान उप से पहले वर्णित संबंध की तरह, लेकिन कोई पाइप है और जब तक यह माता-पिता को मारता है बच्चे backgrounded है। में yesमामला माता पिता वास्तव में प्रतिस्थापित किया गया है के बाद से बच्चे को जन्म दिया था, लेकिन खोल कॉल yesनया एक और के साथ अपने स्वयं प्रक्रिया ओवरले करके तो पीआईडी ही रहता है और उसके ज़ोंबी बच्चे अभी भी जो सब के बाद को मारने के लिए जानता है।


बड़ा बफर:

अब शेल के write()बफर को बढ़ाने के बारे में देखते हैं ।

IFS="
";    set y ""              ### sets up the macro expansion       
until [ "${512+1}" ]        ### gather at least 512 args
do    set "$@$@";done       ### exponentially expands "$@"
printf %s "$*"| wc -c       ### 1 write of 512 concatenated "y\n"'s  

1024

मैंने वह संख्या इसलिए चुनी क्योंकि 1kb से अधिक के आउटपुट स्ट्रिंग्स write()मेरे लिए अलग से विभाजित हो रहे थे । और इसलिए यहाँ फिर से लूप है:

for cmd in 'exec  yes' \
           'until [ "${512+:}" ]; do set "$@$@"; done
            while printf %s "$*"; do :; done'
do      set +m
        sh  -c $'IFS="\n"; { sleep 1; kill "$$"; }&'"$cmd" shyes y ""| wc -l
        set -m
done

268627968
15850496

यह पिछले की तुलना में इस परीक्षण के लिए समान मात्रा में शेल द्वारा लिखे गए डेटा का 300 गुना है। जर्जर भी नहीं। लेकिन ऐसा नहीं है yes


सम्बंधित:

जैसा कि अनुरोध किया गया है, इस लिंक पर यहाँ क्या किया गया है, उस पर मात्र कोड टिप्पणियों की तुलना में अधिक गहन वर्णन है ।


@ हेमायल - शायद? मैं पूरी तरह से यकीन नहीं कर रहा हूँ कि तुम क्या पूछ रहे हो? जब कोई प्रोग्राम आउटपुट लिखने के लिए stdio का उपयोग करता है तो वह बिना बफरिंग (जैसे डिफ़ॉल्ट रूप से stderr) या लाइन बफरिंग (डिफ़ॉल्ट रूप से टर्मिनलों पर) या ब्लॉक बफरिंग (मूल रूप से अधिकांश अन्य सामान डिफ़ॉल्ट रूप से इस तरह सेट होता है) के साथ करता है । im क्या उत्पादन बफर का आकार सेट पर थोड़ा अस्पष्ट - लेकिन यह आमतौर पर 4kb में से कुछ है। और इसलिए stdio lib फ़ंक्शन उनके आउटपुट को इकट्ठा करेंगे जब तक कि वे एक पूरे ब्लॉक नहीं लिख सकते। ddएक मानक उपकरण है जो निश्चित रूप से stdio का उपयोग नहीं करता है, उदाहरण के लिए। अधिकांश अन्य करते हैं।
मोकेसर

3
शेल संस्करण कर रहा है open(मौजूदा) writeऔर close(जो मुझे विश्वास है कि अभी भी फ्लश की प्रतीक्षा कर रहा है), और dateप्रत्येक लूप के लिए एक नई प्रक्रिया और निष्पादित करना ।
dave_thompson_085

@ dave_thompson_085 - / dev / चैट पर जाएं । और जो आप कहते हैं वह जरूरी नहीं कि सच हो, जैसा कि आप वहां देख सकते हैं। उदाहरण के लिए, मेरे wc -lसाथ उस लूप bashको करने पर आउटपुट का 1/5 वां हिस्सा मिलता है जो shलूप करता है - bash100k writes()से dash500k से थोड़ा अधिक का प्रबंधन करता है ।
मोकेसर

क्षमा करें, मैं अस्पष्ट था; मेरे पास प्रश्न में शेल संस्करण था, जो उस समय मैंने पढ़ा था कि for((sec0=`date +%S`;...समय को नियंत्रित करने के लिए केवल मूल संस्करण था और लूप में पुनर्निर्देशन, बाद के सुधार नहीं।
dave_thompson_085

@ dave_thompson_085 - इसका जुर्माना। उत्तर वैसे भी कुछ मूलभूत बिंदुओं के बारे में गलत था, और एल आशा के अनुसार अभी बहुत सही होना चाहिए।
मिकसेर्व

20

एक बेहतर सवाल यह होगा कि आपका शेल फ़ाइल को इतनी धीमी गति से क्यों लिख रहा है। कोई भी स्व-संकलित प्रोग्राम जो ज़िम्मेदार तरीके से फ़ाइल लेखन syscalls का उपयोग करता है (एक समय में प्रत्येक वर्ण को फ्लश नहीं करता है) यह यथोचित रूप से त्वरित होगा। आप क्या कर रहे हैं, एक व्याख्या की गई भाषा (शेल) में लाइनें लिख रहे हैं, और इसके अलावा आप बहुत सारे अनावश्यक इनपुट आउटपुट ऑपरेशन करते हैं। क्या yesकरता है:

  • लिखने के लिए एक फ़ाइल खोलता है
  • एक स्ट्रीम में लिखने के लिए अनुकूलित और संकलित फ़ंक्शन को कॉल करता है
  • धारा बफ़र की जाती है, इसलिए syscall (कर्नेल मोड में एक महंगा स्विच) बहुत कम ही होता है, बड़ी मात्रा में
  • एक फ़ाइल बंद करता है

आपकी स्क्रिप्ट क्या करती है:

  • कोड की एक पंक्ति में पढ़ता है
  • कोड की व्याख्या करता है, वास्तव में आपके इनपुट को पार्स करने के लिए बहुत सारे अतिरिक्त संचालन करता है और यह पता लगाता है कि क्या करना है
  • लूप के प्रत्येक पुनरावृत्ति के लिए (जो कि व्याख्या की गई भाषा में सस्ता नहीं है):
    • dateबाहरी कमांड को कॉल करें और उसके आउटपुट को स्टोर करें (केवल मूल संस्करण में - संशोधित संस्करण में आप ऐसा नहीं करके 10 का कारक प्राप्त करते हैं)
    • परीक्षण करें कि लूप की समाप्ति की स्थिति पूरी हुई है या नहीं
    • एपेंड मोड में एक फाइल खोलें
    • पार्स echoकमांड, इसे पहचानें (कुछ पैटर्न मिलान कोड के साथ) शेल बिलिन के रूप में, कॉल पैरामीटर विस्तार और बाकी सब कुछ तर्क "GNU" पर, और अंत में खुली फाइल के लिए लाइन लिखें
    • फ़ाइल को फिर से बंद करें
    • प्रक्रिया को दोहराएं

महंगे हिस्से: पूरी व्याख्या बेहद महंगी है (बैश सभी इनपुट के एक बहुत अधिक प्रीप्रोसेसिंग कर रहा है - आपके स्ट्रिंग में संभवतः चर प्रतिस्थापन, प्रक्रिया प्रतिस्थापन, ब्रेस विस्तार, भागने के वर्ण और अधिक हो सकते हैं), एक कॉलिन का प्रत्येक कॉल है शायद एक फ़ंक्शन पर पुनर्निर्देशन के साथ एक स्विच स्टेटमेंट जो बिलिन से संबंधित है, और बहुत महत्वपूर्ण बात, आप आउटपुट की प्रत्येक और प्रत्येक पंक्ति के लिए एक फ़ाइल को खोलते हैं और बंद करते हैं। आप >> fileइसे बहुत तेज बनाने के लिए लूप के बाहर रख सकते हैं , लेकिन आप अभी भी एक व्याख्या की हुई भाषा में हैं। आप काफी खुशकिस्मत हैं किechoएक शेल बिल्डिन है, बाहरी कमांड नहीं - अन्यथा, आपके लूप में हर एक पुनरावृत्ति पर एक नई प्रक्रिया (कांटा और निष्पादन) शामिल होगी। जो प्रक्रिया को रोक देगा - आपने देखा कि dateलूप में कमांड होने पर आप कितने महंगे हैं ।


11

अन्य उत्तरों ने मुख्य बिंदुओं को संबोधित किया है। एक साइड नोट पर, आप गणना के अंत में आउटपुट फ़ाइल पर लिखकर अपने लूप के थ्रूपुट को बढ़ा सकते हैं। की तुलना करें:

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU" >>/tmp/f; done;

real    0m0.080s
user    0m0.032s
sys     0m0.037s

साथ में

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU"; done>>/tmp/f;

real    0m0.030s
user    0m0.019s
sys     0m0.011s

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