Форум: "Media";
Текущий архив: 2006.05.28;
Скачать: [xml.tar.bz2];
ВнизФормат bitmap Найти похожие ветки
← →
Woolen © (2005-12-21 17:43) [0]Помогите разобраться в формате, пожалуйста. Не могу понять, что лежит между строк и какого оно размера.
GetPixel - это слишком медленно (знают все), но и обрабатывать в через ScanLine тоже, мягко говоря, не быстро.
Написал по другому:procedure BitmapDarkness6(Bmp: TBitmap);
var
PixelCicle: Cardinal;
FirstClr,SecClr,ThirdClr: PByte;
LinePtr: PByteArray;
WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
begin
LinePtr := WinBmp.bmBits;
for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight - 1
do
begin
FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
SecClr := Pointer(Cardinal(FirstClr) + 1);
ThirdClr := Pointer(Cardinal(FirstClr) + 2);
FirstClr^ := FirstClr^ div 2;
SecClr^ := SecClr^ div 2;
ThirdClr^ := ThirdClr^ div 2;
end;
end;
end;
Время работы такой процедуры составила менее 39% от варианта со ScanLine, но некоторое количество пикселей не закрашивается. Цикл завершается раньше. То есть, строки, как я понял, лежат не вплотную. Видимо я корежу какую-то служебную информацию между ними, в то время как через ее надо "перешагивать". Поправьте, если я не прав, и помогите разобраться с форматом, пожалуйста. Что там лежит и для чего оно нужно? Спасибо
P.S. Битмап - 24 бита, RGB.
← →
PAVIA © (2005-12-21 18:40) [1]Скорее всего там передается 32битная картика. Для 24битной картике код рабоате верно. Там нету никакой дополнительной информации строки идут одна за другой. Только надо помнить что Бэмпешка храниться в обратном порядке.
С таким же успехом ты мог использовать и ScanLine[bmp.Height - 1]
← →
MBo © (2005-12-21 18:46) [2]1. Доступ через Scanline не медленнее использованного способа
2. Начала строк выравниваются на 4-байтовую границу
← →
Woolen © (2005-12-21 19:13) [3]
> Доступ через Scanline не медленнее использованного способа
Быстрее, но на много смотря как делать. Возможно, я не прав, поправьте, если что.
Даю полные выкладки, потому как объяснять будет долго.
Проц - Celeron (P4) 2 ГГц. На процах с архитектурой, отличной от NetBirst, и (возможно) другим размером кэша, при обработке изображения другого размера, результаты могут немного отличаться. Расчет ведется при помощи вызова процедур, высталяющих и снимающих максимальные приоритеты процесса и потока, снимающих копию регистра RDTSC (счетчика тактов) до и после вызова с вычислением разницы. Есть пограшности на вход, выход таких процедур и т.п.
Вариант 1. Самый простой и распространенный. Принимаю за 100% времени.procedure BitmapDarkness1(Bmp: TBitmap);
var
RowCicle,PixelCicle,FirstClr,SecClr,ThirdClr: Cardinal;
LinePtr: PByteArray;
begin
for RowCicle := 0 to Bmp.Height - 1 do
begin
LinePtr := Bmp.ScanLine[RowCicle];
for PixelCicle := 0 to Bmp.Width - 1 do
begin
FirstClr := PixelCicle * 3;
SecClr := FirstClr + 1;
ThirdClr := FirstClr + 2;
LinePtr[FirstClr] := LinePtr[FirstClr] div 2;
LinePtr[SecClr] := LinePtr[SecClr] div 2;
LinePtr[ThirdClr] := LinePtr[ThirdClr] div 2;
end;
end;
end;
Вариант 2. Оптимизированный со ScanLine"ом. Сотавляет 63% от первого.procedure BitmapDarkness2(Bmp: TBitmap);
var
RowCicle,PixelCicle: Cardinal;
FirstClr,SecClr,ThirdClr: PByte;
LinePtr: PByteArray;
begin
for RowCicle := 0 to Bmp.Height - 1 do
begin
LinePtr := Bmp.ScanLine[RowCicle];
for PixelCicle := 0 to Bmp.Width - 1 do
begin
FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
SecClr := Pointer(Cardinal(FirstClr) + 1);
ThirdClr := Pointer(Cardinal(FirstClr) + 2);
FirstClr^ := FirstClr^ div 2;
SecClr^ := SecClr^ div 2;
ThirdClr^ := ThirdClr^ div 2;
end;
end;
end;
Вариант 3. Составляет 39% от первого.procedure BitmapDarkness6(Bmp: TBitmap);
var
PixelCicle: Cardinal;
FirstClr,SecClr,ThirdClr: PByte;
LinePtr: PByteArray;
WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
begin
LinePtr := WinBmp.bmBits;
for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight - 1
do
begin
FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
SecClr := Pointer(Cardinal(FirstClr) + 1);
ThirdClr := Pointer(Cardinal(FirstClr) + 2);
FirstClr^ := FirstClr^ div 2;
SecClr^ := SecClr^ div 2;
ThirdClr^ := ThirdClr^ div 2;
end;
end;
end;
Подозреваю, что можно сделать вариант, работающий еще быстрее.
> Начала строк выравниваются на 4-байтовую границу
Спасибо
← →
Sapersky (2005-12-21 20:01) [4]Указатель лучше не высчитывать для каждого пикселя, а инкрементировать.
for RowCicle := 0 to Bmp.Height - 1 do begin
FirstClr := PByte( Bmp.ScanLine[RowCicle] );
for PixelCicle := 0 to Bmp.Width*3 - 1 do begin
FirstClr^ := FirstClr^ shr 1; // хотя по идее компилятор сам должен заменять на это
Inc(FirstClr);
end;
end;
Поскольку все цвета обрабатываются одинаково, можно цикл сделать по байтам. Не знаю, будет ли это быстрее; с одной стороны, меньше переменных - они лучше влезают в регистры, с другой - расписывание каждого цвета по отдельности можно считать за развёртку цикла (приём оптимизации).
Ещё варианты:
1) MMX
2) Аппаратный вывод графики
3) 8-битные картинки, у которых нужно обрабатывать только палитру.
← →
wicked © (2005-12-21 21:54) [5]
> 2. Начала строк выравниваются на 4-байтовую границу
афаир размеры строк выравниваются на 4 байтовую границу.... соответственно, между ними будут промежутки, если картинка не 32 битная или кол-во пикселей в строке не делится нацело на 4....
← →
Woolen © (2005-12-22 12:36) [6]
> Указатель лучше не высчитывать для каждого пикселя, а инкрементировать.
А это мысль!
> хотя по идее компилятор сам должен заменять на это
Так и есть. Он сам понимает, что деление на 2, это сдвиг на 1. Он так и делает
> for RowCicle := 0 to Bmp.Height - 1 do begin
> FirstClr := PByte( Bmp.ScanLine[RowCicle] );
> for PixelCicle := 0 to Bmp.Width*3 - 1 do begin
> FirstClr^ := FirstClr^ shr 1;
> Inc(FirstClr);
> end;
> end;
Нет, это будет очень медленно. Я уже убедился, что от ScanLine надо избавляться, только процесорное время жрет. От вложенного цикла тоже. При каждом завершении внутреннего цикла - останов, сброс и перезагрузка конвейера гарантирована. А на большинстве нынешних процов - это очень жрет производительность. В особенности это касается Prescott. Там 31 ступень. Кроме того, обрабатывать по одной цветовой компоненте за итерацию медленно. Как правило, процессор может гораздо больше комманд вертеть внутри ядра и кэша, причем NetBurst вообще имеет очередь на 12000 микроопераций уже во внутреннем представлении ядра. Так что я думаю, что надо еще побольше как-то тело цикла сделать. Должно быть быстрее так. А одна компонента за итерацию - медленно, я вчера уже проверял. Так и есть, как я думал.
> MMX
А у него есть побайтовый сдвиг? Я знаю только о пословном!
> Аппаратный вывод графики
Нужно затенить bmp очень быстро и все. Вывод потом не мое дело. Я просто делаю Canvas.Draw.
> 8-битные картинки, у которых нужно обрабатывать только палитру
Нет, 24-бита. И только.
← →
MBo © (2005-12-22 13:54) [7]Ограничивает не арифметика, а скорость работы с памятью.
В следующем коде (не вполне корректном) нет почти ничего лишнего. Проверь на своей машине. У меня в полтора раза быстрее, чем BitmapDarkness6
procedure DarkBmp(B:TBitmap);
var
Delta:Integer;
P:PInteger;
i, Size:Integer;
begin
if B.Height>1 then
Delta:=Integer(B.Scanline[1])-Integer(B.Scanline[0]);
Size:=B.Height*Abs(Delta);
P:=B.Scanline[(B.Height-1)*Ord(Delta<0)];
for i:= 0 to (Size shr 2)- 1 do begin
P^:=(P^ and $FEFEFEFE) shr 1;
Inc(P);
end;
end;
← →
Sapersky (2005-12-22 14:11) [8]от ScanLine надо избавляться, только процесорное время жрет
Сейчас посмотрел в реализацию TBitmap - да, накручено изрядно. Я-то обычно использую FastLIB:
http://prdownloads.sourceforge.net/skinner/FastLib.zip?download
Там Scanline - просто свойство, даже поле объекта, и поэтому никаких тормозов не добавляет.
А у него есть побайтовый сдвиг? Я знаю только о пословном!
Точно, нету :(
Тогда лучше MBo © (22.12.05 13:54) [7]
← →
MBo © (2005-12-22 14:50) [9]> MMX
>А у него есть побайтовый сдвиг? Я знаю только о пословном!
Это ничему не мешает. однако использование MMX в данном случае ускоряет незначительно, процентов на 10 (проба на скорую руку)
PMask^:= $FEFEFEFEFEFEFEFE;
asm
mov eax, PMask
movq mm1, [eax]
mov eax,p
mov edx, Size
@@Start:
movq mm0,[eax]
pand mm0, mm1
psrlq mm0,1
movq [eax],mm0
add eax,8
dec edx
jnz @@Start
emms
end;
end;
← →
miek © (2005-12-23 09:51) [10]если использовать оптимизацию под две трубы и еще раскрыть цикл на 2 итерации (итого в каждой итерации чтение и запись 4 регистров MMX), ускорение будет сильнее, +50% должно быть точно
← →
MBo © (2005-12-23 10:08) [11]>miek © (23.12.05 09:51) [10]
Пример есть? Я не сталкивался с оптимизацией под пайпы, а интересно было бы посмотреть, например, простенькую обработку массива Int64.
З.Ы. Приведенный в [9] код обрабатывает массив 400К за 0.7 мс на AthlonXP1900, память PC2700(333 мгц), т.е. 560 мег в секунду. Поэтому я и склонен считать, что узкое место - доступ к памяти, а не арифметика. Может, конечно, префетчинг поможет.
← →
Woolen © (2005-12-23 17:06) [12]
> если использовать оптимизацию под две трубы и еще раскрыть
> цикл на 2 итерации (итого в каждой итерации чтение и запись
> 4 регистров MMX), ускорение будет сильнее, +50% должно быть
> точно
Попробовал без MMX. Видимо, я чего-то недопонимаю. Вот вариант (растр должен иметь ширину, кратную 4):if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
begin
FirstDWORD := WinBmp.bmBits;
SecDWORD := Pointer(Cardinal(FirstDWORD) + 4);
for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1 do
begin
FirstDWORD^ := (FirstDWORD^ and $FEFEFEFE) div 2;
Inc(FirstDWORD,2);
SecDWORD^ := (SecDWORD^ and $FEFEFEFE) div 2;
Inc(SecDWORD,2);
end;
end;
По сравнению с обработкой в один DWORD за цикл прироста практически нет. Даже небольшое замедление. Хотя рагистры использует разные и зависимости между командами я не нашел. То есть, теоретически FirstDWORD и SecDWORD должны обрабатываться разными конвейрами. Но этого, судя по скорости, не происходит.
← →
Woolen © (2005-12-23 17:17) [13]
procedure BitmapDarkness9(Bmp: TBitmap);
var
PixelCicle: Cardinal;
FirstDWORD: PInt64;
WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
begin
FirstDWORD := WinBmp.bmBits;
for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1 do
begin
FirstDWORD^ := (FirstDWORD^ and $FEFEFEFEFEFEFEFE) shr 1;
Inc(FirstDWORD);
end;
end;
end;
И так тоже не быстрее. Хотя тут это можно объяснить зависимостью.
shrd eax,edx,1 - зависимость явная. Из-за этой команды проц точно ничего не разбросает по разным пайпам.
← →
Woolen © (2005-12-27 12:28) [14]
> если использовать оптимизацию под две трубы и еще раскрыть
> цикл на 2 итерации (итого в каждой итерации чтение и запись
> 4 регистров MMX), ускорение будет сильнее, +50% должно быть
> точно
При развертке цикла в 2 раза (с использованием разных регистров) прибавки в скорости, почему-то, не видно. Может что-то не так делаю?
procedure BitmapDarkness11(Bmp: TBitmap);
var
PixelCicle: Cardinal;
WinBmp: Windows.TBitmap;
Mask: Int64;
PMask: PInt64;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
begin
Mask := $FEFEFEFEFEFEFEFE;
PMask := @Mask;
PixelCicle := WinBmp.bmWidth * WinBmp.bmHeight * 3 div 16;
asm
mov eax,PMask
movq mm1,[eax]
movq mm3,mm1
mov eax,WinBmp.bmBits
lea edx,[eax+8]
mov ecx,PixelCicle
@@LoopBegin:
movq mm0,[eax]
pand mm0,mm1
psrlq mm0,1
movq [eax],mm0
movq mm2,[edx]
pand mm2,mm3
psrlq mm2,1
movq [edx],mm2
add eax,$10
add edx,$10
loop @@LoopBegin
emms
end;
end;
end;
← →
_Lucky_ (2005-12-27 13:49) [15]
WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1
а зачем ваще такие вещи каждый раз вычислять?
← →
Woolen © (2005-12-27 14:45) [16]
> а зачем ваще такие вещи каждый раз вычислять?
Какой именно каждый? В коде, приведенном в 12 и 13 постах, он вычисляется 1 раз. При инициализации цикла.
← →
Woolen © (2005-12-27 14:45) [17]
> а зачем ваще такие вещи каждый раз вычислять?
Какой именно каждый? В коде, приведенном в 12 и 13 постах, он вычисляется 1 раз. При инициализации цикла.
← →
_Lucky_ (2005-12-27 18:30) [18]
> Какой именно каждый? В коде, приведенном в 12 и 13 постах,
> он вычисляется 1 раз. При инициализации цикла.
Лично я думаю, что как раз таки на оборот, значения пересчитываются каждый раз, да это и логично, там же ведь переменные стоят, т.е. в теле цикла любая из них молга изменится, а если цикл был инициализирова - как вы утверждаете, то изменения не учтутся
смотри оптимизацию циклов
http://www.delphimaster.ru/articles/optimization.html
← →
Anton_K © (2005-12-27 18:43) [19]
> _Lucky_
Сколько раз выполнится тело цикла?
var i,n:Integer;
...
n:=2;
for i:=1 to n do n:=n+10;
...
← →
MBo © (2005-12-27 20:44) [20]>_Lucky_ (27.12.05 18:30) [18]
>Лично я думаю, что как раз таки на оборот, значения пересчитываются каждый раз, да это и логично
Читай описание языка
>смотри оптимизацию циклов
>http://www.delphimaster.ru/articles/optimization.html
А этого "добра" лучше не читать ;)
← →
Woolen © (2005-12-27 23:34) [21]
> Лично я думаю, что как раз таки на оборот, значения пересчитываются
> каждый раз, да это и логично, там же ведь переменные стоят,
> т.е. в теле цикла любая из них молга изменится, а если
> цикл был инициализирова - как вы утверждаете, то изменения
> не учтутся
Да, представляешь, они действительно не учтутся! Даже, если ты сделаешь цикл до переменной, а не до выражения и будешь ее в цикле менять, не учтутся!!! Даже если сделать так:for Cicle := 0 to A do
Inc(A);
А еще я тебя удивлю: в Delphi даже циклы to компилируются с прокруткой в обратную сторону - по типу downto. То есть в вышеуказанном случае будет крутиться от A до 0. А если написать так:for Cicle := 0 to A do
B := Cicle;
то опять же будет крутиться обратно, но для переменной Cicle тебе компилятор специально будет крутить одно число (отдельно) вперед, на увеличение, чтоб ты мог использовать значение своей переменной.
Короче, будешь читать буквари - узнаешь много нового.
Рекомендую для начала: Зубков - Ассемблер для DOS,Windows и UNIX; Delphi Help; ассемблерный отладчик.
> смотри оптимизацию циклов
Смотри, что компилирует Delphi
← →
_Lucky_ (2005-12-28 13:53) [22]
> Anton_K © (27.12.05 18:43) [19]
Да действительно - это не то что я ожидал.
> MBo © (27.12.05 20:44) [20]
> Woolen © (27.12.05 23:34) [21]
Не буду я читать ваши буквари, потому что я пишу на сях, а пас так для расширения кругозора (теперь буду знать как оно на самом деле), а ассемблер ваше не причем.
Кстати такое поведение на мой взгляд не правильное - раз переменные, то должны пересчитываться, но видимо у разработчиков другое мнение на этот счет.
http://www.delphimaster.ru/articles/optimization.html - очень жаль, что допущена такая ошибка.
← →
Woolen © (2005-12-28 15:23) [23]
> я пишу на сях
А в них циклы пересчитываются на лету?
> ассемблер ваше не причем
При понимании того, что выдает компилятор и при написании вставок на ассемблере. Короче, при производительности. Не при чем, это если ты под виртуальную машину пишешь. Почти во всех остальных случаях, очень даже причем.
> раз переменные, то должны пересчитываться
А цикл сначала начинать? Или что с ним делать? А еще, видимо, неправильно, что адресация с 0, и что битов в байте 8, а не 10. :-)
← →
Sapersky (2005-12-28 15:49) [24]очень жаль, что допущена такая ошибка.
Никакой ошибки нет.
Если есть вызов метода - вызывает на каждой итерации. Если нет - считает один раз. В [12] bmWidth и bmHeight - это поля записи, а не свойства объекта...
← →
MBo © (2005-12-28 15:56) [25]>Если есть вызов метода - вызывает на каждой итерации
???
Поясни
← →
Sapersky (2005-12-28 16:21) [26]Если есть вызов метода - вызывает на каждой итерации
Это автор в статье утверждал.
Но до меня уже дошло, что он действительно не прав :(
Забыл, что в ассемблерном листинге for пишется два раза (проверка в начале и jump в конце), посмотрел на первый - ага, call в "теле цикла" есть, ну и решил, что зря на человека наезжаете.
← →
_Lucky_ (2005-12-29 13:37) [27]
> Woolen © (28.12.05 15:23) [23]
Ну дак в сях все циклы с условием, т.е. там вычисляется некоторое выражение, и соответственно делает вывод true/false и т.д.
Про ассемблер, вовсе не обязательно его знать, хотя и хотелось бы :-), но нет времени - уже другая история, да и дело не в этом. Имелось ввиду, что ассемблер не влияет на поведение, того же паскаля, он ведет себя так как написали разработчики, и знание ассемблера этого не изменит.
> А цикл сначала начинать? Или что с ним делать? А еще, видимо,
> неправильно, что адресация с 0, и что битов в байте 8,
> а не 10. :-)
м.б. и смешно, но циклы типаwhile (bEnd) do
begin
bEnd := ....
end;
вопросов начинать ли сначала или пересчитывать не вызывают?
Что еще не правильно решать Вам.
← →
_Lucky_ (2005-12-29 14:21) [28]А что действительно это так: "по стандарту Паскаля значение переменной цикла после выхода не определено"?
← →
MBo © (2005-12-29 15:23) [29]>_Lucky_ (29.12.05 14:21) [28]
Да
Страницы: 1 вся ветка
Форум: "Media";
Текущий архив: 2006.05.28;
Скачать: [xml.tar.bz2];
Память: 0.55 MB
Время: 0.01 c