Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Замена Move для маленьких объёмов данных. [D5, Win95/98]


MegaVolt ©   (2005-07-20 16:01:00) 

Вот решил потестировать как работает Move для маленьких объёмов данных в данном случае для перемещения одного байта. Получил любопытные результаты:
Стандартная Move 7,35 сек
Move1 1,06 сек
Move2 1,15 сек
Move3 1,06 сек
Move4 1,15 сек

1. Почему изменение типа к которому я привожу данные влияет на результат?
2. Есть ли ещё более оптимальные методы?

Вот код:

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 StdCtrls;

type
 TForm1 = class(TForm)
   Button1: TButton;
   procedure FormCreate(Sender: TObject);
   procedure Button1Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

 TBA = array of byte;

 TBA2 = array [0..Maxint-1] of byte;

var
 Form1: TForm1;
 z: array [0..32767] of byte;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
 i:integer;
begin
 for i:=0 to 32767 do z[i]:=i;
end;

procedure move1(const Source; var Dest; Count: Integer);
var
 i:integer;
begin
 for i:=0 to Count-1 do
   TByteArray(Dest)[i]:=TByteArray(Source)[i];
end;

procedure move2(const Source; var Dest; Count: Integer);
var
 i:integer;
begin
 for i:=0 to Count-1 do
   TBA(@Dest)[i]:=TBA(@Source)[i];
end;

procedure move3(const Source; var Dest; Count: Integer);
var
 i:integer;
begin
 for i:=0 to Count-1 do
   TBA2(Dest)[i]:=TBA2(Source)[i];
end;

procedure move4(const Source; var Dest; Count: Integer);
var
 i:integer;
begin
 for i:=0 to Count-1 do
   PChar(@Dest)[i]:=PChar(@Source)[i];
end;

procedure Read(var Buffer; Count,ind:integer);
begin
 move4(z[ind],Buffer,Count);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 i,j,time:integer;
 b:byte;
begin
 time:=0;
 Dec(time,GetTickCount);
 for j:=0 to 1000 do
   for i:=0 to 32767 do
     begin
       Read(b,1,i);
       if b<>byte(i) then showmessage(IntToStr(b)+'<>'+IntToStr(i));
     end;
 Inc(time,GetTickCount);
 ShowMessage(IntToStr(time));
end;

end.


REA   (2005-07-20 17:37:00) [1]

>2) А если просто привести pointer к cardinal и начать к нему прибавлять вместо адресации по индексам?

приводить ничего не надо, можно просто Inc(PByte), но выигрыш от этого небольшой. Можно организовать цикл downto - будет небольшой выигрыш, но видимо не на всех процессорах.


MegaVolt   (2005-07-20 17:09:00) [2]

Anatoly Podgoretsky
>Корректно, если не будешь выходить за границы.

Ок. Спасибо.
А есть способы ещё ускорить работу по сравнению с Move1?


Anatoly Podgoretsky   (2005-07-20 17:04:00) [3]

MegaVolt ©   (20.07.05 16:59) [13]
А если DEST является куском реального динамического массива причём не с середины то TByteArray(Dest) это корректно будет?

Корректно, если не будешь выходить за границы.


MegaVolt   (2005-07-20 16:59:00) [4]

PVOzerski
>2) Если ты передал статический массив, у него нет скрытого заголовка, свойственного массивам динамическим.

А если DEST является куском реального динамического массива причём не с середины то TByteArray(Dest) это корректно будет?


PVOzerski   (2005-07-20 16:55:00) [5]

1) Должно зависеть от {$R+/-}
2) Если ты передал статический массив, у него нет скрытого заголовка, свойственного массивам динамическим.


MegaVolt   (2005-07-20 16:49:00) [6]

han_malignспасибо за подробный разбор.

Вот только несколько вопросов:
1. TByteArray(Source)[i] не делает проверки на выход за 32К? Ведь в оригинале TByteArray = array [0..32767] of byte.

2. В чём криминал move2? Ведь у меня Dest может быть и динамическим массивом

3. >для непересекающихся блоков памяти рекомендуется пользовать copy()

А что делать если может возникнуть ситуация когда Dest заполняется за несколько вызовов Read?


han_malign   (2005-07-20 16:43:00) [7]

на вскидку

TByteArray(Source)[i] - обращение к ячейке памяти по индексированному адресу
примерно так:
mov edx, @Source
mov ecx, i
mov AL, [edx+ecx] ; сложение выполняется специальным индексным блоком процессора - без затрат

