क्या मेरी स्क्रिप्ट में कुछ गड़बड़ है या बैश अजगर से ज्यादा धीमा है?


29

मैं 1 अरब बार लूप चलाकर बैश और पायथन की गति का परीक्षण कर रहा था।

$ cat python.py
#!/bin/python
# python v3.5
i=0;
while i<=1000000000:
    i=i+1;

बैश कोड:

$ cat bash2.sh
#!/bin/bash
# bash v4.3
i=0
while [[ $i -le 1000000000 ]]
do
let i++
done

timeकमांड का उपयोग करके मुझे पता चला कि पायथन कोड खत्म होने में सिर्फ 48 सेकंड लगते हैं जबकि स्क्रिप्ट को मारने से पहले बैश कोड को 1 घंटे से अधिक समय लगता था।

ऐसा क्यों है? मुझे उम्मीद थी कि बैश तेज होगा। क्या मेरी स्क्रिप्ट में कुछ गड़बड़ है या बैश इस स्क्रिप्ट के साथ बहुत धीमा है?


49
मुझे यकीन नहीं है कि आपने बैश को पायथन की तुलना में अधिक तेज़ होने की उम्मीद क्यों की थी।
Kusalananda

9
@MatijaNalis नहीं तुम नहीं कर सकते! स्क्रिप्ट को मेमोरी में लोड किया जाता है, पाठ फ़ाइल जिसे वह (स्क्रिप्ट फ़ाइल) से पढ़ रहा था, उसे संपादित करने से रनिंग स्क्रिप्ट पर कोई प्रभाव नहीं पड़ेगा। एक अच्छी बात यह भी है कि, बैश के चलने के बाद हर बार एक फाइल को खोलने और दोबारा पढ़ने के बिना बैश पहले ही काफी धीमा हो जाता है!
terdon


4
बैश फ़ाइल लाइन-बाय-लाइन पढ़ता है क्योंकि यह निष्पादित करता है, लेकिन यह याद रखता है कि यह क्या पढ़ता है अगर यह फिर से उस लाइन पर आता है (क्योंकि यह एक लूप, या फ़ंक्शन में है)। प्रत्येक पुनरावृत्ति को फिर से पढ़ने के बारे में मूल दावा सही नहीं है, लेकिन अभी तक होने वाली लाइनों के लिए संशोधन प्रभावी होंगे। एक दिलचस्प प्रदर्शन: एक फ़ाइल युक्त करें echo echo hello >> $0, और इसे चलाएं।
माइकल होमर

3
@ मतिजा नालिस आह, ठीक है, मैं समझ सकता हूं। यह एक चालू लूप को बदलने का विचार था जिसने मुझे फेंक दिया। संभवतः, प्रत्येक पंक्ति को क्रमिक रूप से पढ़ा जाता है और केवल अंतिम एक समाप्त होने के बाद। हालांकि, एक लूप को एक ही आदेश के रूप में माना जाता है और इसकी संपूर्णता में पढ़ा जाएगा, इसलिए इसे बदलने से रनिंग प्रक्रिया प्रभावित नहीं होगी। दिलचस्प अंतर हालांकि, मैंने हमेशा माना था कि संपूर्ण स्क्रिप्ट को निष्पादन से पहले स्मृति में लोड किया जाता है। इस पर ध्यान दिलाने के लिए धन्यवाद!
terdon

जवाबों:


17

यह बैश में एक ज्ञात बग है; मैन पेज देखें और "BUGS" खोजें:

BUGS
       It's too big and too slow.

;)


शेल स्क्रिप्टिंग और अन्य प्रोग्रामिंग भाषाओं के बीच वैचारिक मतभेदों पर एक उत्कृष्ट प्राइमर के लिए, मैं अत्यधिक पढ़ने की सलाह देता हूं:

सबसे खास अंश:

