2009-07-15 22 views
10

मेरे पास एक बैश स्क्रिप्ट है जो 2 टाइमस्टैम्प के बीच लॉगफाइल के एक सेक्शन को काटती है, लेकिन फ़ाइलों के आकार की वजह से, इसे चलाने में काफी समय लगता है।पर्ल से तेज पर्ल है?

अगर मुझे पर्ल में स्क्रिप्ट को फिर से लिखना था, तो क्या मैं एक महत्वपूर्ण गति वृद्धि प्राप्त कर सकता था - या मुझे इसे पूरा करने के लिए सी जैसे कुछ स्थानांतरित करना होगा?

#!/bin/bash 

if [ $# -ne 3 ]; then 
    echo "USAGE $0 <logfile(s)> <from date (epoch)> <to date (epoch)>" 
    exit 1 
fi 

LOGFILES=$1 
FROM=$2 
TO=$3 
rm -f /tmp/getlogs?????? 
TEMP=`mktemp /tmp/getlogsXXXXXX` 

## LOGS NEED TO BE LISTED CHRONOLOGICALLY 
ls -lnt $LOGFILES|awk '{print $8}' > $TEMP 
LOGFILES=`tac $TEMP` 
cp /dev/null $TEMP 

findEntry() { 
    RETURN=0 
    dt=$1 
    fil=$2 
    ln1=$3 
    ln2=$4 
    t1=`tail -n+$ln1 $fil|head -n1|cut -c1-15` 
    dt1=`date -d "$t1" +%s` 
    t2=`tail -n+$ln2 $fil|head -n1|cut -c1-15` 
    dt2=`date -d "$t2" +%s` 
    if [ $dt -ge $dt2 ]; then 
    mid=$dt2 
    else 
    mid=$(((($ln2-$ln1)*($dt-$dt1)/($dt2-$dt1))+$ln1)) 
    fi 
    t3=`tail -n+$mid $fil|head -n1|cut -c1-15` 
    dt3=`date -d "$t3" +%s` 
    # finished 
    if [ $dt -eq $dt3 ]; then 
    # FOUND IT (scroll back to the first match) 
    while [ $dt -eq $dt3 ]; do 
     mid=$(($mid-1)) 
     t3=`tail -n+$mid $fil|head -n1|cut -c1-15` 
     dt3=`date -d "$t3" +%s` 
    done 
    RETURN=$(($mid+1)) 
    return 
    fi 
    if [ $(($mid-1)) -eq $ln1 ] || [ $(($ln2-1)) -eq $mid ]; then 
    # FOUND NEAR IT 
    RETURN=$mid 
    return 
    fi 
    # not finished yet 
    if [ $dt -lt $dt3 ]; then 
    # too high 
    findEntry $dt $fil $ln1 $mid 
    else 
    if [ $dt -ge $dt3 ]; then 
     # too low 
     findEntry $dt $fil $mid $ln2 
    fi 
    fi 
} 

# Check timestamps on logfiles 
LOGS="" 
for LOG in $LOGFILES; do 
    filetime=`ls -ln $LOG|awk '{print $6,$7}'` 
    timestamp=`date -d "$filetime" +%s` 
    if [ $timestamp -ge $FROM ]; then 
    LOGS="$LOGS $LOG" 
    fi 
done 

# Check first and last dates in LOGS to refine further 
for LOG in $LOGS; do 
    if [ ${LOG%.gz} != $LOG ]; then 
     gunzip -c $LOG > $TEMP 
    else 
     cp $LOG $TEMP 
    fi 
    t=`head -n1 $TEMP|cut -c1-15` 
    FIRST=`date -d "$t" +%s` 
    t=`tail -n1 $TEMP|cut -c1-15` 
    LAST=`date -d "$t" +%s` 
    if [ $TO -lt $FIRST ] || [ $FROM -gt $LAST ]; then 
     # This file is entirely out of range 
     cp /dev/null $TEMP 
    else 
     if [ $FROM -le $FIRST ]; then 
     if [ $TO -ge $LAST ]; then 
      # Entire file is within range 
      cat $TEMP 
     else 
      # Last part of file is out of range 
      STARTLINENUMBER=1 
      ENDLINENUMBER=`wc -l<$TEMP` 
      findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER 
      head -n$RETURN $TEMP 
     fi 
     else 
     if [ $TO -ge $LAST ]; then 
      # First part of file is out of range 
      STARTLINENUMBER=1 
      ENDLINENUMBER=`wc -l<$TEMP` 
      findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER 
      tail -n+$RETURN $TEMP 
     else 
      # range is entirely within this logfile 
      STARTLINENUMBER=1 
      ENDLINENUMBER=`wc -l<$TEMP` 
      findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER 
      n1=$RETURN 
      findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER 
      n2=$RETURN 
      tail -n+$n1 $TEMP|head -n$(($n2-$n1)) 
     fi 
     fi 
    fi 
done 
rm -f /tmp/getlogs?????? 

+2

क्या आप हमें दिखा सकते हैं कि आप बैश स्क्रिप्ट में फ़ाइल कैसे पढ़ रहे हैं? –

+6

@Sinan Ünür: बकवास। पर्ल टैग में आपकी या मेरी अपवॉइंट गिनती वाला कोई भी व्यक्ति यह जानने के लिए पर्याप्त समय के आसपास ब्लॉक कर रहा है कि उसकी बैश स्क्रिप्ट शायद कैसा दिखती है, और उस आधार पर एक अस्थायी उत्तर देने के लिए। और अस्थायी उत्तरों के बारे में बड़ी बात यह है कि उन्हें और प्रतिक्रिया के साथ संशोधित किया जा सकता है। "असली सवाल नहीं" बिल्कुल वही बात नहीं है जैसा आपका मतलब है, जो "मेरे लिए एक अचूक, अंतिम, निश्चित उत्तर देने के लिए पर्याप्त विवरण नहीं है"। जो लंगड़ा है। – chaos

+0

ठीक है, मैंने बैश कोड जोड़ा है। यह टाइमस्टैम्प खोजने के लिए एक रिकर्सिव फ़ंक्शन का उपयोग करता है। – Brent

उत्तर

11

ब्रेंट की टिप्पणी के आधार पर अद्यतन स्क्रिप्ट: यह एक अनचाहे है।

#!/usr/bin/perl 

use strict; 
use warnings; 

my %months = (
    jan => 1, feb => 2, mar => 3, apr => 4, 
    may => 5, jun => 6, jul => 7, aug => 8, 
    sep => 9, oct => 10, nov => 11, dec => 12, 
); 

while (my $line = <>) { 
    my $ts = substr $line, 0, 15; 
    next if parse_date($ts) lt '0201100543'; 
    last if parse_date($ts) gt '0715123456'; 
    print $line; 
} 

sub parse_date { 
    my ($month, $day, $time) = split ' ', $_[0]; 
    my ($hour, $min, $sec) = split /:/, $time; 
    return sprintf(
     '%2.2d%2.2d%2.2d%2.2d%2.2d', 
     $months{lc $month}, $day, 
     $hour, $min, $sec, 
    ); 
} 


__END__ 

संदर्भ के लिए पिछला जवाब: क्या फ़ाइल के स्वरूप है? यहां एक छोटी सी लिपि है जो मानती है कि पहला कॉलम एक टाइमस्टैम्प है और केवल उन पंक्तियों को प्रिंट करता है जिनमें एक निश्चित सीमा में टाइमस्टैम्प होते हैं। यह भी मानता है कि टाइमस्टैम्प को क्रमबद्ध किया जाता है। अपने सिस्टम पर, यह एक दूसरे के बारे में ले लिया 900,000 लाइनों लाख एक से बाहर फिल्टर करने के लिए:

#!/usr/bin/perl 

use strict; 
use warnings; 

while (<>) { 
    my ($ts) = split; 
    next if $ts < 1247672719; 
    last if $ts > 1252172093; 
    print $ts, "\n"; 
} 

__END__ 
+0

यह विचार है - सिवाय इसके कि लॉग प्रारूप की तिथियों का उपयोग करते हैं: जुलाई 15 00:03:19 – Brent

+0

@ ब्रेंट कहीं एक वर्ष है उस तारीख में? –

+0

नहीं, मुझे लगता है कि यह चालू वर्ष मानता है। मेरे मामले में यह केवल 1 जनवरी को एक समस्या हो सकती है :) – Brent

