Перейти к содержанию
  • Регистрация
  • 0
Nick Peterson

TMemo для вывода лога тормозит

Вопрос

Провожу тест: заполняю мемо изначально 100 000 строк, затем в таймере добавляю по 1 строке:

procedure TForm19.Timer1Timer(Sender: TObject);
begin
//  While Memo1.Lines.Count > 1000 do Memo1.lines.delete(0); { <- не влияет на поведение }
  inc(Counter);
  Memo1.Lines.Add(Format('==================  new test string number # %d  -- ', [ Counter ]));
  Memo1.GoToTextEnd;
end;

Примерно раз в 10 секунд это приложение подвисает секунды на 3-4:(на скрине 2х ядерная виртуалка, т.е. основной поток в эти моменты не отвечает).

image.thumb.png.b85c3c6ec1ce734af9861e481c94aa00.png

 

Загрузка ЦПУ в остальное время тоже не кажется мне адекватной, в сравнении с аналогичным VCL приложением:

image.thumb.png.23551b8c9b3f6e99f909beb4b4a17e8d.png

 

Первую проблему с фризами можно убрать таким образом:

image.png.d1bc4211ba4bb95b2b376b6b31a4d974.png

 

В этом случае отображения скроллбаров становится не совсем корректным, зато убираются фризы, сильно раздражающие пользователей.

Вопросы такие:

1) есть ли более быстрый аналог TMemo (для целей вывода лога с возможностью копировать из лога куски, не обязательно целые строки, т.к. ListBox не подходит )

2) Можно ли как-то получше оптимизировать работу с имеющимся TMemo?

P.S. 100 000 строк взято для наглядности. В реальном приложении в логе около 1-3 тыс. строк, но и загружено оно помимо вывода лога другими задачами, так что фризы все равно ощутимы.(хотя и не так видны на графике ЦПУ)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Рекомендуемые сообщения

  • 0

А зачем мемо на 100 тысяч строк? Ты их глазами будет просматривать? А что если ПО крэшнется? Логгируй  БД - событие обычно редки, не затормозит ничего, всегда можно поднять последние события. Я вот с unidac так делал - прекрасно шуршало. Да, думаю, и access не сильно тормознёт от вывода одной строки изредка.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
6 часов назад, POV сказал:

А зачем мемо на 100 тысяч строк?

специально же приписал в конце, зачем:))

Вопрос конечно же не в том, "как сделать TMemo на 100 000 строк"

Вопрос в том, почему TMemo на 100 000 строк так сильно лагает.

TMemo на 1000 стрк тоже лагает, просто не так сильно, чтобы показать это наглядно на скрине, я не знаю как наглядно аргументировать пост на форуме "TMemo на 1000 строк лагает". В приложении это чувствуется по отзывчивости UI как микрофризы. Они частично лечатся выкидыванием кода как на скрине выше.

Именно с этими микрофризами я пытаюсь бороться, при этом сохранив возможность видеть лог в виде текста (почему не в БД? потому что запись в БД ведется параллельно, она не лагает и с ней вопросов нет. Но нужно еще и дать пользователю видеть все последние события и иметь возможноть отмотать выше на N-Ное время для сравнения, и вот в этой части как раз вопросы есть).

А вот мемо на 100 000 лагает так, что это прекрасно видно на графике загрузки ЦПУ, я считаю что это не нормально, поскольку такое же видновое мемо ведет себя совершенно иначе. Значит, это возможно , парням из майкрософта удалось это сделать!:)) Интересно, как...

Изменено пользователем Nick Peterson

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

memo на 5000 строк (в VCL программе, т.е. "те парни из MS") пожирает 80% CPU при изменении/добавлении строки. по сути вся программа работает на лог.

потому что компонент внутри не очень простой, как бы вам ни казалось.

память перевыделять постоянно для этого монстра надо, и пробегать по строкам, перерассчитывать

поэтому просто не надо делать лог в мемо

если вам нужна только функциональность просмотра - написать отображальщик лога из sgtringlist - 10 минут

Изменено пользователем krapotkin

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
8 часов назад, krapotkin сказал:

memo на 5000 строк (в VCL программе, т.е. "те парни из MS") пожирает 80% CPU при изменении/добавлении строки. по сути вся программа работает на лог.

