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

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

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

Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

xchgeaxeax писал(а):А причем тут виртуализация, когда речь про скорости дисков. А на виртуалке там будет так и так прокладка в виде этой самой виртуалки.
В том то и фокус "аппаратной виртуализации" что она может рулить железом напрямую (То есть "Гостевая ОС" по идее может получить аппаратный доступ к физическому разделу диска или целому физическому диску ( причем иногда даже в том случае если основная "хост-ОС" совершенно "не подозревает о его существании") возни там правда может быть довольно много но иногда это оправдано)
xchgeaxeax
постоялец
Сообщения: 198
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

Ну вот это надо диск подключать напрямую, а не использовать файл. Это как раз и замедляет. Какой смысл в таком тестировании.
sts
энтузиаст
Сообщения: 519
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

раньше, как тока появились многоядерные процы, максимально эффективное к-во потоков было = к-во ядер минус одно для операционной системы, последние лет 10, минус два ядра, один для IO, другой для менеджера потоков.
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Брр ... JPEGImage.LoadFromStream(ST); (JPEGImage:TFPMemoryImage) достал ! Нормально читать поток (MemoryStream) с произвольной позиции не хочет совершенно (упроно читает только первую картинку в потоке) причем закидон где-то в TFPReaderJPEG .(Пробовал сделать "заплатку" но тут этот фокус не прошел)

Так что пока плюнул и написал простой конвертер Jpg2Bmp...
(он работает но с потерей универсальности TFPMemoryImage)

Код: Выделить всё

Function Jpg2Bmp( jpg:TStream ):TBitmap;
var b:TBitmap;
   j:TJPEGImage;
begin
 result := nil;
 try
   j :=TJPEGImage.Create;
   try
     j.LoadFromStream(jpg);
      b := TBitmap.Create;
     try
       b.Assign(j);
    except
     FreeAndNil(B);
     end;
   finally
     j.Free;
   end;
   Result:=B;
 except
   Result := Nil;
 end;
end;
Зы
Вообщем пока я все еще пытаюсь протащить контейнер из "общего " MemoryStream, но уже начинаю сомневаться "так ли это полезно как красиво" . :roll:

Добавлено спустя 18 минут 45 секунд:
sts писал(а):раньше, как тока появились многоядерные процы, максимально эффективное к-во потоков было = к-во ядер минус одно для операционной системы, последние лет 10, минус два ядра, один для IO, другой для менеджера потоков.
Раньше я тоже думал примерно так-же и постоянно пытался подстроится под количество "физических" потоков и ядер .
Но потом выяснилось что использование заметно большего количества "виртуальных потоков" ускоряет работу "распараллеленных" алгоритмов более чем успешно . (Что очень наглядно видно и на примере моей "тестовой платформы" )
Последний раз редактировалось Alex2013 22.02.2025 14:51:59, всего редактировалось 2 раза.
xchgeaxeax
постоялец
Сообщения: 198
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

Может проще сделать дополнительный стрим TPartialStream = class(TStream) constructor Create(Source: TStream; Offset: Int64; Size: Int64); соответственно она нужна просто для того, чтобы скормить ей TMemoryStream, из которого она будет отдавать только указанный в конструкторе кусочек от Offset до Offset + Size

Не копировать в себя, а сохранять исходный TMemoryStream и при загрузке через LoadFromStream считывать из него заданный диапазон
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

xchgeaxeax писал(а):Может проще сделать дополнительный стрим TPartialStream = class(TStream) constructor Create(Source: TStream; Offset: Int64; Size: Int64); соответственно она нужна просто для того, чтобы скормить ей TMemoryStream, из которого она будет отдавать только указанный в конструкторе кусочек от Offset до Offset + Size