PChar(@Source)[i] (по симантике соответсвует (PChar(@Source)+i)^)
- сложение адреса + обращение
примерно так:
mov edx, @Source
add edx, i       ;!!!
mov AL, [edx]
хотя тут компилятор может и оптимизировать до предыдущего варианта

отсюда разница - 1,06 / 1,15

move2 - вообще криминал

move - проверка перекрытия блоков(самое длительное) + загрузка EDI, ESI + проверка шага(4/1), копирование - само собой, для исчезающе малых объемов данных, это не выгодно(когда можно PDWORD()^:=PDWORD()^ (PWORD, PBYTE) - за две asm-операции)

moveX - в данном случае(count = 1) - проверка (Count > 0) + описанное выше + проверка (Count > 0)

отсюда разница - 7,35 / 1,06..1,15


короче пройдись в CPU View и все поймешь
реализацию movе можно посмотреть в System.pas

З.Ы. для непересекающихся блоков памяти рекомендуется пользовать copy()- и будет всем щастье


MegaVolt   (2005-07-20 16:43:00) [8]

PVOzerskiMove5 1,7 сек :(


PVOzerski   (2005-07-20 16:40:00) [9]

procedure move5(const Source; var Dest; Count: Integer);
var
i:integer;
p1, p2: pointer;
c1: cardinal absolute p1;
c2: cardinal absolute p2;
begin
p1 := @Source;
p2 := @dest;
for i:=1 to Count do
  begin
    byte(p2^) := byte(p1^);
    inc(c1);
    inc(c2);
  end;

end;


MegaVolt   (2005-07-20 16:40:00) [10]

Удалено модератором
Примечание: Личная переписка


Anatoly Podgoretsky   (2005-07-20 16:39:00) [11]

MegaVolt ©   (20.07.05 16:29) [4]
Что то вроде этого?
byte(pointer(Cardinal(@Dest)+i)^):=byte(pointer(Cardinal(@Source)+i)^);

Тебе разве советовали складывать с индексом, как раз на оборот


MegaVolt   (2005-07-20 16:33:00) [12]

Удалено модератором
Примечание: В персональную почту


MegaVolt   (2005-07-20 16:29:00) [13]

MBo спасибо эти странички я уже нашел предварительно порывшись по интеу. Но там куча ассемблера :( а я в нём не силён :(

PVOzerski
>А уж работать с ними вот так просто не следует.

Это в данном конкретном случае принимающий массив байт. А если принимающий массив не байт а динамический массив?

>в Win32 задействуются 4-байтные регистры

А что делать если действительно нужна побайтная обработка? Или всё равно читать по 4 байта а потом цикл от 1 до 4 крутить?

>А если просто привести pointer к cardinal и начать к нему прибавлять вместо адресации по индексам?

Что то вроде этого?

procedure move5(const Source; var Dest; Count: Integer);
var
 i:integer;
begin
 for i:=0 to Count-1 do
   byte(pointer(Cardinal(@Dest)+i)^):=byte(pointer(Cardinal(@Source)+i)^);
end;


Получил время 2,2 сек :(


Anatoly Podgoretsky   (2005-07-20 16:20:00) [14]

PVOzerski ©   (20.07.05 16:17) [2]
2) А если просто привести pointer к cardinal и начать к нему прибавлять вместо адресации по индексам?

Не трожь адресную математику, а то он такого натворит.


PVOzerski   (2005-07-20 16:17:00) [15]

Замечание к реализации: не надо связываться с динамическими массивами, если хотим получить бОльшую скорость. А уж работать с ними вот так (неявно приводя статические массивы к динамическим) просто не следует.

Еще одно замечание: в Win32 задействуются 4-байтные регистры и такое же выравнивание, и не использовать это обстоятельство, занимаясь побайтовым копированием - вряд ли лучшее решение.

Вопросы к оптимизации:
1) Интересно, а что будет, если описать диапазоны массивов начинающимися не с нуля, а с единицы? Может, тогда в теле функции и вычитать не придется.
2) А если просто привести pointer к cardinal и начать к нему прибавлять вместо адресации по индексам?


MBo   (2005-07-20 16:09:00) [16]

http://dennishomepage.gugs-cats.dk/FastCodeProject.htm
http://dennishomepage.gugs-cats.dk/Libraries.htm


Наверх

Титульная страница Поиск, карта сайта Написать письмо