мой скрин как бы показывает обратное

два приложения, два одинаковых таймера (100мс в данном случае) на добавление строки. Одна и та же машина (2х ядерная виртуалка).

1 скрин FMX приложение, второй VCL

1 потребляет 30-50% CPU второй 6-8%

Мне нужен след. функционал: текст, являющийся логом и позволяющий промотать хоть немного вверх и скопировать любую.часть себя. Сейчас я ограничил до 300 строк, и с учетом убирания вот этого кода

image.png.35e2e0c1750d3abee96f244cec274c0e.png

фризы ушли. Юзеры конечно недовольны, что для просмотра остального надо жать дополнительные кнопки..

Позвольте пока считать вопрос открытым, может у кого есть на примере компонент или идеи с функционалом , описанным выше

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Все ваши проблемы решаются добавлением трех строчек в код:

  1. Перед началом работы, сразу задаем количество строк Memo.Lines.Capacity:=1000000; Мемо сразу зарезервирует в своем TStringList нужный объем. Это позволит сократить накладные расходы на добавление в несколько раз.
  2. Перед добавлением строк в Memo обязательно делаем Memo.BeginUpdate; Это отключит перерисовку и другие операции.
  3. После добавления строк в Memo обязательно делаем Memo.EndUpdate; Это отрисует все изменения которые мы произвели.

Вот итоговый код (в форме еще глобальный счетчик FLinesCounter : Integer;):

procedure TForm1.FormCreate(Sender: TObject);
Var I : Integer;
begin
  Memo.Lines.Capacity:=1000000;
  Timer.Interval:=10;
  FLinesCounter:=1;
  Memo.BeginUpdate;
  for I := 1 to 10000 do
  begin
    Memo.Lines.Add('Это тест ' + FLinesCounter.ToString);
    Inc(FLinesCounter);
  end;
  Memo.EndUpdate;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Timer.Enabled:=True;
end;

procedure TForm1.Log(const AMessage : String);
const ConsMaxLogSize = 50000;
begin
  Memo.BeginUpdate;
//  while Memo.Lines.Count > ConsMaxLogSize do
//    Memo.Lines.Delete(0);
  Memo.Lines.Add(AMessage);
  Memo.GoToTextEnd;
  Memo.EndUpdate;
end;

procedure TForm1.TimerTimer(Sender: TObject);
begin
  Log('Это тест ' + FLinesCounter.ToString);
  Inc(FLinesCounter);
end;

Код добавляет в Мемо 10 тысяч строк за примерно пол секунды. И добавление по таймеру 100 строк в секунду отнимает примерно 0% процессорного времени. Все будет работать без тормозов до разумного предела, при очень больший количествах строк вы столкнетесь с тормозами выделения памяти приложению, тут нужно будет использовать иные механизмы.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, Евгений Корепов сказал:

добавление по таймеру 100 строк в секунду отнимает примерно 0% процессорного времени

Благодарю! Стало чуть понятнее - если добавлять 1 строку без Memo.BeginUpdate; из-за какого-то другого бага FMX в процедуре TStyledMemo.DoViewportPositionChange  ContentSizeChanged выставлялся в TRUE, даже если фактические размеры не менялись. Из-за этого запускался цикл

  if ContentSizeChanged then
  begin
    for I := 0 to FLineObjects.Count - 1 do
    begin
      Line := FLineObjects[I];
      if Line.Layout <> nil then
        FLineObjects.UpdateLayoutParams(Line.Layout);
      Line.InvalidateSize;
    end;
    FLineObjects.RenderLayouts;
  end

приводящий к фризам. Если делать Memo.BeginUpdate, этого не происходит; увидеть фризы, о которых я говорю, можно растягивая форму с Memo.Align = Bottom.

Теперь к вопросу ЦПУ: Ваш пример в работе на 1-ядерной виртуалке

image.thumb.png.2b57eefb42f10c95f82ef75ffac8ec47.png

 

На домашнем компе с GlobalUseDirect2D := false;

image.thumb.png.8743a438c3b7b65c67d54d54cfb7ac68.png

Прикладываю архив с тестовым проектом, кому интересно поиграться :)