Не копировать в себя, а сохранять исходный TMemoryStream и при загрузке через LoadFromStream считывать из него заданный диапазон
Это разумеется тоже вариант (причем он точно лучше чем просто копировать текущую картинку в дополнительный поток или неким "хитрым образом" подменять адрес блока данных (первый вариант работает хотя и подтормаживает , а на второй видимо моей "хитрости" не хватило )) ... но это все равно "костыль" ! :( Однако попробовать можно. Спасибо !

Добавлено спустя 2 часа 42 минуты 56 секунд:
Заплатка работает !

Код: Выделить всё

 Type
     TTmpStream = class(TMemoryStream)
          F_Offset: Int64;
           constructor Create;
           function Read(var Buffer; Count: Longint): Longint; Override;
     end;
   constructor  TTmpStream .Create;
     begin
       F_Offset:= 0;
     inherited Create;

     end;
   function  TTmpStream.Read(var Buffer; Count: Longint): Longint;

   begin
   If  Position< F_Offset then
      Position:= Position+F_Offset;
       inherited;
    end;
...
TTmpStream(MS).F_Offset:=PPos^.P;
 B:=LoadAndScaleImage('',W,H,Ms,Pos^.P);
TTmpStream(MS).F_Offset:=0;
..
Правда это сугубо локальное "местечковое" решение которое работает только потому что в цикле ничего другого кроме "последовательного" LoadAndScaleImage нет
.
Последний раз редактировалось Alex2013 09.03.2025 06:03:51, всего редактировалось 1 раз.
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Еще немного оптимизировал код (отказался о своего "самодельного" масштабирования в пользу того что доступно в TFPMemoryImage неуверен что это масштабирование быстрее но оно заметно уменьшает объем данных которые прокачиваются при "конверсии" в TBitmap )
В общем однопоточную загрузку IrfanView вполне догнал и возможно даже немного перегнал.
Тест загрузки списка 3 (30 "стандартных" файлов)
Время 0,12 c
Время 2,68 c
Тест загрузки списка 3 (78 "тяжелых" файлов)
Время 0,34 c
Время 10,76 c
Тест загрузки списка 3 ( "Стресс тест" 639 "тяжелых" файлов )
Время 39,03 c
Время 114,86 c
Тест загрузки списка 2 ( "Стресс тест" 639 "тяжелых" файлов )
Многопоточный ( версия 2 ("Старая" но с новым масштабированием ) 40 потоков )
Время 103,53 c 639
(Нужно дописать Многопоточную загрузку для "3-й версии" по идее будет еще быстрее )
Зы
Кстати я еще и качество миниатюр поднял

Код: Выделить всё

//вместо 
// JPEGImage.LoadFromStream(ST);
//написал 
       R:= TFPReaderJPEG.Create;
        TFPReaderJPEG(R).Smoothing:=true;
        TFPReaderJPEG(R).Scale:=jsQuarter;
        TFPReaderJPEG(R).ImageRead(ST,JPEGImage);
       R.Free;
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Новая фишка
"Трехфазная " загрузка
Тест загрузки списка 3 (78 "тяжелых" файлов)
Время 0,04 c (Разметка)
Время 0,25 c ( загрузка без промежуточного потока) )
Время 11,77 c ( обработка )

Тест загрузки списка 3
Время 0,04 c
Время 0,18 c ( загрузка с промежуточным потоком)
Время 11,03 c ( обработка )

Тест загрузки списка 3 (немного странный результат ) 82 файла
Время 0,04 c
Время 0,19 c
Время 11,75 c
Тест загрузки списка 3
Время 0,05 c
Время 0,26 c
Время 11,31 c

Тест загрузки списка 2
Многопоточный (45 потоков)
Время 15,05 c 82
Суть "нулевой фазы" в разметке потока для последующей загрузки ( нужно для многопоточного чтения из файлов )
(обработка/загрузка пока однопоточные но уже немного обгоняет старую многопоточную )
Последний раз редактировалось Alex2013 09.03.2025 05:55:59, всего редактировалось 1 раз.
xchgeaxeax
постоялец
Сообщения: 198
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

А в чем смысл "разметки". Можно пример?
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

xchgeaxeax писал(а):А в чем смысл "разметки". Можно пример?
"Звучит гордо, но жужжит тихо " :wink: Да вся "фаза 0" это просто предварительный расчет общего размера для мемористрима и запись списка позиций для каждой картинки( При многопоточном варианте поток будет заполняется не по порядку а рандомным образом, а значит просто добавить запись в поток не получится )

Код: Выделить всё

///Фаза 0
//"Форматирование потока"
SumSZ:=0;
For I:=0 to CC-1 do
begin
L.Add(Nil);
//....
CacheFilename := Memo1.Lines[I];
if  FileExists(CacheFilename) then begin
SZ:= getFileSize (CacheFilename);

If SZ<>-1 then
begin
   Tpos:=SumSZ;
   SumSZ:=SumSZ+SZ;
   
    New(PPos); PPos^.P:=TPos;PPos^.S:=SZ; L[I]:=PPos;
  end
 end
end;
MS1.SetSize(SumSZ); // (!)
Зы
Нужно подумать как это действие для сетевой версии провернуть ... с получением размера не загруженного файла там точно напряженка будет.
xchgeaxeax
постоялец
Сообщения: 198
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

А чем вам не нравится вариант с отдельным стримом для каждого файла? Просто соберите указатели на объекты в массив или лист и все.
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

