2015-07-13 3 views
7

की तुलना में एकाधिक कोर पर 4x धीमी क्यों चलती है, मैं समझने की कोशिश कर रहा हूं कि सीपीथन के जीआईएल कैसे काम करता है और सीपीथन 2.7.एक्स और सीपीथन 3.4.x में जीआईएल के बीच अंतर क्या है। मैं बेंचमार्किंग के लिए इस कोड का उपयोग कर रहा हूँ:यह पाइथन लिपि एक कोर

from __future__ import print_function 

import argparse 
import resource 
import sys 
import threading 
import time 


def countdown(n): 
    while n > 0: 
     n -= 1 


def get_time(): 
    stats = resource.getrusage(resource.RUSAGE_SELF) 
    total_cpu_time = stats.ru_utime + stats.ru_stime 
    return time.time(), total_cpu_time, stats.ru_utime, stats.ru_stime 


def get_time_diff(start_time, end_time): 
    return tuple((end-start) for start, end in zip(start_time, end_time)) 


def main(total_cycles, max_threads, no_headers=False): 
    header = ("%4s %8s %8s %8s %8s %8s %8s %8s %8s" % 
       ("#t", "seq_r", "seq_c", "seq_u", "seq_s", 
       "par_r", "par_c", "par_u", "par_s")) 
    row_format = ("%(threads)4d " 
        "%(seq_r)8.2f %(seq_c)8.2f %(seq_u)8.2f %(seq_s)8.2f " 
        "%(par_r)8.2f %(par_c)8.2f %(par_u)8.2f %(par_s)8.2f") 
    if not no_headers: 
     print(header) 
    for thread_count in range(1, max_threads+1): 
     # We don't care about a few lost cycles 
     cycles = total_cycles // thread_count 

     threads = [threading.Thread(target=countdown, args=(cycles,)) 
        for i in range(thread_count)] 

     start_time = get_time() 
     for thread in threads: 
      thread.start() 
      thread.join() 
     end_time = get_time() 
     sequential = get_time_diff(start_time, end_time) 

     threads = [threading.Thread(target=countdown, args=(cycles,)) 
        for i in range(thread_count)] 
     start_time = get_time() 
     for thread in threads: 
      thread.start() 
     for thread in threads: 
      thread.join() 
     end_time = get_time() 
     parallel = get_time_diff(start_time, end_time) 

     print(row_format % {"threads": thread_count, 
          "seq_r": sequential[0], 
          "seq_c": sequential[1], 
          "seq_u": sequential[2], 
          "seq_s": sequential[3], 
          "par_r": parallel[0], 
          "par_c": parallel[1], 
          "par_u": parallel[2], 
          "par_s": parallel[3]}) 


if __name__ == "__main__": 
    arg_parser = argparse.ArgumentParser() 
    arg_parser.add_argument("max_threads", nargs="?", 
          type=int, default=5) 
    arg_parser.add_argument("total_cycles", nargs="?", 
          type=int, default=50000000) 
    arg_parser.add_argument("--no-headers", 
          action="store_true") 
    args = arg_parser.parse_args() 
    sys.exit(main(args.total_cycles, args.max_threads, args.no_headers)) 

जब अजगर 2.7.6 के साथ Ubuntu 14.04 के तहत मेरे क्वाड-कोर i5-2500 मशीन पर इस स्क्रिप्ट चल रहा है, मैं निम्नलिखित परिणाम (_R वास्तविक समय के लिए खड़ा है, CPU समय के लिए _c, उपयोगकर्ता मोड के लिए _u, कर्नेल मोड के लिए _S):

#t seq_r seq_c seq_u seq_s par_r par_c par_u par_s 
    1  1.47  1.47  1.47  0.00  1.46  1.46  1.46  0.00 
    2  1.74  1.74  1.74  0.00  3.33  5.45  3.52  1.93 
    3  1.87  1.90  1.90  0.00  3.08  6.42  3.77  2.65 
    4  1.78  1.83  1.83  0.00  3.73  6.18  3.88  2.30 
    5  1.73  1.79  1.79  0.00  3.74  6.26  3.87  2.39 

अब अगर मैं एक कोर करने के लिए सभी धागे बाँध, परिणाम बहुत अलग हैं:

taskset -c 0 python countdown.py 
    #t seq_r seq_c seq_u seq_s par_r par_c par_u par_s 
    1  1.46  1.46  1.46  0.00  1.46  1.46  1.46  0.00 
    2  1.74  1.74  1.73  0.00  1.69  1.68  1.68  0.00 
    3  1.47  1.47  1.47  0.00  1.58  1.58  1.54  0.04 
    4  1.74  1.74  1.74  0.00  2.02  2.02  1.87  0.15 
    5  1.46  1.46  1.46  0.00  1.91  1.90  1.75  0.15 