गोले एक उच्च स्तरीय भाषा है। कोई कह सकता है कि यह भाषा भी नहीं है। वे सभी कमांड लाइन दुभाषियों से पहले हैं। काम उन कमांडों द्वारा किया जाता है जिन्हें आप चलाते हैं और शेल केवल उन्हें ऑर्केस्ट्रेट करने के लिए है।

...

IOW, गोले में, विशेष रूप से पाठ को संसाधित करने के लिए, आप संभव के रूप में कुछ उपयोगिताओं को आमंत्रित करते हैं और उन्हें कार्य में सहयोग करते हैं, क्रम में हजारों उपकरण नहीं चलाते हैं ताकि अगले एक को चलाने से पहले शुरू करने, चलाने, साफ करने के लिए प्रतीक्षा करें।

...

जैसा कि पहले कहा गया था, एक कमांड को चलाने में एक लागत है। एक बड़ी लागत अगर वह कमांड नहीं है, लेकिन भले ही वे बिलिन हैं, तो लागत बड़ी है।

और गोले को इस तरह चलाने के लिए डिज़ाइन नहीं किया गया है, उन्हें प्रदर्शनशील प्रोग्रामिंग भाषा होने का कोई दिखावा नहीं है। वे नहीं हैं, वे सिर्फ कमांड लाइन दुभाषिए हैं। इसलिए, इस मोर्चे पर थोड़ा अनुकूलन किया गया है।


शेल स्क्रिप्टिंग में बड़े छोरों का उपयोग न करें।


54

शेल लूप्स धीमे हैं और बैश सबसे धीमे हैं। गोले लूप में भारी काम करने के लिए नहीं हैं। गोले डेटा के बैचों पर कुछ बाहरी, अनुकूलित प्रक्रियाओं को लॉन्च करने के लिए हैं।


वैसे भी, मैं उत्सुक था कि शेल छोरों की तुलना कैसे होती है इसलिए मैंने थोड़ा बेंचमार्क बनाया:

#!/bin/bash

export IT=$((10**6))

echo POSIX:
for sh in dash bash ksh zsh; do
    TIMEFORMAT="%RR %UU %SS $sh"
    time $sh -c 'i=0; while [ "$IT" -gt "$i" ]; do i=$((i+1)); done'
done


echo C-LIKE:
for sh in bash ksh zsh; do
    TIMEFORMAT="%RR %UU %SS $sh"
    time $sh -c 'for ((i=0;i<IT;i++)); do :; done'
done

G=$((10**9))
TIMEFORMAT="%RR %UU %SS 1000*C"
echo 'int main(){ int i,sum; for(i=0;i<IT;i++) sum+=i; printf("%d\n", sum); return 0; }' |
   gcc -include stdio.h -O3 -x c -DIT=$G - 
time ./a.out

( विवरण:

  • CPU: Intel (R) Core (TM) i5 CPU M 430 @ 2.27GHz
  • ksh: संस्करण श (एटी एंड टी रिसर्च) 93u + 2012-08-01
  • bash: GNU bash, संस्करण 4.3.11 (1) -release (x86_64-pc-linux-gnu)
  • zsh: zsh 5.2 (x86_64-अज्ञात-Linux-gnu)
  • पानी का छींटा: 0.5.7-4ubuntu1

)

(संक्षिप्त) परिणाम (समय प्रति पुनरावृत्ति) हैं:

POSIX:
5.8 µs  dash
8.5 µs ksh
14.6 µs zsh
22.6 µs bash

C-LIKE:
2.7 µs ksh
5.8 µs zsh
11.7 µs bash

C:
0.4 ns C

परिणामों से:

