Вопрос по использованию МMX, SSE1,2,3 в Delphi через ASM

Тема в разделе "WASM.BEGINNERS", создана пользователем Anton, 1 дек 2007.

  1. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Большая просьба модератору раздела не удалять этот топик.
    Программированием пытаюсь заниматься недавно :), столкнулся с вопросом на который не могу найти ответ в различной литературе по АSMу и Delphi.
    Дано: Два двумерных массива Mas1, Mas2. Нужно максимально быстро провести с ними вот такую вещь:

    На Паскале:
    -----------------------------------------------------
    for I := 0 to 100 do
    begin
    for J :=0 to 100 do
    begin
    if Mas1[i,j] <= 10 then
    begin
    Mas2[i,j] := 1;
    end;
    end;
    end;
    -----------------------------------------------------
    Как то же самое сделать на встроенном в Delphi асме (BASM) без использования МMX, SSE1,2,3 приблизительно понятно, но все равно получается медленно.
    Кто нибудь может показать на простых примерах (сложение, умножение, массив - обработка с помощью цикла), как использовать Subj, для увеличения быстродействия?

    Примеры типа:

    function plus(x, y: integer): integer;
    asm
    mov eax,x
    add eax,y
    end;

    или

    procedure asm_cycle;
    label
    lb;
    var
    d: integer;
    begin
    asm
    mov ebx,0
    mov d,0
    lb:
    add d,1
    inc ebx
    cmp ebx,10
    jnz lb
    mov ebx,0
    end;
    Writeln(d);
    end;

    Лучше конечно на BASM (очень мало литературы), но в принципе и MASM подайдет.
    Заранее спасибо.
     
  2. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    Anton

    Насколько я понял, задача заключается в том, чтобы для всех элементов Mas1, меньших либо равных 10, присвоить "параллельным" им элементам Mas2 значение 1. Другие элементы Mas2 (соответствующие элементам Mas1, большим 10) не изменяются. Так?

    Кстати, Вы не указали тип элементов массивов. Предполагаю, что это Integer, но он может быть любым числовым, в т.ч. и вещественным.

    Во-первых, чуть-чуть подкорректирую Вашу программку "для красоты" (уменьшить число строк):

    Код (Text):
    1. for I := 0 to 100 do
    2.   for J := 0 to 100 do
    3.     if Mas1[i, j] <= 10 then Mas2[i, j] := 1;
    begin-end здесь не нужны, поскольку внутри каждого оператора имеется лишь один оператор. Ну а меньше строк -- легче понять суть :)

    Массивы хранятся в памяти в виде последовательно расположенных элементов. Т.е. если у нас массив 101*101 (как в данном случае -- индексы изменяются от 0 до 100 включительно) с элементами типа Integer (размер элемента 4 байта), то массив займёт в памяти подряд идущие 101*101*4 = 40804 байта.

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

    Код (Text):
    1.     pushad
    2.  
    3.     mov   esi, offset Mas1
    4.     mov   edi, offset Mas2
    5.     mov   ecx, 101*101
    6.  
    7. @@10:
    8.     lodsd
    9.     cmp   eax, 10
    10.     ja    @@20
    11.     mov   [edi], 1
    12. @@20:
    13.     add   edi, 4
    14.     loop  @@10
    15.  
    16.     popad
    Первая и последняя инструкции -- pushad и popad -- сохраняет, а затем восстанавливает все регистры общего назначения, используя стек. Это делается для того, чтобы не разрушить содержимое регистров, на которые полагается компилятор Дельфи (думает, что мы их не изменяем). Если знать, какие регистры ему нужны, а какие нет, можно сохранять лишь определённые регистры (либо вообще обойтись только теми, которые транслятору не нужны).

    В esi и edi содержатся адреса текущих элементов массивов, начиная с элемента [0, 0]. В ecx -- счётчик элементов (когда он обнулится, массив закончен).

    Инструкция lodsd загружает в eax значение двойного слова (4 байта) из памяти по адресу, находящемуся в esi, после чего увеличивает esi на 4 (может и уменьшать, но для этого надо установить флажок направления DF, а он по умолчанию сброшен, и мы его менять не будем).

    cmp сравнивает содержимое eax с константой 10, следующая инструкция ja выполняет переход, если первый операнд cmp (в нашем случае -- eax, т.е. значение только что прочитанного элемента массива Mas1) больше второго операнда (10).

    Если переход не произошёл, команда mov занесёт 1 в соответствующий элемент массива Mas2.

    Далее идёт инструкция add, увеличивающая на 4 значение edi, т.е. переводящая указатель на следующий элемент Mas2. Понятное дело, она должна выполняться независимо от того, выполнялось ли присваивание 1 или нет.

    Ну и последняя команда loop организует цикл: она уменьшает ecx (там у нас находится кол-во элементов массива) на 1, и если он ещё не равен нулю, производит переход на указанный адрес (т.е. на начало нашего цикла).
     
  3. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Anton
    Любой массив в памяти расположен линейно, рассматриваем весь массив целиком и это сильно упрощает нашу задау. Теоретически нам все равно, рассматривать массивы с начала до конца или с конца до начала - поэтому выбираем второй вариант. Так как задание дано не полностью сделаем некоторые предположения - пусть оба массива содержат байты, тогда решение следующее
    Код (Text):
    1.      pusha
    2.      mov ecx,100*100; размеры массивов
    3. a1: mov al,Mas[ecx] ; получаем значение очередного элемента массива
    4.      mov bl,0
    5.      cmp al,10       ; сравниваем его с 10
    6.      setbe bl         ; если меньше или равен bl:=1
    7.      mov Mas1[ecx],bl
    8.      sub ecx,1      ; уменьшаем индекс массива
    9.      jnz a1 ; если это не последний элемент (тогда ecx=0) начнем все с начала
    10.      popa
    вот и всё!;) Если насчет байтов я ошибся - исправить не сложно меняешь al на eax, Mas[ecx] на Mas[ecx*4] и т.д.
     
  4. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Большое спасибо !!! за приведенные примеры !!!, для меня очень познавательно...
    Извиняюсь что не уточнил задачу сразу, проблема в следующем.
    Mas1 - числа типа 0,98887421
    Mas2 - 1,0
    if Mas1[i,j] <= 0.9 then
    Нужно использовать МMX, SSE1,2,3 в Delphi(или не в Delphi) через ASM.
     
  5. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    Mikl__
    Это лишнее, потому что SETcc сами обнуляют приёмник при невыполнении условия.

    Anton
    Вообще-то это совсем другая задача с точки зрения программирования. Лично я с MMX и SSE не работал, и разбираться лень, так что придётся ждать кого-то более продвинутого.

    Чисел "типа ..." не бывает. Бывают разные целые числа и разные вещественные. Какой конкретно здесь тип данных? Single, Double, Extended?

    Кстати, "," тоже в программировании для разделения целых и дробных частей не используется, только ".".
     
  6. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Anton не может быть
    Mas1 и Mas2 в твоей задаче это адреса, по которым в памяти расположены первые элементы массивов Mas1 и Mas2 - адреса не могут быть типа 0,98887421, а только целыми числами, а вот содержимое Mas1[0] вполне может быть вещественным числом, так что если ставишь кому-либо задачу - ставь ее так, чтобы не было разночтений
     
  7. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    Mikl__
    Не путайте человека. Он пишет не на Си, где массив и указатель, по сути, одно и то же, а на Паскале, где всё это чётко разграничено. Так что Mas1 и Mas2 -- это именно массивы, а не адреса. Вот @Mas1 и @Mas2 -- это адреса этих массивов.
     
  8. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Попробую описать задачу более конкретно:
    Програмка написана в Delphi, работает очень медленно. Хочется повысить быстродействие с помощью
    использования МMX, SSE1,2,3 инструкций в Delphi через ASM. По большому счету не играет никакой роли какой АСМ использовать BASM, или MASM, но лучьше конечно BASM. Скелет проги ниже:

    Const
    V=500
    T=30000
    var
    Vv,V,Tt,T,i,j:word;
    Mas1 : array of array of double;
    Mas2 : array of array of word;
    VremPerem:int64;
    begin
    Vv:=V-1;
    Tt:=T-1;
    VremPerem:=0;
    SetLength(Mas1,V,T);
    SetLength(Mas2,V,T);
    repeat
    for I := 0 to Vv do
    begin
    for J :=0 to Tt do
    begin
    if 0.5 < Mas1[i,j] then
    begin
    Mas2[i,j] := 10;
    end
    else
    begin
    Mas2[i,j] := 20;
    end;
    end;
    end;
    *
    Inc(VremPerem);
    Until VremPerem = 10000000000000;

    Типы переменных, именно такие, порядок (длинна) в массивх то же....
    На первый взгляд бессмыслица, но нужна именно такая реализация.

    Как я уже говорил, найти в литературе элементарные примеры по использованию простых инструкций можно, а вот найти простейшие примеры с использованием MMX и SSE мне не удалось, вот :dntknw:
     
  9. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    Anton
    Так у Вас ещё и открытые массивы? С ними вообще не так работать, как с обычными -- их же поддерживает компилятор достаточно хитрыми методами. Возможно, с этим и связана очень медленная работа программы (как реализована поддержка динамических массивов, я не разбирался).
     
  10. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Если на прогу посмотреть, то длинна масива задается в самом начале, и во время выполнения проги не меняется, так что можно и стандартные использовать
    Mas1 : array [1..500,1..30000] of double;
    Mas2 : array [1..500,1..30000] of word;
     
  11. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    Anton
    Ну а что ты хочешь 10^13 вложенных циклов разумеется медленно работать будет.
    10^13*500*30000=150*10^18 время завершения проекта несколько сот лет работы программы.
    Ждем появления кватновых компьютеров.
     
  12. t00x

    t00x New Member

    Публикаций:
    0
    Регистрация:
    15 фев 2007
    Сообщения:
    1.921
    работа с всеми типами массивов в Delphi реализована одинаково, медленная работа программы не связана с выделением памяти под массивы.
     
  13. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    t00x
    Сомневаюсь. Мы имеем динамический массив динамических массивов; технически это практически наверняка реализуется как динамический одномерный массив указателей на динамические одномерные массивы. А тогда работа с таким псевдодвухмерным массивом будет совершенно отличаться от работы со статическим двухмерным.
     
  14. t00x

    t00x New Member

    Публикаций:
    0
    Регистрация:
    15 фев 2007
    Сообщения:
    1.921
    Mas[x][y]:= A; - что в этой записи может отличаться для динамического массива?
     
  15. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Мне понятно что программа будет работать очень долго, но суть то не в этом, вопрос в том каким образом можно использовать
    в данном случае МMX, SSE1,2,3. Пусть к примеру VremPerem:=1000000000 (1 трилион) , порядок уменьшился... такие расчеты более реальны :)
    Мне кажется если грамотно использовать ASM, время расчетов можно сократить на несколько порядков. Кто бы научил :)
     
  16. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    :) Я предлогаю так использовать. Так как в XMM регистр влазит только два числа Double то и скорость возросла у меня в 2 раза. Правда я не оптимизировал работу с памятью. Так как тут мегабайты гоняем то стоит заняться.

    Код (Text):
    1. Const
    2. V=500;
    3. T=30000;
    4. var
    5.   Vv,Tt,i,j:word;
    6.   Mas1 : array [0..V-1] of array [0..T-1] of double;
    7.   Mas2 : array [0..V-1] of array [0..T-1] of word;
    8.   VremPerem:int64;
    9.   t1,t2:int64;
    10. procedure pp;
    11. const
    12. C1:array [0..1] of double=(0.5,0.5);
    13. C2:array [0..7] of word=(10,10,10,10,10,10,10,10);
    14. C3:array [0..7] of word=(20,20,20,20,20,20,20,20);
    15. begin
    16.    Vv:=V-1;
    17.    Tt:=T-1;
    18.    VremPerem:=0;
    19.  
    20. repeat
    21. asm
    22.  
    23.  
    24. LEA     eax, Mas1        ;// Загружаем адрес Mas1
    25. LEA     edx, Mas2        ;// Загружаем адрес Mas2
    26.  
    27. MOV     ecx,V*T          ;// Всего чисел
    28. SHR     ecx, 3           ;// Всего циклов V*T/8
    29.  
    30.                          ;// будем обробатывать сразу по 8 чисел
    31.  
    32. MOVDQU  xmm7,[C1]        ;// Константы 0.5
    33. MOVDQU  xmm6,[C2]        ;// Константы 10
    34. MOVDQU  xmm5,[C3]        ;// Константы 20
    35.  
    36. @Loop:
    37. MOVDQU  xmm0,[eax+0]     ;// Загружаем числа
    38. MOVDQU  xmm1,[eax+16]
    39. MOVDQU  xmm2,[eax+32]
    40. MOVDQU  xmm3,[eax+48]
    41.  
    42. CMPLTPD xmm0,xmm7       ;// Сравниваем числа
    43. CMPLTPD xmm1,xmm7
    44.  
    45. PACKSSDW xmm0,xmm1      ;// Пакуем числа
    46.  
    47. CMPLTPD xmm2,xmm7
    48. CMPLTPD xmm3,xmm7
    49.  
    50. PACKSSDW xmm2,xmm3
    51. PACKSSDW xmm0,xmm2      ;// Пакуем числа окончательно в Word
    52.  
    53. MOVDQA  xmm1,xmm0       ;//Сравниваем
    54.  
    55. PAND    xmm0,xmm6
    56. PANDN   xmm1,xmm5
    57. POR     xmm0,xmm1
    58.  
    59. MOVDQU  [edx],xmm0
    60.  
    61. ADD   eax,8*8        ;// 8 чисел Double
    62. ADD   edx,8*2        ;// 8 чисел Word
    63. LOOP  @Loop
    64. end;
    65. Inc(VremPerem);
    66. Until VremPerem =  10; // Вот реальное число
    67. end;
     
  17. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    t00x
    Если, например, двухмерный статический массив хранится по строкам (сначала идёт первая строка, затем вторая, третья и т.д.), то для обращения к элементу [x, y] массива Mas[0..100, 0..100] с элементами типа Integer необходимо сделать следующие вычисления:

    ItemPtr:= @Mas + x * 101 * SizeOf(Integer) + y * SizeOf(Integer)

    Учитывая, что и число элементов в массиве (длина строки -- 101), и размер элемента (SizeOf(Integer)) для статического массива известны на этапе компиляции, код получается очень простой.

    Если же мы имеем дело с динамическим массивом Mas : array of array of Integer, то для доступа к элементу Max[x][y] код нужен другой:

    RowPtr:= Mas^.ArrayPtr + x * SizeOf(DynArrayDescriptor);
    ItemPtr:= RowPtr^.ArrayPtr + y * SizeOf(Integer);

    Сам Mas реально является не массивом, а описателем динамического массива (DynArrayDescriptor), в котором хранятся как минимум три величины: текущий размер массива в элементах, указатель на область памяти, где хранятся сами элементы (у меня это поле обозвано ArrayPtr), и размер элемента.

    Соответственно, если у нас динамический двухмерный массив, то технически он является динамическим массивом динамических массивов (что проявляется и в его объявлении: array of array of). Чтобы обратиться к конкретному элементу, сначала надо определить адрес "внутреннего" массива, прочитав информацию из дескриптора внешнего, и лишь затем определить адрес элемента, что и делают два указанных выше оператора. При этом, естественно, может включаться код для контроля выхода за границы массива (зависит от настроек компилятора).

    В общем, код будет совершенно иным, чем при доступе к статическому массиву, а каким конкретно -- надо тщательно разбираться с реализацией динамических массивов Delphi.
     
  18. Anton

    Anton New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2007
    Сообщения:
    13
    Pavia,SII, спасибо за наводки, буду разбираться...с ходу что то не понял...примерчик...
     
  19. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    О Delphi 7.
    Начнем пожалуй с указателей Pointer и его производные. Указатель это структура. Когда мы выделяем память через
    GetMem(p,Size), то размер выделенной области записывается перед выделенной областью.
    Освобождение пишется как FreeMem(p) size здесь нету. Даже если вы напишети FreeMem(p, Size) то второй параметр не будет использоваться.
    pointer = Addr
    (addr-4)^=size+выравнивание на гнарице 4

    Теперь к динамическим массивам. Динамический массив это тоже структура. Вернее это указатель на структуру.
    Структура динамического массива такова.
    -8 Ref число ссылок
    -4 Length длина
    0 elements элементы массива.

    Размер элемента не храниться в этой структуре. Он хрониться во вспомогательной структуре DynArrayTypeInfo и учитывается при компиляции, где надо подставляется размер элемента при компиляции. Где надо идет ссылка на структуру DynArrayTypeInfo .
     
  20. SII

    SII Воин против дзена

    Публикаций:
    0
    Регистрация:
    31 окт 2007
    Сообщения:
    1.483
    Адрес:
    Подмосковье
    Pavia
    Спасибо за инфу -- самому ковыряться лень :) В общем, всё так, как я и предполагал, хотя в деталях и несколько по-другому реализовано. Однако мораль та же самая: на низком уровне (ассемблере) обращаться к динамическому массиву точно так же, как к статическому, нельзя.