【问题标题】:Is There A Fast GetToken Routine For Delphi?Delphi 有快速的 GetToken 例程吗?
【发布时间】:2010-12-14 05:16:53
【问题描述】:

在我的程序中,我处理了数百万个具有特殊字符的字符串,例如“|”分隔每个字符串中的标记。我有一个函数可以返回第 n 个令牌,就是这样:

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 }

我在使用 Delphi 4 时开发了这个函数。它调用了非常高效的 PosEx 例程,该例程最初由 Fastcode 开发,现在包含在 Delphi 的 StrUtils 库中。

我最近升级到 Delphi 2009,我的字符串都是 Unicode。这个 GetTok 函数仍然有效并且仍然有效。

我浏览了 Delphi 2009 中的新库,其中有许多新功能和新增功能。

但是我没有在任何新的 Delphi 库中,在各种 fastcode 项目中看到像我需要的 GetToken 函数,而且除了 Zarko Gajic's: Delphi Split / Tokenizer Functions 之外,我无法通过 Google 搜索找到任何东西,这不是优化的就像我已经拥有的一样。

任何改进,即使是 10%,在我的程序中都会很明显。我知道另一种选择是 StringLists 并始终将标记分开,但这在内存方面有很大的开销,我不确定我是否做了所有工作来转换它是否会更快。

哇。因此,经过所有这些冗长的谈话,我的问题真的是:

您知道 GetToken 例程的任何非常快速的实现吗?汇编器优化版本是理想的吗?

如果没有,您可以看到我上面的代码是否有任何优化可能会有所改进?


跟进:Barry Kelly 提到了我一年前提出的一个关于优化文件中行的解析的问题。那时我什至没有想到我的 GetTok 例程没有用于读取或解析。直到现在我才看到我的 GetTok 例程的开销导致我提出这个问题。在 Carl Smotricz 和 Barry 给出答案之前,我从未想过将两者联系起来。如此明显,但它只是没有注册。感谢您指出这一点。

是的,我的 Delim 是单个字符,所以显然我可以进行一些重大优化。我在 GetTok 例程(上图)中对 Pos 和 PosEx 的使用让我没有意识到我可以用逐个字符的搜索来更快地做到这一点,代码如下:

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

我将浏览每个人的答案并尝试各种建议并进行比较。然后我会发布结果。


困惑:好吧,现在我真的很困惑。

我接受了 Carl 和 Barry 的建议来使用 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 来查看发生了什么。我的运行包括对 GetTok 的 1,108,514 次调用。

AQTime 将原始例程计时为 0.40 秒。对 Pos 的百万次调用只用了 0.10 秒。半百万的 TokenNum = 1 个副本需要 0.10 秒。 600,000 个 PosEx 调用只用了 0.03 秒。

然后我用 AQTime 为我的新例程计时,以进行相同的运行和完全相同的调用。 AQTime 报告说我的新“快速”程序耗时 3.65 秒,是 9 倍。根据 AQTime,罪魁祸首是第一个循环:

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

执行了 1800 万次的 while 行被报告为 2.66 秒。 inc 行执行了 1600 万次,据说耗时 0.47 秒。

现在我想我知道这里发生了什么。在我去年提出的一个问题中,我遇到了与 AQTime 类似的问题:Why is CharInSet faster than Case statement?

同样是 Barry Kelly 向我提供了线索。基本上,像 AQTime 这样的检测分析器不一定能完成微优化的工作。它为每条线路增加了开销,这可能会淹没这些数字中清楚显示的结果。在我的新“优化代码”中执行的 3400 万行超过了我原来的几百万行代码,显然 Pos 和 PosEx 例程的开销很少或没有。

Barry 给了我一个使用 QueryPerformanceCounter 的代码示例来检查他是否正确,在这种情况下他是正确的。

好的,现在让我们对 QueryPerformanceCounter 做同样的事情,以证明我的新例程更快,而不是像 AQTime 所说的那样慢 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 次调用。

我使用 Pos 和 PosEx 调用的旧程序耗时 0.29 秒。 带有 PChars 的新版本耗时 2.07 秒。