MemoSpeedTest.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

С GlobalUseDirect2D := false; приложение и должно грузить процессор, вы же явно указываете что рисуем только процессором. Возможно стоит подумать о реализации приложения в VCL? Или пересмотреть архитектуру приложения?

Я бы на вашем месте пошел одним из трех путей:

1. Лить логи в отдельный TStringList, и использовать Memo в качестве "окна" для просмотра содержимого TStringList. Ограничить количество строк в Мемо к примеру сотней и с помощью контролов и кода дать возможность гонять это "окно" по содержимому TStringList. Этот метод я использовал в одном приложении, которое должно было работать месяцами, с большим количеством логов. Метод вполне себя оправдал. Вот уважаемый krapotkin тоже намекал на примерно этот способ.

2. Отказался от Memo, все таки этот компонент предназначен для редактирования небольших текстов, а не скоростного вывода десятков тысяч строк логов. Посмотрите в строну ListView. Конечно для копирования части строки лога придется придумывать некий велосипед (к примеру по клику кидать строку в Edit/Memo и оттуда уже копировать).

3. Сделал бы отдельное приложение/систему для логов. К примеру основное приложение льет логи по TCP или UDP куда то в другое приложение (желательно на другом компьютере/хостинге, которое в свою очередь заточено по визуализацию исключительно логов. А лучше лить в sql базу как предложил уважаемый POV

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, Евгений Корепов сказал:

С GlobalUseDirect2D := false; приложение и должно грузить процессор, вы же явно указываете что рисуем только процессором. Возможно стоит подумать о реализации приложения в VCL? Или пересмотреть архитектуру приложения?

Позвольте не согласиться:) Виндовое мемо работает в 12 раз быстрее, что видно на моих скринах, используя так же только процессор (на виртуалке где я делаю тесты, видяхи нет).

2 часа назад, Евгений Корепов сказал:

делал бы отдельное приложение/систему для логов.

Ага, тоже об этом подумал уже:) Обычный файл + блокнот:) Оставлю это как запасной вариант, но все же тешу себя надеждой, что существует возможность сделать быстрое мемо в FMX, не прибегая к костылям!

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
  1. возьмите TVertScrollBox, положите в него TLayout (align=top)
  2. храните строки в TStringList, вычисляйте высоту TLayout при добавлении строк.
  3. на событие TLayout.OnPaint рисуйте нужный диапазон строк (Canvas.FillText) опираясь на позицию Viewport скроллбокса.

все будет летать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
3 часа назад, slav_z сказал:

все будет летать.

Благодарю! Осталось дописать выделение и копирование текста, и быстрое мемо готово. Странно только, что в Embarcadero еще не сделали этого, наверное слишком заняты новыми темными темами для IDE.

P.S. Готов заплатить за такой компонент 100$

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
12 минут назад, slav_z сказал:

100$ ? да лаааадно... ща сделаю...

Да только перечитайте эту тему, мне надо чтобы в компоненте можно было выделять и копировать текст. Т.е. по сути нужно Memo)) только быстрое

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

выделение только построчное (одна строка или несколько). Выделение по символам - это сложнее. тоже можно, но в 100$ не влезет. посмотрите во вложении EXE. если устроит, то выложу исходники (лично или прям сюда) и только потом 100$ если все устроит. если не устроит - тогда все отдам бесплатно (сюда).

 

Использование:

 

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, Frame.Log;

type
  TForm5 = class(TForm)
    LogFrame1: TLogFrame;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form5: TForm5;

implementation

{$R *.fmx}

procedure TForm5.FormCreate(Sender: TObject);
begin
  LogFrame1.ReleasedItems:=False; // не удалять "старые" строки
end;

procedure TForm5.Button2Click(Sender: TObject);
begin
  LogFrame1.Add('One item');
  LogFrame1.ScrollTo(MaxInt);
end;

procedure TForm5.Button3Click(Sender: TObject);
begin
  LogFrame1.Clear;
end;

procedure TForm5.Button4Click(Sender: TObject);
begin
  LogFrame1.CopyToClipboard;
end;