17

आप लगभग निश्चित रूप से अभी-अभी पढ़ा है जब आप अपने दूसरे टाइमस्टैम्प पारित फ़ाइल काटकर पर्ल में अपनी स्क्रिप्ट लिखने से एक बड़े पैमाने पर गति लाभ एहसास होगा।

अधिक आम तौर पर, हाँ; bash किसी भी जटिलता की लिपि, जब तक यह जादूगर का वास्तव में अद्भुत टुकड़ा न हो, तब तक समान रूप से इनपुट और आउटपुट के लिए पर्ल स्क्रिप्ट द्वारा बेहतर प्रदर्शन किया जा सकता है।

+8

ठीक है। ज्यादातर मामलों में, भाषा कार्यान्वयन की वजह से अलग-अलग भाषा का प्रदर्शन नहीं होता है; लेकिन क्योंकि यह आपको बेहतर एल्गोरिदम लागू करने देता है। – Javier

+0

मैंने अपनी मूल बैश स्क्रिप्ट को प्रश्न में जोड़ा है, अगर यह – Brent

25

पर्ल बैश की तुलना में बेहद तेज़ है। और, टेक्स्ट मैनिपुलेशन के लिए, आप वास्तव में सी के मुकाबले पर्ल के साथ बेहतर प्रदर्शन प्राप्त कर सकते हैं, जब तक कि आप जटिल एल्गोरिदम लिखने में समय न लें। बेशक, सरल सामान सी के लिए नामुमकिन हो सकता है।

