44

It's already a while we are working with FireMonkey at office. After a while we noticed it wasn't exactly so lightning fast due to GPU acceleration as Embarcadero tells us.

So we built a basic application just for testing FireMonkey performance. Basically it's a form with a panel on the bottom (alBottom) that works as status bar and an all client (alClient) Panel. The panel on the bottom has a progressbar and an animation.

We added a method to the form that frees whatever control is present in the all client panel and fulfil it with cells of a custom type and a "mouse over" style and update the animation, the progress bar and the form's caption with info about the fulfilling progress. The most important info is the required time.

Finally we added such method to the OnResize of the form, run the application and maximized the form (1280x1024).

The result with XE2 was really slow. It took around 11 seconds. In addition since the panel is fulfilled till the application is ready to receive user input there is an additional delay of about 10 seconds (like freezing). For an overall of 21 seconds.

With XE3 the situation got worst. For the same operation it took an overall of 25 seconds (14 + 11 freezing).

And rumours tell XE4 is going to be a lot worst of XE3.

This is quite scaring considering exactly the same application, using VCL instead of FireMonkey and using SpeedButtons in order to have the same "mouse over effect" takes just 1.5 seconds!!! So the problem clearly reside in some internal FireMonkey engine problem(s).

I opened a QC (#113795) and a (paid) ticket to embarcadero support but nothing they won't solve it.

I seriously don't understand how they can ignore such heavy issue. For our enterprise is being a show-stopper and a deal breaker. We cannot offer commercial software to our customer with such poor performance. Earlier or later we will be forced to move to another platform (BTW: the same code Delphi Prism with WPF takes 1.5 seconds as the VCL one).

If anybody has any idea about how to solve the issue or try to improve this test performance and want to help I would be really glad of it.

Thank you in advance.

Bruno Fratini

The application is the following one:

unit Performance01Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects;

const
  cstCellWidth = 45;
  cstCellHeight = 21;

type

  TCell = class(TStyledControl)
  private
    function GetText: String;
    procedure SetText(const Value: String);
    function GetIsFocusCell: Boolean;
  protected
    FSelected: Boolean;
    FMouseOver: Boolean;
    FText: TText;
    FValue: String;
    procedure ApplyStyle; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure DoMouseEnter; override;
    procedure DoMouseLeave; override;
    procedure ApplyTrigger(TriggerName: string);
  published
    property IsSelected: Boolean read FSelected;
    property IsFocusCell: Boolean read GetIsFocusCell;
    property IsMouseOver: Boolean read FMouseOver;
    property Text: String read GetText write SetText;
  end;

  TFormFireMonkey = class(TForm)
    StyleBook: TStyleBook;
    BottomPanel: TPanel;
    AniIndicator: TAniIndicator;
    ProgressBar: TProgressBar;
    CellPanel: TPanel;
    procedure FormResize(Sender: TObject);
    procedure FormActivate(Sender: TObject);
  protected
    FFocused: TCell;
    FEntered: Boolean;
  public
    procedure CreateCells;
  end;

var
  FormFireMonkey: TFormFireMonkey;

implementation

uses
  System.Diagnostics;

{$R *.fmx}

{ TCell }

procedure TCell.ApplyStyle;
begin
  inherited;
  ApplyTrigger('IsMouseOver');
  ApplyTrigger('IsFocusCell');
  ApplyTrigger('IsSelected');
  FText:= (FindStyleResource('Text') as TText);
  if (FText <> Nil) then
    FText.Text := FValue;
end;

procedure TCell.ApplyTrigger(TriggerName: string);
begin
  StartTriggerAnimation(Self, TriggerName);
  ApplyTriggerEffect(Self, TriggerName);
end;

procedure TCell.DoMouseEnter;
begin
  inherited;
  FMouseOver:= True;
  ApplyTrigger('IsMouseOver');
end;

procedure TCell.DoMouseLeave;
begin
  inherited;
  FMouseOver:= False;
  ApplyTrigger('IsMouseOver');
end;

function TCell.GetIsFocusCell: Boolean;
begin
  Result:= (Self = FormFireMonkey.FFocused);
end;

function TCell.GetText: String;
begin
  Result:= FValue;
end;

procedure TCell.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
var
  OldFocused: TCell;
begin
  inherited;
  FSelected:= not(FSelected);
  OldFocused:= FormFireMonkey.FFocused;
  FormFireMonkey.FFocused:= Self;
  ApplyTrigger('IsFocusCell');
  ApplyTrigger('IsSelected');
  if (OldFocused <> Nil) then
    OldFocused.ApplyTrigger('IsFocusCell');
end;

procedure TCell.SetText(const Value: String);
begin
  FValue := Value;
  if Assigned(FText) then
    FText.Text:= Value;
end;

{ TForm1 }

procedure TFormFireMonkey.CreateCells;
var
  X, Y: Double;
  Row, Col: Integer;
  Cell: TCell;
  T: TTime;
  // Workaround suggested by Himself 1
  // Force update only after a certain amount of iterations
  // LP: Single;

  // Workaround suggested by Himself 2
  // Force update only after a certain amount of milliseconds
  // Used cross-platform TStopwatch as suggested by LU RD
  // Anyway the same logic was tested with TTime and GetTickCount
  // SW: TStopWatch;

begin
  T:= Time;
  Caption:= 'Creating cells...';

  {$REGION 'Issue 2 workaround: Update form size and background'}
  // Bruno Fratini:
  // Without (all) this code the form background and area is not updated till the
  // cells calculation is finished
  BeginUpdate;
  Invalidate;
  EndUpdate;
  // Workaround suggested by Philnext
  // replacing ProcessMessages with HandleMessage
  // Application.HandleMessage;
  Application.ProcessMessages;
  {$ENDREGION}

  // Bruno Fratini:
  // Update starting point step 1
  // Improving performance
  CellPanel.BeginUpdate;

  // Bruno Fratini:
  // Freeing the previous cells (if any)
  while (CellPanel.ControlsCount > 0) do
    CellPanel.Controls[0].Free;

  // Bruno Fratini:
  // Calculating how many rows and columns can contain the CellPanel
  Col:= Trunc(CellPanel.Width / cstCellWidth);
  if (Frac(CellPanel.Width / cstCellWidth) > 0) then
    Col:= Col + 1;
  Row:= Trunc(CellPanel.Height / cstCellHeight);
  if (Frac(CellPanel.Height / cstCellHeight) > 0) then
    Row:= Row + 1;

  // Bruno Fratini:
  // Loop variables initialization
  ProgressBar.Value:= 0;
  ProgressBar.Max:= Row * Col;
  AniIndicator.Enabled:= True;
  X:= 0;
  Col:= 0;

  // Workaround suggested by Himself 2
  // Force update only after a certain amount of milliseconds
  // Used cross-platform TStopwatch as suggested by LU RD
  // Anyway the same logic was tested with TTime and GetTickCount
  // SW:= TStopwatch.StartNew;

  // Workaround suggested by Himself 1
  // Force update only after a certain amount of iterations
  // LP:= 0;

  // Bruno Fratini:
  // Loop for fulfill the Width
  while (X < CellPanel.Width) do
  begin
    Y:= 0;
    Row:= 0;
    // Bruno Fratini:
    // Loop for fulfill the Height
    while (Y < CellPanel.Height) do
    begin
      // Bruno Fratini:
      // Cell creation and bounding into the CellPanel
      Cell:= TCell.Create(CellPanel);
      Cell.Position.X:= X;
      Cell.Position.Y:= Y;
      Cell.Width:= cstCellWidth;
      Cell.Height:= cstCellHeight;
      Cell.Parent:= CellPanel;

      // Bruno Fratini:
      // Assigning the style that gives something like Windows 7 effect
      // on mouse move into the cell
      Cell.StyleLookup:= 'CellStyle';

      // Bruno Fratini:
      // Updating loop variables and visual controls for feedback
      Y:= Y + cstCellHeight;
      Row:= Row + 1;
      ProgressBar.Value:= ProgressBar.Value + 1;
      // Workaround suggested by Himself 1
      // Force update only after a certain amount of iterations
      // if ((ProgressBar.Value - LP) >= 100) then

      // Workaround suggested by Himself 2
      // Force update only after a certain amount of milliseconds
      // Used cross-platform TStopwatch as suggested by LU RD
      // Anyway the same logic was tested with TTime and GetTickCount
      // if (SW.ElapsedMilliseconds >= 30) then

      // Workaround suggested by Philnext with Bruno Fratini's enhanchment
      // Skip forcing refresh when the form is not focused for the first time
      // This avoid the strange side effect of overlong delay on form open
      // if FEntered then
      begin
        Caption:= 'Elapsed time: ' + FormatDateTime('nn:ss:zzz', Time - T) +
                  ' (min:sec:msec) Cells: ' + IntToStr(Trunc(ProgressBar.Value));

        {$REGION 'Issue 4 workaround: Forcing progress and animation visual update'}
        // Bruno Fratini:
        // Without the ProcessMessages call both the ProgressBar and the
        // Animation controls are not updated so no feedback to the user is given
        // that is not acceptable. By the other side this introduces a further
        // huge delay on filling the grid to a not acceptable extent
        // (around 20 minutes on our machines between form maximization starts and
        // it arrives to a ready state)

        // Workaround suggested by Philnext
        // replacing ProcessMessages with HandleMessage
        // Application.HandleMessage;
        Application.ProcessMessages;
        {$ENDREGION}

        // Workaround suggested by Himself 1
        // Force update only after a certain amount of iterations
        // LP:= ProgressBar.Value;

        // Workaround suggested by Himself 2
        // Force update only after a certain amount of milliseconds
        // Used cross-platform TStopwatch as suggested by LU RD
        // Anyway the same logic was tested with TTime and GetTickCount
        // SW.Reset;
        // SW.Start;
      end;
    end;
    X:= X + cstCellWidth;
    Col:= Col + 1;
  end;

  // Bruno Fratini:
  // Update starting point step 2
  // Improving performance
  CellPanel.EndUpdate;

  AniIndicator.Enabled:= False;
  ProgressBar.Value:= ProgressBar.Max;
  Caption:= 'Elapsed time: ' + FormatDateTime('nn:ss:zzz', Time - T) +
            ' (min:sec:msec) Cells: ' + IntToStr(Trunc(ProgressBar.Value));

  // Bruno Fratini:
  // The following lines are required
  // otherwise the cells won't be properly paint after maximizing
  BeginUpdate;
  Invalidate;
  EndUpdate;
  // Workaround suggested by Philnext
  // replacing ProcessMessages with HandleMessage
  // Application.HandleMessage;
  Application.ProcessMessages;
end;

procedure TFormFireMonkey.FormActivate(Sender: TObject);
begin
  // Workaround suggested by Philnext with Bruno Fratini's enhanchment
  // Skip forcing refresh when the form is not focused for the first time
  // This avoid the strange side effect of overlong delay on form open
  FEntered:= True;
end;

procedure TFormFireMonkey.FormResize(Sender: TObject);
begin
  CreateCells;
end;

end.
nexial
  • 399
  • 1
  • 3
  • 7
  • This doesn't compile in XE2, so I assume it's for XE3 only? – Warren P Apr 02 '13 at 22:57
  • 2
    Unfortunately, while the visual is accelerated by the GPU through the use of VRAM for bitmaps and the basic 2D features of the GPU itself (such as shader effects [glow, blur, shadow, etc]), the memory performance of the framework leaves much to be desired. I noticed this problem in XE2 when dealing with >10000 objects (ranging from buttons, to layouts, to rectangles/primitives) and while XE3 has gone some way to fix this, it's still advisable to reduce the number of visual objects as much as possible. Even freeing large numbers of objects in FMX is time consuming. – Scott P Apr 02 '13 at 23:53
  • Send the code to Embarcadero and ask for their opinion, they need examples like this to show the kinds of issues there are with FM. – rhody Apr 03 '13 at 00:53
  • @Scott: Sadly performance of XE3 decreased against XE2 instead of improving. In addition as I said there are rumours about XE4 FireMonkey is going to suffer even more this problem. Anyway yes, you're right, It doesn't depend on the type of control, but just in the number of controls. And I also found out that freeing controls is over-time consuming. All this make me thing the problem resides on how they manage the internal controls lists, so I guess improving that wouldn't be so badly hard. In these conditions I don't understand how they think they can gain market share. – nexial Apr 03 '13 at 07:57
  • 3
    What I really don't get is how VCL doesn't suffer this problem at all. Well I guess VCL controls management is done internally by Windows against the custom management done by Embarcadero for FireMonkey. – nexial Apr 03 '13 at 08:00
  • 6
    @rhody: Sadly as I told in my former post, I opened 2 QC tickets and a paid ticket to Embarcadero support and all of them got no resolution at all. They just tell "don't use a lot of controls" :( In addition their R&D team answered me that it is not FireMonkey fault because such kind of many controls at screen will be slow in whatever platform. Meanwhile that is just false. In VCL and .Net/WPF exactly the same application takes around 1.5 seconds against the 25 seconds of FireMonkey :( – nexial Apr 03 '13 at 08:04
  • For who wanna check out the original QC that has a lot more info than this same post, it is 113795. [link]http://qc.embarcadero.com/wc/qcmain.aspx?d=113795 – nexial Apr 03 '13 at 08:06
  • @Warren. Hi there. Well formerly it was in XE2 but due to the changes done to the FireMonkey platform we had to make some lesser change in order to compile it in XE3. It was more or less one year ago so I cannot remember exactly those changes. The time records I put in the original post were recorded at that time and put in a document, for that reason I'm so sure about it. Anyway, if you tell me which compiler error you get maybe I'll remember what was the difference for making it compile under XE2. – nexial Apr 03 '13 at 08:15
  • @Warren: I noticed I pasted a version I was editing so maybe that's the reason it wasn't compiling. I replaced it with the right one. Let me know if you have still problems with it. – nexial Apr 03 '13 at 09:58
  • 3
    For something like this I suggest you actually put the code up on bitbucket and let people clone it from there. Saves a lot of stupid work. – Warren P Apr 03 '13 at 15:37
  • "don't use a lot of controls"..... Okay, how are we supposed to make rich user-friendly applications then? – Jerry Dodge Apr 03 '13 at 23:38
  • @Warren: Well I put it here for lazy people anyway as I remarked in the post in the QC link there are more details and all the original source code :) – nexial Apr 04 '13 at 10:33
  • 1
    @Jerry: Well I don't know what to tell, honestly it sounds me crazy. Their feedback is "Won't Do" (short) reason "wrong application design"... so every application in this world that has many controls at screen is bad designed... cool!!! What I remarked also at Embarcadero support that frustrated me a lot is: but there is someone checking out the quality of R&D feedback or they can tell whatever they want? Considering the first feedback I got by R&D was that no platform would manage such task in a quick manner meanwhile the same VCL can do it I guess nobody does any quality control. – nexial Apr 04 '13 at 10:37
  • It is to be fair, a different framework with different rules. However obviously it uses the same kind of 3D hardware engine as game engines do, and game engines have no problem showing a few million or hundreds of millions of polygons, including shading and lighting, so it does seem to me that it should be POSSIBLE for them to handle large "scenes". – Warren P Apr 04 '13 at 17:58
  • @Warren. Definitely it is possible. Games shows quite a lot more of polygons quite a lot faster. But letting apart games that everybody know on average they are high optimized for performance (when well done) let's focus on WPF and VCL. How is it possible that more or less the same application with the same goal in these frameworks goes 20 times faster than in FireMonkey? In addition the fact it badly slow down increasing the number of controls in a no linear way, by my point of view indicates clearly the problem resides in how they manage the internal control lists and operations. – nexial Apr 05 '13 at 07:41
  • Have you tried profiling it with [Sampling Profiler](http://delphitools.info/samplingprofiler/)? – Anders E. Andersen Apr 05 '13 at 16:58
  • @Anders, honestly no. I tought that considering this same sample passed by the hand of Embarcadero R&D team and they weren't able to improve it, I tought there was no need to try the profiler. Anyway I'll give it a try and let you know. Thank you in advance. – nexial Apr 06 '13 at 08:42
  • @ Everybody there: Sorry if my feedback times are slow. It is week-end and I'm at home so I shouldn't be working right now and I'm neither sure I have here all the tools I have at office for doing a complete test and give the right feedback for all the suggestion I'm getting. Anyway I thank you all and as soon I'll be able to run a full test, I'll give you an as detailed feedback as I can. – nexial Apr 06 '13 at 11:50
  • This is not the framework for what you are trying to do. Both the VCL and FMX frameworks are designed to be easy to use; they abstract the whole message pumping / queues from you, unlike VC++, for example. But by doing this they added a lot of "weight", which is what you are experiencing here. – Leonardo Herrera Apr 17 '13 at 17:14
  • If you want to add hundreds of "cells" better write your own component (some form of Panel, for example) and do the drawing and event handling yourself. You can be as fast and optimized as you want. – Leonardo Herrera Apr 17 '13 at 17:16
  • @Leonardo: Well considering the timing difference of FireMonkey about 20 times slower than VCL I wouldn't say they are designed the same. – nexial Apr 18 '13 at 13:51
  • @Leonardo: What you says about custom Panel is quite out of the FireMonkey directives. FireMonkey is not VCL and it is draw object primitives composition based as WPF. Doing what you says would completely broke FireMonkey basics, makes impossible to use styles and with that invalidate our architecture. We built it basing on Embarcadero's guidelines of using styles for FireMonkey, so our views use the same base custom controls with different styles depending on what has to be displayed. – nexial Apr 18 '13 at 13:55
  • BTW WPF works in the same way but the same application using Delphi Prism with WPF runs about 20 times faster than the one under FireMonkey. Something wrong on it. – nexial Apr 18 '13 at 13:56
  • Firemonkey limitations in your scenario are architectural, and you already have found that Embarcadero is not going to fix them for you. The primitives used for drawing pretty things on screen have nothing to do with the slowness you see; this is probably a case of leaking abstractions, probably deep in Firemonkey's guts. What I'm trying to say is that you are probably trying to hammer round pegs into square holes. – Leonardo Herrera Apr 18 '13 at 14:59
  • Hi Leo, maybe you're right. After endless tries I made by myself I wasn't able to find a real solution to this issue. That's the reason I've opened this thread. StackOverflow has one of the most large and skilled Delphi community, so if neither here a solution comes out it means FireMonkey just doesn't work as expected. – nexial Apr 19 '13 at 08:52

3 Answers3

28

I tried your code, it takes 00:10:439 on my PC on XE3 to fill screen with cells. By disabling these lines:

  //ProgressBar.Value:= ProgressBar.Value + 1;
  //Caption:= 'Elapsed time: ' + FormatDateTime('nn:ss:zzz', Time - T) +
  //          ' (min:sec:msec) Cells: ' + IntToStr(Trunc(ProgressBar.Value));
  ...
  //Application.ProcessMessages;

This goes down to 00:00:106 (!).

Updating visual controls (such as ProgressBar or Form.Caption) is very expensive. If you really think you need that, do that only every 100-th iteration, or better, only every 250 processor ticks.

If that does not help with performance, please run your code with these lines disabled and update the question.

Further, I've added code to test the repainting time:

T:= Time;
// Bruno Fratini:
// The following lines are required
// otherwise the cells won't be properly paint after maximizing
//BeginUpdate;
Invalidate;
//EndUpdate;
Application.ProcessMessages;
Caption := Caption + ', Repaint time: '+FormatDateTime('nn:ss:zzz', Time - T);

When run for a first time, creating all the controls takes 00:00:072, repainting takes 00:03:089. So it's not the object management but the first time repainting which is slow.

Second time repainting is considerably faster.

Since there's a discussion in comments, here's how you do progress updates:

var LastUpdateTime: cardinal;
begin
  LastUpdateTime := GetTickCount - 250;
  for i := 0 to WorkCount-1 do begin
    //...
    //Do a part of work here

    if GetTickCount-LastUpdateTime > 250 then begin
      ProgressBar.Position := i;
      Caption := IntToStr(i) + ' items done.';
      LastUpdateTime := GetTickCount;
      Application.ProcessMessages; //not always needed
    end;
  end;
end;
himself
  • 4,806
  • 2
  • 27
  • 43
  • Thank you for your feedback. BY my memory the application is as is for a certain reason but having more than one year I cannot remember it. Cutting off those line will probably generate some unwanted side-effect but hey, I could be wrong so I'll try and test your suggestion and I'll let you know my findings. – nexial Apr 06 '13 at 08:48
  • Anyway, your test result of about 11 seconds is just a little faster than our one. As you can see by the original QC, the oberall delay of about 25 seconds is due to 15 seconds fulfilling the panel with the cells that is the time reported in the form caption, plus around 10 seconds (manually recorded with a clock) that the application gets like frozen. Meaning there are around 10 seconds since the cells calculation is over and the timer caption stops and the user can really interact with the form. Anyway thank you and I'll let you know and of course if it works the Bounty is yours. – nexial Apr 06 '13 at 08:50
  • 10
    Tested out the code as well and I think I agree. Firemonkey or not, this is bad design. Any time you are thinking about using Application.ProcessMessages, you are 99% surely doing something fundamentally wrong in your code. – Anders E. Andersen Apr 06 '13 at 09:06
  • @nexial: Of those 25 seconds you're talking about, 15 seconds are probably NOT due to creation of large number of controls but due to those lines I commented out. Every time you create a control, you update progress and therefore repaint it. The remaining 10 seconds are due to first time repainting, yeah. – himself Apr 06 '13 at 09:44
  • @Himself: Indeed but 2 issues: 1- If you are reading data from a DB or getting them from a remote connection you cannot let the user waiting without some sort of visual feedback as in this case the progress bar. In VCL we would used just the ProgressBar.Invalidate and the performance decrese would had been minimal. In FireMonkey this seems no possible. 2 - Well I consider that 10 seconds for the first time repainting when everything is already ready is something really crazy, they should work to improve this a lot. – nexial Apr 06 '13 at 09:54
  • @nexial: If you really need to display progress, do it only sometimes. For instance check that GetTickCount-LastProgressBarUpdate > 250, and only update/repaint the progress bar if so. This way you don't slow the processing down while still informing the user. – himself Apr 06 '13 at 10:49
  • @Himself: Hi again and thank you for being so fast reacting. I get your point but, probably for lack of overall info (because it would be too large) you cannot understand the point behind. We have a fully featured software architecture based on FireMonkey almost done. Such architecture as one of the main feature has an UI virtualized set of visual controls. – nexial Apr 06 '13 at 11:43
  • Best way to achieve UI virtualization is with a ristrected set of controls that are reused for display the data at screen and it is exactly what we have. By the other side having the minimum amount required doesn't mean not arriving to hundred of controls at screen at the same time. – nexial Apr 06 '13 at 11:44
  • Adding to this an architecture is not a goal specific framework but some kind of elastic and flexible base for developing software, we cannot use some kind of "dirty trick" for each specific case that is what you suggest for this specific one. Following this path would completely broke the concept of "we have a good base architecture, let's go to build application based on it without worring of low level details that are alreday embeddede in the architecture itself". – nexial Apr 06 '13 at 11:44
  • Going for the short version, it seems quite absurd to me that you cannot just live update a simple progress bar in FireMonkey without suffering such kind of heavy delay. By my point of view Embarcadero should improve this aspect right now. How many time, to me, to you, to a lot of other developer out there, it happened to have some kind of feedback to display live to the user without the need to update all the rest of the view/form? I'm sure a lot of times. Are you telling me the right way is everytime there is this need to rely on timer counters and/or threads? – nexial Apr 06 '13 at 11:45
  • Such kind of over-work for developers is out of mind specially when competitors offer you the required feature without needing to suffer so many headhache. Well at least all of this IMHO. – nexial Apr 06 '13 at 11:45
  • @nexial> "we cannot use some kind of "dirty trick" for each specific case" Nope that's not what I suggest. Only updating UI every once in a while is not a "dirty trick", it's common sense and what every programmer should do every time they update UI during a process. Doing otherwise (updating every cycle) is a bad idea. Embarcaderro has nothing to do with this particular subproblem as even raw Windows controls would be slow with it. – himself Apr 06 '13 at 12:26
  • @Himself: Ehm the SAME application sample takes 1.5 sceonds under VCL... so I guess is an FMX problem instead – nexial Apr 06 '13 at 18:32
  • @Himself: I saw your sample. It is not that I don't get it. I swer I do :D What I'm telling is: there are a lot of situation the user wants a live update and anyway, if competitors don't force you to extra work (yeah in your sample is a little extra work) why should an enterprise invest on Embarcadero tools? They should improve their Platform performance to be at least at a rasonable comparison level against their direct competitors. – nexial Apr 06 '13 at 18:36
  • I wasn't able to test it yet but it would be quite embarassing if the same iOS app built with XE4 would result slower than the one built with PhoneGap that essentially generates web apps embedded in a browser. – nexial Apr 06 '13 at 18:36
  • OK I tested your suggestion and I sadly have to tell you those don't work. Details: – nexial Apr 07 '13 at 11:39
  • Start point: At home I have another resolution of 1920x1200 so there are a lot more cells 2255. The base test (no changes) in this condition takes 20.650 (seconds) for maximizing. – nexial Apr 07 '13 at 11:41
  • And another terrible point: at such resolution with so many cells the extra delay between the cells calculation and when the form is ready for input increased about 30 seconds. – nexial Apr 07 '13 at 11:42
  • Disabling the progress bar update took 01.433 for maximizing. Surely quite better but still the problem we cannot let the user without any visual update meanwhile it is calculating. – nexial Apr 07 '13 at 11:43
  • Disabling the final "BeginUpdate & EndUpdate" resulted in a strange effect, meaning the maximizing time a little increased till 01.626 – nexial Apr 07 '13 at 11:44
  • About using GetTickCount I've got quite confused. Letting be that using a Windows API call is out of the picture because it would invalidate the cross-Platform capabilities. Doing the while passed check by a standard TTIme type resulted in the same strange result: it seems it doesn't work at all. – nexial Apr 07 '13 at 11:46
  • I used a very similar code than the your one and it didn't work. Did you try it? Using a tickcount difference Greater than about 50 resulted (for some unknown reason) it never entered in the refresh "if code". Or the same using a TTime of 300 milliseconds as difference. – nexial Apr 07 '13 at 11:48
  • In addition for some another unknown reason, it seemed to enter on it just the first time. So I got 1 as cells counter. – nexial Apr 07 '13 at 11:49
  • 1
    @nexial, use `TStopWatch` in the `System.Diagnostics` unit. It's cross-platform. – LU RD Apr 07 '13 at 12:02
  • I'm so getting Populist badge for this one. – himself Apr 08 '13 at 06:04
  • @nexial: Do what LU RD told you to do, or replace GetTickCount with Now(), declare LastUpdateTime: TDatetime and compare it to "0.25*OneSecond" (and add DateUtils to "uses"). It may be a bit suboptimal but better than updating every cycle. – himself Apr 08 '13 at 06:14
  • @LU RD: Thank you dude, surely useful because we are targeting 100% cross platform. By the other side it sadly doesn't resolve this issue. – nexial Apr 08 '13 at 07:33
  • @Himself: As I said I already tried with both TTime and GetTickCount. Sadly they both suffer the same strange side effect it seems they happen only once. I'll make a second try just in case I made some mistake but it seemed to me everything was coded properly. – nexial Apr 08 '13 at 07:35
  • Ok retried and it sounds quite bizarre. I added a "W" TStopWatch variable and added this code before entering the first while: "W:= TStopwatch.StartNew;" As second step I've changed the code for force ProgressBar & Co. visual update as following: – nexial Apr 08 '13 at 08:02
  • ProgressBar.Value:= ProgressBar.Value + 1; if (W.ElapsedMilliseconds >= 30) then begin Caption:= 'Elapsed time: ' + FormatDateTime('nn:ss:zzz', Time - T) + ' (min:sec:msec) Cells: ' + IntToStr(Trunc(ProgressBar.Value)); Application.ProcessMessages; {$ENDREGION} W.Reset; W.Start; end; – nexial Apr 08 '13 at 08:03
  • First strange effect: using a value around 300 instead of 30 the code in the loop got never executed. – nexial Apr 08 '13 at 08:04
  • Second even more strange effect: Letting apart the really bad visual effect of the segmented update that gives the user an app freezing feeling, using this technique increased the delay instead of reducing it. As the use of TStopWatch and an "if" are an heavy overhead in such buckle. – nexial Apr 08 '13 at 08:06
  • In my work PC using this periodical update instead of at each step resulted in the CreateCells passed from a required time of 13.933 to 19.276 – nexial Apr 08 '13 at 08:11
  • So I'm sorry Himself but definitely your suggested solution doesn't work out. Note: I'm not telling that conceptually was wrong but for some reason I cannot get it seems it increases the overall delay :( Thank you anyway. – nexial Apr 08 '13 at 08:13
  • Note 2: Also using an update every 100 iteration resulted in an overall time of 19.047 (so still worst) :( – nexial Apr 08 '13 at 08:18
  • Look, I have no mind-reading device so I don't know why you can't implement this standard technique. If you have some specific problems, at least post some code so we can tell you what you did wrong. (But frankly, this "I tried your code, it didn't work, no luck" attitude discourages me from even caring) – himself Apr 09 '13 at 12:39
  • Hey Himself, don't take it in this way. I tried it exactly as you suggested for real. Anyway, in a while I'll update the original sample with these changes I told you just the registered time till now ok? ;) Be friend :D – nexial Apr 09 '13 at 13:55
  • @Himself, I changed the code in the original code with the introduction of both the suggestions by you and by Philnext. Those suggestions are commented out so you have to uncomment what you want to test and use to check the results. Cheers, Bruno. – nexial Apr 09 '13 at 14:55
  • This is not (only) user's fault. FMX is indeed damn slow and it gets slower with each new release. It has countless bugs and crashes. Even the shipped examples. Scrolling a TListBox with detailed style lags by its own without any user code at all. One of the reasons, is that FMX uses a lot (!) semi-transparent images for almost everything, since it is not native as Embarcadero says - everything is styled. This all needs to be drawn then. And images are not simply drawn, they use effects (cropping, stretching, etc.). It is a bad design from ground up (sadly, because FMX is a very cool idea). – StanE Dec 03 '17 at 14:08
5

I only have XE2 and the code is not exactlly the same but, as said by some other guys the pb seems to be on the

Application.ProcessMessages;

line. So I sugess to 'refresh' your components with realign ex :

  ProgressBar.Value:= ProgressBar.Value + 1;
  Caption:= 'Elapsed time: ' + FormatDateTime('nn:ss:zzz', Time - T) +
            ' (min:sec:msec) Cells: ' + IntToStr(Trunc(ProgressBar.Value));

  // in comment : Application.ProcessMessages;
  // New lines : realign for all the components needed to be refreshes
  AniIndicator.Realign;
  ProgressBar.Realign;

On my PC, a 210 Cells screen is generated in 0.150 s instead of 3.7 s with the original code, to be tested in your environnement...

philnext
  • 3,242
  • 5
  • 39
  • 62
  • Thank you for your feedback. We tested more or less every combination of FireMonkey call, meaning "Repaint", "InvalidateRect", "Scene.EndUpdate" and so on. So I'm almost sure we tested also Realign, but I could be wrong. So I'll test your suggestion and I'll let you know if everything goes well. – nexial Apr 06 '13 at 11:48
  • Extra note: I totally agree Application.ProcessMessages is the right way to force a visual update. The problem is in all those tests we didn't find another way to get the visual controls properly updated at screen if not Application.ProcessMessages – nexial Apr 06 '13 at 12:00
  • @nexial For visual update, the correct way is to 'refresh' the right components and only them. Not always easy in general but difficul with FM because there is no standard 'refresh' method. – philnext Apr 06 '13 at 14:06
  • sorry but I don't get your post. I mean it's exactly what I said. I would like to just call the Invalidate of only the ProgressBar... if it only would work properly in FMX – nexial Apr 06 '13 at 18:31
  • @nexial I simply wanted to summarize the 'right' way to visual update when you say that "Application.ProcessMessages is the right way to force a visual update" which is in fact, the wrong way. I also know that, in real life, the right way is not always the best way and, sometimes, with succes, we take the wrong one. By the way did you try Realign ? – philnext Apr 06 '13 at 20:48
  • honestly no, today I had to do the "chacha system" aka cleaning my flat :P – nexial Apr 06 '13 at 22:19
  • Hi Philnext, I'm sorry but also your solution didn't work. First of all (I don't remember if in XE2 is the same but in XE3 it is so) the "Realign" method is not public. So I had to "hack" both classes in order to access their protected member as the typical way "THackClass = class(TOriginalClass) end;" and cast "THackClass(Object).ProtectedMethod". Anyway also after that (in XE3) the ProgressBar and the AniIndicator do NOT get live updated meanwhile cells calculation and the overall time recahed the 5+ seconds. – nexial Apr 07 '13 at 11:57
  • Essentially I agree with you that one of the problem it is surely calling Application.ProcessMessages that seems to add quite a huge overhead. By the other side, till now I wasn't able to find any other way for getting the ProgressBar updated meanwhile the cells are calculated. – nexial Apr 08 '13 at 07:38
  • Meaning all the methods supposed to somehow refresh the control state at screen as repaint, invalidate and so on seems to fail in their task. – nexial Apr 08 '13 at 07:38
  • @Nexial In some cases the use of HandleMessages instaed of ProcessMessages is a good idea... – philnext Apr 08 '13 at 11:24
  • Don't know what to tell you. Meaning trying your suggested change generated a really strange behave: Surely the maximizing time improved consistently, going down to 4.220 seconds. At the same time this same modification increased the required time for opening the application (small size CreateCells) from 0.017 to 12.994 seconds!!! This FireMonkey platform is so strange to manage:( – nexial Apr 08 '13 at 12:32
  • I changed the code in the original code with the introduction of both the suggestions by you and by Himself. Those suggestions are commented out so you have to uncomment what you want to test and use to check the results. Cheers, Bruno. – nexial Apr 09 '13 at 14:55
  • @nexial no time to test and not sure that XE2 has the same comportement. So I started a bounty ! – philnext Apr 14 '13 at 18:34
4

Why are you testing

"Repaint", "InvalidateRect", "Scene.EndUpdate"

I can see from your code that most expensive operation is recreating controls. And why are you doing it in OnResize event (maybe put some button for recreating controls)

this loop alone can eat like 30% of execution time

  while (CellPanel.ControlsCount > 0) do
    CellPanel.Controls[0].Free;

it should be like: (avoid list memory copy after each free)

for i := CellPanel.ControlsCount - 1 downto 0 do
   CellPanel.Controls[i].Free;

and don't run ProcessMessages in loop (or at least run only in every 10th iteration or so)

use AQTime to profile your code (it will show what is tacking that long)

VitaliyG
  • 1,837
  • 1
  • 11
  • 11
  • Hi VitaliyG, apart from changing the while with a for the other suggestions you gave were already tried with no luck. I appreciate you want to help and I understand this thread is quite long, but if you want to help you may before read it in order to know the tests that were already done. Thank you anyway – nexial Apr 20 '13 at 16:20