现在我完全糊涂了!谁能告诉我为什么 PChar 过程不仅慢,而且慢 8 到 9 倍!?


谜团解开了! Andreas 在他的回答中说将 Delim 参数从字符串更改为 Char。我将永远只使用一个字符,所以至少对于我的实现来说这是很有可能的。我对发生的事情感到惊讶。

100 万次调用的时间从 1.88 秒缩短到 0.22 秒。

令人惊讶的是,当我将其 Delim 参数更改为 Char 时,我原来的 Pos/PosEx 例程的时间从 0.29 秒增加到 0.44 秒。

坦率地说,我对 Delphi 的优化器很失望。 Delim 是一个常数参数。优化器应该注意到循环中发生了相同的转换,应该将其移出以便只执行一次。

仔细检查我的代码生成参数,是的,我确实有优化 True 和字符串格式检查关闭。

底线是 Andrea 修复的新 PChar 例程比我原来的快 25%(0.22 对 0.29)。

我还想在这里跟进其他 cmets 并进行测试。


关闭优化并打开字符串格式检查只会将时间从 0.22 增加到 0.30。它与原始版本大致相同。

使用汇编程序代码或调用用 Pos 或 PosEx 等汇编程序编写的例程的优点是它们不受您设置的代码生成选项的影响。它们将始终以相同的方式运行,一种预先优化且不臃肿的方式。

我在过去几天重申,比较微优化代码的最佳方法是查看和比较 CPU 窗口中的汇编程序代码。如果 Embarcadero 可以让那个窗口更方便一点,并允许我们将部分复制到剪贴板或打印它的部分,那就太好了。

另外,我在这篇文章的前面不公平地抨击了 AQTime,认为为我的新例程增加的额外时间仅仅是因为它添加了仪器。现在我返回并检查 Char 参数而不是 String,while 循环下降到 0.30 秒(从 2.66 开始),而 inc 行下降到 0.14 秒(从 0.47 开始)。奇怪的是,inc 线也会下降。但是我已经厌倦了所有这些测试。


我采用了 Carl 的按字符循环的想法,并用这个想法重写了该代码。它做出了另一项改进,从 0.22 秒降至 0.19 秒。所以这是迄今为止最好的:

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比较,应该是相同的类型,Integer或者Byte,取快者。

但是让我们说,我现在很开心。

再次感谢 StackOverflow Delphi 社区。​​p>

【问题讨论】:

  • 为什么要处理数百万个字符串?也许您的程序可以进行优化,使其不必这样做。
  • 这个问题是我优化的尝试之一。当您有一个处理 300 MB 文件的程序时,无论如何它都必须做大量的工作、优化和技巧。但是,如果输入文件对我的程序来说那么大,如果不先处理它,我就无法将它变小。
  • 感谢下午 1.01 的链接(一个有趣的讨论),但我相信如果偏离主题的内容以其他方式传输,例如,StackOverflow 社区将不胜感激。作为对我博客的评论。
  • 您的实现仍然有一个非常错误的地方:检查 PChar 的字符串结尾是不必要的,因为您开始使用的字符串包含它的长度以非常快速的管理形式,因此您可以将其用作 for 的最终值。不过,这需要更多的思考。我会这样做并回复你。
  • 我已将我的想法转化为代码。我很想知道它的表现如何!

标签: delphi optimization parsing token aqtime


【解决方案1】:

Delphi 编译成非常高效的代码;根据我的经验,很难在汇编程序中做得更好。

我认为你应该在字符串的开头指向一个 PChar(它们仍然存在,不是吗?我在 4.0 左右与 Delphi 分道扬镳)并在计算“|”的同时递增它,直到你找到其中 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;

