2008-09-25 27 views
5

अभी मैं सादे-पाठ फ़ाइलों के 515mb के माध्यम से पढ़ने एक लॉग पार्सर (पिछले 4 साल से अधिक प्रत्येक दिन के लिए एक फ़ाइल) है। मेरा कोड वर्तमान में इस प्रकार है: http://gist.github.com/12978। मैंने psyco का उपयोग किया है (जैसा कि कोड में देखा गया है) और मैं इसे संकलित कर रहा हूं और संकलित संस्करण का उपयोग कर रहा हूं। यह हर 0.3 सेकंड में लगभग 100 लाइनें कर रहा है। मशीन एक मानक 15 "मैकबुक प्रो (2.4GHz C2D, 2GB RAM)आप पाइथन/पोस्टग्रेएसक्यूएल को तेज़ी से कैसे बनाते हैं?

यह संभव इस तेजी से जा सकते हैं या है करने के लिए है यह है कि भाषा/डेटाबेस पर एक सीमा?

उत्तर

6

समय प्रोफाइलिंग बर्बाद मत करो। समय हमेशा डेटाबेस संचालन में है। जितना संभव हो उतना करो। बस आवेषण की न्यूनतम संख्या।

तीन चीजें।

वन। दिनांक, होस्टनाम और व्यक्ति आयामों को अनुरूप बनाने के लिए बार-बार चयन न करें। एक ही पायथन शब्दकोश में सभी डेटा एक बार लाएं और इसे स्मृति में उपयोग करें। दोहराना सिंगलटन का चयन न करें। पायथन का प्रयोग करें।

दो। अपडेट न करें

विशेष रूप से, ऐसा मत करें। यह दो कारणों से खराब कोड है।

cursor.execute("UPDATE people SET chats_count = chats_count + 1 WHERE id = '%s'" % person_id) 

इसे एक साधारण चयन COUNT (*) से बदल दिया गया है ...। गिनती बढ़ाने के लिए कभी भी अपडेट न करें। बस एक पंक्ति कथन के साथ पंक्तियों की गिनती करें। [यदि आप इसे सरल चयन COUNT या SELECT COUNT (DISTINCT) के साथ नहीं कर सकते हैं, तो आप कुछ डेटा खो रहे हैं - आपका डेटा मॉडल हमेशा सही पूर्ण गणना प्रदान करना चाहिए। कभी अपडेट न करें।]

और। स्ट्रिंग प्रतिस्थापन का उपयोग करके कभी भी SQL का निर्माण न करें। पूरी तरह से गूंगा।

यदि किसी कारण से SELECT COUNT(*) पर्याप्त तेज़ नहीं है (पहले किसी भी लंगड़े से पहले बेंचमार्क) तो आप किसी अन्य तालिका में गिनती के परिणाम को कैश कर सकते हैं। सभी भार के बाद। SELECT COUNT(*) FROM whatever GROUP BY whatever करें और इसे गणना की एक तालिका में डालें। अपडेट न करें कभी।

तीन। बाइंड वैरिएबल का प्रयोग करें। हमेशा।

cursor.execute("INSERT INTO ... VALUES(%(x)s, %(y)s, %(z)s)", {'x':person_id, 'y':time_to_string(time), 'z':channel,}) 

एसक्यूएल कभी नहीं बदलता है। मूल्य परिवर्तन में बंधे हैं, लेकिन एसक्यूएल कभी नहीं बदलता है। यह बहुत तेज़ है। गतिशील रूप से एसक्यूएल कथन कभी न बनाएं। कभी नहीँ।

+0

अपडेट किया गया है क्योंकि बाद में चालू (रेल अनुप्रयोग पर रूबी में) मैं तुरंत यह जानना चाहता हूं कि उपयोगकर्ता की कितनी लाइनें हैं। यह संख्या सैद्धांतिक रूप से कभी गलत नहीं होनी चाहिए, और तेज़ी से होगी। बाइंड वैरिएबल सिंटैक्स गलत है (इसलिए यह मेरे psycopg2 के लिए लगता है), कुछ ऐसा काम करेगा जो काम करेगा? –

+0

क्या आपने psycopg2 विशिष्ट बाइंड परिवर्तनीय वाक्यविन्यास के बारे में मेरे अन्य उत्तर में मेरी टिप्पणी पढ़ी? –

+0

एक सारणी पर एक चुनिंदा गिनती (*) जिसमें एक इंडेक्स है (किसी भी इंडेक्स को पर्याप्त होना चाहिए) बहुत तेज़ ऑपरेशन है क्योंकि तत्वों की संख्या सीधे इंडेक्स में संग्रहीत होती है। पहले अद्यतन गणना का चयन करना कोई तेज़ नहीं होना चाहिए। – gooli

3