यदि आप थोड़ा तेज़ शेल लूप चाहते हैं, तो यदि आपके पास [[सिंटैक्स है और आप एक तेज़ शेल लूप चाहते हैं, तो आप एक उन्नत शेल में हैं और आपके पास लूप के लिए सी-लाइक भी है। लूप के लिए सी का उपयोग करें, फिर। वे while [एक ही शेल में लगभग 2 गुना तेज -लूप हो सकते हैं ।

  • ksh के पास for (लगभग 2.7shs प्रति पुनरावृत्ति पर सबसे तेज़ लूप है
  • पानी का छींटा सबसे तेज़ while [लगभग 5.8µs प्रति यात्रा है

लूप्स के लिए C तीव्रता के 3-4 दशमलव क्रम हो सकते हैं। (मैंने टॉर्वाल्ड्स लव सी को सुना)।

लूप के लिए अनुकूलित C, बैश के while [लूप (सबसे धीमी शेल लूप) की तुलना में 56500 गुना तेज है और ksh के for (लूप (सबसे तेज शेल लूप) की तुलना में 6750 गुना तेज है ।


फिर से, गोले की सुस्ती हालांकि बहुत मायने नहीं रखती है, क्योंकि गोले के साथ विशिष्ट पैटर्न बाहरी, अनुकूलित कार्यक्रमों की कुछ प्रक्रियाओं को लोड करना है।

इस पैटर्न के साथ, शेल अक्सर पायथन स्क्रिप्ट से बेहतर प्रदर्शन के साथ स्क्रिप्ट लिखना बहुत आसान बनाते हैं (पिछली बार जब मैंने जाँच की थी, तो अजगर में प्रक्रिया पाइपलाइन बनाने के बजाय भद्दा था)।

एक और बात पर विचार करने के लिए स्टार्टअप समय है।

time python3 -c ' '

मेरे पीसी पर 30 से 40 एमएस लगते हैं जबकि गोले 3ms के आसपास लगते हैं। यदि आप बहुत सारी स्क्रिप्ट लॉन्च करते हैं, तो यह जल्दी से जुड़ जाता है और आप अतिरिक्त 27-37 एमएस में बहुत कुछ कर सकते हैं जो कि अजगर सिर्फ शुरू करने के लिए लेता है। छोटी स्क्रिप्ट को उस समय सीमा में कई बार खत्म किया जा सकता है।

(NodeJs शायद इस विभाग में सबसे खराब स्क्रिप्टिंग रनटाइम है क्योंकि इसे शुरू करने के लिए लगभग 100ms लगते हैं (भले ही एक बार यह शुरू हो गया हो, आपको स्क्रिप्टिंग भाषाओं में बेहतर प्रदर्शन करने के लिए कड़ी मेहनत करनी होगी)।


Ksh के लिए, आप (एटी एंड टी कार्यान्वयन निर्दिष्ट करने के लिए चाहते हो सकता है ksh88, एटी एंड टी ksh93, pdksh, mkshवहाँ काफी उन दोनों के बीच बदलाव की एक बहुत कुछ है के रूप में ...)। इसके लिए bash, आप संस्करण को निर्दिष्ट करना चाह सकते हैं। इसने हाल ही में कुछ प्रगति की (जो अन्य गोले पर भी लागू होती है)।
स्टीफन चेजलस

@ स्टीफनचैलेजस धन्यवाद। मैंने प्रयुक्त सॉफ्टवेयर और हार्डवेयर के संस्करण जोड़े।
PSkocik

संदर्भ के लिए: एक प्रक्रिया पाइप लाइन अजगर में आप की तरह कुछ करने के लिए बनाने के लिए: from subprocess import *; p1=Popen(['echo', 'something'], stdout=PIPE); p2 = Popen(['grep', 'pattern'], stdin=p1.stdout, stdout=PIPE); Popen(['wc', '-c'], stdin=PIPE)। यह वास्तव में अनाड़ी है, लेकिन किसी pipelineफ़ंक्शन को कोड करना कठिन नहीं होना चाहिए, जो किसी भी संख्या में प्रक्रियाओं के लिए आपके लिए ऐसा करता है, जिसके परिणामस्वरूप pipeline(['echo', 'something'], ['grep', 'patter'], ['wc', '-c'])
बाकूरी

1
मैंने सोचा कि शायद gcc अनुकूलक पूरी तरह से लूप को खत्म कर रहा था। यह नहीं है, लेकिन यह अभी भी एक दिलचस्प अनुकूलन कर रहा है: यह सिमड के निर्देशों का उपयोग 4 करने के लिए समानांतर में करता है, लूप पुनरावृत्तियों की संख्या को 250000 तक कम कर देता है।
मार्क प्लॉटनिक

1
@PSkocik: यह 2016 में ऑप्टिमाइज़र क्या कर सकता है इसके किनारे पर सही है। ऐसा लगता है कि C ++ 17 यह जनादेश देगा कि कंपाइलर्स को समान समय पर (समान रूप से अनुकूलन भी नहीं) समान भावों की गणना करने में सक्षम होना चाहिए। उस C ++ क्षमता के साथ, GCC इसे C के अनुकूलन के रूप में भी चुन सकता है।
MSalters

18

मैंने थोड़ा परीक्षण किया, और मेरी प्रणाली ने निम्नलिखित पर काम किया - किसी ने भी परिमाण स्पीडअप का क्रम नहीं बनाया, जिसे प्रतिस्पर्धी होने की आवश्यकता होगी, लेकिन आप इसे तेज कर सकते हैं:

टेस्ट 1: 18.233 से

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]
do
    let i++
done

टेस्ट 2: 20.45 से

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]
do 
    i=$(($i+1))
done

टेस्ट 3: 17.64

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]; do let i++; done

टेस्ट 4: 26.69 एस

#!/bin/bash
i=0
while [ $i -le 4000000 ]; do let i++; done

टेस्ट 5: 12.79 एस

#!/bin/bash
export LC_ALL=C

for ((i=0; i != 4000000; i++)) { 
:
}

इस अंतिम एक में महत्वपूर्ण हिस्सा निर्यात LC_ALL = C है। मैंने पाया है कि कई बैश ऑपरेशन काफी तेजी से खत्म होते हैं अगर यह विशेष रूप से किसी भी रेगेक्स फ़ंक्शन का उपयोग किया जाता है। यह {} और: का उपयोग करने के लिए वाक्यविन्यास के लिए एक अनिर्दिष्ट भी दिखाता है।


3
LC_ALL सुझाव के लिए +1, मुझे यह नहीं पता था।
ईनपोकलुम -

+1 दिलचस्प है कि यह कैसे की [[तुलना में बहुत तेज है [। मुझे पता नहीं था LC_ALL = C (BTW आपको इसे निर्यात करने की आवश्यकता नहीं है) ने एक अंतर बनाया।
पीएसकोकिक

@ पीस्कोकिक जहाँ तक मुझे पता है, [[एक बैश बिलियन है, और [वास्तव में है /bin/[, जो कि /bin/testएक बाहरी प्रोग्राम जैसा ही है । जिसके कारण थाय का धीरज है।
tomsmeding

@tomsmending [सभी सामान्य गोले (कोशिश type [) में एक बिलिन है । बाहरी कार्यक्रम अब ज्यादातर अप्रयुक्त है।
पीएसकोकिक १४'१६ को

10

एक शेल कुशल है यदि आप इसका उपयोग उस चीज़ के लिए करते हैं जिसे इसके लिए डिज़ाइन किया गया है (हालांकि दक्षता शायद ही कोई है जिसे आप शेल में खोजते हैं)।

एक शेल एक कमांड-लाइन दुभाषिया है, यह कमांड चलाने के लिए डिज़ाइन किया गया है और उन्हें एक कार्य में सहयोग करता है।

आप 1000000000 करने के लिए गणना करना चाहते हैं, तो आप गिनती करने के लिए, की तरह एक (एक) कमांड seq, bc, awkया python/ perl... 1000000000 रनिंग [[...]]आदेश और 1000000000 letआदेशों बहुत अक्षम हो के लिए बाध्य है, विशेष रूप से साथ bashहै जो सभी की सबसे धीमी खोल है।

उस संबंध में, एक शेल बहुत तेज होगा:

$ time sh -c 'seq 100000000' > /dev/null
sh -c 'seq 100000000' > /dev/null  0.77s user 0.03s system 99% cpu 0.805 total
$ time python -c 'i=0
> while i <= 100000000: i=i+1'
python -c 'i=0 while i <= 100000000: i=i+1'  12.12s user 0.00s system 99% cpu 12.127 total

हालांकि, बेशक, अधिकांश कार्य कमांड द्वारा किया जाता है जो शेल को आमंत्रित करता है, जैसा कि यह होना चाहिए।

अब, आप निश्चित रूप से ऐसा ही कर सकते हैं python:

python -c '
import os
os.dup2(os.open("/dev/null", os.O_WRONLY), 1);
os.execlp("seq", "seq", "100000000")'

लेकिन यह वास्तव में नहीं है कि आप चीजों को कैसे pythonकरेंगे क्योंकि pythonयह मुख्य रूप से एक प्रोग्रामिंग भाषा है, कमांड लाइन दुभाषिया नहीं है।

ध्यान दें कि आप कर सकते हैं:

python -c 'import os; os.system("seq 100000000 > /dev/null")'

लेकिन, pythonवास्तव में उस कमांड लाइन की व्याख्या करने के लिए एक शेल कॉल किया जाएगा!


मुझे आपका जवाब अच्छा लगा। इतने सारे अन्य उत्तर "कैसे" तकनीकों में सुधार पर चर्चा करते हैं, जबकि आप "क्यों" दोनों को कवर करते हैं और ओपी के दृष्टिकोण की कार्यप्रणाली में त्रुटि को संबोधित करते हुए "क्यों" नहीं।
greg.arnott


3

कुछ भी गलत नहीं है (आपकी उम्मीदों को छोड़कर) क्योंकि गैर-संकलित भाषा के लिए अजगर वास्तव में तेज है, https://wiki.python.org/moin/PythonSpeed ​​देखें


1
मैं इस तरह के उत्तर से हतोत्साहित करता हूं, यह टिप्पणी IMHO की है।
LinuxSecurityFreak

2

टिप्पणियों के अलावा, आप कोड को थोड़ा अनुकूलित कर सकते हैं , जैसे

#!/bin/bash
for (( i = 0; i <= 1000000000; i++ ))
do
: # null command
done

इस कोड को थोड़ा कम समय लेना चाहिए ।

लेकिन स्पष्ट रूप से उपवास वास्तव में प्रयोग करने योग्य नहीं है।


-3

मैंने तार्किक रूप से समतुल्य "जबकि" और "जब तक" भाव के उपयोग से बैश में एक नाटकीय अंतर देखा है:

time (i=0 ; while ((i<900000)) ; do  i=$((i+1)) ; done )

real    0m5.339s
user    0m5.324s
sys 0m0.000s

time (i=0 ; until ((i=900000)) ; do  i=$((i+1)) ; done )

real    0m0.000s
user    0m0.000s
sys 0m0.000s

ऐसा नहीं है कि यह वास्तव में सवाल के लिए जबरदस्त प्रासंगिकता रखता है, इसके अलावा शायद कभी-कभी छोटे अंतर एक बड़ा अंतर बनाते हैं, भले ही हम उम्मीद करेंगे कि वे समकक्ष होंगे।


6
इस एक के साथ प्रयास करें ((i==900000))
टॉमाज़

2
आप =असाइनमेंट के लिए उपयोग कर रहे हैं । यह तुरंत सच हो जाएगा। कोई लूप नहीं लगेगा।
वाइल्डकार्ड

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