每次在运行时通过提供 REST API 数据的动态(增加数据)TFDMemTable 定义创建时,TGrid 的性能都会降低

Slow performance of TGrid everytime it is created at runtime through dynamic (increasing data) TFDMemTable definition provided REST API data

我正在为 iOS 和 Android 开发 Firemonkey 应用程序。我注意到每次在运行时使用 TFDMemTable REST API 数据和结构创建 TGrid 时,应用程序在 iOS 和 Android 调试中的性能会变慢。

我已经应用 FreeAndNil(TGrid1); 在 TGrid 一遍又一遍地创建之前清理它。

一个值得注意的事件,每次创建 TGrid 时,行都会增加,固定 7 列,性能会变慢。通常,当我达到 10 行或记录时会发生这种情况。

我的一个又大又快的问题:

您认为导致性能下降的开销来自哪里?

  1. TGrid — 在创建之前我已经应用了它 FreeAndNil(TGrid1);

  2. TFMemTable — 我还没有检查过这个,我不知道如何检查

  3. TButton — 触发创建数据并将数据加载到 TGrid 的按钮。大多数代码都在这里

让我们假设所有其他组件在此案例之前工作正常。如果你愿意,我可以与你分享一些代码,但指导我你想看哪一个。

更新 1: 最小可重现示例

FMX 文件

object Form9: TForm9
  Left = 0
  Top = 0
  Caption = 'MRE TeeGrid Runtime'#13#10
  ClientHeight = 480
  ClientWidth = 294
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object btn1: TButton
    Align = Bottom
    Position.Y = 440.000000000000000000
    Size.Width = 294.000000000000000000
    Size.Height = 40.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 9
    Text = 'CREATE TEEGRID'
    OnClick = btn1Click
  end
  object aniSearchProcess: TAniIndicator
    Position.X = 128.000000000000000000
    Position.Y = 216.000000000000000000
  end
  object lyt1: TLayout
    Align = Client
    Size.Width = 294.000000000000000000
    Size.Height = 440.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 11
  end
  object cur1: TFDGUIxWaitCursor
    Provider = 'FMX'
    Left = 32
    Top = 32
  end
  object dvr1: TFDPhysSQLiteDriverLink
    Left = 88
    Top = 32
  end
  object con1: TFDConnection
    Params.Strings = (
      'DriverID=SQLite')
    Connected = True
    LoginPrompt = False
    Left = 144
    Top = 32
  end
  object loc1: TFDLocalSQL
    Connection = con1
    Active = True
    Left = 200
    Top = 32
  end
  object rsc1: TRESTClient
    Accept = 'application/json, text/plain; q=0.9, text/html;q=0.8,'
    AcceptCharset = 'utf-8, *;q=0.8'
    BaseURL = 
      'https://me6hwinr2k.execute-api.ap-southeast-1.amazonaws.com/v0/d' +
      'bqueries?item-var=9&qty=25'
    Params = <>
    Left = 32
    Top = 112
  end
  object rsq1: TRESTRequest
    Client = rsc1
    Params = <>
    Response = rsp1
    SynchronizedEvents = False
    Left = 32
    Top = 176
  end
  object rsp1: TRESTResponse
    ContentType = 'application/json'
    Left = 32
    Top = 240
  end
  object rsd1: TRESTResponseDataSetAdapter
    Active = True
    Dataset = mtb1
    FieldDefs = <>
    Response = rsp1
    Left = 32
    Top = 304
  end
  object mtb1: TFDMemTable
    Active = True
    FieldDefs = <
      item
        Name = 'Category'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'ID'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Item'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Qty'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Container'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Size'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Ex temporibus dolore consequatur.'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Et cum aut est nostrum...'
        DataType = ftWideString
        Size = 255
      end
      item
        Name = 'Sequi quibusdam eum.'
        DataType = ftWideString
        Size = 255
      end>
    IndexDefs = <>
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates]
    UpdateOptions.CheckRequired = False
    UpdateOptions.AutoCommitUpdates = True
    StoreDefs = True
    Left = 32
    Top = 368
  end
end

FMX 程序

unit Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FireDAC.UI.Intf, FireDAC.FMXUI.Wait, FireDAC.Stan.ExprFuncs,
  FireDAC.Phys.SQLiteDef, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
  FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite, Data.DB,
  FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, REST.Types,
  FMX.Controls.Presentation, FMX.StdCtrls, FireDAC.Comp.DataSet,
  FireDAC.Comp.Client, REST.Response.Adapter, REST.Client, Data.Bind.Components,
  Data.Bind.ObjectScope, FireDAC.Phys.SQLiteVDataSet, FireDAC.Comp.UI,
  FMXTee.Control, FMXTee.Grid, FMX.Layouts;

type
  TForm9 = class(TForm)
    cur1: TFDGUIxWaitCursor;
    dvr1: TFDPhysSQLiteDriverLink;
    con1: TFDConnection;
    loc1: TFDLocalSQL;
    rsc1: TRESTClient;
    rsq1: TRESTRequest;
    rsp1: TRESTResponse;
    rsd1: TRESTResponseDataSetAdapter;
    mtb1: TFDMemTable;
    btn1: TButton;
    aniSearchProcess: TAniIndicator;
    lyt1: TLayout;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form9: TForm9;
  tgd1: TTeeGrid;

implementation

{$R *.fmx}

procedure TForm9.btn1Click(Sender: TObject);
var
  i, CanvassItemId, e : Integer;
