delphi - डेल्फी के लिए एक फास्ट गेटोकन रूटीन है?




optimization parsing (5)

मेरे कार्यक्रम में, मैं लाखों तारों को संसाधित करता हूं जिनमें एक विशेष चरित्र है, उदाहरण के लिए "|" प्रत्येक स्ट्रिंग के भीतर टोकन अलग करने के लिए। मेरे पास n'th टोकन वापस करने के लिए एक फ़ंक्शन है, और यह है:

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
var
 I, P, P2: integer;
begin
  P2 := Pos(Delim, Line);
  if TokenNum = 1 then begin
    if P2 = 0 then
      Result := Line
    else
      Result := copy(Line, 1, P2-1);
  end
  else begin
    P := 0; { To prevent warnings }
    for I := 2 to TokenNum do begin
      P := P2;
      if P = 0 then break;
      P2 := PosEx(Delim, Line, P+1);
    end;
    if P = 0 then
      Result := ''
    else if P2 = 0 then
      Result := copy(Line, P+1, MaxInt)
    else
      Result := copy(Line, P+1, P2-P-1);
  end;
end; { GetTok }

जब मैं डेल्फी 4 का उपयोग कर रहा था, तो मैंने इस फ़ंक्शन को वापस विकसित किया। यह बहुत ही कुशल पॉज़ेक्स रूटीन को कॉल करता है जिसे मूल रूप से फास्टकोड द्वारा विकसित किया गया था और अब डेल्फी की स्ट्रूट्स लाइब्रेरी में शामिल है।

मैंने हाल ही में डेल्फी 200 9 में अपग्रेड किया है और मेरे तार सभी यूनिकोड हैं। यह GetTok फ़ंक्शन अभी भी काम करता है और अभी भी अच्छी तरह से काम करता है।

मैं डेल्फी 200 9 में नई पुस्तकालयों से गुजर चुका हूं और इसके लिए कई नए कार्य और परिवर्धन हैं।

लेकिन मैंने विभिन्न फास्टकोड परियोजनाओं में मुझे किसी भी नए डेल्फी पुस्तकालयों में गेटोकन फ़ंक्शन नहीं देखा है, और मुझे ज़ारको गजिक के अलावा Google खोज के साथ कुछ भी नहीं मिला : डेल्फी स्प्लिट / टोकनेज़र फ़ंक्शंस , जो नहीं है जैसा कि मेरे पास पहले से ही अनुकूलित है।

कोई भी सुधार, यहां तक ​​कि 10% मेरे कार्यक्रम में ध्यान देने योग्य होगा। मुझे पता है कि एक विकल्प स्ट्रिंगलिस्ट है और हमेशा टोकन को अलग रखने के लिए, लेकिन इसमें एक बड़ा ओवरहेड मेमोरी-वार है और मुझे यकीन नहीं है कि मैंने यह काम करने के लिए यह सब किया है कि यह कोई तेज़ होगा या नहीं।

वाह। तो इस लंबी हवादार बातचीत के बाद, मेरा सवाल वास्तव में है:

क्या आप GetToken दिनचर्या के किसी भी बहुत तेज़ कार्यान्वयन के बारे में जानते हैं? एक असेंबलर अनुकूलित संस्करण आदर्श होगा?