procedure TForm5.Button1Click(Sender: TObject);
var I: Integer;
begin
  LogFrame1.BeginUpdate;
  try
    for I:=0 to 100000 do
    LogFrame1.Add('Item '+I.ToString);
  finally
    LogFrame1.EndUpdate;
  end;
  LogFrame1.ScrollTo(MaxInt);
end;

end.

 

LogViewExe.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
12 минут назад, slav_z сказал:

но в 100$ не влезет.

Ну мое дело предложить:) Посмотрел, в первом приближении то что надо !,Ок, выделение по буквам не нужно, но нужно:

1) выделение при удержании мышки и движении вверх (скроллится должно выше и продолжать выделение) , и так же вниз

2) контекстное меню Copy и CTRL-C

Это влезет в 100?)) компонент нужен как класс для рантайма

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

да не...  я лучше выложу так..  пилите сами... там не долго все это добавить...

класс от TFrame. там не TLayout а TText чтобы можно было настраивать шрифт в designtime.

короче ничего сложного.

LogViewSource.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Спасибо @slav_z за идею, чуть допилил выделение и контекстное меню, еще бы прикрутить CTRL-C грамотно:) Впрочем уже и так отлично

FastMemo.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

сделай так чтобы элемент мог принимать фокус ввода CanFocus:=True и обработку нажатия клавиш Ctrl+C.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
В 04.01.2019 в 21:22, Nick Peterson сказал:

Спасибо @slav_z за идею, чуть допилил выделение и контекстное меню, еще бы прикрутить CTRL-C грамотно:) Впрочем уже и так отлично

FastMemo.zip

Огромное спасибо за реализацию.

Как-раз выискивал реализацию для цветного лога и решил было делать на основе TMemo, но думаю данный вариант подойдёт лучше всего.

Репозиторий на GitHub не создавался под эту реализацию?

Думаю можно было-бы туда залить свои правки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Подготовил за пол часа реализацию с цветом и стилем строки.

Правда поменял форматирование кода под то которое мне удобней и понятней.

image.png.e2c44686daff3d9c16f223d040b29301.png

FastMemo.zip

 

Изменено пользователем Владимир Б.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

А кто-то знает как это всё дело оформить в виде компонента?

