سؤال

في برنامجي، أقوم بمعالجة الملايين من السلاسل التي لها طابع خاص، على سبيل المثال."|" لفصل الرموز داخل كل سلسلة.لدي وظيفة لإرجاع الرمز المميز، وهذا هو:

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.إنه يستدعي روتين PosEx الفعال للغاية والذي تم تطويره في الأصل بواسطة Fastcode وتم تضمينه الآن في مكتبة StrUtils في دلفي.

لقد قمت مؤخرًا بالترقية إلى Delphi 2009 وكانت سلاسلي كلها Unicode.لا تزال وظيفة GetTok هذه تعمل ولا تزال تعمل بشكل جيد.

لقد قمت بالاطلاع على المكتبات الجديدة في دلفي 2009 ويوجد بها العديد من الوظائف والإضافات الجديدة.

لكنني لم أر وظيفة GetToken التي أحتاجها في أي من مكتبات دلفي الجديدة، وفي مشاريع الرمز السريع المختلفة، ولا يمكنني العثور على أي شيء باستخدام بحث Google بخلاف زاركو جايتش:وظائف دلفي سبليت / Tokenizer, ، وهو ليس محسّنًا مثل ما لدي بالفعل.

أي تحسن ولو بنسبة 10% سيكون ملحوظا في برنامجي.أعلم أن البديل هو StringLists وأن أبقي الرموز المميزة منفصلة دائمًا، ولكن هذا يحتوي على ذاكرة علوية كبيرة ولست متأكدًا مما إذا كنت قد قمت بكل هذا العمل لتحويل ما إذا كان سيكون أسرع.

يا للعجب.وبعد كل هذا الحديث الطويل، سؤالي الحقيقي هو:

هل تعرف أي تطبيقات سريعة جدًا لروتين GetToken؟هل سيكون الإصدار الأمثل للمجمع مثاليًا؟

إذا لم يكن الأمر كذلك، فهل هناك أي تحسينات يمكنك رؤيتها على الكود الخاص بي أعلاه والتي قد تؤدي إلى تحسين؟


متابعة:ذكر باري كيلي سؤالاً طرحته قبل عام حول تحسين تحليل الأسطر في الملف.في ذلك الوقت لم أفكر حتى في روتين GetTok الخاص بي والذي لم يتم استخدامه للقراءة أو التحليل.الآن فقط رأيت الحمل الروتيني الخاص بـ GetTok والذي دفعني إلى طرح هذا السؤال.حتى إجابات كارل سموتريش وباري، لم أفكر مطلقًا في الربط بين الاثنين.واضح جدا، لكنه لم يسجل.شكرا للإشارة إلى ذلك.

نعم، إن شخصية Delim الخاصة بي هي شخصية واحدة، لذا من الواضح أن لدي بعض التحسينات الرئيسية التي يمكنني القيام بها.إن استخدامي لـ Pos وPosEx في روتين GetTok (أعلاه) أعماني عن فكرة أنه يمكنني القيام بذلك بشكل أسرع باستخدام البحث عن حرف تلو الآخر بدلاً من ذلك، باستخدام أجزاء من التعليمات البرمجية مثل:

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

سأقوم بمراجعة إجابات الجميع وتجربة الاقتراحات المختلفة ومقارنتها.ثم سأقوم بنشر النتائج.


ارتباك:حسنا، الآن أنا في حيرة حقا.

لقد أخذت بتوصية كارل وباري باستخدام PChars، وهذا هو التنفيذ الخاص بي:

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 }

على الورق، لا أعتقد أنك تستطيع أن تفعل أفضل من هذا.

لذلك قمت بتطبيق كلا الإجراءين على المهمة واستخدمت AQTime لمعرفة ما يحدث.لقد تضمنت الجولة 1,108,514 مكالمة إلى GetTok.

قام AQTime بتوقيت الروتين الأصلي عند 0.40 ثانية.استغرقت مليون مكالمة إلى نقطة البيع 0.10 ثانية.استغرق نصف مليون من TokenNum = 1 نسخة 0.10 ثانية.استغرقت مكالمات PosEx البالغ عددها 600000 0.03 ثانية فقط.

