1

I am developing Firemonkey App for iOS and Android. I noticed that the app gets slower performance in iOS and Android debugging everytime the TGrid is created at runtime using the TFDMemTable REST API data and structure.

I already applied the FreeAndNil(TGrid1); to cleanup TGrid before it creates over and over again.

One notable event, everytime the TGrid is created the rows are increasing with fixed 7 columns the performance gets slower. Normally, this happens when I reached to 10 rows or records.

My one big and real quick question:

Where do you think the overheads are coming from that caused the performance to slow?

  1. TGrid — which I already applied FreeAndNil(TGrid1); before its creation.

  2. TFMemTable — I haven't checked this and I do not know how

  3. TButton — the one that triggers the creation and loading of data to TGrid. Most of the codes reside here

Let's assume that all other components are working fine prior to this case. I could share to you some code if you want to but guide me which one you want to see.

UPDATE 1 : MINIMUM REPRODUCIBLE EXAMPLE

FMX File

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 Procedures

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.
RickyBelmont
  • 619
  • 4
  • 11
  • I doubt whether using FreeAndNil makes the slightest difference here compared with Free and the FDMemTable does not generally slow down with re-use, so the cause of your problem is something you haven't told us. You should do what you are supposed to do when asking for help with a problem like this and provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). Obvs the code should include how you load the FDMemTable and how you populate the TGrid from it. – MartynA Sep 06 '20 at 09:01
  • @MartynA Actually I did (see MRE update above). I just want it nice and clean initially. Nevertheless, you mentioned about FreeAndNil. Can you expound about it? – RickyBelmont Sep 06 '20 at 09:10
  • Well, the usual use of FreeAndNil seems to be to prevent the object accidentally being re-used but I don't see that really applies to your situation. The other thing is, not meaning to be pedantic, your code still isn't an MRE because apart from anything else, readers do not have access to your REST source. Also it is not at all clear why you are evidently wrapping your code in a transaction, nor what your `conn` is doing, if you're getting your data from a REST source. I will post something as an answer which may be of some assistance. – MartynA Sep 06 '20 at 11:14
  • @MartynA I have added the REST source API in the MRE. Looking forward to your answer. Thanks in advance. – RickyBelmont Sep 06 '20 at 11:40
  • 1
    Well you may be somewhat underwhelmed by my answer, but it should at least be able to demonstrate whether the observed slowdown is anything to do with repeatedly creating/freeing the TTeeGrid. Btw instead of all the `if .. then ..` code setting the column widths, why not just use a `case ColumnNumber of` statement. – MartynA Sep 06 '20 at 11:58

3 Answers3

3

The problem you are facing here is that because of the way ARC works in Delphi your TTeeGrid doesn't really gets destroyed.

Why is that?
As soon as you set Parent to tgd1 component a reference to it is added to lyt1 Controls collection which lists all children components. So when you call FreeAndNil(tgd1); you only release the reference to your TTeeGrid object from your global variable tgd1 but the one from layout's controls collection still remains. And since your TTeeGrid reference count hasn't reached ZERO the object doesn't gets destroyed.

So instead of using:

FreeAndNil(tgd1);

you need to use:

tgd1.DisposeOf;
tgd1 := nil;

This makes sure that destructor of your TTeeGrid object is executed regardless of objects reference count which in turn notifies the Layout that your TTeeGrid object is getting destroyed and thus it needs to be removed from Layouts Controls collection and thus allows TTeeGrid object reference count to reach ZERO.

In fact you should use DisposeOf for destroying any components at run-time.

I suggest you read more about this topic in How to free a component in Android / iOS

EDIT This problem is encountered only on mobile platforms like Android and iOS where ARC is used. On Windows your code will work just fine. This is probably the reason why others didn't manage to reproduce your problem.

Also note that since in Delphi 10.4 ARC was removed your code should also work. But I'm guessing your are not using newest version of Delphi.

You may want to edit the question and include your Delphi version in order to improve this question as the version of Delphi used affects the answer to the question.

