Максимальное разумное количество работающих потоков .

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 15.02.2025 01:02:22

1. Вначале при тестировании очередного своего «отладочного стенда» я в принципе не ограничивал количество одновременно работающих потоков. (Загрузок было фиксированное количество, так что идея была из серии «Что может пойти не так при загрузке 20–30 файлов?»)

2. Потом я начал гонять «стресс-тест» с загрузкой 500–1000 файлов, и тут оказалось, что «терпению машины» тоже есть предел, так что поставил ограничение (при работе с файлами 40–45 потоков со скрипом проскакивают, хотя и почти в ноль выедают выделяемые программе «ресурсы времени CPU» (прочие программы почти не тормозят); при использовании 50-ти потоков программа начинает пропускать файлы, (видимо упираясь в максимально доступное количество открытых файлов); выше 100-а потоков приводит к полному зависанию очереди «синхронизированных секций»).

3. Спокойная работа доступна при 10–30 потоках (хотя «максимальное ускорение» при 45-ти потоках весьма заметно — где-то на треть: 653 совершенно разных файла отработало за минимальные 80-90 секунд, а на 20-ти потоках туже работу выполнило в лучшем случае за 147 секунд).

4. Кстати, что забавно, число реальных ядер и аппаратных потоков почти ни на что не влияют (главное, чтобы тут их было больше одного).

В общем, понятно, что вся многопоточная часть загрузки нуждается в переработке (например, буквально только что нашел и исправил утечку памяти при обработке исключения, и это явно еще не всё, что там нужно исправить...), но сама идея понятна: нужно проверять не только (и не столько) количество ядер, а в первую очередь число работающих в системе потоков, + если внутри потока используются файловые операции, нужно точно знать доступное программе количество одновременно открытых файлов.

Но, возможно, это ещё не всё. Так что народ,какие у вас есть идеи на этот счёт? :idea:
Зы
Скрин "тестового стенда" ( время загрузки заметно плавает и каждый раз немного другое )
Изображение
Последний раз редактировалось Alex2013 15.02.2025 11:12:35, всего редактировалось 1 раз.
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 15.02.2025 09:04:00

А вы все это грузите из одного источника или из разных. На скорость может так же влиять пропускная способность сети и ограничения сервера.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 15.02.2025 11:34:49

xchgeaxeax писал(а):А вы все это грузите из одного источника или из разных.

"Стресс тест " грузит файлы (общий объем 500-1000 файлов суммарный объем данных 0.5 -1 гб ) из одного источника (с локального диска),но основной режим задуман для сетевой загрузки с одного сервера (через парсинг веб-сайта) .
xchgeaxeax писал(а):На скорость может так же влиять пропускная способность сети и ограничения сервера.

Ну это уже от меня не зависит. А для "защиты от ДДОС защиты" файлы после первого чтения кэшируются на диск.
Стандартно задумано постраничное чтение порциями по 20-40 файлов (протокол http/https)
Зы
Но вообще вначале нужно однопоточное чтение оптимизировать (сравнивал с гарантировано однопоточной загрузкой галереи в IrfanView, многопоточное чтение еще туда сюда, примерно сравнимо, но однопоточное пролетает "со страшным свистом" )
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение Seenkao » 15.02.2025 15:03:17

Alex2013 писал(а):но однопоточное пролетает "со страшным свистом"

если у тебя однопоточное приложение пролетает "со страшным свистом", то у тебя проблема в реализации.

Однопоточное должно последовательно друг за другом грузить ресурсы и только после всей загрузки обрабатывать их (в играх делают поблажку, чтоб вывести какое-нибудь видео, чтоб пользователя занять).
При, даже двух, потоках, первый загруженный ресурс уже начинает обрабатываться вторым потоком, а первый продолжает грузить ресурсы. И надо понимать, что обработка ресурсов, зачастую намного быстрее, чем загрузка, потому большую часть времени, второй поток просто простаивает.

Добавлено спустя 2 минуты 17 секунд:
Не, там есть вариант для всех приложений, когда на диске много хлама и разбросано всё, то есть вероятность, что идёт поиск на диске, что сжирает значительную часть времени. Но сейчас используют в основном SDD что намного уменьшает данную проблему.
Seenkao
энтузиаст
 
Сообщения: 547
Зарегистрирован: 01.04.2020 03:37:12

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 16.02.2025 00:21:15

Seenkao писал(а):если у тебя однопоточное приложение пролетает "со страшным свистом", то у тебя проблема в реализации.

