2013-08-21 3 views
5

प्रसंग:एक शब्द पार्सर का अनुकूलन

मैं तुलना में मैं अनुकूलन करने के लिए कोशिश कर रहा हूँ एक कोड/पाठ संपादक है। वर्तमान में, कार्यक्रम की बाधा सभी कीवर्ड स्कैन करने की तुलना में भाषा पार्सर है (एक से अधिक है, लेकिन वे आम तौर पर समान रूप से लिखे जाते हैं)।

मेरे कंप्यूटर पर, संपादक 1,000,000 कोड की रेखाओं के आसपास फ़ाइलों पर देरी करता है। रास्पबेरी पीई जैसे निचले अंत कंप्यूटर पर, देरी बहुत जल्द हो रही है (मुझे बिल्कुल याद नहीं है, लेकिन मुझे लगता है कि कोड के बारे में 10,000 लाइनें)। और यद्यपि मैंने कभी भी कोड की 1,000,000 लाइनों से बड़े दस्तावेज़ों को कभी नहीं देखा है, मुझे यकीन है कि वे वहां हैं और मैं चाहता हूं कि मेरा प्रोग्राम उन्हें संपादित करने में सक्षम हो।

प्रश्न:

यह मैं सवाल की ओर जाता है: बड़े, गतिशील स्ट्रिंग के भीतर शब्दों की एक सूची के लिए स्कैन करने का सबसे तेज़ तरीका क्या है?

यहाँ कुछ जानकारी है कि एल्गोरिथ्म के डिजाइन को प्रभावित कर सकता है:

  1. कीवर्ड
  2. योग्यता पात्रों एक कीवर्ड का हिस्सा बनने के लिए अनुमति दी, (मैं उन्हें क्वालिफायर कहते हैं)
  3. बड़े स्ट्रिंग

टोंटी-समाधान:

यह (लगभग) है विधि मैं वर्तमान में तार पार्स करने के लिए उपयोग कर रहा हूँ:

// this is just an example, not an excerpt 
// I haven't compiled this, I'm just writing it to 
// illustrate how I'm currently parsing strings 

struct tokens * scantokens (char * string, char ** tokens, int tcount){ 

    int result = 0; 
    struct tokens * tks = tokens_init(); 

    for (int i = 0; string[i]; i++){ 

     // qualifiers for C are: a-z, A-Z, 0-9, and underscore 
     // if it isn't a qualifier, skip it 

     while (isnotqualifier (string[i])) i++; 

     for (int j = 0; j < tcount; j++){ 

      // returns 0 for no match 
      // returns the length of the keyword if they match 
      result = string_compare (&string[i], tokens[j]); 

      if (result > 0){ // if the string matches 
       token_push (tks, i, i + result); // add the token 
       // token_push (data_struct, where_it_begins, where_it_ends) 
       break; 
      } 
     } 

     if (result > 0){ 
      i += result; 
     } else { 
      // skip to the next non-qualifier 
      // then skip to the beginning of the next qualifier 

      /* ie, go from: 
       'some_id + sizeof (int)' 
       ^

      to here: 
       'some_id + sizeof (int)' 
         ^
      */ 
     } 
    } 

    if (!tks->len){ 
     free (tks); 
     return 0; 
    } else return tks; 
} 

संभव समाधान:


प्रासंगिक समाधान:

मैं निम्नलिखित पर विचार कर रहा हूं:

  • एक बार बड़ी स्ट्रिंग स्कैन करें, और प्रत्येक बार उपयोगकर्ता इनपुट (पूरे दस्तावेज़ को फिर से स्कैन करने के बजाय) टोकन मार्करों का मूल्यांकन/समायोजन करने के लिए एक फ़ंक्शन जोड़ें। मुझे उम्मीद है कि इससे बाधा ठीक हो जाएगी क्योंकि बहुत कम पार्सिंग शामिल है। लेकिन, यह प्रोग्राम को पूरी तरह से ठीक नहीं करता है, क्योंकि प्रारंभिक स्कैन अभी भी वास्तव में लंबे समय तक ले सकता है।

  • अनुकूलन टोकन स्कैनिंग एल्गोरिथ्म (नीचे देखें)

