संभवतः हर कोई जो कम से कम एक बार अजगर में रुचि रखता है, वह जीआईएल के बारे में जानता है - यह एक मजबूत और कमजोर स्थान है।
एकल-थ्रेडेड स्क्रिप्ट को काम करने से रोकने के बिना, वह सीपीयू-बाउंड कार्यों (जब थ्रेड्स निष्पादित होते हैं, बजाय I / O, आदि की प्रत्याशा में वैकल्पिक रूप से लटकाए जाते हैं) पर बहु-थ्रेडेड कार्य के दौरान पहिया में उचित मात्रा में चिपक जाती है।
विवरण
दो साल पुराने अनुवाद में अच्छी तरह से वर्णित हैं। हम सही थ्रेड समानांतरकरण के लिए आधिकारिक पायथन असेंबली में जीआईएल को दूर नहीं कर सकते हैं, लेकिन हम दूसरे तरीके से जा सकते हैं - सिस्टम को गुठली के बीच पायथन धागे को स्थानांतरित करने से रोक सकते हैं। सामान्य तौर पर, श्रृंखला की एक पोस्ट, "यदि आपको ज़रूरत नहीं है, लेकिन वास्तव में" :)
यदि आप प्रोसेसर / सीपीयू आत्मीयता, ctypes और pywin32 के बारे में जानते हैं, तो कोई नई बात नहीं होगी।
यह सब कैसे शुरू हुआ
आइए एक सरल कोड लेते हैं (लगभग अनुवाद लेख की तरह):
cnt = 100000000 trying = 2 def count(): n = cnt while n>0: n-=1 def test1(): count() count() def test2(): t1 = Thread(target=count) t1.start() t2 = Thread(target=count) t2.start() t1.join(); t2.join() seq1 = timeit.timeit( 'test1()', 'from __main__ import test1', number=trying )/trying print seq1 par1 = timeit.timeit( 'test2()', 'from __main__ import test2', number=trying )/trying print par1
अजगर 2.6.5 पर चलाएं (ubuntu 10.04 x64, i5 750):
10.41
13.25
और अजगर 2.7.2 पर (win7 x64, i5 750):
19.25
27.41
तुरंत छोड़ दें कि जीत-संस्करण स्पष्ट रूप से धीमा है। दोनों मामलों में, समानांतर संस्करण की एक महत्वपूर्ण मंदी दिखाई देती है।
यदि आप वास्तव में चाहते हैं, तो आप कर सकते हैं
जीआईएल किसी भी स्थिति में बहु-थ्रेडेड संस्करण को रैखिक की तुलना में तेजी से चलने की अनुमति नहीं देगा। हालांकि, यदि कोड में थ्रेडिंग शुरू करके एक निश्चित कार्यक्षमता के कार्यान्वयन को सरल बनाया जाता है, तो यह कम से कम इस अंतर को कम करने की कोशिश करने के लायक है।
जब एक बहु-थ्रेडेड एप्लिकेशन चल रहा होता है, तो ओएस मनमाने ढंग से कोर के बीच विभिन्न थ्रेड्स को "ट्रांसफर" कर सकता है। और जब एक ही अजगर प्रक्रिया के दो (या अधिक) धागे एक साथ जीआईएल को पकड़ने की कोशिश करते हैं, तो ब्रेक शुरू हो जाते हैं। स्थानांतरण भी एक एकल-थ्रेडेड प्रोग्राम के लिए किया जाता है, लेकिन वहां यह गति को प्रभावित नहीं करता है।
तदनुसार, धागे के लिए जीआईएल को एक-एक करके कैप्चर करने के लिए, आप अजगर प्रक्रिया को एक कोर तक सीमित कर सकते हैं। और एफिनिटी मास्क सीपीयू हमें इसमें मदद करेगा, जो कि बिट्स फ्लैग के प्रारूप में यह इंगित करने के लिए
अनुमति देता है कि प्रोग्राम को चलाने के लिए कौन से कर्नेल / प्रोसेसर की
अनुमति है।
विभिन्न ऑपरेटिंग सिस्टम पर, यह ऑपरेशन अलग-अलग तरीकों से किया जाता है, लेकिन अब हम Ubuntu Linux और WinXP + पर विचार करेंगे।
इंटेल एक्सॉन पर FreeBSD 8.2 का भी अध्ययन किया गया था, लेकिन यह लेख के दायरे से बाहर रहेगा।और हमारे पास कितने विकल्प हैं?
गुठली चुनने से पहले, आपको यह तय करने की आवश्यकता है कि उनमें से कितने हमारे निपटान में हैं। यहाँ यह मंच की विशेषताओं से नाचने लायक है: मल्टीप्रोसेसिंग। सीपीयू_काउंट () पायथन 2.6+ में, os.sysconf ('SC_NPROCESSORS_ONLN') POSIX द्वारा, आदि। एक उदाहरण की परिभाषा
यहाँ मिल सकती
है ।
प्रोसेसर आत्मीयता के साथ सीधे काम करने के लिए, निम्नलिखित का चयन किया गया था:
लिनक्स उबंटू
Libc तक पहुँचने के लिए हम
ctypes मॉड्यूल का उपयोग करेंगे। इच्छित पुस्तकालय लोड करने के लिए, हम ctypes.CDLL का उपयोग करते हैं:
libc = ctypes.CDLL( 'libc.so.6' ) libc.sched_setaffinity
सब कुछ ठीक होगा, लेकिन दो बिंदु हैं:
- Libc.so.6 नाम का हार्ड असाइनमेंट पोर्टेबल नहीं है, और libc.so फ़ाइल, जिसे वास्तविक संस्करण के लिए सिमलिंक होना चाहिए, को डेबियन / उबंटू पर एक टेक्स्ट फ़ाइल बनाया गया है।
फिलहाल, सभी फाइलों की खोज के लिए एक बैसाखी बनाई गई है, जिनके नाम "libc.so" से शुरू होते हैं और उन्हें OSError प्रसंस्करण के साथ लोड करने का प्रयास किया जाता है। डाउनलोड किया गया - यह हमारा पुस्तकालय है।
अगर कोई सबसे अच्छा और सार्वभौमिक समाधान जानता है - मैं इसे टिप्पणियों में या पीएम में देखूंगा।
- एक फ़ंक्शन नाम निर्दिष्ट करना पर्याप्त नहीं है। मापदंडों की संख्या और उनके प्रकार भी आवश्यक हैं। ऐसा करने के लिए, हम उन कार्यों के लिए "जादू" विशेषता argtypes का उपयोग करेंगे जिनकी हमें आवश्यकता है।
हमारे कार्य:
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
pid_t एक int है, cpu_set_t आकार में 1024 बिट्स की एकल-फ़ील्ड संरचना है (यानी 1024 कोर / प्रोसेसर के साथ काम करना संभव है)।
हम सभी कर्नेल के साथ तुरंत काम नहीं करने के लिए cpusetsize का उपयोग करेंगे और यह मानते हैं कि cpu_set_t एक अहस्ताक्षरित लंबी है।
सामान्य तौर पर, ctypes.Arrays का उपयोग किया जाना चाहिए, लेकिन यह लेख के दायरे से परे है।यह भी ध्यान देने योग्य है कि मुखौटा एक सूचक के रूप में पारित किया जाता है, अर्थात्। ctypes.POINTER (<मूल्य का ही प्रकार>)।
C और ctypes प्रकारों के मिलान के बाद हमें:
__setaffinity = _libc.sched_setaffinity __setaffinity.argtypes = [ctypes.c_int, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] __getaffinity = _libc.sched_getaffinity __getaffinity.argtypes = [ctypes.c_int, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
Argtypes को निर्दिष्ट करने के बाद, पारित किए गए मानों के प्रकार की निगरानी ctypes द्वारा की जाती है। ताकि मॉड्यूल शपथ न लें, लेकिन अपना काम करता है, हम कॉल करते समय मूल्यों को सही ढंग से इंगित करेंगे:
def get_affinity(pid=0): mask = ctypes.c_ulong(0)
जाहिर है, ctypes खुद ही स्पष्ट रूप से सूचक के साथ निपटा। यह भी ध्यान देने योग्य है कि वर्तमान प्रक्रिया पर pid = 0 के साथ कॉल किया जाता है।
Windows XP +
हमारे द्वारा बताए गए कार्यों के लिए प्रलेखन:
न्यूनतम समर्थित क्लाइंट - विंडोज एक्सपी
न्यूनतम समर्थित सर्वर - विंडोज सर्वर 2003
DLL - कर्नेल 32.dll
अब हम जानते हैं कि यह कब काम करेगा और किस पुस्तकालय को लोड किया जाना चाहिए।
हम इसे लिनक्स संस्करण के साथ सादृश्य द्वारा करते हैं। सुर्खियाँ लें:
BOOL WINAPI SetProcessAffinityMask( __in HANDLE hProcess, __in DWORD_PTR dwProcessAffinityMask ); BOOL WINAPI GetProcessAffinityMask( __in HANDLE hProcess, __out PDWORD_PTR lpProcessAffinityMask, __out PDWORD_PTR lpSystemAffinityMask );
HANDLE के रूप में, हम ctypes.c_uint के साथ काफी खुश हैं, लेकिन बाहर के प्रकारों के साथ आपको रहना आवश्यक है:
DWORD_PTR एक ही ctypes.c_uint है, और
P DWORD_PTR पहले से ही ctypes.POINTER (ctypes.c_uint) है।
कुल हमें मिलता है:
__setaffinity = ctypes.windll.kernel32.SetProcessAffinityMask __setaffinity.argtypes = [ctypes.c_uint, ctypes.c_uint] __getaffinity = ctypes.windll.kernel32.GetProcessAffinityMask __getaffinity.argtypes = [ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)]
और ऐसा लगता है कि हम इसे वहां करेंगे और यह काम करेगा:
def get_affinity(pid=0): mask_proc = ctypes.c_uint(0) mask_sys = ctypes.c_uint(0) if not __getaffinity(pid, mask_proc, mask_sys): raise ValueError return mask_proc.value def set_affinity(pid=0, mask=1): mask_proc = ctypes.c_uint(mask) res = __setaffinity(pid, mask_proc) if not res: raise OSError return
लेकिन अफसोस । कार्य स्वीकार नहीं करते हैं, लेकिन प्रक्रिया का आधार नहीं है। इसे अभी भी प्राप्त करने की आवश्यकता है। ऐसा करने के लिए, हम
OpenProcess फ़ंक्शन और
क्लोजहैंडल "युग्मित" का उपयोग करते हैं:
PROCESS_SET_INFORMATION = 512 PROCESS_QUERY_INFORMATION = 1024 __close_handle = ctypes.windll.kernel32.CloseHandle def __open_process(pid, ro=True): if not pid: pid = os.getpid() access = PROCESS_QUERY_INFORMATION if not ro: access |= PROCESS_SET_INFORMATION hProc = ctypes.windll.kernel32.OpenProcess(access, 0, pid) if not hProc: raise OSError return hProc
विवरणों में जाने के बिना, हमें बस उस प्रक्रिया का आधार मिलता है जिसकी हमें मापदंडों को पढ़ने के लिए पहुँच की आवश्यकता है, और जब आरओ = गलत, और उन्हें बदलने के लिए। यह SetProcessAffinityMask और GetProcessAffinityMask के दस्तावेज़ में लिखा गया है:
SetProcessAffinityMask:
HProcess [में]
प्रक्रिया का एक संभाल जिसका आत्मीयता मुखौटा सेट किया जाना है। इस हैंडल में PROCESS_SET_INFORMATION पहुंच सही होनी चाहिए।
GetProcessAffinityMask:
HProcess [में]
प्रक्रिया का एक संभाल जिसका आत्मीयता मुखौटा वांछित है।
Windows Server 2003 और Windows XP: हैंडल में PROCESS_QUERY_INFORMATION पहुंच सही होनी चाहिए।
तो कोई मोंटे कार्लो विधि :)
परिवर्तनों को प्रतिबिंबित करने के लिए हमारी get_affinity और set_affinity को फिर से लिखें:
def get_affinity(pid=0): hProc = __open_process(pid) mask_proc = ctypes.c_uint(0) mask_sys = ctypes.c_uint(0) if not __getaffinity(hProc, mask_proc, mask_sys): raise ValueError __close_handle(hProc) return mask_proc.value def set_affinity(pid=0, mask=1): hProc = __open_process(pid, ro=False) mask_proc = ctypes.c_uint(mask) res = __setaffinity(hProc, mask_proc) __close_handle(hProc) if not res: raise OSError return
WindowsXP + आलसी के लिए
विन कार्यान्वयन के लिए कोड की मात्रा को थोड़ा कम करने के लिए, आप
pywin32 मॉड्यूल को स्थापित कर सकते हैं। यह हमें स्थिरांक स्थापित करने और पुस्तकालयों और कॉल मापदंडों से निपटने से बचाता है। ऊपर हमारा कोड कुछ इस तरह दिखाई दे सकता है:
import win32process, win32con, win32api, win32security import os def __open_process(pid, ro=True): if not pid: pid = os.getpid() access = win32con.PROCESS_QUERY_INFORMATION if not ro: access |= win32con.PROCESS_SET_INFORMATION hProc = win32api.OpenProcess(access, 0, pid) if not hProc: raise OSError return hProc def get_affinity(pid=0): hProc = __open_process(pid) mask, mask_sys = win32process.GetProcessAffinityMask(hProc) win32api.CloseHandle(hProc) return mask def set_affinity(pid=0, mask=1): try: hProc = __open_process(pid, ro=False) mask_old, mask_sys_old = win32process.GetProcessAffinityMask(hProc) res = win32process.SetProcessAffinityMask(hProc, mask) win32api.CloseHandle(hProc) if res: raise OSError except win32process.error as e: raise ValueError, e return mask_old
संक्षेप में, स्पष्ट रूप से, लेकिन यह एक तृतीय-पक्ष मॉड्यूल है।
और परिणाम क्या है?
यदि आप इसे एक साथ रखते हैं और हमारे प्रारंभिक परीक्षणों में एक और जोड़ते हैं:
def test3(): cpuinfo.affinity.set_affinity(0,1)
फिर परिणाम निम्नानुसार होंगे:
linux:
test1: 10.41 | 102.89
test2: 13.25 | 135.29
test3: 10.45 | 104.51
विंडोज:
test1: 19.25 | 191.97
test2: 27.41 | 269.78
test3: 19.52 | 196.17
दूसरे कॉलम में नंबर एक ही टेस्ट हैं, लेकिन
cnt 10 गुना बड़ा है।
हमें एकल-थ्रेडेड संस्करण की तुलना में गति में लगभग कोई नुकसान नहीं हुआ।
एफिनिटी दोनों ओएस पर एक बिट मास्क द्वारा निर्धारित की जाती है। 4-कोर मशीन पर,
get_affinity 15 (1 + 2 + 4 + 8) देता है।
जीथब पर पोस्ट किए गए लेख के लिए एक उदाहरण और सभी कोड।
मैं किसी भी सुझाव और शिकायत को स्वीकार करता हूं।
इसके अलावा एचटी और लिनक्स के अन्य संस्करणों के लिए एक प्रोसेसर पर परिणाम में दिलचस्पी है।
पहली अप्रैल से सभी!
यह कोड वास्तव में काम करता है :)