У меня с FMX опыт небольшой, а при попытке динамического создания элементов получается чёрти-что.

  TLogMemo = class(TStyledControl)
  private
    const
      MAX_COUNT = 20000;
      RELEASE_COUNT = 15000;
  private
    FBackRectangle: TRectangle;
    FScrollBox: TFramedVertScrollBox;
    FText: TText;
    FActionsMenu: TPopupMenu;
    FMemoActionCopy: TMenuItem;
    FMemoActionSelectAll: TMenuItem;
    FMemoActionSplitter1: TMenuItem;
    procedure OnTextPainting(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure OnTextMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure OnTextMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure OnTextMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
    procedure OnTextMouseLeave(Sender: TObject);
    procedure FMemoActionCopyClick(Sender: TObject);
    procedure FMemoActionSelectAllClick(Sender: TObject);
  protected
    FItems: array of PLogItem;
    FItemIndex: Integer;
    FToItemIndex: Integer;
    FItemIndexColor: TAlphaColor;
    FItemHeight: Single;
    FOnChange: TNotifyEvent;
    FReleasedItems: Boolean;
    FAniCalculationsStarting: Boolean;
    FSelectTimer: TTimer;
    FMovingDirection: Integer;

    function GetCount: Integer;
    procedure SetItemHeight(Value: Single);
    procedure SetItemIndex(Value: Integer);
    procedure SetToItemIndex(Value: Integer);
    procedure DoChange;
    function GetItemText(AIndex: Integer): string;
    function GetItemIndexText: string;
    function GetSelectedText: string;
    procedure DoTextChange;
    procedure RepaintItems(OldItemIndex, OldToItemIndex: Integer);
    function GetIsTracking: Boolean;
    procedure DoFSelectTimer(Sender: TObject);
    procedure ExtendSelection(Y: Single);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    {}
    procedure Clear;
    procedure Add(const AText: string; const AColor: TAlphaColor = claNull; const AStyle: TFontStyles = []);
    function GetText: string;
    procedure ScrollTo(ToItemIndex: Integer);
    procedure CopyToClipboard;
    {}
    property ItemText[Index: Integer]: string read GetItemText;
    property ItemIndexText: string read GetItemIndexText;
    property SelectedText: string read GetSelectedText;
    property ItemIndex: Integer read FItemIndex write SetItemIndex;
    property ToItemIndex: Integer read FToItemIndex write SetToItemIndex;
    property ItemHeight: Single read FItemHeight write SetItemHeight;
    property ItemIndexColor: TAlphaColor read FItemIndexColor write FItemIndexColor;
    {}
    property Count: Integer read GetCount;
    property ReleasedItems: Boolean read FReleasedItems write FReleasedItems;
    property IsTracking: Boolean read GetIsTracking;
    {}
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  published
    property Align;
    property ClipChildren default False;
    property ClipParent default False;
    property Cursor default crDefault;
    property DragMode default TDragMode.dmManual;
    //property DragWithParent: Boolean read FDragWithParent write SetDragWithParent default False;
    property EnableDragHighlight default True;
    property Enabled;
    property Locked default False;
    property Height;
    property HelpContext;
    property HelpKeyword;
    property HelpType;
    property HitTest default True;
    //property HorizontalOffset: Single read FHorizontalOffset write FHorizontalOffset;
    property Padding;
    property Opacity;
    property Margins;
    //property Placement: TPlacement read FPlacement write SetPlacement default TPlacement.Bottom;
    //property PlacementRectangle: TBounds read FPlacementRectangle write SetPlacementRectangle;
    //property PlacementTarget: TControl read FPlacementTarget write SetPlacementTarget;
    property PopupMenu;
    property Position;
    property RotationAngle;
    property RotationCenter;
    property Scale;
    property Size;
    //property StyleBook: TStyleBook read FStyleBook write SetStyleBook;
    property StyleLookup;
    property TabOrder;
    //property VerticalOffset: Single read FVerticalOffset write FVerticalOffset;
    property Visible stored VisibleStored;
    property Width;
  end;

 

constructor TLogMemo.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FBackRectangle := TRectangle.Create(Self);
  FScrollBox := TFramedVertScrollBox.Create(Self);
  FText := TText.Create(Self);

  with FBackRectangle do
  begin
    Name := 'Background';
    Parent := Self;
    Align := TAlignLayout.Client;
    Fill.Kind := TBrushKind.None;
    Stroke.Kind := TBrushKind.None;
    Stroke.Thickness := 2;
    Size.Width := 320;
    Size.Height := 240;
    Size.PlatformDefault := False;
  end;

  with FScrollBox do
  begin
    Name := 'ScrollBox';
    Parent := FBackRectangle;
    Align := TAlignLayout.Contents;
    ClipChildren := True;
    //PopupMenu := FActionsMenu;
    Size.PlatformDefault := False;
    TabOrder := 0;
    AniCalculations.Animation := False;
  end;

  with FText do
  begin
    Name := 'Text';
    Parent := FScrollBox;
    Align := TAlignLayout.Top;
    //PopupMenu = FActionsMenu
    Size.Height := 0;
    Size.PlatformDefault := False;
    AutoCapture := True;
    TextSettings.Font.Size := 14;
    TextSettings.WordWrap := False;
    TextSettings.HorzAlign := TTextAlign.Leading;
    TextSettings.VertAlign := TTextAlign.Leading;
    OnMouseDown := OnTextMouseDown;
    OnMouseMove := OnTextMouseMove;
    OnMouseUp := OnTextMouseUp;
    OnMouseLeave := OnTextMouseLeave;
    OnPainting := OnTextPainting;
  end;

  FReleasedItems := True;
  FItemIndex := -1;
  FToItemIndex := -1;
  FItemHeight := 20;
  FItemIndexColor := claSilver;

  TThread.CreateAnonymousThread( procedure
    begin
      Sleep(10);
      TThread.Queue(nil, procedure begin FItemHeight := Round(Canvas.TextHeight('A') + 4); end);
    end).Start;

  FSelectTimer := TTimer.Create(nil);
  FSelectTimer.Interval := 100;
  FSelectTimer.Enabled := false;
  FSelectTimer.OnTimer := DoFSelectTimer;
end;

TLogMemo.zip

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу

×
×
  • Создать...