В том то и фокус "аппаратной виртуализации" что она может рулить железом напрямую (То есть "Гостевая ОС" по идее может получить аппаратный доступ к физическому разделу диска или целому физическому диску ( причем иногда даже в том случае если основная "хост-ОС" совершенно "не подозревает о его существании") возни там правда может быть довольно много но иногда это оправдано)xchgeaxeax писал(а):А причем тут виртуализация, когда речь про скорости дисков. А на виртуалке там будет так и так прокладка в виде этой самой виртуалки.
Максимальное разумное количество работающих потоков .
Модератор: Модераторы
-
xchgeaxeax
- постоялец
- Сообщения: 198
- Зарегистрирован: 11.05.2023 02:51:40
Ну вот это надо диск подключать напрямую, а не использовать файл. Это как раз и замедляет. Какой смысл в таком тестировании.
раньше, как тока появились многоядерные процы, максимально эффективное к-во потоков было = к-во ядер минус одно для операционной системы, последние лет 10, минус два ядра, один для IO, другой для менеджера потоков.
Брр ... JPEGImage.LoadFromStream(ST); (JPEGImage:TFPMemoryImage) достал ! Нормально читать поток (MemoryStream) с произвольной позиции не хочет совершенно (упроно читает только первую картинку в потоке) причем закидон где-то в TFPReaderJPEG .(Пробовал сделать "заплатку" но тут этот фокус не прошел)
Так что пока плюнул и написал простой конвертер Jpg2Bmp...
(он работает но с потерей универсальности TFPMemoryImage)
Зы
Вообщем пока я все еще пытаюсь протащить контейнер из "общего " MemoryStream, но уже начинаю сомневаться "так ли это полезно как красиво" .
Добавлено спустя 18 минут 45 секунд:
Но потом выяснилось что использование заметно большего количества "виртуальных потоков" ускоряет работу "распараллеленных" алгоритмов более чем успешно . (Что очень наглядно видно и на примере моей "тестовой платформы" )
Так что пока плюнул и написал простой конвертер 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, но уже начинаю сомневаться "так ли это полезно как красиво" .
Добавлено спустя 18 минут 45 секунд:
Раньше я тоже думал примерно так-же и постоянно пытался подстроится под количество "физических" потоков и ядер .sts писал(а):раньше, как тока появились многоядерные процы, максимально эффективное к-во потоков было = к-во ядер минус одно для операционной системы, последние лет 10, минус два ядра, один для IO, другой для менеджера потоков.
Но потом выяснилось что использование заметно большего количества "виртуальных потоков" ускоряет работу "распараллеленных" алгоритмов более чем успешно . (Что очень наглядно видно и на примере моей "тестовой платформы" )
Последний раз редактировалось Alex2013 22.02.2025 14:51:59, всего редактировалось 2 раза.
-
xchgeaxeax
- постоялец
- Сообщения: 198
- Зарегистрирован: 11.05.2023 02:51:40
Может проще сделать дополнительный стрим TPartialStream = class(TStream) constructor Create(Source: TStream; Offset: Int64; Size: Int64); соответственно она нужна просто для того, чтобы скормить ей TMemoryStream, из которого она будет отдавать только указанный в конструкторе кусочек от Offset до Offset + Size
Не копировать в себя, а сохранять исходный TMemoryStream и при загрузке через LoadFromStream считывать из него заданный диапазон
Не копировать в себя, а сохранять исходный TMemoryStream и при загрузке через LoadFromStream считывать из него заданный диапазон
Это разумеется тоже вариант (причем он точно лучше чем просто копировать текущую картинку в дополнительный поток или неким "хитрым образом" подменять адрес блока данных (первый вариант работает хотя и подтормаживает , а на второй видимо моей "хитрости" не хватило )) ... но это все равно "костыль" !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;
..
.
Последний раз редактировалось Alex2013 09.03.2025 06:03:51, всего редактировалось 1 раз.
Еще немного оптимизировал код (отказался о своего "самодельного" масштабирования в пользу того что доступно в TFPMemoryImage неуверен что это масштабирование быстрее но оно заметно уменьшает объем данных которые прокачиваются при "конверсии" в TBitmap )
В общем однопоточную загрузку IrfanView вполне догнал и возможно даже немного перегнал.
Зы
Кстати я еще и качество миниатюр поднял
В общем однопоточную загрузку IrfanView вполне догнал и возможно даже немного перегнал.
(Нужно дописать Многопоточную загрузку для "3-й версии" по идее будет еще быстрее )Тест загрузки списка 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
Зы
Кстати я еще и качество миниатюр поднял
Код: Выделить всё
//вместо
// JPEGImage.LoadFromStream(ST);
//написал
R:= TFPReaderJPEG.Create;
TFPReaderJPEG(R).Smoothing:=true;
TFPReaderJPEG(R).Scale:=jsQuarter;
TFPReaderJPEG(R).ImageRead(ST,JPEGImage);
R.Free;
Новая фишка
"Трехфазная " загрузка
(обработка/загрузка пока однопоточные но уже немного обгоняет старую многопоточную )
"Трехфазная " загрузка
Суть "нулевой фазы" в разметке потока для последующей загрузки ( нужно для многопоточного чтения из файлов )Тест загрузки списка 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 писал(а):А в чем смысл "разметки". Можно пример?
Код: Выделить всё
///Фаза 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 писал(а):А чем вам не нравится вариант с отдельным стримом для каждого файла? Просто соберите указатели на объекты в массив или лист и все.
(И дело не в том что я против "отдельного стрима для каждого файла" если можно выделить память для каждого файла ДО его загрузки то это тоже неплохой вариант (хотя и чуть более сложный в плане контроля выделения и очистки памяти ) )
-
xchgeaxeax
- постоялец
- Сообщения: 198
- Зарегистрирован: 11.05.2023 02:51:40
Для предопределения размеров файла вы по прежнему генерируете медленные операции с диском. А вот дополнительные взаимодействия по выделению рам идут гораздо быстрее таковых. В принципе должно работать быстрее, если вы просто будете грузить отдельно каждый файл в каждом потоке просто создавая новый мемористрим и сохраняя его в массив / лист. Так будет меньше дисковых операций чтения, а вот операции с памятью и поиску блоков в куче должны быть достаточно быстрыми. К тому же ОС обычно переадресует работу с памятью для загрузки блока данных от медленного устройства в контроллер DMA, что освобождает процессор для выполнения работы в это время. Может для ускорения стоит рассмотреть конвейер: поток для загрузки (достаточно одного т.к. мы уже выяснили, что эти операции быстрее чем само перекодирование), несколько потоков перекодирования, которые выбирают свой мемори стрим из потока загрузки сразу по готовности. В такой схеме должно происходить следующее. Процессор занят перекодированием загруженной картинки во время пока DMA контроллер передает данные в очередной блок памяти при загрузке. Ну а по готовности нового блока, скорее всего, поток перекодирования все еще будет занят и новый поток перекодирования заберет следующий блок в работу итд.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 раза.
Я тут перевел "фазу 0" в многопоток и "прослезился"...
В общем похоже смысл использовать мп для индексации есть только для сетевой версии .. Зато погоняв потоки в песочнице убедился что во первых поведение и взаимодействие потоков с кодом основной программы очень странная штука с "непредсказуемым прошлым" (то есть есть весьма странные взаимосвязи причин и следствий ) во вторых "если жизнь и рассудок дороги вам " никогда не пытайтесь удалять элементы внешнего списка из потока (даже синхронизация иногда не особо помогает).
( при удалении элемента списка сбивается индексация и даже при самом безобидном "параллельном" использовании чего-то вроде IndexOff выплывет такая ЕГОГГ-логия что, любой программируемый калькулятор от зависти интенсивно позеленеет )
( при удалении элемента списка сбивается индексация и даже при самом безобидном "параллельном" использовании чего-то вроде IndexOff выплывет такая ЕГОГГ-логия что, любой программируемый калькулятор от зависти интенсивно позеленеет )