यह कहा गया है कि, यदि आपकी "बैश" स्क्रिप्ट लूपिंग नहीं है, तो बस अन्य प्रोग्रामों को कॉल करें, तो कोई लाभ नहीं हुआ है। उदाहरण के लिए, यदि आपकी स्क्रिप्ट "cat X | grep Y | tr -f 3-5 | sort | uniq" जैसी दिखती है, तो अधिकांश समय बैट पर नहीं, बिल्ली, grep, tr, sort और uniq पर खर्च किया जाता है।

यदि स्क्रिप्ट में कोई लूप है, तो आप प्रदर्शन प्राप्त करेंगे, या यदि आप एक ही फ़ाइल के एकाधिक रीड को सहेजते हैं।

आप कहते हैं कि आप फ़ाइल पर दो टाइमस्टैम्प के बीच सामान काटते हैं। मान लें कि आपका बैश स्क्रिप्ट इस तरह दिखता है:

LINE1=`grep -n TIMESTAMP1 filename | head -1 | cut -d ':' -f 1` 
LINE2=`grep -n TIMESTAMP2 filename | head -1 | cut -d ':' -f 1` 
tail +$LINE1 filename | head -$(($LINE2-$LINE1)) 

तो फिर तुम, प्रदर्शन प्राप्त करेंगे क्योंकि आप पूरी फ़ाइल तीन बार पढ़ रहे हैं: एक बार प्रत्येक आदेश जहां "फ़ाइल नाम" प्रकट होता है के लिए।

my $state = 0; 
while(<>) { 
    exit if /TIMESTAMP2/; 
    print $_ if $state == 1; 
    $state = 1 if /TIMESTAMP1/; 
} 

यह केवल एक बार फ़ाइल को पढ़ने और भी एक बार आप TIMESTAMP2 पढ़ बंद हो जाएगा: पर्ल में, आप कुछ इस तरह करना होगा। चूंकि आप एकाधिक फ़ाइलों को संसाधित कर रहे हैं, इसलिए आप "निकास" के बजाय "अंतिम" या "ब्रेक" का उपयोग करेंगे, ताकि स्क्रिप्ट फ़ाइलों को संसाधित कर सके।

वैसे भी, आपकी स्क्रिप्ट को देखते हुए मैं सकारात्मक हूं, आपको पर्ल में इसे फिर से लिखकर बहुत कुछ हासिल होगा। छोरों, फ़ाइल नाम (जिसका गति में सुधार होगा, लेकिन शायद नगण्य है) के साथ काम कर जो नहीं पूरी तरह से अंदर है प्रत्येक फ़ाइल या बाहर गुंजाइश आप करते हैं के लिए के होते हुए भी:

  1. लाइनों गिनती करने के लिए पूरे फ़ाइल पढ़ें!
  2. "सिर" या "पूंछ" फाइल एक बार फिर से