ثم قمت بتوقيت روتيني الجديد مع AQTime لنفس التشغيل ونفس المكالمات بالضبط.تشير تقارير AQTime إلى أن روتيني "السريع" الجديد استغرق 3.65 ثانية، وهو ما يعادل 9 أضعاف المدة.كان السبب وفقًا لـ AQTime هو الحلقة الأولى:

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

تم الإبلاغ عن خط while، الذي تم تنفيذه 18 مليون مرة، في 2.66 ثانية.قيل أن سطر inc، الذي تم تنفيذه 16 مليون مرة، يستغرق 0.47 ثانية.

الآن اعتقدت أنني أعرف ما كان يحدث هنا.واجهت مشكلة مماثلة مع AQTime في سؤال طرحته العام الماضي: لماذا يعتبر CharInSet أسرع من بيان الحالة؟

مرة أخرى كان باري كيلي هو من أرشدني.في الأساس، لا يقوم ملف تعريف الأدوات مثل AQTime بالضرورة بمهمة التحسين الدقيق.فهو يضيف حملاً إضافيًا لكل سطر قد يغرق النتائج التي تظهر بوضوح في هذه الأرقام.إن الـ 34 مليون سطر التي تم تنفيذها في "الكود المحسّن" الجديد الخاص بي تطغى على عدة ملايين من الأسطر من الكود الأصلي الخاص بي، مع القليل من الحمل أو عدمه على ما يبدو من إجراءات Pos وPosEx.

أعطاني باري عينة من التعليمات البرمجية باستخدام QueryPerformanceCounter للتأكد من صحته، وفي هذه الحالة كان كذلك.

حسنًا، فلنفعل الشيء نفسه الآن مع QueryPerformanceCounter لإثبات أن روتيني الجديد أسرع وليس أبطأ 9 مرات كما تقول AQTime.حتى هنا أذهب:

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.

استغرق الإجراء القديم الخاص بي مع مكالمات Pos وPosEx 0.29 ثانية.استغرق الإصدار الجديد المزود بـ PChars 2.07 ثانية.

الآن أنا مرتبك تماما!هل يمكن لأحد أن يقول لي لماذا إجراء PChar ليس أبطأ فحسب، بل أبطأ بمقدار 8 إلى 9 مرات!؟


حل اللغز!قال أندرياس في إجابته لتغيير معلمة Delim من سلسلة إلى Char.سأستخدم دائمًا Char فقط، لذلك على الأقل بالنسبة لتنفيذي، يكون هذا ممكنًا جدًا.لقد دهشت مما حدث.

وانخفض وقت إجراء المليون مكالمة من 1.88 ثانية إلى 0.22 ثانية.

ومن المثير للدهشة أن الوقت اللازم لروتين Pos/PosEx الأصلي الخاص بي قد ارتفع من 0.29 إلى 0.44 ثانية عندما قمت بتغيير معلمة Delim إلى Char.

بصراحة، أشعر بخيبة أمل من مُحسِّن دلفي.أن Delim هي معلمة ثابتة.من المفترض أن يكون المُحسِّن قد لاحظ أن نفس التحويل يحدث داخل الحلقة ويجب أن ينقله للخارج بحيث يتم إجراؤه مرة واحدة فقط.

التحقق مرة أخرى من معلمات إنشاء التعليمات البرمجية الخاصة بي، نعم لدي خيار Optimization True وتنسيق String معطل.

خلاصة القول هي أن روتين PChar الجديد مع إصلاح Andrea أسرع بحوالي 25% من روتيني الأصلي (.22 مقابل .29).

ما زلت أرغب في متابعة التعليقات الأخرى هنا واختبارها.


يؤدي إيقاف تشغيل التحسين وتشغيل التحقق من تنسيق السلسلة إلى زيادة الوقت من .22 إلى .30 فقط.ويضيف تقريبا نفس الشيء إلى الأصل.

