Перейти к содержанию
Fire Monkey от А до Я
  • 0

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


Nick Peterson

Вопрос

Провожу тест: заполняю мемо изначально 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
В 04.01.2019 в 21:22, Nick Peterson сказал:

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

FastMemo.zip

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

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

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

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

Ссылка на комментарий
  • 0

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

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

image.png.e2c44686daff3d9c16f223d040b29301.png

FastMemo.zip

 

Изменено пользователем Владимир Б.
Ссылка на комментарий
  • 0

Первая тестовая версия в виде компонента: https://github.com/ange007/TLogViewer

Изменения по сравнению с изначальной версией: 

  • Параметры строки: сообщение, ссылка (для перехода при двойном клике), стиль, цвет
  • Добавлено выделение "от и до" при помощи Shift мышкой
  • Добавлена смена строк с клавиатуры (вверх, вниз)
  • Добавление строк списком (правда пока без стилизации и ссылок)
  • Ну и по мелочи в связи с переводом в компонент

Чего нет но надо-бы:

  • Выделение через Ctrl (по одному)
  • Выделение "от и до" при помощи Shift с клавиатуры
  • Расширение функциональности меню
  • Хранение в каком-то виде "удалённых" в связи с "заполнением буфера" пунктов, с дальнейшим повторным отображением при необходимости
  • Многострочный текст
Изменено пользователем Владимир Б.
Ссылка на комментарий

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

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

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

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

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

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

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

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

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

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