Jump to content
domus

Extreme slow-down in Windows FMX app UI since upgrading to 12.1

Recommended Posts

Posted (edited)

Before I start digging deeper, I thought I'd ask if anyone experienced a big slow-down in the FMX UI in their Windows applications since upgrading to 12?

 

It has become unusable in my case. Not using SKIA.

 

If anyone has any pointers about easy fixes, very eager to hear them!

 

Thx!

 

Edit: anything change with TImage rendering? Just resizing and repositioning is extremely slow (especially large batches, even with Begin/Endupdate).

Edited by domus

Share this post


Link to post

Wrongly attributed the problem to TImage. Culprit was the BringToFront procedure, which appears to be one million times slower in Delphi 12.1 compared to 11.

Share this post


Link to post
23 minutes ago, domus said:

Culprit was the BringToFront procedure, which appears to be one million times slower in Delphi 12.1 compared to 11.

There does not appear to have been anything changed in relation to BringToFront between (at least) 11.3 and 12.1, unless I missed something in the downstream calls. Can you provide code that reproduces the problem?

Share this post


Link to post

Very strange. I didn't see this on my apps (but I mainly use them on macOS except for developers tools).

 

Windows 24H2 (in a VM) had an bad effect under Parallels (and sometimes on "real" PC) but nothing seen linked to FireMonkey itself.

 

Are you using the BringToFront often in your app ?

Have the screens many layers of visual components?

Do you use animations ?

Do you use BeginUpdate/EndUpdate when you fill or draw things ?

Share this post


Link to post

Thank you, Dave and Patrick, for replying.

 

During my effort to provide some code that reproduces the problem, I stumbled upon something that I had not experienced with 11.3, and may be the cause of the problem, but it only confuses the matter a lot more, for me.

 

I wrote some code to create a thousand TRectangles and then reposition them in a loop, inside Begin/EndUpdates, of course. They were parented to the TForm. The

 arrRect[i].Position.X := arrRect[i].Position.X + 1

line took absolute aaages to execute.

 

I then created a TLayout, parenting it to the form and reparenting all the rectangles to the TLayout. It now executes in a flash.

 

The problem obviously lies with TForm's way to refresh itself and Z-order all its components. Something I never experienced with 11.3.

 