begin
  aniSearchProcess.Visible := True;
  aniSearchProcess.Enabled := True;
  {$IFDEF MSWINDOWS}
    Application.ProcessMessages;
  {$ENDIF}
  {$IF DEFINED(iOS) or DEFINED(ANDROID)}
    Application.HandleMessage;
  {$ENDIF}

  FreeAndNil(tgd1); //free old grid

  //create new grid
  tgd1 := TTeeGrid.Create(lyt1);
  With tgd1 do
  begin
    Parent := lyt1;
    Align := TAlignLayout.Client;
    Margins.Top := 5;
    Margins.Left := 5;
    Margins.Right := 5;
    Margins.Bottom := 5;
    ScrollBars.Visible := True;
    Header.Format.Font.Size := 11;
    Cells.Format.Font.Size := 11;
    TabOrder := 0;
    ScrollBars.Visible := False;
  end;

  con1.StartTransaction;
  try
    //define the API here for duplicate/update, initial click and subsequent clicks
    rsc1.BaseURL := 'https://0rgvn0s0gk.execute-api.ap-southeast-1.amazonaws.com/v0/dbqueries?item-var=1&qty=10';
    rsq1.Execute;
    rsd1.Active := True;
    mtb1.Active;
    tgd1.DataSource := mtb1;
    tgd1.Enabled := True;

    // adjust the column properties dynamically
    with tgd1 do
    begin
      for i := 0 to Columns.Count -1 do
      begin
        if i = 0 then
        begin
          Columns[i].Visible := False; // category column
        end
        else if (i = 1) then
        begin
          Columns[i].Visible := False; // id column
        end
        else if (i = 2) then
        begin
          Columns[i].Width.Value := 120; // item column
        end
        else if (i = 3) then
        begin
          Columns[i].Width.Value := 30; // qty column
        end
        else if (i = 4) then
        begin
          Columns[i].Width.Value := 50; // container column
        end
        else if (i = 5) then
        begin
          Columns[i].Width.Value := 50; // size column
        end
        else
        begin
          Columns[i].Width.Value := 50; // subsequent random columns
        end;
      end;
    end;
  finally
    con1.Commit;
  end;

  aniSearchProcess.Visible := False;
  aniSearchProcess.Enabled := False;
  {$IFDEF MSWINDOWS}
    Application.ProcessMessages;
  {$ENDIF}
  {$IF DEFINED(iOS) or DEFINED(ANDROID)}
    Application.HandleMessage;
  {$ENDIF}

end;

end.

坦率地说,我怀疑这里是否有人可以解决您的问题,因为其他任何人都无法真正重现该问题,因为我们无权访问您的 REST 源。相反,我建议您回溯到 您之前提出的关于将 TTeeGrid 与 FDMemTable 结合使用的问题。我建议这样做的原因是因为它提供了一种 testing/benchmarking 这两个组件相当 self-contained 并且不依赖于(对于其他人)对您的 REST 源的访问的方式。您可以使用如下代码来调查您报告的减速是否与您的 TTeeGrid 的重复 creation/freeing 有关(我会 如果是,请感到惊讶)。

procedure TForm1.FormCreate(Sender: TObject);
var
  AField : TField;
begin
  AField := TWideStringField.Create(Self);
  AField.FieldName := 'ID';
  AField.Size := 255;
  AField.FieldKind := fkData;
  AField.DataSet := FDMemTable1;

  { repeat for other fields}

  FDMemTable1.CreateDataSet;
  { insert test data using FDMemTable1.InsertRecord in a loop}

  { repeat the following to see if TTeeGrid really slows down be repeated creation/freeing}
    { create TTeeGrid1 here }
    { connect FDMemTable1 to TTeeGrid1 here}
    {  TTeeGrid1.Free }
  { until done }
end;

你在这里面临的问题是,由于 ARC 在 Delphi 中的工作方式,你的 TTeeGrid 并没有真正被破坏。

这是为什么?
一旦将 Parent 设置为 tgd1 组件,就会将对它的引用添加到列出所有子组件的 lyt1 Controls 集合中。因此,当您调用 FreeAndNil(tgd1); 时,您仅从全局变量 tgd1 释放对 TTeeGrid 对象的引用,但来自布局控件集合的引用仍然存在。由于您的 TTeeGrid 引用计数尚未达到零,因此对象不会被销毁。

所以不要使用:

FreeAndNil(tgd1);

您需要使用:

tgd1.DisposeOf;
tgd1 := nil;

这确保 TTeeGrid 对象的析构函数被执行,而不管对象引用计数,这反过来通知 Layout 您的 TTeeGrid 对象正在被销毁,因此需要从中删除布局控制集合,因此允许 TTeeGrid 对象引用计数达到零。

事实上,你应该使用 DisposeOf 来销毁 run-time 处的任何组件。

我建议您阅读 How to free a component in Android / iOS

中有关此主题的更多信息

EDIT 只有在使用 ARC 的 Android 和 iOS 等移动平台上才会遇到此问题。在 Windows 上,您的代码可以正常工作。这可能是其他人无法重现您的问题的原因。

另请注意,由于在 Delphi 10.4 ARC 中已删除,因此您的代码也应该可以工作。但我猜你没有使用最新版本的 Delphi.

您可能需要编辑问题并包含您的 Delphi 版本以改进此问题,因为使用的 Delphi 版本会影响问题的答案。

在运行时创建 TTeeGrid 会累积开销,在某些时候会降低性能。

为了解决这个问题,我在运行时删除了 TTeeGrid 的创建和释放,而是在设计时放置了 TTeeGrid 可视化组件,并通过 属性 每次启用 true 或 false 来刷新它的连接它由 API.

提供的一组新数据和结构触发