• 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 тыс. строк, но и загружено оно помимо вывода лога другими задачами, так что фризы все равно ощутимы.(хотя и не так видны на графике ЦПУ)

 

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


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

17 ответов на этот вопрос

  • 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 пользователей онлайн

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