मैं भी विचार किया गया है, लेकिन अस्वीकार कर दिया है, इन अनुकूलन:

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

वास्तु समाधान:

विधानसभा भाषा, इन तार पार्स करने के लिए रजिस्टरों में पात्रों लोड और उन्हें एक बार में 4 या 8 बाइट्स तुलना करने के लिए किया जाएगा एक तेज तरीका है। कुछ अतिरिक्त उपाय और सावधानियां हैं जिन्हें ध्यान में रखना होगा, जैसे:

  • क्या आर्किटेक्चर असीमित स्मृति पहुंच का समर्थन करता है?
  • सभी श्रृंखलाएं पढ़ने उल्लंघन
  • दूसरों को रोकने के लिए आकार s, जहां s % word-size == 0 का हो सकता है, के लिए होता है?

लेकिन ये समस्याएं प्रतीत होती हैं जैसे उन्हें आसानी से ठीक किया जा सकता है। एकमात्र समस्या (असेंबली भाषा में लिखने वाले सामान्य लोगों के अलावा) यह है कि यह एक एल्गोरिदमिक समाधान नहीं है क्योंकि यह एक हार्डवेयर समाधान है।

एल्गोरिथम समाधान:

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

एक तरीका मैंने उनको पुनर्व्यवस्थित करने के बारे में सोचा है, यह कीवर्ड की सूची के आयामों को स्विच करके है।

// some keywords for the C language 

auto // keywords[0] 
break // keywords[1] 
case char const continue // keywords[2], keywords[3], keywords[4] 
default do double 
else enum extern 
float for 
goto 
if int 
long 
register return 
short signed sizeof static struct switch 
typedef 
union unsigned 
void volatile 
while 

/* keywords[i] refers to the i-th keyword in the list 
* 
*/ 

दो आयामी सरणी के आयाम स्विचिंग यह इस तरह दिखना होगा:: यहाँ C में इस बात का एक उदाहरण है

0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 
    1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 
    ----------------------------------------------------------------- 
1 | a b c c c c d d d e e e f f g i i l r r s s s s s s t u u v v w 
2 | u r a h o o e o o l n x l o o f n o e e h i i t t w y n n o o h 
3 | t e s a n n f u s u t o r t t n g t o g z a r i p i s i l i 
4 | o a e r s t a b e m e a o  g i u r n e t u t e o i d a l 
5 | k  i u l  r t   s r t e o i c c d n g t e 
6 |   n l e  n    t n d f c t h e n i 
7 |   u t      e    f e l 
8 |   e       r     d e 

// note that, now, keywords[0] refers to the string "abccccdddeeefffiilrr" 

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

क्या इस एल्गोरिदम को बेहतर बनाने के लिए किया जा सकता है?

क्या प्रदर्शन बढ़ाने के लिए कोई अन्य दृष्टिकोण लिया जा सकता है?

नोट्स:

This question अतः से मेरी मदद नहीं करता है। बॉयर-मूर-हॉर्सपूल एल्गोरिदम (जैसा कि मैं इसे समझता हूं) एक स्ट्रिंग के भीतर उप-स्ट्रिंग खोजने के लिए एक एल्गोरिदम है।चूंकि मैं एकाधिक तारों के लिए पार्सिंग कर रहा हूं, मुझे लगता है कि अनुकूलन के लिए और अधिक जगह है।

+1

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

उत्तर

3

Aho-Corasick एक बहुत ही शांत एल्गोरिथ्म है लेकिन यह, कीवर्ड मैचों के लिए आदर्श नहीं है क्योंकि कीवर्ड मैचों गठबंधन कर रहे हैं; आपके पास ओवरलैपिंग मैचों नहीं हो सकते हैं क्योंकि आप केवल एक पूर्ण पहचानकर्ता से मेल खाते हैं।

मूल पहचानकर्ता लुकअप के लिए, आपको बस अपने कीवर्ड से trie बनाने की आवश्यकता है (नीचे नोट देखें)।