यदि नहीं, तो क्या कोई अनुकूलन है जो आप उपरोक्त मेरे कोड में देख सकते हैं जो सुधार कर सकता है?

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

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

      while (cp^ > #0) and (cp^ <= Delim) do    
        Inc(cp);

मैं हर किसी के जवाबों के माध्यम से जा रहा हूं और विभिन्न सुझावों को आजमा सकता हूं और उनकी तुलना करता हूं। फिर मैं परिणाम पोस्ट करूंगा।

भ्रम: ठीक है, अब मैं वास्तव में परेशान हूँ।

मैंने पीसीहर्स के साथ जाने के लिए कार्ल और बैरी की सिफारिश की, और यहां मेरा कार्यान्वयन है:

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
{ LK Nov 7, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; https://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
 I: integer;
 PLine, PStart: PChar;
begin
  PLine := PChar(Line);
  PStart := PLine;
  inc(PLine);
  for I := 1 to TokenNum do begin
    while (PLine^ <> #0) and (PLine^ <> Delim) do
      inc(PLine);
    if I = TokenNum then begin
      SetString(Result, PStart, PLine - PStart);
      break;
    end;
    if PLine^ = #0 then begin
      Result := '';
      break;
    end;
    inc(PLine);
    PStart := PLine;
  end;
end; { GetTok }

कागज पर, मुझे नहीं लगता कि आप इससे बेहतर कर सकते हैं।

इसलिए मैंने दोनों दिनचर्या को कार्य में रखा और यह देखने के लिए कि क्या हो रहा है, एकटाइम का इस्तेमाल किया। रन में मैंने गेटोक को 1,108,514 कॉल शामिल किए थे।

AQTime ने मूल दिनचर्या को 0.40 सेकंड पर समय दिया। Pos को लाखों कॉलों ने 0.10 सेकंड लिया। टोकननम = 1 प्रतियों में से आधे मिलियन ने 0.10 सेकेंड लिया। 600,000 PosEx कॉल केवल 0.03 सेकंड ले गए।

फिर मैंने एक ही रन और बिल्कुल वही कॉल के लिए एकटाइम के साथ अपना नया दिनचर्या का समय दिया। एकटाइम रिपोर्ट करता है कि मेरी नई "तेज़" दिनचर्या 3.65 सेकेंड ले गई, जो 9 गुना लंबी है। एकटाइम के अनुसार अपराधी पहला लूप था:

     while (PLine^ <> #0) and (PLine^ <> Delim) do
       inc(PLine);

समय रेखा, जिसे 18 मिलियन बार निष्पादित किया गया था, 2.66 सेकेंड में रिपोर्ट किया गया था। 16 लाख बार निष्पादित इंक लाइन को 0.47 सेकेंड लेने के लिए कहा गया था।

अब मैंने सोचा कि मुझे पता था कि यहाँ क्या हो रहा था। पिछले साल मैंने एक प्रश्न में एकटाइम के साथ एक ही समस्या की थी: केस स्टेटमेंट की तुलना में CharInSet तेज क्यों है?

फिर यह बैरी केली थी जिसने मुझे अंदर चिपकाया। असल में, एकटाइम जैसे एक उपकरण प्रोफाइलर माइक्रोप्टाइमाइज़ेशन के लिए काम नहीं करता है। यह प्रत्येक पंक्ति में एक ओवरहेड जोड़ता है जो परिणाम को स्वैप कर सकता है जो इन संख्याओं में स्पष्ट रूप से दिखाया गया है। मेरे नए "अनुकूलित कोड" में निष्पादित 34 मिलियन लाइनों को पॉज़ और पॉज़ेक्स रूटीन से स्पष्ट रूप से कम या कोई ओवरहेड के साथ, मेरे मूल कोड की कई मिलियन लाइनों को खत्म कर दिया गया है।

बैरी ने मुझे QueryPerformanceCounter का उपयोग करके कोड का एक नमूना दिया ताकि यह जांच सके कि वह सही था, और उस मामले में वह था।

ठीक है, तो चलिए अब क्वेरीरीफॉर्मेंस काउंटर के साथ ऐसा करते हैं कि यह साबित करने के लिए कि मेरा नया दिनचर्या तेज़ है और 9 गुना धीमा नहीं है क्योंकि एकटाइम कहता है। तो मैं यहाँ जाता हूं:

function TimeIt(const Title: string): double;
var  i: Integer;
  start, finish, freq: Int64;
  Seconds: double;
begin
  QueryPerformanceCounter(start);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 1);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 2);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 3);
  for i := 1 to 250000 do
    GetTokOld('This is a string|that needs|parsing', '|', 4);
  QueryPerformanceCounter(finish);
  QueryPerformanceFrequency(freq);
  Seconds := (finish - start) / freq;
  Result := Seconds;
end;

तो यह GetTok को 1,000,000 कॉल का परीक्षण करेगा।

पॉस और पॉज़ेक्स कॉल के साथ मेरी पुरानी प्रक्रिया में 0.2 9 सेकेंड लगे। पीसीएचर्स के साथ नया एक 2.07 सेकंड लिया।

अब मैं पूरी तरह से परेशान हूँ! क्या कोई मुझे बता सकता है कि पीसीहर प्रक्रिया न केवल धीमी है, लेकिन 8 से 9 गुना धीमी है !?

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

1 मिलियन कॉल का समय 1.88 सेकेंड से 22 सेकंड तक चला गया।

और आश्चर्य की बात है कि, मेरे मूल Pos / PosEx दिनचर्या का समय यूपी को .29 से .44 सेकेंड तक चला गया जब मैंने इसे एक डेरिम पैरामीटर को चार में बदल दिया।

वाकई, मैं डेल्फी के अनुकूलक से निराश हूं। वह Delim एक निरंतर पैरामीटर है। ऑप्टिमाइज़र ने ध्यान दिया होगा कि लूप के भीतर एक ही रूपांतरण हो रहा है और इसे बाहर ले जाना चाहिए ताकि यह केवल एक बार किया जा सके।

मेरे कोड जनरेशन पैरामीटर को दो बार जांचना, हां मेरे पास अनुकूलन सही और स्ट्रिंग प्रारूप की जांच बंद है।

निचली पंक्ति यह है कि एंड्रिया के फिक्स के साथ नया पीसीहर दिनचर्या मेरे मूल (.22 बनाम .29) से लगभग 25% तेज है।

मैं अभी भी अन्य टिप्पणियों का पालन करना चाहता हूं और उन्हें जांचना चाहता हूं।

ऑप्टिमाइज़ेशन को बंद करना और स्ट्रिंग प्रारूप जांच चालू करना केवल 22 से 30 तक का समय बढ़ाता है। यह मूल के बारे में इसके बारे में बताता है।

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

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

इसके अलावा, मैंने इस पोस्ट में पहले एकटाइम को गलत तरीके से झुका दिया था, यह सोचकर कि मेरे नए दिनचर्या के लिए अतिरिक्त समय जोड़ा गया था, यह पूरी तरह से जोड़ा गया उपकरण था। अब जब मैं वापस जाता हूं और स्ट्रिंग के बजाय चार पैरामीटर के साथ जांचता हूं, जबकि लूप डाउन 30 सेकंड (2.66 से) तक होता है और इंक लाइन 14 सेकंड (.47 से) तक होती है। अजीब बात यह है कि इंक लाइन भी नीचे जायेगी। लेकिन मैं पहले से ही इस परीक्षण से पहना जा रहा हूँ।

मैंने कार्ल के पात्रों द्वारा लूपिंग का विचार लिया, और उस विचार के साथ उस कोड को दोबारा लिखा। यह 2222 से नीचे 1 9 सेकेंड तक एक और सुधार करता है। तो अब अब तक का सबसे अच्छा है:

function GetTok(const Line: string; const Delim: Char; const TokenNum: Byte): string;
{ LK Nov 8, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; https://stackoverflow.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
  I, CurToken: Integer;
  PLine, PStart: PChar;
begin
  CurToken := 1;
  PLine := PChar(Line);
  PStart := PLine;
  for I := 1 to length(Line) do begin
    if PLine^ = Delim then begin
      if CurToken = TokenNum then
        break
      else begin
        CurToken := CurToken + 1;
        inc(PLine);
        PStart := PLine;
      end;
    end
    else
      inc(PLine);
  end;
  if CurToken = TokenNum then
    SetString(Result, PStart, PLine - PStart)
  else
    Result := '';
end;

इसके लिए अभी भी कुछ मामूली अनुकूलन हो सकते हैं, जैसे CurToken = Tokennum तुलना, जो एक ही प्रकार, इंटेगर या बाइट होना चाहिए, जो भी तेज़ हो।

लेकिन मान लें, अब मैं खुश हूं।

StackOverflow डेल्फी समुदाय के लिए फिर से धन्यवाद।


असेंबलर का उपयोग माइक्रो-ऑप्टिमाइज़ेशन होगा। एल्गोरिदम अनुकूलित करके बहुत अधिक लाभ प्राप्त किए जा सकते हैं। काम नहीं कर रहा है हर बार सबसे तेज़ संभव तरीके से काम कर रहा है।

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

लेकिन आम तौर पर मैं कार्ल के उत्तर (+1) से सहमत हूं, स्कैनिंग के लिए एक PChar का उपयोग करके शायद आपके वर्तमान कोड से तेज़ होगा।


आपका नया फ़ंक्शन (पीसीहर वाला एक) को "डेलीम" को चार के रूप में घोषित करना चाहिए और स्ट्रिंग के रूप में नहीं। आपके वर्तमान कार्यान्वयन में संकलक को "डेलीम" के साथ इसकी तुलना करने के लिए प्लिन ^ char को स्ट्रिंग में परिवर्तित करना होगा। और यह एक तंग लूप में होता है जिसके परिणामस्वरूप एक विशाल प्रदर्शन हिट होता है।

function GetTok(const Line: string; const Delim: Char{<<==}; const TokenNum: Byte): string;
{ LK Feb 12, 2007 - This function has been optimized as best as possible }
{ LK Nov 7, 2009 - Reoptimized using PChars instead of calls to Pos and PosEx }
{ See; http://.com/questions/1694001/is-there-a-fast-gettoken-routine-for-delphi }
var
 I: integer;
 PLine, PStart: PChar;
begin
  PLine := PChar(Line);
  PStart := PLine;
  inc(PLine);
  for I := 1 to TokenNum do begin
    while (PLine^ <> #0) and (PLine^ <> Delim) do
      inc(PLine);
    if I = TokenNum then begin
      SetString(Result, PStart, PLine - PStart);
      break;
    end;
    if PLine^ = #0 then begin
      Result := '';
      break;
    end;
    inc(PLine);
    PStart := PLine;
  end;
end; { GetTok }

डेल्फी बहुत कुशल कोड के लिए संकलित; मेरे अनुभव में, असेंबलर में बेहतर करना बहुत मुश्किल था।

मुझे लगता है कि आपको केवल एक पीसीहर को इंगित करना चाहिए (वे अभी भी मौजूद हैं, है ना? मैंने स्ट्रिंग की शुरुआत में डेल्फी के साथ तरीकों को अलग किया) और "गिनती" करते समय इसे बढ़ाया, जब तक आपको एन -1 नहीं मिला उनमें से। मुझे संदेह है कि बार-बार पॉज़ेक्स को कॉल करने से तेज़ होगा।

उस स्थिति को ध्यान में रखें, फिर जब तक आप अगली पाइप हिट न करें तब तक पॉइंटर को और बढ़ाएं। अपने सबस्ट्रिंग खींचो। किया हुआ।

मैं केवल अनुमान लगा रहा हूं, लेकिन मुझे आश्चर्य नहीं होगा अगर यह जल्दी से हल हो गया था तो इस समस्या को हल किया जा सकता है।

संपादित करें: यहां मेरे मन में क्या था। यह कोड, हां, असम्पीडित और अवांछित है, लेकिन यह दर्शाता है कि मेरा क्या मतलब था।

विशेष रूप से, डेलीम को एक सिंगल के रूप में माना जाता है, जो मेरा मानना ​​है कि अंतर की दुनिया बनाता है अगर वह आवश्यकताओं को पूरा करेगा, और प्लिन के चरित्र को केवल एक बार परीक्षण किया जाता है। अंत में, टोकननम के खिलाफ कोई और तुलना नहीं है; मेरा मानना ​​है कि डेलीमीटर की गिनती के लिए 0 से काउंटर कम करना तेज है।

function GetTok(const Line: string; const Delim: string; const TokenNum: Byte): string;
var 
  Del: Char;
  PLine, PStart: PChar;
  Nth, I, P0, P9: Integer;
begin
  Del := Delim[1];
  Nth := TokenNum + 1;
  P0 := 1;
  P9 := Line.length + 1;
  PLine := PChar(line);
  for I := 1 to P9 do begin
    if PLine^ = Del then begin
      if Nth = 0 then begin
        P9 := I;
        break;
      end;
      Dec(Nth);
      if Nth = 0 then P0 := I + 1
    end;
    Inc(PLine);
  end;
  if (Nth <= 1) or (TokenNum = 1) then
    Result := Copy(Line, P0, P9 - P0);
  else
    Result := '' 
end;

मैं व्यक्ति हमेशा एल्गोरिदम को दोष नहीं देता हूं, लेकिन अगर मैं स्रोत के पहले भाग को देखता हूं, तो समस्या यह है कि स्ट्रिंग एन के लिए, आप स्ट्रिंग 1..एन -1 के लिए भी पीओएस / पॉसेक्स करते हैं।

इसका मतलब एन वस्तुओं के लिए है, आप योग (एन, एन -1, एन -2 ... 1) पीओएस (= + / - 0.5 * एन ^ 2) करते हैं, जबकि केवल एन की आवश्यकता होती है।

यदि आप केवल अंतिम पाए गए परिणाम की स्थिति को कैश करते हैं, उदाहरण के लिए VAR पैरामीटर द्वारा पारित रिकॉर्ड में, आप बहुत कुछ प्राप्त कर सकते हैं।

प्रकार
TLastPosition = रिकॉर्ड elementnr: पूर्णांक; // अंतिम टोकनंबर तत्व: पूर्णांक; अंतिम मैच अंत की // चरित्र सूचकांक;

और फिर कुछ

अगर tokennum = (lastposition.elementnr + 1) तो newpos शुरू करें: = posex (delim, line, lastposition.elementpos); समाप्त;

दुर्भाग्यवश, मेरे पास अब लिखने का समय नहीं है, लेकिन मुझे उम्मीद है कि आपको यह विचार मिल जाएगा


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

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

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

अद्यतन : ठीक है, मैंने इसे देखने में लगभग 40 मिनट बिताए। यदि आप जानते हैं कि डिलीमीटर एक चरित्र होने वाला है, तो आप दूसरे संस्करण (यानी पीसीहर स्कैनिंग) के साथ हमेशा बेहतर होते हैं, लेकिन आपको Delim को एक चरित्र के रूप में पास करना होगा। लिखने के समय, आप PLine^ की PLine^ में PLine^ अभिव्यक्ति - प्रकार के प्रकार - एक स्ट्रिंग में कनवर्ट कर रहे हैं। वह बहुत धीमा होगा; स्ट्रिंग में भी अनुक्रमणित, Delim[1] साथ कुछ हद तक धीमा हो जाएगा।

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

यहां एक संस्करण है जो सभी टोकननाइजेशन को एक बार करता है, और एक सरणी देता है। सरणी बनाने के लिए कितना बड़ा पता करने के लिए, इसे दो बार टोकननाइज़ करने की आवश्यकता है। दूसरी तरफ, केवल दूसरी टोकननाइजेशन को स्ट्रिंग निकालने की आवश्यकता होती है:

// Do all tokenization up front.
function GetTok4(const Line: string; const Delim: Char): TArray<string>;
var
  cp, start: PChar;
  count: Integer;
begin
  // Count sections
  count := 1;
  cp := PChar(Line);
  start := cp;
  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      Inc(count);
      Break;
    end;
  end;

  SetLength(Result, count);
  cp := start;
  count := 0;

  while True do
  begin
    if cp^ <> #0 then
    begin
      if cp^ <> Delim then
        Inc(cp)
      else
      begin
        SetString(Result[count], start, cp - start);
        Inc(cp);
        Inc(count);
      end;
    end
    else
    begin
      SetString(Result[count], start, cp - start);
      Break;
    end;
  end;
end;

यहां पुन: शुरू करने योग्य दृष्टिकोण है। वर्तमान स्थिति और डिलीमीटर चरित्र के भार और स्टोर की लागत होती है, हालांकि:

type
  TTokenizer = record
  private
    FSource: string;
    FCurrPos: PChar;
    FDelim: Char;
  public
    procedure Reset(const ASource: string; ADelim: Char); inline;
    function GetToken(out AResult: string): Boolean; inline;
  end;

procedure TTokenizer.Reset(const ASource: string; ADelim: Char);
begin
  FSource := ASource; // keep reference alive
  FCurrPos := PChar(FSource);
  FDelim := ADelim;
end;

function TTokenizer.GetToken(out AResult: string): Boolean;
var
  cp, start: PChar;
  delim: Char;
begin
  // copy members to locals for better optimization
  cp := FCurrPos;
  delim := FDelim;

  if cp^ = #0 then
  begin
    AResult := '';
    Exit(False);
  end;

  start := cp;
  while (cp^ <> #0) and (cp^ <> Delim) do
    Inc(cp);

  SetString(AResult, start, cp - start);
  if cp^ = Delim then
    Inc(cp);
  FCurrPos := cp;
  Result := True;
end;

बेंचमार्किंग के लिए उपयोग किया जाने वाला पूरा प्रोग्राम यहां दिया गया है।

परिणाम यहां दिए गए हैं:

*** count=3, Length(src)=200
GetTok1: 595 ms
GetTok2: 547 ms
GetTok3: 2366 ms
GetTok4: 407 ms
GetTokBK: 226 ms
*** count=6, Length(src)=350
GetTok1: 1587 ms
GetTok2: 1502 ms
GetTok3: 6890 ms
GetTok4: 679 ms
GetTokBK: 334 ms
*** count=9, Length(src)=500
GetTok1: 3055 ms
GetTok2: 2912 ms
GetTok3: 13766 ms
GetTok4: 947 ms
GetTokBK: 446 ms
*** count=12, Length(src)=650
GetTok1: 4997 ms
GetTok2: 4803 ms
GetTok3: 23021 ms
GetTok4: 1213 ms
GetTokBK: 543 ms
*** count=15, Length(src)=800
GetTok1: 7417 ms
GetTok2: 7173 ms
GetTok3: 34644 ms
GetTok4: 1480 ms
GetTokBK: 653 ms

आपके डेटा की विशेषताओं के आधार पर, क्या डिलीमीटर एक चरित्र होने की संभावना है या नहीं, और आप इसके साथ कैसे काम करते हैं, अलग-अलग दृष्टिकोण तेजी से हो सकते हैं।

(मैंने अपने पहले के कार्यक्रम में गलती की थी, मैं नियमित रूप से प्रत्येक शैली के लिए एक ही परिचालन को माप नहीं रहा था। मैंने पेस्टबिन लिंक और बेंचमार्क परिणामों को अपडेट किया।)





aqtime