Есть такой рабочий код: Code (Text): // af.h typedef struct _AF_DATA { unsigned long int h; double * x; double * y; //double * w; } AF_DATA, * PAF_DATA; extern AF_DATA af_data; typedef double _fastcall (* fptr)(void); extern const fptr ptr_af; double _fastcall _af(void); #define af_g(x) (__emit__(0xDD, 0x05, &x), _af()) // только для глобальных x и static x #define af_l(x) (__emit__(0xDD, 0x85, &x), _af()) // только для локальных x // af.cpp #include "af.h" AF_DATA af_data; #pragma warning (disable : 4035) __declspec(naked) double _fastcall _af(void) // Аппаратная функция { asm { push EDX mov EDX, dword ptr [af_data].(_AF_DATA)x // EDX = &x FLD qword ptr [EDX] // FLD x[0] FCOMIP ST, ST(1) // xmin > x jae @zero1 push EAX mov EAX, dword ptr [af_data].(_AF_DATA)h // high = af_data.h FLD qword ptr [EDX + 8*EAX] // FLD x[high] FCOMIP ST, ST(1) // xmax < x jbe @zero2 push EAX // h = high push 0 // l = 0 @bisec: dec EAX // *h -> h - 1 cmp EAX, dword ptr [ESP] // h - l == 1 je @laprx // if true then EAX == l == *h inc EAX // h -> *h + 1 add EAX, dword ptr [ESP] // EAX = h + l shr EAX, 1 // EAX = (h + l)/2 FLD qword ptr [EDX + 8*EAX] // FLD x[(h + l)/2] FCOMIP ST, ST(1) // x[(h + l)/2] < x ja @h mov dword ptr [ESP], EAX // lower // l = (h + l)/2 mov EAX, dword ptr [ESP + 0x04] // EAX = h jmp @bisec @h: mov dword ptr [ESP + 0x04], EAX // higher // h = (h + l)/2 jmp @bisec @laprx: FLD qword ptr [EDX + 8*EAX] // xl, x FSUB ST(1), ST // xl, x - xl FLD qword ptr [EDX + 8*EAX + 0x08] // xh, xl, x - xl FSUBRP ST(1), ST // xh - xl, x - xl mov EDX, dword ptr [af_data].(_AF_DATA)y // EDX = &y FDIVP ST(1), ST // (x - xl)/(xh - xl) FLD qword ptr [EDX + 8*EAX] // yl, (x - xl)/(xh - xl) FLD qword ptr [EDX + 8*EAX + 0x08] // yh, yl, (x - xl)/(xh - xl) pop EAX pop EAX pop EAX pop EDX FSUB ST, ST(1) // yh - yl, yl, (x - xl)/(xh - xl) FMULP ST(2), ST // yl, (yh - yl)*(x - xl)/(xh - xl) FADDP ST(1), ST // yl + (yh - yl)*(x - xl)/(xh - xl) RET @zero2: pop EAX @zero1: pop EDX FSTP ST FLDZ // out of table => 0.0 RET } } #pragma warning (default : 4035) На C++Builder 6. Делает следующее: линейно интерполирует в таблице, вне - функция возвращает нуль. Таблица находится в af_data.x af_data.y. Поиск в таблице - бинарный (или как его там... bisection из Numerical Recipes in C). Параметр уже лежит в ST(0). Единственное, что здесь относится к оптимизации - это воткнуты инструкции матсопроцессора перед RET/после pop в соответствие с выводами, сделанными из http://www.wasm.ru/forum/viewtopic.php?pid=327834#p327834. Какие есть недочёты явные и как это всё оптимизировать? Функция вызывается Очень много раз за время решения задачи оптимизации - является частью целевой функции.
Вызывается конечно так, что при каждом следующем вызове аргумент растёт. Но от этого не легче - функция вызывающая так устроена, что нет возможности ввести параметр, помнящий, каков аргумент был на предыдущем вызове. Так что по-сути можно считать, что функция вызывается в методе Монте-Карло =).
По общепринятому соглашению о вызовах функции могут свободно использовать\изменять регистры EAX,EDX и ECX, поэтому пушить\попить EAX и EDX да еще по нескольку раз - незачем, и уж тем более юзать локальную переменную [ESP], когда есть свободный регистр ECX
Функция вызывается из динамически скомпилированной функции типа _fastcall Borland. Та в свою очередь имеет два параметра - EAX и EDX. Один из которых указывает на массив с константами, другой - на массив с переменными. Функция использует только (исключительно) комманды работы с матсопроцессором, записывая в ESP - 8, ESP - 16, ESP - 24... параметры, переполняющие стек ST. Потом забирает. При компиляции функция _af может быть в любом месте выражения, то есть если будет так "a + @(x) + b", (@() - так она обозначается у меня в компиляторе), то при разборе строки справа налево регистры EAX и EDX будут испорчены к моменту вызова переменной a (для того чтобы не затёрлись запушеные параметры делается sub ESP, n*8 перед и add ESP, n*8 после). Вывод: пушить - надо. В методе оптимизации (Хука-Дживса) используются ВСЕ регистры (EAX, EDX, EBX, ECX). Трогать их в вызываемых процедурах нельзя. Ну или тоже пушить. Думаю стек кэшируется, так что можно и поиспользовать локальные переменные, так? Ну исправить на ECX и EBX например тоже не долго. Может есть какие-то существенные фишки с порядком инструкций?
Code (Text): pop EAX pop EAX pop EAX pop EDX ? =) это)? Вообще-то первые два раза попаются совсем не для надёжности))), а для того чтобы ESP восстановить. Они вроде две в сумме короче и быстрее, чем add ESP, 8?
Если хочешь юзать свои нестандартные конвенции вызова, то пушь себе наздоровье А при стандартном соглашении вызывющий код сам должен знать, что EAX и EDX могут затереться и в случае необходимости запушить их до вызова функции и воостановить после - "от перестановки мест слагаемых сумма не меняется", т.е. что внутри ф-и сохранять\восстанавливать, что снаружи - без особой разницы, но так уж принято, что заботиться о сохранении EAX,EDX и ECX должен сам вызывающий код
Да, всё так. Вызывающий код, повторяю, генерируется динамически. Так что мне проще не в компиляторе вписывать, чтобы он в массив байтов добавлял 0x50, а в этой процедуре написать push EAX. Если конечно отсутствие работы со стеком между call и ret увеличит скорость ret'а, то не поленюсь перенести это. Кроме того дериктива _fastcall в определение функции __declspec(naked) double _fastcall _af(void) роли не играет. Играла бы, если бы было __declspec(naked) double _fastcall _af(a, b) , тогда бы не смотря на naked в точке входа был бы код mov [EBP - 08h], EDX mov [EBP - 04h], EAX , несмотря на то, что под них в фрейме не выделялось место (таков уже Borland C++ 6). Просто рудимент. Эти мои как раз в моём компиляторе очень даже стандартные - я сам придумываю их и им же следую и учитываю их. Мои стандарты. Вопросы то были про оптимизацию, при учёте что окружение моей функции такое, какое я указал. И вообще про оптимизацию даже алгоритма. Может я ошибся с выбором способа поиска в таблице?? Может бинарный поиск - не лучшее? Я ж Х.З. (Хочу знать). Кстати, вызывающий код их не пушит, а переменные во фрейме под них использует (это не одно и то же), через указатель базы адресуя. Всегда. Потому что должны быть доступны из любой точки кода в области видимости без рытья в стеке. Насчёт Code (Text): pop EAX pop EAX : Borland C++ 6 поднимает стек таким же образом для 1 дворда, только ECX использует.
так получается оптимальнее? Code (Text): __declspec(naked) double _fastcall _af(void) // Àïïàðàòíàÿ ôóíêöèÿ // @ { asm { push EDX mov EDX, dword ptr [af_data].(_AF_DATA)x // EDX = &x FLD qword ptr [EDX] // FLD x[0] FCOMIP ST, ST(1) // xmin > x jae @zero1 push EAX mov EAX, dword ptr [af_data].(_AF_DATA)h // high = af_data.h FLD qword ptr [EDX + 8*EAX] // FLD x[high] FCOMIP ST, ST(1) // xmax < x jbe @zero2 push ECX mov ECX, EAX // h = high push EBX xor EBX, EBX // l = 0 @bisec: dec EAX // *h -> h - 1 cmp EAX, EBX // h - l == 1 je @laprx // if true then EAX == l == *h inc EAX // h -> *h + 1 add EAX, EBX // EAX = h + l shr EAX, 1 // EAX = (h + l)/2 FLD qword ptr [EDX + 8*EAX] // FLD x[(h + l)/2] FCOMIP ST, ST(1) // x[(h + l)/2] < x ja @h mov EBX, EAX // lower // l = (h + l)/2 mov EAX, ECX // EAX = h jmp @bisec @h: mov ECX, EAX // higher // h = (h + l)/2 jmp @bisec @laprx: FLD qword ptr [EDX + 8*EAX] // xl, x FSUB ST(1), ST // xl, x - xl FLD qword ptr [EDX + 8*EAX + 08h] // xh, xl, x - xl FSUBRP ST(1), ST // xh - xl, x - xl mov EDX, dword ptr [af_data].(_AF_DATA)y // EDX = &y FDIVP ST(1), ST // (x - xl)/(xh - xl) FLD qword ptr [EDX + 8*EAX] // yl, (x - xl)/(xh - xl) FLD qword ptr [EDX + 8*EAX + 08h] // yh, yl, (x - xl)/(xh - xl) pop EBX pop ECX pop EAX pop EDX FSUB ST, ST(1) // yh - yl, yl, (x - xl)/(xh - xl) FMULP ST(2), ST // yl, (yh - yl)*(x - xl)/(xh - xl) FADDP ST(1), ST // yl + (yh - yl)*(x - xl)/(xh - xl) RET @zero2: pop EAX @zero1: pop EDX FSTP ST FLDZ // out of table => 0.0 RET } }
Может так сделать? Code (Text): @@:mov eax,ecx repeat add eax,ebx ;i=(max+min)/2 shr eax,1 fld qword[edx+eax*8] ;if X[i]>x then fcomip st,st(1) ;max=i cmova ecx,eax ;else cmovna ebx,eax ;min=i lea eax,[ecx-1] cmp eax,ebx ;until max-1=min jne @b
Меня по скорости оптимизация интересует FSUB [reg,reg] | 486 8-20 а FSTP reg | 486 3 FLDZ | 486 4 так что оставлю.
Dukales Ты надеешься еще где-то 486 найти ?! На современных компах парочка FSTP + FLDZ действительно чуть быстрее FSUB, но от силы на 1-2 такта, которые на фоне call+ret и пары непредсказанных переходов - вообще никакой роли не играют
Dukales Раз тебя "по скорости оптимизация интересует", то прежде чем заниматься мелочной экономией тактов не мешало бы на сам алгоритм взглянуть и попытаться основные тормоза убрать. Например, почему бы в таблице _AF_DATA не задать еще один массив double *dx предвычесленных значений 1/(x[i+1]-x) и соотв-но вместо тормозного деления использовать умножение на dx ? Или почему бы не использовать таблицу с равномерным шагом, если функция достаточно гладкая ?