Ну я немного преувеличиваю (надо будет померить точнее)+я специально сделал копию того кода что я использую в многопоточном режиме (для удобства отладки и тестирования )+ миниатюры в галереи IrfanView меньше раза в два и вообще это довольно большой, развитый и надежный инструмент так что было бы странно если бы он внезапно продул тестовой программе переживший чуть большей недели "глубокой отладки"
Зы
Тест загрузки списка 2
Однопоточный
Время 71,92 c (78 довольно больших (до 3 мб) файлов jpg)

Тест загрузки списка 2
Многопоточный
Время 27,33 c 78ф (10 потоков)

Тест загрузки списка 2
Многопоточный
Время 27,01 c 78ф (20 потоков)

Тест загрузки списка 2 (30 потоков)
Многопоточный
Время 23,29 c 78ф

IrfanView (Примерно)
Однопоточный
Время 15,10 c



LSI_01 Однопоточный vs IrfanView (Примерно пятикратная разница IrfanView чистая победа! )

LSI_01 Многопоточный vs IrfanView (Победа "по очкам" полуторная разница )
Зы
Тест загрузки списка 2
Многопоточный
Время 21,69 c 78ф (40 потоков но это уже "на гране" )
Зы Зы
Кстати это 32 бита по идее при выполнения 64-х битного кода "Многопоточный режим" будет быстрее
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 16.02.2025 08:46:12

Ну давайте тогда посчитаем: до 3Мб буду считать за 3Мб. Итого: 78*3=234 Мб загрузить с диска. 234*8=1872Мбит. Для SSD M2 это не более 1 сек, а для SATA SSD или HDD 2.5" или 3,5" это уже упирается в скорость SATA3=3000 Мбит или SATA2=1500 Мбит т.е. 0,624 сек или 1,248 сек. Без учета поиска по ФС, что на HDD занимает больше времени чем у SSD. Для локальной сети до 1000 Гбит это еще чуть увеличит время передачи по сети (до 1,872 сек). Для интернет при 100 Мбит вот тут уже скорость передачи сильно увеличивает время загрузки (в 10 раз), но это все еще не более 18 сек. Про скорости работы памяти даже говорить не будем (они просто ничего не ограничивают)...

Теоретически только чтение файлов с диска в память должно занимать не так много времени. Т.е. в вашем тесте большую часть съедает не загрузка, а все же обработка (вычислительная способность CPU или множество ошибок работы с памятью из-за многопоточности). Попробуйте в многопоточном режиме грузить ваши картинки в MemoryStream как есть, а вот в однопоточном режиме из этиз стимов их обрабатывать. Это не будет давать постоянные промахи кеша и увеличит скорость работы освободив шину данных (ваше 20 потоков порождают перегрузку шины из-за того, что кеш данных много меньше ваших 234 Мб и постоянно загружает повторно одни и теже данные для продолжения обработки их в потоке при переключениях). Думаю получится куда более лучший результат из-за правильного использования CPU для обработки. Или ограничьтесь для обработки 3-4 потоками, а не 10-40.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 17.02.2025 00:18:24

xchgeaxeax писал(а): Попробуйте в многопоточном режиме грузить ваши картинки в MemoryStream как есть, а вот в однопоточном режиме из этих стимов их обрабатывать.