The weird thing now, however, is that, in the code where I removed the BringToFront, everything is still parented to the TForm, and it now executes in a flash too. (another weird thing is that I had commented out this BringToFront line before, to test if it made any difference, and it didn't, but now it does).

 

In any case, if you have any idea what might cause it to suddenly work fast with TForm.......glad to hear it!

 

I'll keep investigating, but I lost quite a bit of time trying to pinpoint this thing, so it'll be in (obsessed) background mode.

 

Thanks again.

 

Share this post


Link to post

Just a shot in the dark. Have you try to enable Skia, which is included in Delphi since Version 12.

Just right click your Project und choose "Enable Skia"

 

edit: just saw your comment on skia. I missed that one.

Edited by fisipjm

Share this post


Link to post
12 minutes ago, fisipjm said:

Just a shot in the dark. Have you try to enable Skia, which is included in Delphi since Version 12.

Just right click your Project und choose "Enable Skia"

 

edit: just saw your comment on skia. I missed that one.

Yes, tried several times with GlobalUseSkia := True.

 

Everything is noticeably slower then.

Share this post


Link to post
1 hour ago, domus said:

I'll keep investigating, but I lost quite a bit of time trying to pinpoint this thing, so it'll be in (obsessed) background mode.

Have you tried with a profiler?

Share this post


Link to post
1 minute ago, Cristian Peța said:

Have you tried with a profiler?

Let's say I have pinpointed it to BringToFront. To find this out, I used my own profiler and bug tracking tool, encapsulating every call with a cycle-precision timer, accumulating the totals over the application's lifetime.

 

What I have not found out, is why it works so well now with a TForm as a parent in my applications, while it's dead slow in the example code.

Share this post


Link to post
1 hour ago, Cristian Peța said:

Can you reproduce in a small project that you can post here?

unit DemoSlowBringToFrontUnit;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects, FMX.Layouts;

type
  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
  private
    arrRect: TArray<TRectangle>;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.btn1Click(Sender: TObject);
var i, j, x, y: Integer;
    //l: TLayout;
begin
  SetLength(arrRect,961);
  // l := TLayout.Create(Self);
  //l.Parent := Self;
  //l.Align := TAlignLayout.Client;
  i := 0;
   for x := 0 to 30 do begin
     for y := 0 to 30 do begin
       arrRect[i] := TRectangle.Create(Self);   // change to l to test for layout parent
       arrRect[i].Parent := Self;  // change to l to test for layout parent
       arrRect[i].SetBounds(x * 30,y * 30,20,20);
       Inc(i)
     end
  end;

  for j := 0 to 1000 do begin
    BeginUpdate;
    for i := 0 to 960 do begin
      arrRect[i].Position.X := arrRect[i].Position.X + 1
    end;
    EndUpdate;
    Application.ProcessMessages
  end
end;

end.

Quick test to see difference between using TForm and TLayout as parent.

 

If this performs quickly with TForm on your system, let me know, then I'll have to figure out why on earth it's crawling on mine!

 

Thanks and cheers.

 

 

Edit: this doesn't show the BringToFront problem.

Here is a timing example of introducing BringToFront. Change following code:

  sw := TStopwatch.Create;
  sw.Start;
  for j := 0 to 1000 do begin
    l.BeginUpdate;
    for i := 0 to 960 do begin
      arrRect[i].Position.X := arrRect[i].Position.X + 1;
      arrRect[i].BringToFront
    end;
    l.EndUpdate;
    Application.ProcessMessages
  end;
  sw.Stop;
  ShowMessage(IntToStr(sw.ElapsedMilliseconds))

On my system, this takes twice as long to execute.

Edited by domus

Share this post


Link to post
3 hours ago, domus said:

Yes, tried several times with GlobalUseSkia := True.

 

Everything is noticeably slower then.

If you use Skia and have any things to draw on screen, enable other graphic libraries on other platforms and  disable the raster by adding unit FMX.Types in your program and this after the GlobalUseSkia:

 

Quote

  GlobalUseSkia := True;
  {$IF Defined(OSX) or Defined(IOS)}
  GlobalUseMetal := true;
  {$ENDIF}
  {$IFDEF ANDROID}
  GlobalUseVulkan := true;
  GlobalUseSkiaRasterWhenAvailable:=false;
  {$ENDIF}
  {$IFDEF MSWINDOWS}
  GlobalUseSkiaRasterWhenAvailable:=false;
  {$ENDIF}

PS : the "add code" tool don't work on my browser (latest Safari), I used QUOTE instead of it 

 

Edited by Patrick PREMARTIN

Share this post


Link to post

Hi @domus 

 

Just to be sure: do you really need TRectangle instances or is it only to draw cubes "falling" (to the right) on the screen ?

 

In your loop, if they are outside the screen, do you need to change their Position.x value ?

 

I understand why you used the Application.ProcessMessages, but for me it's like FreeAndNil() for Nick H. : never use it, find an other way. 🙂

 

I'll try your code, but i'm on an ARM Windows on Mac, it's "slow" by design.

Edited by Patrick PREMARTIN
  • Like 1

Share this post


Link to post

Even if using visual components in threads is a real bad idea, in your case (because you stop the refresh), you can use Parallel.For() loop from System.Threading. It's a simple case where it works well.

 

Quote

  for j := 0 to 1000 do
  begin
    BeginUpdate;
    try
      tparallel.for(0, 960,
        procedure(i: Integer)
        begin
          arrRect.Position.x := arrRect.Position.x + 1
        end);
    finally
      EndUpdate;
    end;
    Application.ProcessMessages;
  end;

 

Share this post


Link to post

And to remove the Application.ProcessMessages, you can do that :

 

Quote

unit Unit1;

interface

uses
  System.SysUtils,
  System.Types,
  System.UITypes,
  System.Classes,
  System.Variants,
  FMX.Types,
  FMX.Controls,
  FMX.Forms,
  FMX.Graphics,
  FMX.Dialogs,
  FMX.Controls.Presentation,
  FMX.StdCtrls,
  FMX.Objects;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    arrRect: TArray<TRectangle>;
    Counter: integer;
    procedure DoMove;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  System.Threading;

procedure TForm1.Button1Click(Sender: TObject);
var
  i, j, x, y: integer;
begin
  Button1.Enabled := false;

  SetLength(arrRect, 961);
  BeginUpdate;
  try
    i := 0;
    for x := 0 to 30 do
    begin
      for y := 0 to 30 do
      begin
        arrRect := TRectangle.Create(Self);
        arrRect.Parent := Self;
        arrRect.SetBounds(x * 30, y * 30, 20, 20);
        Inc(i)
      end
    end;
  finally
    EndUpdate;
  end;

  Counter := 1000;
  DoMove;
end;

procedure TForm1.DoMove;
begin
  BeginUpdate;
  try
    tparallel.for(0, 960,
      procedure(i: integer)
      begin
        arrRect.Position.x := arrRect.Position.x + 1
      end);
  finally
    EndUpdate;
  end;

  dec(Counter);
  if (Counter > 0) then
    tthread.ForceQueue(nil,
      procedure
      begin
        DoMove;
      end);
end;

end.

 

I know the TThread.ForceQueue() has its detractors, but in this context it works very well.

 

PS : it works for Mac, but the display is not done on Windows... 

Edited by Patrick PREMARTIN
  • Thanks 1

Share this post


Link to post
19 minutes ago, Patrick PREMARTIN said:

Just to be sure: do you really need TRectangle instances or is it only to draw cubes "falling" (to the right) on the screen ?

No, of course not. This is not production code, it's just something to demonstrate the slow-down.

 

20 minutes ago, Patrick PREMARTIN said:

In your loop, if they are outside the screen, do you need to change their Position.x value ?

Of course not. That's not what I'm doing in my production code, but here, to demonstrate the problem, it doesn't matter.

 

21 minutes ago, Patrick PREMARTIN said:

I understand why you used the Application.ProcessMessages, but for me it's like FreeAndNil() : never use it, find an other way. 🙂

 

Of course. Not using ProcessMessages in the production code. There it's reacting to mouse events, and, trust me, I'm using ALOT of parallel procedures to speed things up.

 

The ProcessMessages is just used in this code to demonstrate the slow-down. It'd be similar if any other way were used.

Share this post


Link to post
12 minutes ago, domus said:

This is not production code, it's just something to demonstrate the slow-down.

Ok, in that case I confirm the big speed loss between 11.3 Alexandria and 12.3 Athens for this project, even with my changes. Did you opened an issue on QP or can I open it ?

  • Thanks 1

Share this post


Link to post
6 minutes ago, Patrick PREMARTIN said:

Ok, in that case I confirm the big speed loss between 11.3 Alexandria and 12.3 Athens for this project, even with my changes. Did you opened an issue on QP or can I open it ?

Be my guest!

 

I tried to look for the issue in QP a few days ago, but got sent from site A to B to A, then not finding anything in either, and finally giving up. I do remember QP to work, back in 1972, but I'm getting the impression they changed the furniture down there, and now it's a hassle to just locate it. Probably my age.

 

Thanks for confirming! I can now cancel that psychiatric evaluation appointment. At least for this issue.

 

 

Btw, I have been using TThread.Queue quite a bit, but never considered ForceQueue. Thanks for making me look into that one.

Edited by domus
  • Haha 1

Share this post


Link to post

I open the issue https://embt.atlassian.net/servicedesk/customer/portal/1/RSS-3711 You can follow it by enabling the notifications after login on this URL (quality.embarcadero.com).

 

The difference between TThread.Queue() and TThread.ForceQueue() happens in the main thread : in the first case the code is executed, in the second it's delayed to next message processing iteration. Unfortunately, the behavior is not the same on Mac and Windows and in this project the form is only refreshed on macOS, for Windows it's done at the end.

  • Like 1
  • Thanks 1

Share this post


Link to post

FMX.Types.AlignObjects is taking the most time: 97.4% (from VTune)

 

AlignObjects is not called from TControl.Realign and is going fast when rectangles are on TLayout.

 

But when rectangles are on a form, AlignObjects is called from TCustomForm.Realign and is taking the most time

FMX.Types.AlignObjects($4BD50A0,$4C4C860,640,480,640,480,False)
FMX.Forms.TCustomForm.Realign
FMX.Controls.TControlHelper.PositionChanged(???)
FMX.Types.TPosition.DoChange
FMX.Types.TPosition.SetX(1)
Unit1.TForm1.Button1Click($4BEFDE0)

 

 

  • Thanks 1

Share this post


Link to post
16 minutes ago, Cristian Peța said:

But when rectangles are on a form, AlignObjects is called from TCustomForm.Realign and is taking the most time

Thanks very much for going to the trouble of pinpointing the culprit! I know what to avoid now.

 

It is doing this with all controls, right, not just TRectangles?

 

Cheers!

 

 

Edited by domus

Share this post


Link to post
1 hour ago, domus said:

It is doing this with all controls, right, not just TRectangles?

More or less all Form.Children.

AlignObjects().DoAlign() enumerates all Form.Children but it depends a little if the object supports IAlignableObject interface.

Edited by Cristian Peța

Share this post


Link to post

At the moment, I'm trying to figure out why, in my project, with hundreds of TLayouts parented to a TForm, all positional and size updates of those layouts (inside the Form's BeginUpdate and EndUpdate) perform quasi instantly. This happens inside mouse events (panning and zooming (scrollwheel)). I haven't tried doing the same in a loop, but wonder that would make a difference.

 

The nondeterministic nature of this observation annoys me, but I can't dwell on it, although it'll keep bugging me. :classic_unsure:

Share this post


Link to post

Ohforcryingoutloud! I found the reason why it's so much faster in my project than in the demo!

 

To put it in the demo's terms, I use

arrRect[i].SetBounds(arrRect[i].Position.X + 1,arrRect[i].Position.Y,arrRect[i].Width,arrRect[i].Height)

instead of

arrRect[i].Position.X := arrRect[i].Position.X + 1

Check out the difference in speed!

Edited by domus

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×