2014-10-02 7 views
16

मैं एक पाइथन प्रोग्राम लिख रहा हूं जो किसी साइट के डोमेन नाम की गणना करने के लिए उपयोग किया जाता है। उदाहरण के लिए, 'a.google.com'।इस आई/ओ-बाउंड ऑपरेशन के लिए थ्रेड की तुलना में एसिन्सीओ लाइब्रेरी धीमी क्यों है?

पहले, मैं यह करने के लिए threading मॉड्यूल का इस्तेमाल किया:

import string 
import time 
import socket 
import threading 
from threading import Thread 
from queue import Queue 

''' 
enumerate a site's domain name like this: 
1-9 a-z + .google.com 
1.google.com 
2.google.com 
. 
. 
1a.google.com 
. 
. 
zz.google.com 

''' 

start = time.time() 
def create_host(char): 
    ''' 
    if char is '1-9a-z' 
    create char like'1,2,3,...,zz' 
    ''' 
    for i in char: 
     yield i 
    for i in create_host(char): 
     if len(i)>1: 
      return False 
     for c in char: 
      yield c + i 


char = string.digits + string.ascii_lowercase 
site = '.google.com' 


def getaddr(): 
    while True: 
     url = q.get() 
     try: 
      res = socket.getaddrinfo(url,80) 
      print(url + ":" + res[0][4][0]) 
     except: 
      pass 
     q.task_done() 

NUM=1000 #thread's num 
q=Queue() 

for i in range(NUM): 
    t = Thread(target=getaddr) 
    t.setDaemon(True) 
    t.start() 

for host in create_host(char): 
    q.put(host+site) 
q.join() 

end = time.time() 

print(end-start) 

''' 
used time: 
9.448670148849487 
''' 

बाद में, मैं एक किताब जो कुछ मामलों coroutines तेजी से धागे से कर रहे हैं में कहा पढ़ें। तो, मैं asyncio उपयोग करने के लिए कोड दुबारा लिखा:

import asyncio 
import string 
import time 


start = time.time() 
def create_host(char): 
    for i in char: 
     yield i 
    for i in create_host(char): 
     if len(i)>1: 
      return False 
     for c in char: 
      yield c + i 


char = string.digits + string.ascii_lowercase 
site = '.google.com' 

@asyncio.coroutine 
def getaddr(loop, url): 
    try: 
     res = yield from loop.getaddrinfo(url,80) 
     print(url + ':' + res[0][4][0]) 
    except: 
     pass 

loop = asyncio.get_event_loop() 
coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)]) 
loop.run_until_complete(coroutines) 

end = time.time() 

print(end-start) 


''' 
time 
120.42313003540039 
''' 

क्यों getaddrinfo की asyncio संस्करण इतनी धीमी गति से है? क्या मैं किसी भी तरह coroutines का दुरुपयोग कर रहा हूँ?

+1

मुझे अपने सिस्टम पर लगभग एक प्रदर्शन अंतर दिखाई नहीं दे रहा है। थ्रेडेड संस्करण 20 सेकंड था, asyncio संस्करण 24 था। 'Getaddr' विधि से प्रिंट स्टेटमेंट को हटाने का प्रयास करें। क्या यह प्रदर्शन में एक बड़ा अलग बनाता है? प्रिंटिंग जीआईएल जारी करती है, इतने सारे थ्रेड एक साथ ऐसा कर सकते हैं, जबकि 'asyncio' नहीं कर सकता है। यदि प्रिंटिंग आपके सिस्टम पर विशेष रूप से धीमी है, तो यह गति अंतर के लिए जिम्मेदार हो सकती है। – dano

उत्तर

26

सबसे पहले, मैं अपने लिनक्स मशीन पर जितना बड़ा देख रहा हूं उतना बड़ा प्रदर्शन अंतर पुन: उत्पन्न नहीं कर सकता। मैं थ्रेडेड संस्करण के लिए लगातार 20-25 सेकंड देख रहा हूं, और asyncio संस्करण के लिए 24-34 सेकंड के बीच देख रहा हूं।

अब, asyncio धीमा क्यों है? इसमें कुछ चीजें हैं जो इसमें योगदान देती हैं। सबसे पहले, asyncio संस्करण अनुक्रमिक रूप से प्रिंट करना है, लेकिन थ्रेडेड संस्करण नहीं है। प्रिंटिंग I/O है, इसलिए यह होने पर जीआईएल जारी किया जा सकता है। इसका मतलब है कि संभावित रूप से दो या दो से अधिक थ्रेड एक ही समय में प्रिंट कर सकते हैं, हालांकि व्यवहार में यह अक्सर नहीं होता है, और शायद प्रदर्शन में इतना अंतर नहीं करता है।

दूसरा, और भी बहुत कुछ महत्वपूर्ण बात, getaddrinfo की asyncio संस्करण वास्तव में just calling socket.getaddrinfo in a ThreadPoolExecutor है:

def getaddrinfo(self, host, port, *, 
       family=0, type=0, proto=0, flags=0): 
    if self._debug: 
     return self.run_in_executor(None, self._getaddrinfo_debug, 
            host, port, family, type, proto, flags) 
    else: 
     return self.run_in_executor(None, socket.getaddrinfo, 
            host, port, family, type, proto, flags) 

यह इस के लिए डिफ़ॉल्ट ThreadPoolExecutor उपयोग कर रहा है, which only has five threads:

# Argument for default thread pool executor creation. 
_MAX_WORKERS = 5 