उपयोग बाँध बजाय शाब्दिक की चर एसक्यूएल बयान में मूल्यों और इतना है कि बयान अगली बार यह प्रयोग किया जाता है reparsed करने की आवश्यकता नहीं है एक कर्सर के लिए प्रत्येक विशिष्ट SQL विवरण बनाने अजगर db एपीआई डॉक से:।

Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. Variables are specified in a database-specific notation (see the module's paramstyle attribute for details). [5]

A reference to the operation will be retained by the cursor. If the same operation object is passed in again, then the cursor can optimize its behavior. This is most effective for algorithms where the same operation is used, but different parameters are bound to it (many times).

हमेशा हमेशा हमेशा का उपयोग बाँध चर।

3

लूप के लिए, आप बार-बार 'चैट' तालिका में डाल रहे हैं, इसलिए आपको अलग-अलग मानों के साथ निष्पादित करने के लिए केवल बाध्य चर के साथ एक एकल एसक्यूएल कथन की आवश्यकता है।

insert_statement=""" 
    INSERT INTO chats(person_id, message_type, created_at, channel) 
    VALUES(:person_id,:message_type,:created_at,:channel) 
""" 

फिर प्रत्येक एसक्यूएल बयान के स्थान पर आप जगह में डाल निष्पादित:: तो आप पाश के लिए पहले इस डाल सकता है चीजों को तेजी से चलाने के

cursor.execute(insert_statement, person_id='person',message_type='msg',created_at=some_date, channel=3) 

यह कर देगा क्योंकि:

  1. कर्सर ऑब्जेक्ट को प्रत्येक बार
  2. पर बयान को दोबारा नहीं करना पड़ेगा डीबी सर्वर को एक नई निष्पादन योजना उत्पन्न नहीं करनी पड़ेगी क्योंकि यह इसका उपयोग कर सकती है पहले बनाओ
  3. आप बाँध चर में santitize() के रूप में विशेष वर्ण कॉल करने के लिए एसक्यूएल बयान निष्पादित हो जाता है इस बात का नहीं होगा हिस्सा नहीं होगा।

नोट: मैंने उपयोग किए गए बाध्य परिवर्तनीय वाक्यविन्यास ओरेकल विशिष्ट है। आपको सटीक वाक्यविन्यास के लिए psycopg2 लाइब्रेरी के दस्तावेज़ों को जांचना होगा।

अन्य अनुकूलन:

  1. आप प्रत्येक पाश यात्रा के बाद "अद्यतन सेट chatscount लोगों" के साथ incrementing रहे हैं। एक शब्दकोश मैपिंग उपयोगकर्ता को chat_count पर रखें और फिर आपके द्वारा देखे गए कुल नंबर के कथन को निष्पादित करें। यह हर रिकॉर्ड के बाद डीबी मारने के बाद तेजी से होगा।
  2. अपने सभी प्रश्नों पर बाइंड चर का उपयोग करें। केवल सम्मिलित कथन नहीं, मैं इसे एक उदाहरण के रूप में चुनता हूं।
  3. बदलें सभी को खोजने _ *() फ़ंक्शन कि डाटाबेस अप दिखते हैं उनके परिणाम कैश करने के लिए तो वे db हर बार हिट करने के लिए नहीं है।
  4. मनोवैज्ञानिक अजगर प्रोग्राम हैं जो numberic आपरेशन की एक बड़ी संख्या में प्रदर्शन का अनुकूलन। लिपि आईओ महंगा है और सीपीयू महंगा नहीं है इसलिए यदि कोई अनुकूलन हो तो मैं आपको ज्यादा देने की उम्मीद नहीं करूंगा।
+0

उपरोक्त कोड काम नहीं करता है, यह मुझे एक टाइप एरर देता है: 'डी' इस फ़ंक्शन के लिए एक अवैध कीवर्ड तर्क है। स्पष्ट रूप से वाक्यविन्यास psycopg2 के लिए मान्य नहीं है, और मैंने विशेष रूप से कहा है कि मैं PostgreSQL डेटाबेस पर चल रहा था। –

+0

कर्सर.एक्सक्यूट() विधि विभिन्न डीबी एपीआई कार्यान्वयन के बीच थोड़ा अलग तर्क लेती है। यह psycopg2 कार्यान्वयन पसंद करता है एक शब्दकोश में बाध्य चर लेता है। उदाहरण के लिए: –

+0

bind_vars = {'person_id:' person ',' message_type ':' msg ',' create_at ': some_date,' channel ': 3) कर्सर.एक्सक्यूट (sql_statement, bindvars) –

2

के रूप में मार्क का सुझाव दिया, बंधन चर का उपयोग करें। डेटाबेस को केवल प्रत्येक कथन को एक बार तैयार करना होता है, फिर प्रत्येक निष्पादन के लिए "रिक्त स्थान भरें"। एक अच्छा दुष्प्रभाव के रूप में, यह स्वचालित रूप से स्ट्रिंग-उद्धरण समस्याओं का ख्याल रखेगा (जो आपका प्रोग्राम संभाल नहीं रहा है)।

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

आपके log_hostname, log_person, और log_date फ़ंक्शंस टेबल पर आवश्यक चयन कर रहे हैं। उपयुक्त कुंजी विशेषता प्राथमिक कुंजी या अद्वितीय बनाओ। फिर, INSERT से पहले कुंजी की उपस्थिति की जांच करने के बजाय, बस INSERT करें। यदि व्यक्ति/दिनांक/होस्टनाम पहले से मौजूद है, तो INSERT बाधा उल्लंघन से विफल हो जाएगा। (यदि आप ऊपर दिए गए अनुसार एक एकल प्रतिबद्धता के साथ लेनदेन का उपयोग करते हैं, तो यह काम नहीं करेगा।)

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

1
इसके अतिरिक्त कई ठीक सुझाव @Mark रोडी दिया है करने के लिए

, निम्न करें:

  • readlines का उपयोग नहीं करते हैं, तो आप फ़ाइल पर पुनरावृति कर सकते हैं वस्तुओं
  • executemany बजाय execute इस्तेमाल करने की कोशिश: बैच आवेषण बल्कि एक आवेषण करने की कोशिश, इस क्योंकि कम भूमि के ऊपर तेजी से हो जाता है। यह भी करता
  • str.rstrip की संख्या कम कर देता है बजाय

बैचिंग आवेषण एक regex के साथ न्यू लाइन की अलग करना अस्थायी रूप से अधिक स्मृति का उपयोग करेगा के ठीक से काम करेंगे, लेकिन जब आप पढ़ नहीं है कि ठीक होना चाहिए स्मृति में पूरी फाइल।

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