आपका मूल एल्गोरिदम ठीक है: पहचानकर्ता की शुरुआत पाएं, और फिर देखें कि यह एक कीवर्ड है या नहीं। दोनों भागों में सुधार करना महत्वपूर्ण है। जब तक आपको मल्टीबाइट वर्णों से निपटने की आवश्यकता न हो, तब तक किसी कीवर्ड की शुरुआत खोजने का सबसे तेज़ तरीका 256-एंट्री टेबल का उपयोग करना है, प्रत्येक संभावित चरित्र के लिए एक प्रविष्टि के साथ। तीन संभावनाएं हैं:

  1. चरित्र पहचानकर्ता में प्रकट नहीं हो सकता है। (स्कैन जारी रखें)

  2. चरित्र पहचानकर्ता में दिखाई दे सकता है लेकिन चरित्र के साथ कोई कीवर्ड प्रारंभ नहीं होता है। (पहचानकर्ता को छोड़ें)

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

वास्तव में चरण 2 और 3 पर्याप्त रूप से पर्याप्त हैं कि आपको विशेष तर्क की आवश्यकता नहीं है।

उपर्युक्त एल्गोरिदम के साथ कुछ अपर्याप्तता है क्योंकि ऐसे कई मामले हैं जहां आपको पहचानकर्ता की तरह दिखने वाला कुछ मिलता है लेकिन जो वाक्य रचनात्मक रूप से नहीं हो सकता है। सबसे आम मामले टिप्पणियां और उद्धृत तार हैं, लेकिन अधिकांश भाषाओं में अन्य संभावनाएं हैं। उदाहरण के लिए, सी में आप हेक्साडेसिमल फ्लोटिंग पॉइंट नंबर प्राप्त कर सकते हैं; जबकि कोई सी कीवर्ड [a-f] से सिर्फ निर्माण किया जा सकता है, एक उपयोगकर्ता के आपूर्ति शब्द हो सकता है:

0x1.deadbeef 

दूसरी ओर, सी ++ उपयोगकर्ता परिभाषित संख्यात्मक प्रत्यय है, जो आपको अच्छी तरह से कीवर्ड के रूप में पहचान करने के लिए चाहते हो सकता है की अनुमति देता है उपयोगकर्ता सूची में उन्हें कहते हैं:

274_myType 

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


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

+0

trie एक बहुत ही आशाजनक समाधान की तरह दिखता है, धन्यवाद। आपके दूसरे खंड के बारे में, मैं टोकन को समायोजित करने के लिए मूल्यांकन फ़ंक्शन को कार्यान्वित करने की योजना बना रहा हूं (प्रश्न देखें: 'ctrl + f स्कैन करें') फिर से स्कैनिंग के बिना। साथ ही, '* /' में टाइपिंग संभावित रूप से पिछले पंक्तियों को प्रभावित कर सकती है, लेकिन मैं आपका बिंदु देखता हूं। – tay10r

+0

@ टेलरफ्लोरेस: '* /' पिछली लाइनों को कैसे प्रभावित कर सकता है? यह उस टिप्पणी को समाप्त करता है जो संभावित रूप से पिछली पंक्ति में शुरू किया गया था, लेकिन यह पिछली पंक्ति अचानक एक टिप्पणी या टिप्पणी नहीं करता है। (त्रुटिपूर्ण टिप्पणियों या उद्धरणों को त्रुटियों के रूप में पहचानने से बचें और उनके साथ कुछ कर रहे हैं। इससे अनावश्यक दृश्य शोर भी होता है; 99.99% समय, उपयोगकर्ता बस उन्हें समाप्त करने वाला है। एक रणनीति जिसका मैंने उपयोग किया है, बिना किसी अनुक्रमित होने से बचने के लिए है लाइनें तब भी बदलती हैं जब तक कि उपयोगकर्ता कुछ समय के लिए टाइपिंग बंद नहीं कर लेता है, उम्मीद है कि इसकी आवश्यकता नहीं होगी।) – rici

+0

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

1

ऐसा करने का सबसे तेज़ तरीका शब्द सेट में निर्मित एक सीमित राज्य मशीन होगी। एफएसएम बनाने के लिए लेक्स का प्रयोग करें।

