На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
  
> Передача по ссылке в ассемблер и соглашения о вызовах.
    Есть рабочий код (Лазарус64бита):
    ExpandedWrap disabled
       {$ASMMODE INTEL}
      procedure DivMod(numerator, denominator, quotient: DWord);
      begin
        asm
        mov rdx, 0;
        mov rax, [numerator];
        mov rcx, [denominator];
        div rcx;
        mov [quotient], rax;
        mov [denominator], rdx;
        mov [numerator], rcx;
        end;
      end;

    Параметры процедуры здесь не объявлены как var, поэтому не передадутся во вне, но зато доступны из ассемблера напрямую. Если их объявить как var, то код перестанет работать, потому что значения надо будет достать по ссылке. Обойти это можно, создав 3 локальные переменные и работая в ассемблере через них, что на мой взгляд - избыточно. Второй вариант, доставать значение по ссылке через промежуточный регистр, что тоже не особо красиво, примерно так:
    ExpandedWrap disabled
       
      mov rax, [numerator];
      mov rax, [rax];

    и потом, в обратную сторону.
    Соответственно вопрос, возможно проблема решается каким-нибдуь соглашением о вызовах, типа register, assembler, cdecl? Хотелось бы, что бы в коде происходило минимум лишних телодвижений, в том числе и самим компилятором, поскольку используются всего 3 регистра. Помогите правильно переписать на значения по ссылке, а то я боюсь что-нить где-нить не сохранить/восстановить или сделать лишнее.


    P.S. Кстати, не знаете, на Королевстве Дельфи при попытке отправить вопрос выдаёт ошибку, так уже больше года, там вообще есть кто живой?
    Сообщение отредактировано: KIA -
      Откуда взялась такая процедура и какой от неё прок?
      В дельфийской, естественно:

      procedure DivMod(Dividend: UInt64; Divisor: UInt64; var Result, Remainder: UInt64); overload;

      ExpandedWrap disabled
        {$ELSEIF Defined(CPUX64)}
        asm
                MOV     R10,RDX
                MOV     RAX,RCX
                XOR     EDX,EDX
                DIV     R10
                MOV     [R8],RAX
                MOV     [R9],RDX
        end;
        Цитата MBo @
        Откуда взялась такая процедура и какой от неё прок?

        Вычисление подходящих дробей, не нашёл в языке процедуры, которая одновременно частное и остаток предоставляет, хотя инструкции процессора именно так и делают. Можно пояснить, по Асму, какие регистры можно использовать и почему не надо их сохранять\восстанавливать, а так же зачем тут оверлоад?
          Про регистры вот из хелпа


          32-bit
          In general, the rules of register use in an asm statement are the same as those of an external procedure or function. An asm statement must preserve the EDI, ESI, ESP, EBP, and EBX registers, but can freely modify the EAX, ECX, and EDX registers. On entry to an asm statement, EBP points to the current stack frame and ESP points to the top of the stack. Except for ESP and EBP, an asm statement can assume nothing about register contents on entry to the statement.

          64-bit
          In line with the x64 Application Binary Interface (ABI), the contents of the following registers must be preserved and restored within inline assembly functions: R12, R13, R14, R15, RDI, RSI, RBX, RBP, RSP, XMM4, XMM5, XMM6, XMM7, XMM8, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, and XMM15.

          The first four parameters to inline assembler functions are passed via RCX, RDX, R8, and R9 respectively, except for floating-point arguments which use XMMO, XMM1, XMM2, XMM3. The math coprocessor is not normally used from x64 code. Registers used for function parameters can be modified freely.

          Оверлоад - поскольку есть еще процедура для Word со старых времён.

          P.S. На Королевстве при ошибке отправки ответа он часто все-таки отправляется, т.е. не стоит посылать заново - будет куча дубликатов, достаточно обновить страницу. Как с вопросами обстоит дело - не знаю.
          Сообщение отредактировано: MBo -
            Цитата KIA @
            не нашёл в языке процедуры, которая одновременно частное и остаток предоставляет, хотя инструкции процессора именно так и делают.

            DivMod есть в модуле Math. Просто добавь uses Math
              Я вот такое себе наваял
              ExpandedWrap disabled
                // Деление с остатком с одновременным получением частного и остатка.
                // Удобнее версии из Math, т.к. оперирует нативными числами.
                procedure DivMod(Dividend, Divisor: NativeUInt; var Result, Remainder: NativeUInt);
                {$IFDEF PUREPASCAL}
                begin
                  Result := Dividend div Divisor;
                  Remainder := Dividend mod Divisor;
                end;
                {$ELSE !PUREPASCAL}
                asm
                  {$IFDEF CPU32}
                  //  =>
                  //   EAX: Dividend
                  //   EDX: Divisor
                  //   ECX: [Result]
                  //  <=
                  //   EAX: [Result]
                  //   EDX: [Remainder]
                  PUSH    EBX
                  MOV     EBX, EDX       // EBX := Divisor
                  XOR     EDX, EDX       // EDX (старшее слово) := 0
                  DIV     EBX            // EAX := (EAX:EDX) div EBX, EDX := (EAX:EDX) div EBX
                  MOV     [ECX], EAX     // Result := EAX
                  MOV     EBX, Remainder // Remainder := EDX
                  MOV     [EBX], EDX     //
                  POP     EBX
                  {$ENDIF}
                 
                  {$IFDEF CPU64}
                  MOV     R10,RDX
                  MOV     RAX,RCX
                  XOR     EDX,EDX
                  DIV     R10
                  MOV     [R8],RAX
                  MOV     [R9],RDX
                  {$ENDIF}
                 
                  {$IF NOT DEFINED(CPU32) AND NOT DEFINED(CPU64)}
                  Unknown architecture
                  {$IFEND}
                end;
                {$ENDIF !PUREPASCAL}


              Для 64-битных чисел под 32-битные CPU не стал мудрить, уж больно едрёный код получается
              Сообщение отредактировано: Fr0sT -
                :blush: Вроде бы лучше поменять местами "MOV [ECX], EAX" и "MOV EBX, Remainder", ибо последняя завязана на последующую "MOV [EBX], EDX" и может случиться небольшой простой...
                  Цитата Fr0sT @
                  Вроде бы лучше поменять местами "MOV [ECX], EAX" и "MOV EBX, Remainder", ибо последняя завязана на последующую "MOV [EBX], EDX" и может случиться небольшой простой...

                  Можно и поменять, но современные процы с переупорядочиванием микроопераций сами все переставят, т.к. "MOV [ECX], EAX" "завязана" на результат предыдущей DIV, а "MOV EBX, Remainder" от нее не зависит, и соотв-но м.б. выполнена еще до окончания DIV (потенциально - как только кол-во оставшихся микроопераций DIV станет меньше общей длины очереди планировщика OoO).

                  Добавлено
                  Цитата Fr0sT @
                  // Удобнее версии из Math, т.к. оперирует нативными числами.
                  procedure DivMod(Dividend, Divisor: NativeUInt; var Result, Remainder: NativeUInt);

                  Для операции деления это "удобство" чревато дополнительными тормозами (по сравнению с кучей overload версий), т.к. DIV (в общем случае) выполняется тем быстрее, чем меньше размер операндов. Поэтому при использовании тормозных операций типа DIV лучше использовать не NativeUInt, а минимально достаточный размер операнда\переменной.
                  Сообщение отредактировано: leo -
                    Цитата leo @
                    Для операции деления это "удобство" чревато дополнительными тормозами (по сравнению с кучей overload версий), т.к. DIV (в общем случае) выполняется тем быстрее, чем меньше размер операндов. Поэтому при использовании тормозных операций типа DIV лучше использовать не NativeUInt, а минимально достаточный размер операнда\переменной.

                    Ты имеешь в виду, сделать варианты под 1- и 2-байтовые целые? А есть ли разница? Мне кажется, она настолько незначительна, что на фоне затрат на вызов функции полностью потеряется.
                      Цитата Fr0sT @
                      Ты имеешь в виду, сделать варианты под 1- и 2-байтовые целые? А есть ли разница?

                      На современных компах разница в латентности DIV для 1-2-4 байтных целых действительно незначительная. А вот между r32 и r64 - весьма существенная. Поэтому с точки зрения быстродействия DivMod нужны overload варианты для UInt32=Cardinal и для UInt64.
                        Цитата leo @
                        между r32 и r64 - весьма существенная

                        Даже для х64? Потому что нативное целое вроде бы всегда самое быстрое.

                        Добавлено
                        Тем более что все равно параметры передаются и результат возвращается в регистрах нативного размера. Ну а если имеется в виду вариант с uint64 под x32, то я не стал заморачиваться с асмом (уж очень суровый код в Math :) ) и сделал по-тупому:
                        ExpandedWrap disabled
                          {$IFDEF CPU32}
                          // mod в 32-битной версии для 64-битных чисел очень медленный (в 20 раз медленней,
                          // чем sub + div + mul!). Поэтому две функции-заменялки оператора mod и функции DivMod.
                          function Mod64(A, B: Int64): Int64;
                          begin
                            Result := A - (A div B)*B;
                          end;
                           
                          // Деление с остатком с одновременным получением частного и остатка.
                          procedure DivMod(Dividend: UInt64; Divisor: UInt64; var Result, Remainder: UInt64);
                          begin
                            Result := Dividend div Divisor;
                            Remainder := Dividend - Result*Divisor;
                          end;
                          {$ENDIF}
                          Цитата Fr0sT @
                          Даже для х64? Потому что нативное целое вроде бы всегда самое быстрое.

                          Удивительная наивность в понимании "нативность" (каламбурчик ;))
                          Во-первых, операция деления в принципе (чисто алгоритмически), выполняется тем дольше, чем больше размер операндов. Особенно наглядно это проявляется в сравнительно бесхитростных Pentium II, III и ниже (см. instruction_tables.pdf by Agner Fog research). Хотя реально время выполнения DIV зависит не от номинального размера операндов, а от реальных значений и соотношения значений делимого и делителя (которые становятся известны только во время исполнения, т.е. через 15 и более тактов = длине конвеера от стадии декодирования до исполнения). Это было учтено в последних моделях Pentium М и затем перекочевало в современную архитектуру Intel Core. Поэтому в современных x86(-64) минимальная задержка DIV для 1-2-4 байтных операндов, действительно различается мало (а максимальная зависит от реальных значений операндов). Однако для 64-битных операндов в x86-64 разница в задержке по сравнению с 32-битными (пока) остается существенной (см. упомянутую instruction_tables).

                          PS: Теоретически и умножение, и даже сложение чисел большой разрядности должно выполняться дольше. Однако за счет повышения быстродействия (не номинальной частоты, а уменьшения переходных процессов в транзисторах\вентилях) и схемотехнических ухищрений удается уложить 64-битные операции в те же такты, что и 32-битные (плюс возможные неявные хитрости - например, основной результат умножения 64-бит в RAX вычисляется за то же кол-во тактов, что и 32-битный в EAX, но в случае переполнения RAX результат в RDX выдается на такт позже).

                          Во-вторых, насколько я понимаю, речь идет не о 64-битной архитектуре вообще, а конкретно об x86-64 (= AMD-64 и Intel 64), которая по существу является "примочкой" (расширением) над IA-32 со всеми вытекающими отсюда особенностями и ограничениями. В частности, она создавалась в первую очередь для расширения потенциально адресуемой памяти до 64-бит (и дополнительно для увеличения кол-ва регистров до 16 штук), а не просто для увеличения разрядности РОН до 64 бит, которая большинству приложений нафиг не нужна (см. дискуссию о "перспективах" 128-битных РОН). В итоге в x86-64 размер операнда по умолчанию составляет 32 бита, а любые операции с 64-битными регистрами используют префикс REX, что как-то не вяжется с традиционными представлениями о "нативности" в предыдущих 16- и 32-битных архитектурах, в которых "нативными" были те операции, которые не требовали никаких префиксов. В итоге злоупотребление 64-битными операциями в x86-64 ведет к увеличению размера кода за счет REX-префиксов, а злоупотребление 64-битными данными в памяти (особенно в массивах) - к неэффективному использованию ОЗУ и кэша, со всеми вытекающими отсюда последствиями по быстродействию. Вот вам и вся пресловутая "нативность" ;)

                          Добавлено
                          Цитата Fr0sT @
                          Тем более что все равно параметры передаются и результат возвращается в регистрах нативного размера.

                          Не все равно, т.к. в x86-64 наконец-то отказались от доступа к старшей части 64-битных регистров, поэтому для беззнаковых данных с реальным размером до 32 бит регистры EAX и RAX по сути эквивалентны. Т.е. запись типа MOV EAX,EDX по сути эквивалента MOV RAX, RDX (с автоматическим обнулением старших разрядов RAX) но 1) короче по размеру кода, т.к. не требует лишнего байта префикса, 2) не вызывает никаких partial register stall при последующем обращении к RAX. Поэтому для DivMod без разницы, передается ли UInt32 через 32-битный или 64-битный регистр, но для (потенциального) сокращения времени выполнения лучше использовать не DIV r64, а DIV r32 с использованием результата хоть из EDX:EAX, хоть RDX:RAX - без разницы.
                          Сообщение отредактировано: leo -
                            leo, спасибо за просвещение! В очередной раз снимаю шляпу перед гуру :)
                            Вот что у меня в итоге получилось
                            ExpandedWrap disabled
                              procedure DivMod(Dividend, Divisor: UInt32; var Result, Remainder: UInt32);
                              asm
                                {$IFDEF CPU64}
                                MOV     R10D,EDX
                                MOV     EAX,ECX
                                XOR     EDX,EDX
                                DIV     R10D
                                MOV     [R8],EAX
                                MOV     [R9],EDX
                                {$ENDIF}
                                ...
                              end;

                            Разница по времени выполнения - 2521 у нативного против 1253 у 32-битного варианта.

                            Ну и финальный вариант
                            ExpandedWrap disabled
                              // Деление с остатком с одновременным получением частного и остатка.
                              // Удобнее версии из Math, т.к. оперирует числами одинаковой разрядности
                              // ! Под CPU64 вариант с 32-битными операндами быстрее почти в 2 раза. Поэтому
                              // не нативные типы, а явное указание размера
                               
                              procedure DivMod(Dividend, Divisor: UInt32; out Result, Remainder: UInt32);
                              {$IFDEF PUREPASCAL}
                              begin
                                Result := Dividend div Divisor;
                                Remainder := Dividend mod Divisor;
                              end;
                              {$ELSE !PUREPASCAL}
                              asm
                                {$IFDEF CPU32}
                                //   EAX: Dividend
                                //   EDX: Divisor
                                //   ECX: @Result
                                PUSH    EBX
                                MOV     EBX, Divisor
                                XOR     EDX, EDX       // EDX (старшее слово для div) := 0
                                DIV     EBX            // EAX := (EDX:EAX) div EBX, EDX := (EDX:EAX) mod EBX
                                MOV     [Result], EAX
                                MOV     EBX, Remainder // Remainder := EDX
                                MOV     [EBX], EDX     //
                                POP     EBX
                                {$ENDIF}
                               
                                {$IFDEF CPU64}
                                //   RCX (ECX): Dividend
                                //   RDX (EDX): Divisor
                                //   R8: @Result
                                //   R9: @Remainder
                                .NOFRAME
                                MOV     R10D, Divisor
                                MOV     EAX, Dividend
                                XOR     EDX, EDX       // RDX (старшее двойное слово для div) := 0
                                DIV     R10D           // EAX := (EDX:EAX) div R10D, EDX := (EDX:EAX) mod R10D
                                MOV     [Result], EAX
                                MOV     [Remainder], EDX
                                {$ENDIF}
                               
                                {$IF NOT DEFINED(CPU32) AND NOT DEFINED(CPU64)}
                                Architecture not implemented
                                {$IFEND}
                              end;
                              {$ENDIF !PUREPASCAL}
                               
                              procedure DivMod(Dividend, Divisor: UInt64; out Result, Remainder: UInt64);
                              // mod в 32-битной версии для 64-битных чисел очень медленный (в 20 раз медленней,
                              // чем sub + div + mul!). А asm код уж очень большой.
                              {$IFDEF CPU32}
                              begin
                                Result := Dividend div Divisor;
                                Remainder := Dividend - Result*Divisor;
                              end;
                              {$ENDIF}
                              {$IFDEF CPU64}
                              {$IFDEF PUREPASCAL}
                              begin
                                Result := Dividend div Divisor;
                                Remainder := Dividend mod Divisor;
                              end;
                              {$ELSE !PUREPASCAL}
                              asm
                                //   RCX: Dividend
                                //   RDX: Divisor
                                //   R8:  @Result
                                //   R9:  @Remainder
                                .NOFRAME
                                MOV     R10, Divisor
                                MOV     RAX, Dividend
                                XOR     RDX, RDX      // RDX (старшее двойное слово для div) := 0
                                DIV     R10           // RAX := (RDX:RAX) div R10, RDX := (RDX:RAX) mod R10
                                MOV     [Result], RAX
                                MOV     [Remainder], RDX
                              end;
                              {$ENDIF !PUREPASCAL}
                              {$ENDIF CPU64}
                              {$IF NOT DEFINED(CPU32) AND NOT DEFINED(CPU64)}
                              Architecture not implemented
                              {$IFEND}
                            Сообщение отредактировано: Fr0sT -
                              Цитата Fr0sT @
                              procedure DivMod(Dividend, Divisor: UInt64; out Result, Remainder: UInt64);
                              // mod в 32-битной версии для 64-битных чисел очень медленный (в 20 раз медленней,
                              // чем sub + div + mul!). А asm код уж очень большой.

                              В общем случае - да. Но можно оптимизировать частные случаи с Int64Rec(Divisor).Hi = 0.
                              ExpandedWrap disabled
                                procedure DivMod64_CPU32(Dividend, Divisor: UInt64; out Result, Remainder: UInt64);
                                begin
                                  //если Divisor <= 32 бит
                                  if Int64Rec(Divisor).Hi = 0 then
                                  asm
                                    push edi
                                    push esi
                                    mov edi, eax  //edi = @Result
                                    mov esi, edx  //esi = @Remainder
                                    mov ecx, [dword ptr Divisor] //ecx = Divisor.Lo
                                    mov edx, [dword ptr Dividend + 4] //edx = Dividend.Hi
                                    xor eax,eax
                                    mov [esi + 4], eax //-> Remainder.Hi = 0
                                    cmp edx,ecx //if Dividend.Hi >= Divisor.Lo then
                                    jb @@1
                                    mov eax,edx
                                    xor edx,edx
                                    div ecx  //eax = Result.Hi, edx - остаток Dividend.Hi < Divisor.Lo
                                @@1: //else
                                    mov [edi+4], eax  //-> Result.Hi = 0 или результат 1-го деления
                                    mov eax,[dword ptr Dividend] //eax = Dividend.Lo, edx = остаток Dividend.Hi < Divisor.Lo
                                    div ecx
                                    mov [edi], eax  //-> Result.Lo
                                    mov [esi], edx  //-> Remainder.Lo
                                    pop esi
                                    pop edi
                                  end
                                  else
                                  begin
                                    Result := Dividend div Divisor;
                                    Remainder := Dividend - Result*Divisor;
                                  end;
                                end;

                              При Divisor.Hi = 0 этот код дает выигрыш по скорости до 1.7-2 раз при Dividend.Hi = 0, и в 20 с лишним раз при Dividend.Hi > 0.
                              (Цифры для Delphi 7 на Intel Core i5-3xxx SandyBridge).
                                Толково! Жаль, Fastcode помер, а то можно было бы добавить в копилку.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0578 ]   [ 17 queries used ]   [ Generated: 29.03.24, 09:49 GMT ]