为什么我的代码运行这么慢?

5

对于那些没有时间深入了解但可能遇到类似问题的人,以下是按照顶部回复(抱歉)的答案。

规则#1,始终将尽可能多的内容移出循环。
2. 将 TField var := ADODataSet.FieldByname() 移出循环。 3. 在循环周围使用 ADODataSet.DisableControls(); 和 ADODataSet.EnableControls(); 4. 对每一行使用 stringGrid.Rows[r].BeginUpdate() 和 EndUpdate()(无法在整个控件上执行) 这些操作每次都可以节省几秒钟,但是通过更改,我将其缩短到“眨眼之间”。

loop
  stringGrid.RowCount := stringGrid.RowCount + 1;
end loop

stringGrid.RowCount := ADODataSet.RecordCount;放在循环之前

对于所有帮助过我的人,我表示+1和衷心的感谢。

(现在我将去看看如何优化绘制TChart,因为它也很慢;-)


表格中有大约3600行数据,这需要45秒才能填充字符串网格。我做错了什么?

   ADODataSet := TADODataSet.Create(Nil);
   ADODataSet.Connection := AdoConnection;
ADODataSet.CommandText := 'SELECT * FROM measurements'; ADODataSet.CommandType := cmdText; ADODataSet.Open();
while not ADODataSet.eof do begin TestRunDataStringGrid.RowCount := TestRunDataStringGrid.RowCount + 1;
measurementDateTime := UnixToDateTime(ADODataSet.FieldByname('time_stamp').AsInteger); DoSQlCommandWithResultSet('SELECT * FROM start_time_stamp', AdoConnection, resultSet); startDateTime := UnixToDateTime(StrToInt64(resultSet.Strings[0])); elapsedTime := measurementDateTime - startDateTime; TestRunDataStringGrid.Cells[0, Pred(TestRunDataStringGrid.RowCount)] := FormatDateTime('hh:mm:ss', elapsedTime); TestRunDataStringGrid.Cells[1, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('inputTemperature').AsFloat); TestRunDataStringGrid.Cells[2, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('outputTemperature').AsFloat); TestRunDataStringGrid.Cells[3, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('flowRate').AsFloat); TestRunDataStringGrid.Cells[4, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('waterPressure').AsFloat * convert); TestRunDataStringGrid.Cells[5, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('waterLevel').AsFloat); TestRunDataStringGrid.Cells[6, Pred(TestRunDataStringGrid.RowCount)] := FloatToStrWithPrecision(ADODataSet.FieldByname('cod').AsFloat); ADODataSet.Next; end;
ADODataSet.Close(); ADODataSet.Free();

更新:

Function  DoSQlCommandWithResultSet(const command : String; AdoConnection : TADOConnection; resultSet : TStringList): Boolean;
  var
        i : Integer;
        AdoQuery : TADOQuery;
begin Result := True; resultSet.Clear();
AdoQuery := TADOQuery.Create(nil); try AdoQuery.Connection := AdoConnection; AdoQuery.SQL.Add(command); AdoQuery.Open(); i := 0; while not AdoQuery.eof do begin resultSet.Add(ADOQuery.Fields[i].Value); i := i + 1; AdoQuery.Next; end;
finally AdoQuery.Close(); AdoQuery.Free(); end; end;

最好的方法是对代码进行一些分析。另外,我们不知道select * from time_stamp应该返回多少行;也许有数十亿行? - 9000
1
你应该尝试使用SamplingProfiler,它可以在http://delphitools.info/samplingprofiler上获得。它可以让你清楚地了解程序花费时间的情况,与Delphi应用程序兼容,并且是免费的。 :) - Mason Wheeler
1
程序运行越慢,就越容易找出问题所在。尝试使用随机暂停技术进行性能分析 - Mike Dunlavey
@Mike:我甚至会说这比随机暂停更好,因为它可以每秒采样1000次,而不需要实际暂停和重新启动您的程序,这意味着您可以正常地与程序交互,无需中断,并且只需让它观察并收集标准工作流程中的瓶颈数据。 - Mason Wheeler
@Mason:我相信它很不错,但是就我个人而言,这是我的想法。1)你真正需要的是仅在你关心的时间内获取样本,比如当它在主观上让你等待时。2)在该间隔期内,有用的信息包含在任何100个或更少的样本中(远远少于此),额外的样本提供了更多的时间精度,但并没有提供任何问题位置的更高精度。3)当你自己采样时,你可以看到整个堆栈,因此你可以看到为什么花费了时间,这就是你知道它是否实际上是浪费的方式。这是测量和定位之间的区别。 - Mike Dunlavey
显示剩余4条评论
6个回答

11
  1. 您正在执行命令 SELECT * FROM start_time_stamp 3600次,但我看不出它与您的外部循环有任何关联。为什么不在循环之前执行一次呢?

  2. 该SELECT命令似乎仅返回单个记录的单个列,但您使用“*”来加载所有列,并且没有WHERE子句将结果限制为单个行(如果表中有多个行)。

  3. 您只使用了一些测量值中的列,但仍然检索了所有列使用“*”。

  4. 您没有显示DoSQlCommandWithResultSet的内容,因此该程序的问题是否存在于该例程中并不清楚。

  5. 不清楚问题是出现在数据库访问还是字符串网格中。请注释掉所有相关字符串网格的代码并运行程序。仅通过数据库访问需要多长时间?