इसके अलावा, अपने पूंछ सिर से

  • समाप्त फ़ाइल में अनेक पूंछ करो। हर बार जब आप ऐसा करते हैं, तो कोड का कुछ टुकड़ा उस डेटा को पढ़ रहा है। उनमें से कुछ लाइनें 10 गुना या उससे अधिक तक पढ़ी जा रही हैं!

  • +9

    की सहायता करता है, वैसे, यह बिल्ली का अनावश्यक उपयोग है। –

    +6

    व्यक्तिगत रूप से, मैं शैली के कारणों के लिए हर समय बिल्ली का यह 'अनावश्यक' उपयोग करता हूं - मुझे डेटा हमेशा बाएं से दाएं तक प्रवाह करना पसंद है, दाएं से बाएं नहीं, फिर दाईं ओर दाईं ओर दाईं ओर grep वाई < X | foo > Z – bdonlan

    +2

    बिल्ली बहुत है हल्के वजन, इसलिए इसे हटाने से समयपूर्व अनुकूलन होता है। बिल्ली का उपयोग करना निम्नलिखित चरणों के orderer को बदलने में आसान बनाने का अतिरिक्त लाभ है। इसके अलावा, आवश्यकता होने पर इसे आसानी से gzcat के साथ प्रतिस्थापित किया जा सकता है। अंत में, कौन परवाह करता है? मैं इसका सुझाव नहीं दे रहा था। –

    2

    मैं सभी तीन समाधानों को प्रोफाइल करता हूं और शुरुआती स्टार्टअप गति, प्रसंस्करण गति और स्मृति उपयोग के मामले में सबसे अच्छा विकल्प चुनता हूं।

    कुछ पर्ल/पायथन/रूबी की तरह निरपेक्ष सबसे तेजी से नहीं हो सकता है, लेकिन आप तेजी से उन भाषाओं में विकसित कर सकते हैं - सी में और यहां तक ​​कि बैश तुलना में बहुत तेज।

    1

    यह कैसे अपने bash स्क्रिप्ट लिखा है पर निर्भर करता है। यदि आप लॉग फ़ाइल को पार्स करने के लिए अजीब का उपयोग नहीं कर रहे हैं, तो लूप को पढ़ने के दौरान बैश का उपयोग करने के बजाय, इसे अजीब में बदलने से गति में सुधार होगा।

    1

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

    1

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

    हालांकि, अगर निकास एक बैश स्क्रिप्ट द्वारा गठित करने के लिए सक्षम था जो पैरामीटर बनाता है और उसके बाद grep को regex के साथ कॉल करता है तो यह 'शुद्ध' स्क्रिप्ट से तेज़ हो सकता है।

    5

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

    पर्ल की एक उच्च स्टार्टअप लागत है , या तो दावा किया गया है। ईमानदारी से, मैंने कभी नहीं देखा है। यदि आपका विकल्प जावा में ऐसा करना है, तो पर्ल की कोई स्टार्टअप लागत नहीं है। बैश की तुलना में, मैंने बस ध्यान नहीं दिया है। मैंने जो देखा है वह यह है कि जब मैं सभी विशेष यूनिक्स टूल्स को कॉल करने से दूर हो जाता हूं, जो आपके पास विकल्प नहीं होते हैं, और एक ही प्रक्रिया में इसे करने की ओर बढ़ते हैं, तो गति बढ़ जाती है। यूनिक्स पर नई प्रक्रियाओं बनाने की भूमि के ऊपर के रूप में गंभीर के रूप में यह विंडोज पर हो सकता है नहीं है, लेकिन यह अभी भी पूरी तरह से नगण्य नहीं है के रूप में आप, सी क्रम पुस्तकालय (libc) हर बार reinitialise पार्स तर्क, खुली फ़ाइलों (शायद) के लिए है , आदि। पर्ल में, आप मेमोरी के विशाल स्वार्थ का उपयोग करते हुए समाप्त करते हैं क्योंकि आप किसी सूची या किसी चीज़ में सबकुछ पास करते हैं, लेकिन यह सब स्मृति में है, इसलिए यह तेज़ है। और उपकरणों के लिए आप या तो (map/grep, regexes) या में निर्माण कर रहे हैं इस्तेमाल कर रहे हैं के कई CPAN पर मॉड्यूल में उपलब्ध हैं। इनके अच्छे संयोजन से काम आसानी से हो जाएगा।

    बड़ी बात फिर से पढ़ने फ़ाइलें से बचना है। यह महंगा है। और आप इसे कई बार कर रहे हैं। बिल्ली, आप अपने जीजीआईपी फ़ाइलों को सीधे पढ़ने के लिए :gzip संशोधक का उपयोग कर सकते हैं, अभी तक एक और पास सहेज सकते हैं - और यह तेज़ होगा कि आप डिस्क से कम पढ़ रहे होंगे।

    1

    अपने bash स्क्रिप्ट में, यह डाल:

    perl -ne "print if /$FROM/../$TO/" $LOGFILES 
    

    $ FROM और TO $ वास्तव में अपने प्रारंभ और समाप्ति समय के लिए regex कर रहे हैं।

    वे समावेशी हैं, इसलिए आप अपने अंतिम समय के लिए 2009-06-14 23:59:59 डाल सकते हैं, क्योंकि 2009-06-15 00:00:00 मध्यरात्रि में लेनदेन शामिल करेगा।

    0

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

    पर्ल सी की तरह अधिक है, यह काफी हद तक नि: शुल्क कोड की एक बड़ी लाइब्रेरी के साथ स्वयं निहित है और यह संकलित है, इसलिए यह बहुत तेजी से चलता है, उदाहरण के लिए 80-90% सी की गति, लेकिन प्रोग्राम के लिए आसान (उदाहरण के लिए चर आकार गतिशील)।