Спасибо за совет! Но этот вариант я опробовал, по сути, первым. К сожалению, несмотря на небольшой (и к тому же нестабильный) выигрыш в скорости, имеется заметный проигрыш в стабильности. (Суть проблемы в том, что после запуска группы потоков никогда нельзя гарантировать их своевременное завершение (флаг, счетчик и даже отправка postMessage совершенно не гарантируют, что после их «детектирования» всё, что происходило в рамках потока, реально остановилось (особые проблемы, как ни покажется странным, связаны с «синхронными секциями»)). Поэтому (ИМХО) вся работа внутри потока должна быть максимально «локальной», а исходящие данные — «минимизированы» и идти на максимально «статическую структуру» так, чтобы любая возможная асинхронная «хроноаномалия» имела минимальные шансы что-либо испортить.

В данном случае я заранее создаю пустую "мозаичную таблицу " (большой TImage нужного размера помещенный в ScrollBox), а работающие "малыми группам" потоки совершенно независимо создают "карточки-вклейки " по сути в совершенно произвольном(по времени завершения) порядке ни как не взаимодействуя с тем что может "несвоевременно исчезнуть" или напротив не появится вовремя . (это решение тоже не идеально "но поймать баг" из за рассинхронизации между загрузкой и обработкой заметно труднее + это явная экономия памяти "несжатый битамап "(который может быть довольно впечатляющего размера ) как и загруженный в память JPG, существует только в момент чтения и масштабирования в рамках конкретного потока (а их количество я жестко ограничил) )
Зы
+ Нужно учитывать что в отличии от "стресс теста" в "реальной галереи" я могу подчитывать только то что видно в окне читая за один раз сравнительно не большую процию данных.
Зы Зы
Но думаю что вариант с массивом MemoryStream все-же можно пробовать (Хотя держать в памяти примерно до одного гигабайта данных без особой нужды как-то не хочется ... Так сказать "олдскулы сводит".. :wink: )
Последний раз редактировалось Alex2013 19.02.2025 14:45:16, всего редактировалось 1 раз.
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 19.02.2025 13:57:04

xchgeaxeax писал(а):Теоретически только чтение файлов с диска в память должно занимать не так много времени

Уже не только "теоретически" (пока только однопроточный режим)
Код: Выделить всё
Тест загрузки списка 3 (Первый запуск )
Время 1,12 c (Время загрузки в память)
Время 7,84 c  (Общее время работы)
Тест загрузки списка 3 (Второй запуск (быстрее из за системного кэширования ))
Время 0,12 c
Время 6,81 c


Загрузка в память (Решил что MemoryStream можно и в качестве контейнера для временного хранения данных использовать)
Код: Выделить всё
var
MS1,MS2:TMemoryStream ;
PPos:^int64;
TPos:int64;
L:Tlist;
begin
//..
Ms1:=TMemoryStream.Create;
L:=Tlist.Create;
For I:=0 to CC-1 do
begin
L.Add(Nil);
//..
if  FileExists(CacheFilename) then begin
  TPos:=MS1.Position;
  Ms2:=TMemoryStream.Create ;
   try
     Ms2.LoadFromFile(CacheFilename);
   except
     Ms2.Free;
     Continue;
   end;
Ms2.Seek(0,soBeginning);
  try
    Ms1.CopyFrom(Ms2,Ms2.Size);
   except
    Ms2.Free;
    Continue;
   end;
   New(PPos);PPos^:=TPos; L[I]:=PPos;
   Ms2.Free;
end
end;
//...

Но тут возник вопрос: можно ли загружать данные из разных файлов сразу в "основной" MemoryStream без создания промежуточного потока ?
( Ms1.CopyFrom(Ms2,Ms2.Size); работает быстро но "нафига козе боян?" :idea: )
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 19.02.2025 15:17:57

Может лучше так?
Код: Выделить всё
type
  PLoadableFunc = function (ASender: TObject; out AFileName: String): Boolean;
  PLoadableFileRecord = ^TLoadableFileRecord;
  TLoadableFileRecord = packed record
    fName: String;
    fData: TMemoryStream;
    Fail: LongInt; // -1 файл прочитан, 0 - ошибка чтения файла
  end;
  TLoadableFileThread = class(TThread)
  private
    FLoad: TList;
    FName: String;
    FNext: PLoadableFunc;
    FWork: Boolean;
    function GetCount: LongInt;
    function GetData(Index: LongInt): PLoadableFileRecord;
    procedure GetNext;
  protected
    procedure Execute; override;
  public
    constructor Create(fnGetNextFileName: PLoadableFunc);
    destructor Destroy; override;
    property Count: LongInt read GetCount;
    property Data[Index: LongInt]: PLoadableFileRecord read GetData;
  end;

constructor TLoadableFileThread.Create(fnGetNextFileName: PLoadableFunc);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FList := nil;
  FName := EmptyStr;
  FNext := fnGetNextFileName;
  FWork := False;
end;

function TLoadableFileThread.GetCount: LongInt;
begin
  Result := FList.Count;
end;

function TLoadableFileThread.GetData(Index: LongInt): PLoadableFileRecord;
begin
  Result := FList[Index]; // Можете конечно проверить дополнительно Index но это и так ни к чему. Так и так получите Exception, если такого Index нет.
end;

procedure TLoadableFileThread.GetNext;
begin
  FWork := FNext(Self, FName); // Синхронно вызываем, чтобы получить новое имя, а если имен нет, тогда и этого же вызова и забираем дынные...
end;

procedure TLoadableFileThread.Execute;
var
  AFile: String;
  PFile: PLoadableFileRecord;
  i: LongInt;
begin
  FList := TList.Create;
  while not Terminated do begin
    Synchronize(@GetNext);
    if FWork then begin
      New(PFile);
      with PFile^ do begin
        fName := AFile;
        fData := TMemoryStream.Create;
        Fail := 10; // 10 попыток прочитать, если нужно.
        while Fail > 0 do begin try
          fData.LoadFromFile(AFile);
          Fail := -1;
        except
          dec(Fail);
        end;
      end;
      FList.Add(PFile);
    end else Sleep(1000); // Не важно сколько. Просто ветка должна подождать до её завершения т.к. файлов больше не будет
  end;
  for i := 0 to FList.Count - 1 do with PLoadableFileRecord(FList[i])^ do begin
    FreeAndNil(fData);
    Dispose(PLoadableFileRecord(FList[i]));
  end;
  FreeAndNil(FList);
end;

var
  aLoadders: array [0 .. 3] of TLoadableFileRecord;
  aFileNames: TStringList;

function EnumerateList(ASender: TObject; out AFileName: String): Boolean;
var
  S: String;
begin
  S := aFileNames.Pop();
  Result := (aFileNames.Count >= 0) and (Trim(S) <> EmptyStr);
  AFileName := S;
  if not Result then with ASender as TLoadableFileThread do begin
    // Тут можно безопасно брать результаты работы ветви
    // Нет необходимости копировать в дополнительный TMemoryStream, а можно сразу запускать преобразование из JPEG в BMP
    for i := 0 to Count - 1 do with Data[i]^ do begin
     
    end;
    Terminate; // Ну а дальше ветка умрет через 1 сек и почистит за собой память
  end;
end;

begin
  aFileNames := TStringList.Create;
  aFileNames.LoadFromFile('FilesToLoad.txt');
  for i := Low(aLoadders) to High(aLoadders) do aLoadders[i] := TLoadableFileThread.Create(@EnumerateList);
  for i := 0 to 999999 do Sleep(100); // Что-то делаем, пока загружается
end.


PS печатал просто в браузере. Если есть очепятки, тогда поправьте. Но думаю принцип вы поймете.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 19.02.2025 18:05:27

Спасибо, попробую вникнуть....
Начну с конца ...
1. "for i := 0 to 999999 do Sleep(100); // Что-то делаем, пока загружается" Такой "финт ушами" череват неприятностями
(ИМХО) Уж лучше как-то так
"
Код: Выделить всё
LoadListButton.Enabled:=False; //Блокирую кнопку 
    ...
    while TL.Count > 0 do Application.ProcessMessages; // где TL.Count текущие количество активных потоков 
   LoadListButton.Enabled:=True;// Разблокирую кнопку   

Хотя это тоже не самый лучший вариант но работает "без остановки на обед" ...

2. "for i := Low(aLoadders) to High(aLoadders) do aLoadders[i] := TLoadableFileThread.Create(@EnumerateList);"
"Кучный" запуск большого числа потоков опасная шутка (в основном из за наличия секции Synchronize )

3 "procedure TLoadableFileThread.Execute;" нужно подумать проверить ( сход полностью не вник)
Код: Выделить всё
with PLoadableFileRecord(FList[i])^ do begin
    FreeAndNil(fData);
    Dispose(PLoadableFileRecord(FList[i]));
  end;

Остроумно, но что тут не то ... а понял TLoadableFileThread.Create(fnGetNextFileName: PLoadableFunc);
.
4 "FLoad: TList;" это вариант уже "окучен" по идее поддержка MemoryStream в качестве основного контейнера проще потому что не требует "по элементной очистки" ( Разумеется в TLoadableFileThread есть механизм "само-очистки", но вся суть теста "третьего варианта загрузки" была в том что-бы проверить отношение скорости загрузки в память к общему времени обработки )

5 "EnumerateList" Вау, а это интересно !
Зы
Общее "первое впечатление": интересно но немного тяжеловесно и слегка "непрозрачно" ( Короче нужно тестировать )
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 19.02.2025 20:57:35

1) Это совершенно не важная строчка, которая полагает, что основной поток чем-то занят.
2) У меня такое еще ни разу не падало/блочилось в Synchronize. Какой-то прикол в виндовом коде. Надо проверить на глюки.
3) Это просто высвобождение всего загруженного перед выходом из потоковой функции. Оно более не нужно. А если было нужно, тогда последний вызов функции чтения очередного файла должен был все забрать через Synchronize.
4) Как хотите, но вы придумываете велосипед на костылях.
5) Это как раз функция, которая выдает потокам файлы из общего списка. По одному как они освобождаются. Как только список закончился, тогда эта же функция собирает из потоков всю загруженную инфу. После чего этот вызов завершается и поток умирает. Вот как раз тут, если угодно, и нужно использовать TMemoryStream для сбора информации уже загруженной в потоке. Но лучше просто грузить сразу в TBitmap и преобразовывать. Как вариант создавайте два класса. Один класс ветви для обработки и второй класс ветви для загрузки. Соответственно ветви для загрузки будут создавать в большем количестве чем ветви для обработки. Скажем по 5 на каждую из 4 ветвей для обработки. Итого 4 + 5 * 4 = 24 ветви.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 20.02.2025 13:15:42