कि के रूप में लगभग नहीं है इस उपयोग-मामले के लिए आप जितनी समानांतरता चाहते हैं। यह threading संस्करण की तरह व्यवहार करते हैं और अधिक बनाने के लिए, आप loop.set_default_executor के माध्यम से एक ThreadPoolExecutor 1000 धागे का उपयोग करने, डिफ़ॉल्ट निष्पादक के रूप में यह निर्धारित करके आवश्यकता होगी:

loop = asyncio.get_event_loop() 
loop.set_default_executor(ThreadPoolExecutor(1000)) 
coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)]) 
loop.run_until_complete(coroutines) 

अब, इस व्यवहार अधिक threading के बराबर कर देगा , लेकिन यहां वास्तविकता है, आप वास्तव में एसिंक्रोनस I/O का उपयोग नहीं कर रहे हैं - आप बस एक अलग API के साथ threading का उपयोग कर रहे हैं। तो सबसे अच्छा आप यहां कर सकते हैं threading उदाहरण के लिए समान प्रदर्शन है।

अंत में, आप वास्तव में प्रत्येक उदाहरण में बराबर कोड नहीं चला रहे हैं - threading संस्करण, कार्यकर्ताओं, जो एक queue.Queue साझा कर रहे हैं का एक पूल उपयोग कर रहा है, जबकि asyncio संस्करण यूआरएल की सूची में हर एक आइटम के लिए एक coroutine को उत्पन्न करने के है । अगर मैं संस्करण asyncio.Queue और कोरआउट के पूल का उपयोग करने के लिए प्रिंट स्टेटमेंट को हटाने और एक बड़ा डिफ़ॉल्ट निष्पादक बनाने के अलावा, मुझे दोनों संस्करणों के साथ अनिवार्य रूप से समान प्रदर्शन मिलता है।यहाँ नए asyncio कोड है:

import asyncio 
import string 
import time 
from concurrent.futures import ThreadPoolExecutor 

start = time.time() 
def create_host(char): 
    for i in char: 
     yield i 
    for i in create_host(char): 
     if len(i)>1: 
      return False 
     for c in char: 
      yield c + i 


char = string.digits + string.ascii_lowercase 
site = '.google.com' 

@asyncio.coroutine 
def getaddr(loop, q): 
    while True: 
     url = yield from q.get() 
     if not url: 
      break 
     try: 
      res = yield from loop.getaddrinfo(url,80) 
     except: 
      pass 

@asyncio.coroutine 
def load_q(loop, q): 
    for host in create_host(char): 
     yield from q.put(host+site) 
    for _ in range(NUM): 
     yield from q.put(None) 

NUM = 1000 
q = asyncio.Queue() 

loop = asyncio.get_event_loop() 
loop.set_default_executor(ThreadPoolExecutor(NUM)) 
coros = [asyncio.async(getaddr(loop, q)) for i in range(NUM)] 
loop.run_until_complete(load_q(loop, q)) 
loop.run_until_complete(asyncio.wait(coros)) 

end = time.time() 

print(end-start) 

और प्रत्येक के आउटपुट:, नेटवर्क की वजह से कुछ परिवर्तनशीलता है

[email protected]:~$ python3 threaded_example.py 
20.409344911575317 
[email protected]:~$ python3 asyncio_example.py 
20.39924192428589 

ध्यान दें कि वहाँ हालांकि। उनमें से दोनों कभी-कभी कुछ सेकंड धीमे हो जाएंगे।

+0

समस्या को हल करने में मेरी सहायता करने के लिए बहुत बहुत धन्यवाद। यह मुझे समझता है कि मेरा एसिन्सीओ संस्करण एसिंक्रोनस I/O का उपयोग नहीं कर रहा है। तब मैंने ट्यूलिप मुद्दों 160 (https://code.google.com/p पर समस्या को खोजने के लिए खोज की/ट्यूलिप/मुद्दों/विवरण? आईडी = 160) का भी उल्लेख किया गया है। मैं पायथन 2 में गीवेंट का उपयोग करूंगा या एसिंक्रोनस I/O का उपयोग करने के लिए पायथन 3 में एओडेंस का उपयोग करूंगा। –

+0

असल में, कोरिनिन का उपयोग करने के उच्च प्रभाव के कारण एसिन्सीओ बहुत धीमी है। मेरे पास कोई संख्या नहीं है, इसलिए यह एक पोस्ट की बजाय सिर्फ एक टिप्पणी है, लेकिन आप इसे दोनों शैलियों में लिखे गए एक साधारण http echo सर्वर के साथ सत्यापित कर सकते हैं। पायथन + उच्च प्रदर्शन async IO एक साथ काम नहीं करते हैं, दुख की बात है। गोलांग या जावा की तुलना में, पायथन + असिनियो (केवल आईओ बाध्य), पायथन लगभग 9 x धीमी है। ~ 32.000 रिक/एस बनाम 3.700 रिक/एस। यहां तक ​​कि थ्रेड समाधान भी अजगर के साथ तेज है, जब तक आप 200 ~ 250 ग्राहकों से अधिक का उपयोग नहीं करते हैं। Asyncio ग्राहकों की इस संख्या पर भी प्रदर्शन छोड़ देता है। – Kr0e

+0

मुझे यकीन नहीं है, शायद यह कार्यान्वयन में भी एक बग है। अफसोस की बात है, अभी तक कोई आधिकारिक मानक नहीं है, इसलिए मेरी धारणा को मान्य या साबित करना अभी काफी अलग है ... – Kr0e

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