Delphi循环速度问题

时间:2010-01-20 04:06:24

标签: delphi loops for-loop performance

有更快的方法吗?我基本上需要一次将AA-ZZ添加到数千条记录中。

只需要35个项目的列表就需要很长时间才能完成一千个列表。


procedure Tmainform.btnSeederClick(Sender: TObject);
var
  ch,ch2:char;
  i:integer;
  slist1, slist2:TStrings;
begin
  slist1:= TStringList.Create;
  slist2:= TStringList.Create;
  slist1.Text :=queuebox.Items.Text;
  for ch := 'a' to 'z' do
    begin
      for ch2 := 'a' to 'z' do
        begin
          //

for I := 0 to slist1.Count - 1 do begin application.ProcessMessages; // so it doesn't freeze the application in long loops. Not 100% sure where this should be placed, if at all. sleep(1); //Without this it doesn't process the cancel button. if cancel then Break; slist2.Add(slist1.Strings[i]+ch+ch2); end; end; end; insertsingle(slist2,queuebox); freeandnil(slist1); freeandnil(slist2);

端;

感谢您的帮助

10 个答案:

答案 0 :(得分:14)

您的代码存在一些明显的问题。

首先,你浪费了很多CPU周期一遍又一遍地计算相同的值。 AA..ZZ值不会改变,所以不需要一遍又一遍地构建它们。尝试这样的事情:创建第三个TStringList。通过双循环完成所有可能的AA..ZZ排列。一旦结束,循环并将此预先计算的字符串列表与slist1中的值合并。你应该看到相当大的推动力。

(或者,如果时间绝对非常重要,请编写一个小程序,计算排列列表并将其保存到文本文件中,然后将其作为字符串资源编译到您的应用程序中,您可以在运行时加载它。)

其次,这可能是什么在扼杀你,你不应该在最里面的循环中拥有ProcessMessages和Sleep调用。 Sleep(1);听起来意味着“睡眠1毫秒”,但Windows并没有提供那种精确度。你最终得到的是“为至少 1毫秒睡眠”。它会释放CPU,直到Windows重新启动它,通常大约为16毫秒。因此,您需要将16毫秒的延迟(加上ProcessMessages所需的时间)添加到一个非常紧凑的循环中,该循环可能只需要几个微秒来执行其余的代码。

如果你需要这样的东西来保持UI响应,那么它应该在最外层的循环中,而不是内部循环中,你甚至可能不需要每次迭代都运行它。尝试类似if ch mod 100 = 0 then //sleep and process messages here的内容。 Craig建议将这个任务转移到一个工作线程也会有所帮助,但前提是你对线程有足够的了解才能做到正确。它们可能很棘手。

答案 1 :(得分:11)

您应该使用slist2.BeginUpdate()slist2.EndUpdate()包围您的代码,以阻止TStringList进行额外处理。

根据我的经验,如其他答案所示,您可以使用较少的ProcessMessages(); Sleep(1);语句获得非常大幅提升。

尝试将它移到第一个for循环的下方,看看你得到了什么改进。

答案 2 :(得分:5)

一个如何使用secundary线程来完成繁重工作的示例。

请注意,对于你提到的35个项目,启动另一个帖子真的不值得。对于几千件物品,游戏会发生变化。在桌面计算机上处​​理10.000项需要10秒钟。

多线程的一些好处:

  • 主线程保持响应。
  • 计算可以随意停止。

和offcourse一些陷阱:

  • 必须小心(在其当前实现中),以便在播种运行时不会弄乱传递的字符串列表。
  • 多线程增加了复杂性,是难以发现错误的来源。

在我们最喜欢的编辑器中粘贴下面的代码,你应该很高兴。

procedure TForm1.btnStartClick(Sender: TObject);
var
  I: Integer;
begin
  //***** Fill the sourcelist
  FSource := TStringList.Create;
  FDestination := TStringList.Create;
  for I := 0 to 9999 do
    FSource.Add(Format('Test%0:d', [I]));

  //***** Create and fire Thread
  FSeeder := TSeeder.Create(FSource, FDestination);
  FSeeder.OnTerminate := DoSeederDone;
  FSeeder.Resume;
end;

procedure TForm1.btnStopClick(Sender: TObject);
begin
  if Assigned(FSeeder) then
    FSeeder.Terminate;
end;

procedure TForm1.DoSeederDone(Sender: TObject);
var
  I, step: Integer;
begin
  I := 0;
  step := 0;
  while I < FDestination.Count do
  begin
    //***** Don't show every item. OutputDebugString is pretty slow.
    OutputDebugString(PChar(FDestination[I]));
    Inc(step);
    Inc(I, step);
  end;
  FSource.Free;
  FDestination.Free;
end;

{ TSeeder }