تتمثل ميزة استخدام كود المجمع أو إجراءات الاتصال المكتوبة في المجمع مثل Pos أو PosEx في أنها لا تخضع لخيارات إنشاء التعليمات البرمجية التي قمت بتعيينها.سوف يعملون دائمًا بنفس الطريقة، وهي طريقة محسنة مسبقًا وغير منتفخة.

لقد أكدت مجددًا في اليومين الماضيين أن أفضل طريقة لمقارنة التعليمات البرمجية للتحسين الدقيق هي إلقاء نظرة على كود المجمع ومقارنته في نافذة وحدة المعالجة المركزية.سيكون من الرائع أن يتمكن Embarcadero من جعل هذه النافذة أكثر ملاءمة، ويسمح لنا بنسخ أجزاء إلى الحافظة أو طباعة أجزاء منها.

أيضًا، لقد انتقدت AQTime بشكل غير عادل في وقت سابق من هذا المنشور، معتقدًا أن الوقت الإضافي الإضافي لروتيني الجديد كان فقط بسبب الأدوات التي أضافها.الآن بعد أن عدت وتحققت من معلمة Char بدلاً من String، انخفضت حلقة while إلى .30 ثانية (من 2.66) وانخفض خط inc إلى .14 ثانية (من .47).الغريب أن خط المؤتمر الوطني العراقي سوف ينخفض ​​كذلك.لكنني أشعر بالإرهاق من كل هذا الاختبار بالفعل.


لقد أخذت فكرة كارل عن التكرار حسب الأحرف، وأعدت كتابة هذا الرمز بهذه الفكرة.إنه يحقق تحسنًا آخر، وصولاً إلى 0.19 ثانية من 0.22.إذن هذا هو الأفضل حتى الآن:

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 Delphi.

هل كانت مفيدة؟

المحلول

يجب أن تعلن وظيفتك الجديدة (التي تحتوي على PChar) عن "Delim" كـ شار وليس كما خيط.في تطبيقك الحالي، يجب على المترجم تحويل حرف PLine^ إلى سلسلة لمقارنته بـ "Delim".ويحدث ذلك في حلقة ضيقة مما يؤدي إلى تحقيق أداء هائل.

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://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 }

نصائح أخرى

إنه يُحدث فرقًا كبيرًا فيما يُتوقع أن تكون عليه "Delim".إذا كان من المتوقع أن يكون حرفًا واحدًا، فمن الأفضل أن تتنقل عبر حرف السلسلة حرفًا تلو الآخر، ومن الأفضل أن تكون من خلال PChar، وتختبره على وجه التحديد.

إذا كانت سلسلة طويلة، فإن Boyer-Moore وعمليات البحث المشابهة لها مرحلة إعداد لتخطي الجداول، وأفضل طريقة هي إنشاء الجداول مرة واحدة، وإعادة استخدامها لكل اكتشاف لاحق.هذا يعني أنك بحاجة إلى حالة بين الاستدعاءات، وستكون هذه الوظيفة أفضل حالًا كطريقة على كائن بدلاً من ذلك.

كنت قد تكون مهتمة في لقد قدمت هذه الإجابة على سؤال من قبل، حول أسرع طريقة لتحليل خط في دلفي. (لكنني أرى أنك أنت الذي طرحت السؤال!ومع ذلك، في حل مشكلتك، أود أن ألتزم بالطريقة التي وصفت بها التحليل، لا باستخدام PosEx كما تستخدمه، اعتمادًا على الشكل الطبيعي لـ Delim.)


تحديث:حسنًا، لقد أمضيت حوالي 40 دقيقة في النظر إلى هذا.إذا كنت تعلم أن المحدد سيكون شخصية، فمن الأفضل دائمًا استخدام الإصدار الثاني (أي.مسح PChar)، ولكن عليك اجتيازه Delim كشخصية.في وقت كتابة هذا التقرير، كنت تقوم بتحويل ملف PLine^ تعبير - من النوع Char - إلى سلسلة للمقارنة مع Delim.سيكون ذلك بطيئًا جدًا.حتى الفهرسة في السلسلة، مع Delim[1] سيكون أيضًا بطيئًا إلى حد ما.