До тестирования вашего интересного(без кавычек) решения еще не добрался.( вчера было куча других проблем )
Пока что сделал заплатку к MemoryStream принципу "Ближе мысли". :wink:
Код: Выделить всё
procedure CopyFromFile(ST:TStream;const FileName: string);
   Var S : TFileStream;
        SZ:Int64;
   begin

   S:=TFileStream.Create (FileName,fmOpenRead or fmShareDenyWrite);
   Try
   SZ:= S.Size;
   If Sz>0 then ST.CopyFrom(S,Sz);
   finally
     S.free;
   end;
end;

Может показаться что это "масло масляное" или "тоже самое в профиль" но на самом деле
эта заплатка реально прибивает дополнительный как выяснилось даже второй, а третий поток.
( потому что LoadFromFile тоже создает TFileStream)
Зы
Стандартный LoadFromFile чудесен и пригож но делает мелкую пакость в виде "Stream.Position:=0;" что портит мне "всю малину".
Зы Зы
Скорости загрузки если и прибавилось то почти незаметно
Лучший результат (при тех же условиях )
Тест загрузки списка 3
Время 0,06 c
Время 6,38 c

Но погрешность измерения очень большая так что нужно делать переключатель смотреть режим "Стресс теста "
но и так понято что использование CopyFromFile "уменьшает лишние сущности " что всегда хорошо.
Последний раз редактировалось Alex2013 20.02.2025 14:28:25, всего редактировалось 2 раз(а).
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 20.02.2025 14:04:16