constructor TSeeder.Create(const source, destination: TStringList);
begin
  //***** Create a suspended, automatically freed Thread object.
  Assert(Assigned(source));
  Assert(Assigned(destination));
  Assert(destination.Count = 0);
  inherited Create(True);
  FreeOnTerminate := True; //***** Triggers the OnTerminate event
  FSource := source;
  FDestination := destination;
end;

procedure TSeeder.Execute;
var
  I, J: Integer;
  AString: string;
begin
  FDestination.BeginUpdate;
  try
    FDestination.Capacity := FSource.Count * 26 * 26;
    for I := 0 to Pred(FSource.Count) do
    begin
      AString := FSource[I];
      for J := 0 to Pred(26 * 26) do
      begin
        FDestination.Add(AString + Char(J div 26 + $41) + Char(J mod 26 + $41));
        if Terminated then Exit;
      end;
    end;
  finally
    FDestination.EndUpdate;
  end;
end;

答案 3 :(得分:4)

我会看看你是否可以按照评论在一个循环中完成。还可以尝试在线程中执行此操作,以便在不阻止UI的情况下消除Application.ProcessMessages和Sleep调用。

答案 4 :(得分:4)

行。我试图优化您的代码。对于最终测试,需要一些测试数据。

我做了什么(包括梅森的大部分想法):

  • 注释掉有关“取消”和“
  • 的代码
  • 为类型和变量提供了更有意义的名称
  • 使用Delphi使用的名称(“应用程序”代替“应用程序”等)使其可读
  • 将一些逻辑移至“KeepUIGoing”
  • 将后缀的计算移出主循环进入初始化循环
  • 使它可选地使用TStringBuilder(它可以比TStringList快,并且自Delphi 2009起可用)

以下是经过修改的代码,如果适合您,请告诉我。

procedure TForm2.Button1Click(Sender: TObject);
{$define UseStringBuilder}
  procedure KeepUIGoing(SourceListIndex: Integer);
  begin
    if SourceListIndex mod 100 = 0 then
    begin
      Application.ProcessMessages;
      // so it doesn't freeze the application in long loops.  Not 100% sure where this should be placed, if at all.
      Sleep(1);
    end;
  end;
const
  First = 'a';
  Last = 'z';
type
  TRange = First .. Last;
  TSuffixes = array [TRange, TRange] of string;
var
  OuterIndex, InnerIndex: Char;
  SourceListIndex: Integer;
  SourceList, TargetList: TStrings;
  Suffixes: TSuffixes;
  NewLine: string;
{$ifdef UseStringBuilder}
  TargetStringBuilder: TStringBuilder; // could be way faster than TStringList
{$endif UseStringBuilder}
begin
  for OuterIndex := First to Last do
    for InnerIndex := First to Last do
      Suffixes[OuterIndex, InnerIndex] := OuterIndex + InnerIndex;

  SourceList := TStringList.Create;
  TargetList := TStringList.Create;
{$ifdef UseStringBuilder}
  TargetStringBuilder := TStringBuilder.Create();
{$endif UseStringBuilder}
  try
    SourceList.Text := queuebox.Items.Text;
    for OuterIndex := First to Last do
    begin
      for InnerIndex := First to Last do
      begin
        for SourceListIndex := 0 to SourceList.Count - 1 do
        begin
          KeepUIGoing(SourceListIndex);
          // if cancel then
          // Break;
          NewLine := SourceList.Strings[SourceListIndex] + Suffixes[OuterIndex, InnerIndex];
{$ifdef UseStringBuilder}
          TargetStringBuilder.AppendLine(NewLine);
{$else}
          TargetList.Add(NewLine);
{$endif UseStringBuilder}
        end;
      end;
    end;
{$ifdef UseStringBuilder}
    TargetList.Text := TargetStringBuilder.ToString();
{$endif UseStringBuilder}
    // insertsingle(TargetList, queuebox);
  finally
{$ifdef UseStringBuilder}
    FreeAndNil(TargetStringBuilder);
{$endif UseStringBuilder}
    FreeAndNil(SourceList);
    FreeAndNil(TargetList);
  end;
end;

- 的Jeroen

答案 5 :(得分:1)

试试这个示例代码 - 希望这会有所帮助(Delphi 5 Ent./WinXP)

procedure TForm1.Button1Click(Sender: TObject);
var
   i,k: Integer;
   sourceList, destList: TStringList;
   ch1, ch2: char;
begin
   destList := TStringList.Create;
   sourceList := TStringList.Create;

   //some sample data but I guess your list will have 1000+
   //entries?
   sourceList.Add('Element1');
   sourceList.Add('Element2');
   sourceList.Add('Element3');

   try
      i := 0;
      while i < (26*26) do
      begin
         if (i mod 100) = 0 then
            Application.ProcessMessages;

         ch1 := char(65 + (i div 26));
         ch2 := char(65 + (i mod 26));

         for k := 0 to sourceList.Count -1 do
            destList.Add(Format('%s-%s%s', [sourceList.Strings[k], ch1, ch2]));
         Inc(i);
      end;

      Memo1.Lines.AddStrings(destList);
   finally
      FreeAndNil(destList);
      FreeAndNil(sourceList);
   end;