ومع ذلك، اعتمادًا على حجم خطوطك وعدد القطع المحددة التي تريد سحبها، قد يكون من الأفضل لك اتباع نهج قابل للاستئناف، بدلاً من تخطي القطع المحددة غير المرغوب فيها داخل روتين الترميز.إذا قمت باستدعاء GetTok مع زيادة الفهارس بشكل متتالي، كما تفعل حاليًا في المعيار المصغر الخاص بك، فسوف ينتهي بك الأمر بأداء O(n*n)، حيث n هو عدد الأقسام المحددة.يمكن تحويل ذلك إلى O(n) إذا قمت بحفظ حالة الفحص واستعادتها للتكرار التالي، أو حزم جميع العناصر المستخرجة في مصفوفة.

فيما يلي إصدار يقوم بجميع عمليات الترميز مرة واحدة، ويعيد مصفوفة.ومع ذلك، فإنه يحتاج إلى الترميز مرتين، لمعرفة حجم إنشاء المصفوفة.من ناحية أخرى، يحتاج الترميز الثاني فقط إلى استخراج السلاسل:

// 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

اعتمادًا على خصائص بياناتك، وما إذا كان من المحتمل أن يكون المحدد حرفًا أم لا، وكيفية التعامل معه، قد تكون الطرق المختلفة أسرع.

(لقد ارتكبت خطأ في برنامجي السابق، لم أكن أقيس نفس العمليات لكل نمط من أنماط الروتين.لقد قمت بتحديث رابط Pastebin ونتائج القياس.)

تقوم دلفي بالتجميع إلى تعليمات برمجية فعالة جدًا؛في تجربتي، كان من الصعب جدًا القيام بعمل أفضل في المجمع.

أعتقد أنه يجب عليك فقط الإشارة إلى PChar (لا تزال موجودة، أليس كذلك؟لقد افترقت عن دلفي حوالي 4.0) في بداية السلسلة وقمت بزيادتها أثناء حساب "|" حتى تجد n-1 منها.أظن أن ذلك سيكون أسرع من الاتصال بـ PosEx بشكل متكرر.

لاحظ هذا الموضع، ثم قم بزيادة المؤشر أكثر حتى تصل إلى الأنبوب التالي.اسحب السلسلة الفرعية الخاصة بك.منتهي.

أنا أخمن فقط، لكنني لن أتفاجأ إذا كان هذا قريبًا من أسرع حل لهذه المشكلة.

يحرر: وهنا ما كان يدور في ذهني.للأسف، هذا الرمز لم يتم تجميعه ولم يتم اختباره، ولكن يجب أن يوضح ما أعنيه.

على وجه الخصوص، يتم التعامل مع Delim على أنه حرف واحد، وهو ما أعتقد أنه يحدث فرقًا كبيرًا إذا كان ذلك سيفي بالمتطلبات، ويتم اختبار الشخصية في PLine مرة واحدة فقط.وأخيرًا، ليس هناك المزيد من المقارنة مع TokenNum؛أعتقد أنه من الأسرع إنقاص العداد إلى 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)، باستخدام أ PChar من المحتمل أن يكون المسح أسرع من الكود الحالي الخاص بك.

هذه هي الوظيفة التي أمتلكها في مكتبتي الشخصية منذ بعض الوقت والتي أستخدمها على نطاق واسع.وأعتقد أن هذا هو الإصدار الأحدث منه.لقد قمت بتحسين إصدارات متعددة في الماضي لمجموعة متنوعة من الأسباب المختلفة.يحاول هذا أن يأخذ في الاعتبار السلاسل المقتبسة، ولكن إذا تمت إزالة هذا الرمز، فإنه يجعل الوظيفة أسرع قليلاً.

لدي بالفعل عدد من الإجراءات الأخرى، CountSections وParseSectionPOS هما مثالان على ذلك.