+0

सच है, लेकिन वहां बहुत सारे विवरण गायब हैं, और ऐसा करने के कई तरीके हैं जो * सबसे तेज़ नहीं होंगे। इन मुद्दों को पहले से ही बड़े नाम वाले एल्गोरिदम द्वारा संबोधित किया गया है। संपादित करें: लेक्स एक अच्छा सुझाव है, लेकिन फ्लेक्स बेहतर है। –

+0

मुझे लगता है कि एकमात्र बगबू गतिशील स्ट्रिंग है (जैसे इसे संपादित किया जा रहा है?)। उस स्थिति में, संपादन क्षेत्रों को नोट करें और पुराने टोकन स्ट्रीम को तब तक रखें जब तक कि आप परिवर्तन क्षेत्रों से कुछ दूरी न हों, और फिर से टोकननाइज़ करें। – Jiminion

0

इस समस्या के लिए सबसे अच्छा एल्गोरिदम शायद अहो-कोरसिक है। वहां पहले से ही मौजूद सी कार्यान्वयन, उदाहरण के लिए,

http://sourceforge.net/projects/multifast/ 
+0

लिंक के लिए धन्यवाद। मैं अभी भी इसे पढ़ रहा हूँ। ऐसा लगता है कि मैं सूची में कीवर्ड को गतिशील रूप से जोड़ नहीं सकता, जो एक प्रकार का ड्रॉ है क्योंकि तब मैं उपयोगकर्ता परिभाषित कीवर्ड नहीं जोड़ सकता (प्रश्न देखें: 'ctrl + f उपयोगकर्ता परिभाषित टोकन')। क्या ये सच है? – tay10r

2

इस कोड को हरा करना मुश्किल होगा ।

मान लीजिए कि आपके कीवर्ड "ए", "कुल्हाड़ी" और "foo" हैं। तब

switch(pc[0]){ 
    break; case 'a':{ 
    if (0){ 
    } else if (strcmp(pc, "a")==0 && !alphanum(pc[1])){ 
     // push "a" 
     pc += 1; 
    } else if (strcmp(pc, "ax")==0 && !alphanum(pc[2])){ 
     // push "ax" 
     pc += 2; 
    } 
    } 
    break; case 'f':{ 
    if (0){ 
    } else if (strcmp(pc, "foo")==0 && !alphanum(pc[3])){ 
     // push "foo" 
     pc += 3; 
    } 
    // etc. etc. 
    } 
    // etc. etc. 
} 

यदि आप एक खोजशब्द नहीं दिख रहा है, बस pc को बढ़ा देते और फिर कोशिश करें:

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

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

+0

मैं देखता हूं कि आपका क्या मतलब है। मैं अभी हैश के साथ एक विधि पर काम कर रहा हूं, कि मैंने अभी तक पोस्ट नहीं किया है, और यह भी काफी कुशल हो सकता है। – tay10r

+0

@ टेलर: हैश कोडिंग एक मजेदार अभ्यास होगा, लेकिन इनपुट इनपुट प्रति चक्र चक्र के संदर्भ में, यहां कोड को तब तक हरा करना मुश्किल होगा जब तक आपके पास लाखों कीवर्ड न हों। जब तक आप इनपुट शब्द के हैश कोड उत्पन्न करते हैं, तब तक आप पहले से ही अधिक चक्र खर्च कर चुके हैं। स्ट्रिंग के साथ हैश कोडिंग जीत कहाँ है यदि वे डेटाबेस जैसे धीमी मीडिया पर संग्रहीत हैं। ट्राई (जो मजेदार लेखन था) का उपयोग करने के बाद –

+0

, मैं वास्तव में इस दृष्टिकोण का उपयोग कर समाप्त हुआ। हालांकि इसे लिखने में थोड़ी देर लग गई (1000 लाइनें अब तक), यह ** ** ** ** तेज है, फिर मैंने उपयोग किए गए आखिरी दृष्टिकोणों में से कोई भी (त्रिभुज सहित)। एक बार फिर धन्यवाद! – tay10r

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