2011-01-30 18 views
6

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

#... 
File.open(file, 'r') do |f| 
    f.each_line do |line| 
    # do stuff here to line 
    end 
end 

विशेष रूप से इस फ़ाइल में 642,868 पंक्तियां हैं:

$ wc -l nginx.log                                  /code/src/myimport 
    642868 ../nginx.log 

किसी को भी एक अधिक कुशल (स्मृति/CPU) जिस तरह से इस फ़ाइल में प्रत्येक पंक्ति पर कार्रवाई करने के बारे में पता है?

अद्यतन

ऊपर से f.each_line के अंदर कोड बस लाइन के खिलाफ एक regex मिलान किया जाता है। यदि मैच विफल रहता है, तो मैं लाइन को @skipped सरणी में जोड़ता हूं। यदि यह गुजरता है, तो मैं मैचों को हैश में प्रारूपित करता हूं (मैच के "फ़ील्ड्स" द्वारा की गई) और इसे @results सरणी में जोड़ दें।

# regex built in `def initialize` (not on each line iteration) 
@regex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - (.{0})- \[([^\]]+?)\] "(GET|POST|PUT|DELETE) ([^\s]+?) (HTTP\/1\.1)" (\d+) (\d+) "-" "(.*)"/ 

#... loop lines 
match = line.match(@regex) 
if match.nil? 
    @skipped << line 
else 
    @results << convert_to_hash(match) 
end 

मैं एक अक्षम प्रक्रिया होने के लिए पूरी तरह से खुला हूं। मैं convert_to_hash के अंदर कोड को हर बार गणना को समझने के बजाय प्रीकंप्यूटेड लैम्ब्डा का उपयोग कर सकता हूं। मुझे लगता है कि मैंने अभी माना है कि यह रेखा ही पुनरावृत्ति थी जो समस्या थी, प्रति पंक्ति कोड नहीं।

+0

पर सबसे अधिक मेमोरी प्रभावी तरीका यह है कि आप इसे 'प्रत्येक_लाइन' के साथ कैसे कर रहे हैं। आप फ़ाइलों को तेज़ी से ब्लॉक में पढ़ सकते हैं, फिर ब्लॉक सीमाओं को पार करने वाली आंशिक रूप से लोड की गई लाइनों को फिर से जोड़ने के साथ-साथ व्यक्तिगत लाइनों को पकड़ने के लिए 'स्ट्रिंग # लाइन'' का उपयोग करें। यह लाइनों को विभाजित करने और टूटे हुए लोगों से जुड़ने के लिए धोने वाला हो जाता है। –

उत्तर

5

मैंने अभी 600,000 लाइन फ़ाइल पर एक परीक्षण किया है और यह फ़ाइल पर आधे सेकेंड से भी कम समय में पुनरावृत्त हुआ है। मुझे लगता है कि धीमापन फ़ाइल लूपिंग में नहीं है लेकिन लाइन पार्सिंग है। क्या आप अपना पार्स कोड भी पेस्ट कर सकते हैं?

+0

कोड का एकमात्र टुकड़ा जिसका कोई महत्व है कि मैं अर्द्ध जटिल रेगेक्स के खिलाफ लाइन से मेल खाता हूं। रेगेक्स कोई पिछड़ा/आगे नहीं देखता है, यह ज्यादातर एक चार-दर-चार मैच है। मैं प्रासंगिक कोड के साथ उपरोक्त अद्यतन पोस्ट करूंगा। – localshred

+0

ओह, और रेगेक्स एक बार गणना की जाती है, प्रत्येक पुनरावृत्ति पर नहीं (केवल स्पष्ट होने के लिए)। – localshred

+0

ऐसा प्रतीत होता है कि यह मेरी मूर्खता थी जो स्मृति वृद्धि को जन्म दे रही थी। मैं मिलान किए गए परिणामों (और छोड़ी गई रेखाएं) को उन सरणी में संग्रहीत कर रहा था जिन्हें मैं बाद में डीबी सम्मिलित करने के लिए उपयोग कर रहा था (या स्किप के आकार को प्रिंट करना)। मुझे पता है, मैं गूंगा हूँ।:) अब मैं छोड़ी गई लाइनों पर 'रखता हूं' कर रहा हूं और मैच वैध होने पर डीबी डालने का अधिकार कर रहा हूं। असली मेम 30 एमबी से ऊपर कभी नहीं चला जाता है। यह इंगित करने के लिए धन्यवाद कि मैं शायद एक गूंगा तरीके से चीजें कर रहा था। :) (ओह और मैंने आपके मूल उत्तर की तरह 'IO.foreach' पर स्विच किया)। – localshred

1

आप बैश उपयोग कर रहे हैं (या समान) आप इस तरह अनुकूलन करने के लिए सक्षम हो सकता है:

input.rb में:

while x = gets 
     # Parse 
end 
तो बैश में

:

cat nginx.log | ruby -n input.rb 

-n ध्वज रूबी को assume 'while gets(); ... end' loop around your script पर बताता है, जो इसे अनुकूलित करने के लिए कुछ विशेष करने का कारण बन सकता है।

आप समस्या के पूर्वलेखित समाधान को भी देखना चाहते हैं, क्योंकि यह तेज़ होगा।

+0

इस बिंदु पर मुझे थोड़ा अधिक हैकी लगता है, लेकिन मैं इसे ध्यान में रखूंगा। – localshred

4

यह blogpost में बड़ी लॉग फ़ाइलों को पार्स करने के लिए कई दृष्टिकोण शामिल हैं। शायद यह एक प्रेरणा है। file-tail gem