لسوء الحظ، هذا الروتين يعتمد على ansi/pchar فقط.على الرغم من أنني لا أعتقد أنه سيكون من الصعب نقله إلى Unicode.ربما فعلت ذلك بالفعل...سأضطر إلى التحقق من ذلك.

ملحوظة:هذا الروتين هو 1 بناءً على فهرسة ParseNum.

function ParseSection(ParseLine: string; ParseNum: Integer; ParseSep: Char; QuotedStrChar:char = #0) : string;
var
   wStart, wEnd : integer;
   wIndex : integer;
   wLen : integer;
   wQuotedString : boolean;
begin
   result := '';
   wQuotedString := false;
   if not (ParseLine = '') then
   begin
      wIndex := 1;
      wStart := 1;
      wEnd := 1;
      wLen := Length(ParseLine);
      while wEnd <= wLen do
      begin
         if (QuotedStrChar <> #0) and (ParseLine[wEnd] = QuotedStrChar) then
            wQuotedString := not wQuotedString;

         if not wQuotedString and (ParseLine[wEnd] = ParseSep) then
         begin
            if wIndex=ParseNum then
               break
            else
            begin
               inc(wIndex);
               wStart := wEnd+1;
            end;
         end;
         inc(wEnd);
      end;

      result := copy(ParseLine, wStart, wEnd-wStart);
      if (length(result) > 0) and (QuotedStrChar <> #0) and (result[1] = QuotedStrChar) then
         result := AnsiDequotedStr(result, QuotedStrChar);
   end;
end; { ParseSection }

في الكود الخاص بك، أعتقد أن هذا هو السطر الوحيد الذي يمكن تحسينه:

Result := copy(Line, P+1, MaxInt)

إذا قمت بحساب الطول الجديد هناك، فقد يصبح أسرع قليلاً، ولكن ليس الـ 10% التي تبحث عنها.

تبدو خوارزمية الترميز الخاصة بك جيدة جدًا.لتحسينه، أود تشغيله من خلال ملف التعريف (مثل وقتي من AutomatedQA) مع مجموعة فرعية تمثيلية لبيانات الإنتاج الخاصة بك.سيوجهك ذلك إلى أضعف نقطة.

وظيفة RTL الوحيدة التي تقترب هي هذه الموجودة في وحدة الفئات:

procedure TStrings.SetDelimitedText(const Value: string);

إنه رمز، لكنه يستخدم كليهما QuoteChar و محدد, ، لكنك تستخدم محددًا فقط.

يستخدم SetString وظيفة في وحدة النظام وهي طريقة سريعة جدًا لتعيين محتوى سلسلة بناءً على PChar/PAnsiChar/PUnicodeChar وطولها.

قد يمنحك ذلك بعض التحسن أيضًا؛على الجانب الآخر، ينسخ سريع حقًا أيضًا.

I'm not the person always blaming the algorithm, but if I look at the first piece of source, the problem is that for string N, you do the POS/posexes for string 1..n-1 again too.

هذا يعني أنه بالنسبة للعناصر N، يمكنك جمع (n, n-1,n-2...1) POSes (=+/- 0.5*N^2) ، بينما هناك حاجة إلى N فقط.

إذا قمت ببساطة بتخزين موضع آخر نتيجة تم العثور عليها، على سبيل المثال.في السجل الذي تم تمريره بواسطة معلمة VAR، يمكنك الحصول على الكثير.

يكتب
tlastposition = elementnr السجل:عدد صحيح؛// Last Tokennumber ElementPos:عدد صحيح؛فهرس أحرف المباراة الأخيرة انتهاء;

ثم شيء ما

إذا كان الرمز المميز = (lastposition.elementnr 1) إذن بدأ newpos: = posex (delim ، line ، lastposition.elementpos) ؛نهاية؛

لسوء الحظ، ليس لدي الوقت الآن لكتابة ذلك، ولكن أتمنى أن تكون الفكرة قد وصلت إليك

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top