【讨论】:

  • 我有 80% 的把握会。毕竟,PChar 仍然是作为一个字符的指针。我怀疑 Inc 运算符将其移动了 Unicode 字符的宽度。
  • 是的,它适用于 Unicode,正如我上面的实现清楚地表明的那样。但在给出一些解释之前,它看起来要慢得多。
  • 我更新了我的答案。最终结果:这取决于。如果您的分隔符可能不止一个字符,那么您的原始例程还不错,尽管使用 Boyer-Moore 可能会更快。如果它单个字符,则PChar扫描将击败它。顺便说一句,如果速度非常重要并且您的数据源不是 UTF-16,那么使用 AnsiString 和 PAnsiChar 也会更快。
  • Carl:你的代码有很多错别字,line.length 应该是 length(line),Substring 应该是 copy。但我对其进行了测试,将 Delim 参数更改为 Char,它在大约 0.26 秒时出现,就在那里。但我喜欢你将我的令牌循环更改为字符循环的概念,这有可能缩短时间。不过,不知道我们还能挤出多少。
  • 抱歉那些错别字和错误。我懒得在虚拟机中安装 Delphi。我希望我能提供一点帮助,只要我同意其他人的观点,即如果分隔符只有 1 长,则它必须是一个字符。
【解决方案2】:

我不是总是责备算法的人,但如果我查看第一条来源, 问题在于,对于字符串 N,您也再次为字符串 1..n-1 执行 POS/posexes。

这意味着对于 N 个项目,您需要对 (n, n-1,n-2...1) POSes (=+/- 0.5*N^2) 求和,而只需要 N 个。

如果您只是缓存最后找到的结果的位置,例如在通过VAR参数传递的记录中,您可以获得很多。

类型
TLastPosition = 记录 elementnr:整数; // 最后一个令牌号 elementpos:整数; // 最后匹配的字符索引 结尾;

然后是一些东西

如果 tokennum=(lastposition.elementnr+1) 那么 开始 newpos:=posex(delim,line,lastposition.elementpos); 结束;

很遗憾,我现在没有时间写出来,但我希望你能明白

【讨论】:

  • 好吧,重写的算法完全摆脱了 Pos 和 PosEX。但是您的想法在优化原件方面很好。
  • @lkessler:这一点也适用于重写的算法,这就是我在回答中的意思。如果您一个接一个地从同一个字符串中获取前 5 个标记,那么您将扫描第一个 5 次,第二个扫描 4 次,...返回数组中所有 5 个标记的不同过程应该更快,如果您注意如何返回结果(不重新分配数组)。这与您是否使用PosEx() 无关。对于重写的算法,您可以返回令牌地址并将其用作下一个函数调用的搜索起点。
  • mghie:是的。好点子。对于我需要按顺序获取它们的情况,最好的可能是 GetFirstTok 和 GetNextTok 的实现。
【解决方案3】:

“Delim”的预期是什么有很大的不同。如果预计它是单个字符,那么最好逐个字符地遍历字符串,最好是通过 PChar,并专门进行测试。

如果它是一个长字符串,Boyer-Moore 和类似的搜索有一个跳过表的设置阶段,最好的方法是构建表一次,然后为每个后续查找重复使用它们。这意味着您需要调用之间的状态,而这个函数最好作为对象上的方法来代替。

您可能对this answer I gave to a question some time before, about the fastest way to parse a line in Delphi. 感兴趣(但我看到是您提出了这个问题!不过,在解决您的问题时,我会遵守我描述的解析方式,使用 PosEx就像你正在使用的那样,这取决于 Delim 通常的样子。)


更新:好的,我花了大约 40 分钟看这个。如果您知道分隔符将是一个字符,那么使用第二个版本(即 PChar 扫描)几乎总是更好,但您必须将 Delim 作为字符传递。在撰写本文时,您正在将 Char 类型的 PLine^ 表达式转换为字符串以与 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;

Here's the full program I used for benchmarking.

结果如下:

*** 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 链接和基准测试结果。)

【讨论】:

  • 巴里:感谢您的回复。在我的问题中查看我的“后续行动”。
  • 很好... +1! GetTok3 这么慢的原因实际上我们可以发现您在编译器选项“字符串格式检查”中启用了开关。关闭此开关并重复测量!
  • Barry:我的最终“最佳”代码由 PChar 而不是 Token 循环,与您的预先标记非常相似。这可能是此类问题的最佳选择,并且表明了一种通过字符串进行顺序处理以快速执行的很好的通用方法。
【解决方案4】:

您的新函数(带有 PChar 的函数)应将“Delim”声明为 Char 而不是 String。在您当前的实现中,编译器必须将 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 }