SilverWarior
  • 7,372
  • 2
  • 16
  • 22
  • Great response! I think most of us here haven't realized this kind of issue. I working on this one now. I haven't updated yet to 10.4 I am still using 10.3. Do you think 10.4 is stable already? and my 10.3 developed apps will not encounter any issues with 10.4? – RickyBelmont Sep 07 '20 at 10:04
  • I'm not sure about 10.4 stability. I myself am still on 10.3 as you do. I did see some post from people who experienced some difficulties due to some changes in underlying code that Delphi 10.4 introduced. Some of those difficulties should have been fixed with Delphi 10.4 update 1 and update 2. So I would recommend checking Embarcadero forums and Embarcadero quality center portal for more information on this matter. Or you can set up a virtual environment and install Delphi 10.4 there and simply test it out. – SilverWarior Sep 07 '20 at 11:48
  • I thought about setting up virtual environment with my parallels setup but with limited space I'll have to arrange it. Meanwhile, I just found out (flag as error on iOS target) that TTeeGrid does not contain a member named `DisposeOf`. I am not sure yet still verifying. – RickyBelmont Sep 07 '20 at 12:01
  • Its okay. `DisposeOf` is a member of TeeGrid. I am still having issues with slow performance with your suggestion. I am looking at my code this time where exactly the issue is coming from. With windows I do not have any issue like you said. – RickyBelmont Sep 07 '20 at 12:23
  • The performance still slow on iOS and Android. I am sure that there are overheads on repeated create/disposeof/nil teegrid at runtime because it consistently happened when I reach 10 repetitions (equivalent to 10 records/rows). BTW, the triggering of create/disposeof/nil happens when I add items/rows. Any other hint or clue @SilverWarior? – RickyBelmont Sep 07 '20 at 12:50
  • 1
    To be honest I don't have actual experience working with `TTeeGrid` component. And I have pretty limited experience working with database components. Anyway I assume that connecting your TTeeGrid component to the data source may also add reference to your TTeeGrid component somewhere in data source object as it would need such information in order to notify your TTeeGrid that data has been updated. I'm afraid you will have to go and test this on your own. I recommend commenting out specific lines that deal with TTreeGrid in your code to spot which one of them may be leading to such scenario – SilverWarior Sep 07 '20 at 18:16
1

Frankly, I doubt whether anyone here can solve your problem because it isn't really reproducible by anyone else because we don't have access to your REST source. Instead, I suggest you backtrack to my answer here to an earlier question of yours about using the TTeeGrid with an FDMemTable. The reason I suggest that is because it provides a way of testing/benchmarking the two components that's fairly self-contained and doesn't depend (for others) on access to your REST source. You could use code like the following to investigate whether your reported slowdown is anything to do with the repeated creation/freeing of your TTeeGrid (I would be surprised if it is).

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;
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • Thanks for this @MartynA. I like this though a lot of work to be done here but its okay, I can follow this through. Can I get back to you as soon I figured this out. I feel like it is better to do it this way for easy debugging than what I did — direct definition of teegrid from fdmemtable. – RickyBelmont Sep 06 '20 at 12:37
  • By the way, the columns here are variable it depends on the REST API data so I cannot define TField initially. I can only be done at runtime. – RickyBelmont Sep 06 '20 at 21:18
  • Solved! I removed the create/nil at runtime. Instead, I placed the TTeeGrid at design time and enable to true/false every time new API data are provided. – RickyBelmont Sep 07 '20 at 22:57
  • 1
    Well, I'm glad you sorted it out, though I did wonder why you were doing create/nil each time but assumed you had your reasons. – MartynA Sep 08 '20 at 08:12
  • Actually I got the clue from your answer while trying it. So, thank you so much! – RickyBelmont Sep 13 '20 at 00:11
1

The creation of TTeeGrid at runtime accumulates overheads that slows down the performance at some point.

To solve it, I have removed the creation and freeing of TTeeGrid at runtime, instead, I placed TTeeGrid visual component at design time and have it refresh its connection by property enabled to true or false every time it is triggered by the new set of data and structure provided by API.

RickyBelmont
  • 619
  • 4
  • 11