Попробовал сегодня запустить под Linux 100 потоков для загрузки с SSD.M2 картинок где-то на 645 Мб в сумме и 8Гб в виде БМП. Все сработало и заняло ок 20 сек. При уменьшении до 50 потоков ничего особо не поменялось. А вот при использовании 12 потоков время снизилось до 12 сек (у меня 12 ядерный проц).

На Windows не проверял так как она у меня только на VirtualBox и такой стресс тест на ней не погоняешь. Могу для нее выделить 8-10 ядер, но все равно это будет виртуалка.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

Re: Максимальное разумное количество работающих потоков .

Сообщение Alex2013 » 20.02.2025 14:43:35

xchgeaxeax писал(а):Попробовал сегодня запустить под Linux 100 потоков для загрузки с SSD.M2 картинок где-то на 645 Мб в сумме и 8Гб в виде БМП. Все сработало и заняло ок 20 сек. При уменьшении до 50 потоков ничего особо не поменялось. А вот при использовании 12 потоков время снизилось до 12 сек (у меня 12 ядерный проц).

На Windows не проверял так как она у меня только на VirtualBox и такой стресс тест на ней не погоняешь. Могу для нее выделить 8-10 ядер, но все равно это будет виртуалка.

1 Линукс вообще не много по другому работает с потоками на системном уровне . ( Собрать мою "тестовую платформу " под линукс проблемы не составит но особого смысла нет)
2 Я в целом придерживаюсь "теории строго ноутбука" так что разработку веду на "минимально комфортом"(пользоваться еще относительно удобно но мощностью мягко говоря "не блещет ) железе 4 ядра + HDD + 8 гб памяти . (Потом можно будет погонять на более "свежем железе" но и оно "с неба звезд не хватает" (4 ядра 8 "аппаратных" потоков SSD.M2 16 гб ОЗУ) )
3 Современные "Виртуалки" поддерживают "аппаратную виртуализацию" так что для любых "не графических" тестов вполне (ИМХО) годятся .
Последний раз редактировалось Alex2013 20.02.2025 14:57:47, всего редактировалось 1 раз.
Alex2013
долгожитель
 
Сообщения: 3100
Зарегистрирован: 03.04.2013 11:59:44

Re: Максимальное разумное количество работающих потоков .

Сообщение xchgeaxeax » 20.02.2025 14:56:16

Alex2013 писал(а):3 Современные "Виртуалки" поддерживают "аппаратную виртуализацию" так что для любых "не графических" тестов вполне (ИМХО) годятся .

А причем тут виртуализация, когда речь про скорости дисков. А на виртуалке там будет так и так прокладка в виде этой самой виртуалки.
xchgeaxeax
постоялец
 
Сообщения: 150
Зарегистрирован: 11.05.2023 03:51:40

След.

Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 9

Рейтинг@Mail.ru