तो सवाल यह है : इस पायथन क्यों चल रहा है एकाधिक कोर पर कोड 1.5x-2x धीमी दीवार घड़ी और 4x-5x धीमी गति से CPU घड़ी द्वारा इसे एक कोर पर चलाने की तुलना में धीमा है?

आसपास पूछ और दो परिकल्पना का उत्पादन किया googling:

  1. जब कई कोर पर चल रहा है, एक धागा एक अलग कोर जिसका अर्थ है स्थानीय कैश अवैध हो जाता है, इसलिए मंदी पर चलाने के लिए फिर से निर्धारित किया जा सकता।
  2. एक कोर पर धागे को निलंबित करने और इसे किसी अन्य कोर पर सक्रिय करने के ऊपरी हिस्से को उसी कोर पर थ्रेड को निलंबित करने और सक्रिय करने से बड़ा है।

क्या कोई अन्य कारण हैं? मैं समझना चाहता हूं कि क्या हो रहा है और संख्याओं के साथ मेरी समझ को वापस करने में सक्षम होने के लिए (जिसका अर्थ है कि अगर मंदी की वजह से मंदी की वजह है, तो मैं दोनों मामलों के लिए संख्याओं को देखना और तुलना करना चाहता हूं)।

+0

हां, मुझे जीआईएल के बारे में पता है और मुझे आश्चर्य नहीं है कि समांतर धागे में उलटी गिनती चलाना वास्तव में एक धागे की तुलना में धीमी है। मुझे क्या आश्चर्य है और मुझे समझ में नहीं आता है कि इस कोर को एकाधिक कोर पर क्यों चलाना एक कोर पर चलने से इतना धीमा है। – user108884

+0

मैंने देखा कि पहले संस्करण में रिपोर्ट किए गए समय को जोड़ने के दौरान (इसलिए टास्कसेट के बिना), राशि 'समय' द्वारा रिपोर्ट किए गए समय के अनुरूप नहीं थी। यदि 'time.clock()' को 'time.time()' में बदल दिया गया है, तो यह विसंगति दूर हो जाती है। हालांकि, 'टास्कसेट' दृष्टिकोण का उपयोग करते समय अभी भी थोड़ा सा फायदा होता है, यह सुनिश्चित नहीं है कि इसका क्या अर्थ है ... – brm

+0