end;    

- 莱因哈德

答案 6 :(得分:1)

我知道这并没有具体回答你的问题,但如果你对Delphi算法感兴趣,Julian Bucknall(DevExpress的首席技术官)写了明确的Delphi算法书

Tomes of Delphi: Algorithms and Data Structures

  • 第1章:什么是算法?
  • 第2章:数组
  • 第3章:链接列表,堆栈和队列
  • 第4章:搜索
  • 第5章:排序
  • 第6章:随机算法
  • 第7章:散列和散列表
  • 第8章:二叉树
  • 第9章:优先级队列和堆栈
  • 第10章:状态机和正则表达式
  • 第11章:数据压缩
  • 第12章:高级主题

您还可以为Delphi 2009Delphi 6-2007获取他的EZDSL(简易数据结构库)。

答案 7 :(得分:1)

如果您希望在循环期间处理事件,例如单击“取消”按钮,则调用Application.ProcessMessages就足够了。如果你定期打电话但不是经常打电话,例如每秒50次,然后您的应用程序将保持响应取消按钮而不会减慢太多。如果没有要处理的消息,Application.ProcessMessages会很快返回。

此技术适用于您希望用户等待的相对较短的计算(几秒钟)。对于长计算,后台线程更合适。然后,您的应用程序可以保持完全响应,特别是如果用户具有多核CPU。

在主线程中调用Sleep不允许您的应用程序处理事件。它允许其他应用程序执行某些操作。调用Sleep实际上会使您的应用程序(实际上是调用线程)在请求的时间内或者线程的时间片的剩余部分中休眠,以较大者为准。

答案 8 :(得分:1)

使用Delphi backgroundworker组件为此目的可以比thread.it更容易和基于事件的backgroundworker功能(附加使用线程):

  • 使用基于事件的代码。不需要创建类
  • 添加进度以处理

示例代码:

procedure TForm2.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  FSource := TStringList.Create;
  FDestination := TStringList.Create;

end;
procedure TForm2.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  try
    FSource.BeginUpdate;
    FSource.Clear;
    for I := 0 to 9999 do
      FSource.Add(Format('Test%0:d', [I]));
    BackgroundWorker1.Execute;
  finally
    FSource.EndUpdate;
  end;

end;



procedure TForm2.StopButtonClick(Sender: TObject);
begin
  BackgroundWorker1.Cancel;
end;



procedure TForm2.FormDestroy(Sender: TObject);
begin
 FreeAndNil(FSource);
 FreeAndNil(FDestination);
end;


procedure TForm2.BackgroundWorker1Work(Worker: TBackgroundWorker);
var
  I, J: Integer;
  AString: string;
begin
  FDestination.BeginUpdate;
  try
    FDestination.Capacity := FSource.Count * 26 * 26;
    for I := 0 to Pred(FSource.Count) do
    begin
      AString := FSource[I];
      for J := 0 to Pred(26 * 26) do
      begin
        FDestination.Add(AString + Char(J div 26 + $41) + Char(J mod 26 + $41));
        if Worker.CancellationPending then
          Exit;
      end;
      if I mod 10 = 0 then
        TThread.Sleep(1);
      Worker.ReportProgress((I * 100) div FSource.Count);
    end;
    Worker.ReportProgress(100); // completed

  finally
    FDestination.EndUpdate;
  end;
end;

procedure TForm2.BackgroundWorker1WorkProgress(Worker: TBackgroundWorker;
  PercentDone: Integer);
begin
  ProgressBar1.Position := PercentDone;
end;

答案 9 :(得分:-1)

如果您正在寻找纯粹的速度,只需将代码展开到一个循环中,并将每一行写为单独的赋值。您可以编写一个程序来自动为您编写行,然后将它们复制并传递到您的代码中。这基本上是关于可能的最快方法。同时关闭上述所有更新。

procedure Tmainform.btnSeederClick(Sender: TObject);
var
  ch,ch2:char;
  i:integer;
  slist1, slist2:TStrings;
begin
  slist1:= TStringList.Create;
  slist2:= TStringList.Create;

  slist1.Text :=queuebox.Items.Text;

  slist2.BeginUpdate() 
     for I := 0 to slist1.Count - 1 do
        begin
        application.ProcessMessages; // so it doesn't freeze the application in long loops.  Not 100% sure where this should be placed, if at all.
         if cancel then Break; 
         slist2.Add(slist1.Strings[i]+'AA');
         slist2.Add(slist1.Strings[i]+'AB');
         slist2.Add(slist1.Strings[i]+'AC');
         ...
         slist2.Add(slist1.Strings[i]+'ZZ');
        end;
slist2.EndUpdate()
insertsingle(slist2,queuebox);
freeandnil(slist1);
freeandnil(slist2);
end;