每次在运行时通过提供 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 行或记录时会发生这种情况。
我的一个又大又快的问题:
您认为导致性能下降的开销来自哪里?
TGrid — 在创建之前我已经应用了它 FreeAndNil(TGrid1);
。
TFMemTable — 我还没有检查过这个,我不知道如何检查
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.
提供的一组新数据和结构触发
我正在为 iOS 和 Android 开发 Firemonkey 应用程序。我注意到每次在运行时使用 TFDMemTable REST API 数据和结构创建 TGrid 时,应用程序在 iOS 和 Android 调试中的性能会变慢。
我已经应用 FreeAndNil(TGrid1);
在 TGrid 一遍又一遍地创建之前清理它。
一个值得注意的事件,每次创建 TGrid 时,行都会增加,固定 7 列,性能会变慢。通常,当我达到 10 行或记录时会发生这种情况。
我的一个又大又快的问题:
您认为导致性能下降的开销来自哪里?
TGrid — 在创建之前我已经应用了它
FreeAndNil(TGrid1);
。TFMemTable — 我还没有检查过这个,我不知道如何检查
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 源。相反,我建议您回溯到
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.
提供的一组新数据和结构触发