【问题标题】:Why is my code so slow?为什么我的代码这么慢?
【发布时间】:2023-03-28 05:16:01
【问题描述】:

顶贴(抱歉)的答案,适合那些没有时间进入但可能有类似问题的人。

与往常一样,规则 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,这也很慢 ;-)


表格中有大约 3,600 行,填充字符串网格需要 45 秒。我做错了什么?

ADODataSet := TADODataSet.Create(Nil); ADODataSet.Connection := AdoConnection; ADODataSet.CommandText := 'SELECT * FROM 测量值'; ADODataSet.CommandType := cmdText; ADODataSet.Open(); 而不是 ADODataSet.eof 做 开始 TestRunDataStringGrid.RowCount := TestRunDataStringGrid.RowCount + 1; measureDateTime := UnixToDateTime(ADODataSet.FieldByname('time_stamp').AsInteger); DoSQlCommandWithResultSet('SELECT * FROM start_time_stamp', AdoConnection, resultSet); startDateTime := UnixToDateTime(StrToInt64(resultSet.Strings[0])); elapsedTime := 测量日期时间 - 开始日期时间; 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; 结尾; ADODataSet.Close(); ADODataSet.Free();

更新:

函数 DoSQlCommandWithResultSet(const command : String; AdoConnection : TADOConnection; resultSet : TStringList): Boolean; 变量 我:整数; AdoQuery:TADOQuery; 开始 结果:=真; 结果集.清除(); AdoQuery := TADOQuery.Create(nil); 尝试 AdoQuery.Connection := AdoConnection; AdoQuery.SQL.Add(命令); AdoQuery.Open(); 我:= 0; 而不是 AdoQuery.eof 做 开始 resultSet.Add(ADOQuery.Fields[i].Value); 我 := 我 + 1; AdoQuery.Next; 结尾; 最后 AdoQuery.Close(); AdoQuery.Free(); 结尾; 结尾;

【问题讨论】:

  • 解决问题的最佳方法是以某种方式分析您的代码。此外,不知道select * from time_stamp 应该返回多少行;也许是无数行?
  • +1 谢谢。关于剖析有什么建议吗?在这种情况下,我以一秒的间隔进行测量大约一小时(加/减几秒)
  • 您应该尝试使用 SamplingProfiler,网址为 delphitools.info/samplingprofiler。它可以让您很好地了解您的程序花时间在什么地方,它可以很好地与 Delphi 应用程序配合使用,而且它是免费的。 :)
  • 你的程序越慢,就越容易找出原因。试试random-pausing
  • @Mason:您链接到的分析器看起来可以做所有正确的事情。它在挂钟时间对堆栈进行采样,并按代码行给出百分比,类似于 RotateRight 的 Zoom。我会随机暂停,但我认为这种分析器绝对是次优的。

标签: delphi profiling ado tstringgrid


【解决方案1】:

您还可以尝试使用检测分析器而不是采样分析器来获得更好的结果(采样分析器会丢失很多详细信息,并且大多数情况下它们每秒的样本数少于 1000 个,而 1000 个已经很低:只有获得快速概览)。

仪器分析器:

【讨论】:

    【解决方案2】:

    您应该为每个字段声明 TField 类型的局部变量,而不是在循环内调用 ADODataSet.FieldByname('Fieldname'),将 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 是浮点数,我可以不使用它吗?如果没有,你能贴几行,让我看看怎么做?谢谢
    【解决方案3】:

    除了 Larry Lustig 点之外:

    1. 一般来说,FieldByName 是比较慢的方法。您正在为相同的字段循环调用它。将字段引用的获取移出循环并将引用存储在变量中。喜欢:InputTempField := ADODataSet.FieldByname('inputTemperature');
    2. 您正在调整循环TestRunDataStringGrid.RowCount := TestRunDataStringGrid.RowCount + 1 中的网格大小。就是这样,当你应该在循环之前使用ADODataSet.RecordCount 时:TestRunDataStringGrid.RowCount := ADODataSet.RecordCount
    3. 在循环之前调用ADODataSet.DisableControls 并在循环之后调用ADODataSet.EnableControls 是一个很好的做法。更实际的是 ADO 数据集,它没有最佳实现,这些调用有帮助。
    4. 根据您使用的 DBMS,您可以通过设置更大的“行集大小”来提高提取性能。不确定,它如何在 ADO 中控制,可能将 ADODataSet.CacheSize 设置为更大的值会有所帮助。此外,还有光标设置:)

    【讨论】:

    • +1 谢谢。 #1 和 2 绝对是好主意(你是说 InputTempField 是一个 TField 并且我在循环中使用 InputTempField.AsFloat 吗?)。我已经把#3 带上了船。对于#4,我有点n00b,所以稍后再说。此外,它必须是 ODBC 投诉方。
    • TestRunDataStringGrid.RowCount := ADODataSet.RecordCount;原来是有所作为。其他一些人提供了帮助,但那个人确实做到了。查看更新的问题了解更多差异
    • ADODataSet.DisableControls 在迭代 850.000 条记录结果集时,将(系好安全带)速度提高了 1000 倍。我怎样才能多次投票?为什么默认启用那个该死的东西?
    【解决方案4】:

    如果您不使用数据感知控件,您应该在循环之前使用TestRunDataStringGrid.BeginUpdate,在循环之后使用TestRunDataStringGrid.EndUpdate。如果没有这个,您的网格会在每次修改后不断重绘(添加新行、单元格更新)。

    在打开查询之前设置另一个提示AdoQuery.LockType := ltReadOnly

    【讨论】:

      【解决方案5】:

      除了 Larry Lustig 的回答之外,请考虑使用 data-aware 控件,例如 TDbGrid 组件。

      【讨论】:

        【解决方案6】:
        1. 您正在执行命令SELECT * FROM start_time_stamp 3,600 次,但在我看来,它与您的外部循环没有任何关联。为什么不在循环前执行一次呢?

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

        3. 您仅使用了 Measurements 中有限数量的列,但您检索了所有带有“*”的列。

        4. 你没有显示DoSQlCommandWithResultSet的内容,所以不清楚该例程是否有问题。

        5. 不清楚问题出在您的数据库访问还是字符串网格。注释掉与字符串网格有关的所有行并运行程序。单独访问数据库需要多长时间?

        【讨论】:

        • 回来告诉我们你发现了什么。
        • +1 一些很好的反馈,谢谢。 1) 哦!我已将其移至循环之前 2)好品脱 3)实际上我想访问“测量”的每个字段。在那种情况下 SELECT * 可以接受吗? 4)这可能是个问题。它正在访问同一个 ADO 连接。我已经发布了上面的代码。
        • 您发布的 DoSQLCommandWithResultSet 版本不是您的程序调用的版本,签名不同(您程序中的一个具有三个参数,您发布的那个与结果无关)。命名列总是首选使用“*”,因为您的代码会准确地告诉您可以引用哪些列,并且如果数据库无法提供您期望的任何列,该命令将失败(这是您通常想要的)。
        • 哇,要恢复那个单一的日期是一项非常艰巨的工作。将查询移出循环并放弃子例程(为什么要将多行传输到字符串列表中?),只使用 ADOCommand 来获取结果。
        • +1 +1 道歉。我错误地发布了没有resultSet。现已更正。是的,查询在循环之外。关于字符串列表的要点
        猜你喜欢
        • 2015-02-26
        • 1970-01-01
        • 2011-04-24
        • 2017-01-10
        • 2011-08-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多