【讨论】:

  • 你明白了,安德烈亚斯!谜题解决了,当 Chars 会做时,会向那些传递字符串的人发出警告。
【解决方案5】:

在您的代码中,我认为这是唯一可以优化的行:

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

如果你在那里计算新的长度,它可能会快一点,但不是你想要的 10%。

您的标记化算法看起来还不错。 为了优化它,我会通过一个分析器(比如来自 AutomatedQA 的AQTime)使用你的生产数据的代表性子集来运行它。这会指出你最薄弱的地方。

唯一接近的 RTL 函数是 Classes 单元中的这个:

procedure TStrings.SetDelimitedText(const Value: string);

它进行标记,但同时使用 QuoteCharDelimiter,但您只使用 Delimiter。

它使用系统单元中的SetString 函数,这是一种基于 PChar/PAnsiChar/PUnicodeChar 和长度设置字符串内容的非常快速的方法。

这也可能使您有所改善;另一方面,复制也非常快。

【讨论】:

  • 看看你的第一点,我认为你对 MaxInt 的看法是错误的。要计算那里的长度,它是:length(Line) - P,并且减法比使用常量 MaxInt 更昂贵。 Delphi 不关心要复制的长度是否超过了字符串的末尾。它知道当字符串完成时停止。在某处被推荐之后,我已经使用“MaxInt”技巧很长时间了——我不记得了。每次编写代码时,它可以为我节省 5 秒。 :-)
  • TStrings.SetDelimitedText 函数旨在将字符串添加到字符串列表,而不是挑选出一个特定的标记。但它使用了与我上面描述的所谓的最佳 PChar 方法类似的技术。我也使用过 SetString,速度非常快。 AQTime 报告称,对 SetString 的 170 万次调用耗时 0.05 秒。
  • @lkessler:其实SetDelimitedText就是替换stringlist的内容。但是你明白了我的意思:它使用了一种非常相似的技术,但基于 PChar(正如 Carl 和 Bary 所建议的那样),因此值得一看。很好,您验证了 MaxInt 的事情:我表示它可能会得到改进,但您测量 MaxInt 是最好的方法。我现在浏览了所有新的 cmets 和您的问题编辑,看来您已经解决了。伟大的!我非常喜欢这个 stackoverflow 社区的工作方式。
【解决方案6】:

这是我在我的个人库中使用了很长时间的一个函数,我广泛使用。我相信这是它的最新版本。过去我有多个版本出于各种不同的原因进行了优化。这个尝试考虑带引号的字符串,但如果删除该代码,它会使函数稍微快一点。

我实际上还有许多其他例程,CountSections 和 ParseSectionPOS 就是几个例子。

不幸的是,此例程仅基于 ansi/pchar。尽管我认为将其移至 unicode 并不困难。也许我已经这样做了......我必须检查一下。

注意:此例程是基于 ParseNum 索引的 1。

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 }

【讨论】:

  • 感谢您的代码。您会很高兴知道它在使用 Unicode 字符串的 Delphi 2009 中运行良好。计时(使用上面描述的 QueryPerformanceCounter 和 1,000,000 次调用)是 0.74 秒,但保留了 QuotedStrChar 代码。我取出该代码并再次尝试,将其减少到 0.56 秒。这仍然比我最初花费 0.29 秒的 Pos/PosEX 代码慢。
【解决方案7】:

使用汇编程序将是一个微优化。通过优化算法可以获得更大的收益。每次不做工作都胜过以最快的方式工作。

一个例子是,如果您的程序中有一些地方需要同一行的多个标记。另一个返回令牌数组的过程应该比多次调用你的函数更快,特别是如果你让过程不返回所有令牌,而只返回你需要的数量。

但总的来说,我同意 Carl 的回答 (+1),使用 PChar 进行扫描可能会比您当前的代码更快。

【讨论】:

  • 当然,我首先优化算法。我希望在过去的 10 年里我已经完成了大部分工作。现在是时候从岩石中挤出更多的血了。但有趣的是,从微观层面看确实能让你深入了解宏观层面。我现在正在做各种改进,只是因为我正在重新考虑这一切。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多