请回来告诉我们你找到了什么。 - Larry Lustig
+1 一些很好的反馈,谢谢。1)哎呀!我必须把它移到循环之前。2)好观点。3)实际上,我想访问“measurements”的每个字段。在这种情况下,SELECT * 可以接受吗?4)那可能是个问题。它正在访问相同的 ADO 连接。我已经发布了代码。 - Mawg says reinstate Monica
1
你发布的DoSQLCommandWithResultSet版本不是程序调用的版本,签名不同(程序中的版本有三个参数,你发布的版本没有处理结果)。 命名列始终优于使用“*”,因为这样你的代码可以告诉你可以引用哪些列,如果数据库无法提供您期望的任何列,则命令将失败(这通常是您想要的)。 - Larry Lustig
1
哇,为了恢复那个单一的日期需要做很多工作啊。将查询移到循环外并且放弃子程序(为什么要把多行转换成字符串列表?),只使用一个 ADOCommand 来获取结果即可。 - Larry Lustig
+1 +1 抱歉,我误发了没有resultSet的内容。已经更正了。是的,查询在循环外面。关于字符串列表的建议很好。 - Mawg says reinstate Monica

9
此外,除了Larry Lustig提到的内容之外:
1. 一般而言,FieldByName方法速度较慢。您正在为相同的字段在循环中调用它。将获取字段引用移出循环并存储引用于变量中。例如:InputTempField := ADODataSet.FieldByname('inputTemperature'); 2. 您正在循环中调整网格大小TestRunDataStringGrid.RowCount := TestRunDataStringGrid.RowCount + 1。这种情况下,您应该在循环之前使用ADODataSet.RecordCountTestRunDataStringGrid.RowCount := ADODataSet.RecordCount
3. 在循环之前调用ADODataSet.DisableControls,之后再调用ADODataSet.EnableControls是一个好习惯。特别是对于ADO数据集来说更加实际,因为它的实现不够优化,这些调用可以帮助提高性能。
4. 根据您使用的DBMS,您可以通过设置较大的"行集大小"来提高获取性能。不确定在ADO中如何控制,可能将ADODataSet.CacheSize设置为更大的值会有所帮助。此外,还有游标设置 :)

+1 谢谢。#1和2绝对是好主意(你是说InputTempField是一个TField,我在循环中使用InputTempField.AsFloat吗?)。我已经采纳了#3的建议。至于#4,我有点新手,所以会稍后再处理。此外,它需要符合ODBC标准。 - Mawg says reinstate Monica
TestRunDataStringGrid.RowCount := ADODataSet.RecordCount; 这一行代码真的起到了很大的作用,其他一些也有帮助,但这个最关键。请查看更新后的问题以获取更多差异信息。 - Mawg says reinstate Monica
ADODataSet.DisableControls 让我在遍历 85 万条记录的结果集时加速了(系好安全带)1000 倍。我怎么可以投多次赞?为什么这该死的东西默认是启用的? - JensG

5

在循环中,不要调用ADODataSet.FieldByName('Fieldname'),而应该为每个字段声明类型为TField的本地变量,将ADODataset.FindField('Fieldname')分配给这些变量,并在循环内部使用这些变量。FindFieldByName会在每次调用时搜索列表。

更新:

procedure TForm1.Button1Click(Sender: TObject);
var
  InputTemp, OutputTemp: TField;
begin
  ADODataSet := TADODataSet.Create(Nil);
  try
    ADODataSet.Connection := ADOConnection;
    ADODataSet.CommandText := 'SELECT * FROM measurements';
    ADODataSet.Open;
    InputTemp := ADODataSet.FindField('inputTemperature');
    OutputTemp := ADODataSet.FindField('outputTemperature');
    // assign more fields here
    while not ADODataSet.Eof do begin
      // do something with the fields, for example:
      // GridCell := Format ('%3.2f', [InputTemp.AsFloat]);
      // GridCell := InputTemp.AsString;
      ADODataSet.Next;
    end;
  finally
    ADODataSet.Free;
  end;
end;

另一种选择是在表单上放置TADODataset组件(或使用TDataModule),并在设计时定义字段。


+1 如果我知道它是浮点数,我是否可以不使用TFloatField?如果不行,你能发几行代码吗?谢谢。 - Mawg says reinstate Monica

2
除了Larry Lustig的答案之外,还可以考虑使用数据感知控件,例如data-aware控件,如alt text TDbGrid组件。请注意保留HTML标签。

2
如果您没有使用数据感知控件,那么在循环之前应该使用 TestRunDataStringGrid.BeginUpdate,并在循环之后使用 TestRunDataStringGrid.EndUpdate。否则,每次修改(添加新行、单元格更新)后都会不断重绘网格。
另一个提示是在打开查询之前设置 AdoQuery.LockType := ltReadOnly

1
你也可以尝试使用仪器分析器而不是采样分析器来获得更好的结果(采样分析器会错过很多详细信息,大多数时间它们每秒只有不到1000个样本,而且1000已经很低了:只适合快速概述)。
仪器分析器:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接