xchgeaxeax писал(а):А чем вам не нравится вариант с отдельным стримом для каждого файла? Просто соберите указатели на объекты в массив или лист и все.
Идея в том что "единая лента" глобального мемористрима будет еще немного меньше взаимодействовать с элементами программы и операционной системы внутри многопоточной части процедуры загрузки что еще немного уменьшит возможность асинхронных коллизии .
(И дело не в том что я против "отдельного стрима для каждого файла" если можно выделить память для каждого файла ДО его загрузки то это тоже неплохой вариант (хотя и чуть более сложный в плане контроля выделения и очистки памяти ) )
xchgeaxeax
постоялец
Сообщения: 198
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

Alex2013 писал(а):(И дело не в том что я против "отдельного стрима для каждого файла" если можно выделить память для каждого файла ДО его загрузки то это тоже неплохой вариант (хотя и чуть более сложный в плане контроля выделения и очистки памяти ) )
Для предопределения размеров файла вы по прежнему генерируете медленные операции с диском. А вот дополнительные взаимодействия по выделению рам идут гораздо быстрее таковых. В принципе должно работать быстрее, если вы просто будете грузить отдельно каждый файл в каждом потоке просто создавая новый мемористрим и сохраняя его в массив / лист. Так будет меньше дисковых операций чтения, а вот операции с памятью и поиску блоков в куче должны быть достаточно быстрыми. К тому же ОС обычно переадресует работу с памятью для загрузки блока данных от медленного устройства в контроллер DMA, что освобождает процессор для выполнения работы в это время. Может для ускорения стоит рассмотреть конвейер: поток для загрузки (достаточно одного т.к. мы уже выяснили, что эти операции быстрее чем само перекодирование), несколько потоков перекодирования, которые выбирают свой мемори стрим из потока загрузки сразу по готовности. В такой схеме должно происходить следующее. Процессор занят перекодированием загруженной картинки во время пока DMA контроллер передает данные в очередной блок памяти при загрузке. Ну а по готовности нового блока, скорее всего, поток перекодирования все еще будет занят и новый поток перекодирования заберет следующий блок в работу итд.
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

xchgeaxeax писал(а):Для предопределения размеров файла вы по прежнему генерируете медленные операции с диском. А вот дополнительные взаимодействия по выделению рам идут гораздо быстрее таковых.
Я тоже наивно думал, что выделение памяти много времени не занимает, и вначале код «Фазы ноль» был такой.

Код: Выделить всё

///Фаза 0
//"Форматирование потока"

For I:=0 to CC-1 do
begin
//....
SZ:= getFileSize (CacheFilename);

If SZ<>-1 then
begin
..
   MS1.Size:=MS1.Size+SZ; // (!) 
 ..
 end
end
end;

К моему удивлению тормоз был прямо чудовищный (выедая больше половины общего времени работы всей процедуры ) ... (понятно что при загрузке данных мемористрим память выделяется иначе но все-же тоже "не бесплатно " )
В принципе должно работать быстрее, если вы просто будете грузить отдельно каждый файл в каждом потоке просто создавая новый мемористрим и сохраняя его в массив / лист.
Проблема коллизий всеравно сохраняется (раз выделение памяти как выясняется занимает заметное время то есть вероятность совпадения нескольких выделений, перекрытия областей памяти и прочих неприятностей . )
К тому же ОС обычно переадресует работу с памятью для загрузки блока данных от медленного устройства в контроллер DMA,
Загрузка болка данных с "блочного устройства" в память ВСЕГДА происходит через "контроллер DMA" но что с эти блоком ( при многократной буферизации и кэшировании ) происходит дальше дело довольно темное .
К тому же тут вопрос в значительной степени не про чтение файла а про реализацию мемористрима. (как известно он хорошо и надежно работает при последовательном чтении загруженных в него данных но совсем остальным как говорится "возможны варианты" );
Последний раз редактировалось Alex2013 15.05.2025 11:58:02, всего редактировалось 2 раза.
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Я тут перевел "фазу 0" в многопоток и "прослезился"... :D В общем похоже смысл использовать мп для индексации есть только для сетевой версии .. Зато погоняв потоки в песочнице убедился что во первых поведение и взаимодействие потоков с кодом основной программы очень странная штука с "непредсказуемым прошлым" (то есть есть весьма странные взаимосвязи причин и следствий ) во вторых "если жизнь и рассудок дороги вам " никогда не пытайтесь удалять элементы внешнего списка из потока (даже синхронизация иногда не особо помогает).
( при удалении элемента списка сбивается индексация и даже при самом безобидном "параллельном" использовании чего-то вроде IndexOff выплывет такая ЕГОГГ-логия что, любой программируемый калькулятор от зависти интенсивно позеленеет )
Ответить