* nix time.clock() पर CPU समय रिपोर्ट करता है, दीवार घड़ी समय नहीं (https: // दस्तावेज़। python.org/2.7/library/time.html#time.clock)। तो परिणामों को इस तरह से व्याख्या किया जाना चाहिए: इस कोड को एकल कोर की तुलना में एकाधिक कोर पर चलाने के लिए बहुत अधिक CPU प्रयास करना पड़ता है। मैं इन परिणामों पर ठोकर खाने वाला पहला व्यक्ति नहीं हूं (उदा। Https://youtu.be/Obt-vMVdM8s?t=55s), लेकिन मैं स्पष्टीकरण से संतुष्ट नहीं हूं। लेकिन आप सही हैं, मुझे वास्तविक समय को मापना और रिपोर्ट करना चाहिए। मैं कोड अपडेट करूंगा। – user108884

उत्तर

4

जीआईएल के लिए प्रतिस्पर्धा करने के दौरान जीआईएल थ्रैशिंग के कारण जीआईएल थ्रैशिंग के कारण है। इस विषय पर डेविड बेज़ली की सामग्री आपको वह सबकुछ बताएगी जिसे आप जानना चाहते हैं।

क्या हो रहा है के एक अच्छे ग्राफिकल प्रतिनिधित्व के लिए info here देखें।

पायथन 3.2 ने जीआईएल में परिवर्तन पेश किए जो इस समस्या को हल करने में मदद करते हैं ताकि आपको 3.2 और बाद में बेहतर प्रदर्शन देखना चाहिए।

यह भी ध्यान दिया जाना चाहिए कि जीआईएल भाषा के cpython संदर्भ कार्यान्वयन का एक कार्यान्वयन विस्तार है। ज्योथन जैसे अन्य कार्यान्वयन में जीआईएल नहीं है और इस विशेष समस्या का सामना नहीं करते हैं।

The rest of D. Beazley's info on the GIL आपके लिए भी सहायक होगा।

विशेष रूप से आपके प्रश्न का उत्तर देने के लिए कि एकाधिक कोर शामिल होने पर प्रदर्शन इतना खराब क्यों होता है, Inside the GIL प्रस्तुति की स्लाइड 29-41 देखें। यह एक कोर पर एकाधिक धागे के विपरीत मल्टीकोर जीआईएल विवाद पर विस्तृत चर्चा में जाता है। स्लाइड 32 विशेष रूप से दिखाता है कि थ्रेड सिग्नलिंग ओवरहेड के कारण सिस्टम कॉल की संख्या छत के माध्यम से जाती है जब आप कोर जोड़ते हैं। ऐसा इसलिए है क्योंकि धागे अब विभिन्न कोरों पर समान रूप से चल रहे हैं और जो उन्हें एक वास्तविक जीआईएल युद्ध में शामिल होने की अनुमति देता है। एक एकल सीपीयू साझा करने वाले कई धागे के विपरीत। ऊपर प्रस्तुति से एक अच्छा सारांश गोली है:

कई कोर के साथ, सीपीयू बाध्य धागे एक साथ (अलग कोर पर) अनुसूचित हो और फिर एक जीआईएल लड़ाई है।

+0

हां, मैं इस स्पष्टीकरण पर आया जब मैंने पहली बार जीआईएल में खोदना शुरू कर दिया। आगे खुदाई से पता चला कि यह स्पष्टीकरण पर्याप्त नहीं है। पायथन-2.7.6/मॉड्यूल/थ्रेड मॉड्यूल सी: 603 स्थैतिक शून्य टी_बूटस्ट्रैप (शून्य * boot_raw) पर एक नज़र डालें। यह एक ऐसा फ़ंक्शन है जब पायथन थ्रेड प्रारंभ होता है जब पाइथन थ्रेड प्रारंभ होता है (thread_PyThread_start_new_thread द्वारा)। अगर मैं डेव बीज़ली को सही ढंग से समझता हूं, तो उनका मुद्दा यह है कि ओएस थ्रेड चलते रहते हैं और हर समय जीआईएल हासिल करने का प्रयास करते हैं - लेकिन ऐसा नहीं होता है। जीआईएल हासिल करने की कोशिश करते समय ओएस थ्रेड निलंबित हो जाता है और जीआईएल जारी होने तक जागृत नहीं होता है। – user108884

+0

और चलने वाले धागे संभावित रूप से अवरुद्ध I/O संचालन और प्रत्येक sys.getcheckinterval() "ticks" पर जीआईएल जारी करता है। तो उलटी गिनती के मामले में ओएस धागे बहुत बार स्विच हो जाते हैं - इसलिए मंदी। जो मैं पूरी तरह से समझ नहीं पा रहा हूं वह है कि यदि एकाधिक कोर पर धागे चलते हैं तो मंदी अधिक महत्वपूर्ण क्यों होती है। – user108884

+0

वास्तव में सोचा था कि बेज़ले की जानकारी ने इस अंतर को कहीं और संबोधित किया था। हालांकि बाद में इसे देखने की आवश्यकता होगी। अब काम करने के लिए पारगमन में। – mshildt

1

जीआईएल कई पाइथन धागे को एक साथ चलाने के लिए रोकता है। इसका मतलब है कि जब भी एक थ्रेड को पायथन बाइटकोड (कोड का आंतरिक प्रतिनिधित्व) निष्पादित करने की आवश्यकता होती है, तो यह लॉक प्राप्त करेगी (प्रभावी रूप से अन्य कोरों पर अन्य धागे को रोक देगा)। इसके लिए काम करने के लिए, सीपीयू को सभी कैश लाइनों को फ्लश करने की आवश्यकता है। अन्यथा, सक्रिय धागा पुराने डेटा पर काम करेगा।

जब आप एक ही सीपीयू पर धागे चलाते हैं, तो कैश फ्लश आवश्यक नहीं होता है।

यह ज्यादातर मंदी के लिए जिम्मेदार होना चाहिए। यदि आप समानांतर में पायथन कोड को चलाने के लिए चाहते हैं, तो आपको प्रक्रियाओं और आईपीसी (सॉकेट, सेमफोर, मेमोरी मैप किए गए आईओ) का उपयोग करने की आवश्यकता है। लेकिन यह विभिन्न कारणों से धीमा हो सकता है (प्रक्रियाओं के बीच मेमोरी की प्रतिलिपि बनाई जानी चाहिए)।

एक अन्य दृष्टिकोण सी पुस्तकालय में अधिक कोड ले जाया गया है जो इसे निष्पादित करते समय जीआईएल नहीं रखता है। यह समानांतर में अधिक कोड निष्पादित करने की अनुमति देगा।

+0

क्या इस परिकल्पना (कैश के बारे में) संख्याओं के साथ * निक्स पर कोई तरीका है? और क्या आप इसके बारे में अधिक पढ़ने के लिए किसी भी अच्छे स्रोत का सुझाव दे सकते हैं? – user108884

+0

समवर्ती या समांतर? थ्रेड कोड को सिंगल-सीपीयू सिस्टम में भी समवर्ती माना जाता है। – progo

संबंधित मुद्दे