Привет! Поразило одно обстоятельство. Код (Text): class A { float x; float y; float z; __fastcall A(void); __fastcall ~A(void); void __fastcall mul(void); }; void __fastcall A::mul(void) { z=x*y;//z=x+y неважно } class Bclass { A* a; __fastcall B(void); __fastcall ~B(void); }; пусть Код (Text): Bclass *B; B->a->z=B->a->x*B->b->y; интересно ,что это работает быстрее чем это: B->a->mul(); Мне всегда казалось, что лишнее обращение к памяти это плохо и поэтому всегда индексировал длинные ссылки. Теперь я в этом не уверен. Может быть это связано с вызовом функции? Но я не случайно использовал __fastcall. Короче, что-то непонятное! Пожалуйста, разясните ситуацию!
__fastcall влияет на порядок передачи параметров. так что в данном случае ты только тратиш время на вызов процедуры.
Вполне объяснимо. Во втором случае происходит два обращения к памяти для чтения переменных и еще одно для вызова функции, а, как известно, рассчет адреса (в данном случае функции) происходит долго, и обычное произведение, выполняемое командой процессора mul, выполняется намного быстрее. IMHO, в случае, когда нужен быстрый код, использовать классы не нужно.
Но я трачу время и на обращение к памяти, причем в данном случае три раза! В оригинале это умножение производиться в цикле, причем длина цикла-переменная класса А(извините, что не указал сразу). Так что обращений к памяти может быть сколько угодно(у меня порядка 100)! Нужели вызов функции-члена такая дорогая операция.
Да любое изменение code-flow серьезно сказываеться на производительность т.к. происходит перезаполнение кэша инструкций
AFAIK "прямые" call и jmp (и jcc в случае правильно предсказанного ветвления) не приводят к перезагрузке конвеера. А чем компилировалось и с какими опциями? --- А насчёт fastcall, то в данном случае он ничем не лучше thiscall.
Компилил TASM32 (С++Builder5) Code optimization-speed Instruction set-Pentium (У меня Pentium-3) Data Alignment-Quad Word Register variables-Automatic Floating point-fast Может нужны какие нибудь директивы? Project Options->TASM->Add directive: Подскажите. А насчет __fastcall, если нужно передать переменные, какие лучше значения или указатели? Насчет предсказания ветвлений, в моем коде(циклах) нет условных операторов да и Pentium3, наверное, не знает что это такое. Я по своей наивности, честно говоря, не думал, что между данными и инструкциями такая большая разница! Как оптимизировать вычисления на графах(сетях)? Согласитесь, более объестную структуру трудно представить. Но с другой стороны, как показывает этот пример, процессоры еще пока не совсем заточены. С филосовской точки зрения получается ООП опередило железо?
gradient В билдере я не разбираюсь. А как ты меряешь время выполнения? Может просто в цикле крутишь это умножение? Если так, тогда понятно - компилятор вынес его из цикла как независимое от кол. итераций (и, скорее всего, вообще убрал цикл). Т.е. умножение вероятно выполняется один раз, в отличие от вызова ф-ции. не понял.
Время замеряю так: Код (Text): int TICS; asm { RDTSC MOV [TICS],EAX } здесь либо одно либо другое asm { RDTSC SUB EAX,[TICS] mov [TICS],eax } время выполнения-TICS Пример утрирован для наглядности. В реальности вычисляется скалярное произведение, так что в цикле есть индексы да и в ассемблерном листинге это видно. я смотрел. Непонятно все таки! Я имел ввиду вопрос-что лучше передавать в __fastcall-функции в качестве аргумента значение или адрес? например: Код (Text): __fastcall funct(float &) или __fastcall funct(float ) с точки зрения производительности. Или это все равно? Заранее благодарен всем за любой совет по производительности. Вопросы не праздные. Программа может работать часами при полной загрузке CPU. Памяти много не использует. Так что борюсь за каждую наносекунду!
gradient Посмотри для начала на код, который билдер сгенерил. Он вообще-то в этом смысле того... не очень...
Наверное он не очень. Но дело в другом. Я сравниваю два кода скомпилированных одним компилятором! Один код вазывает функцию-член, а другой код носится по памяти как угорелый, но выполняется быстрей!? Что-то не то. Если дело в вызове функции, то как с этим бороться? Мне всетаки кажется дело в другом... Может быть прав nobodyzzz Сейчас подумал, может быть вызвать функцию через указатеь на нее, а не как член класса. Попробую.
Ustus дело говорит! Не глядя на ассемблерный код, пытаться понять почему производительность страдает - гадать на кофейной гуще.
Вариант с вызовом ф-ции должен по любому работать медленнее прямого умножения. Только разница должна быть небольшой, а у тебя, видимо, в разы получается. Может быть ты думаешь, что компилятор делает 3 раза двойное разыменовывание указателя? Вовсе нет, адрес объекта класса А загружается в регистр 1 раз и доступ к множителям x, y, z выглядит наподобие [eax+offset_z]=[eax+offset_x]*[eax+offset_y]
вот чего наделал мой билдер: Код (Text): .586p ifdef ??version if ??version GT 500H .mmx endif endif model flat ifndef ??version ?debug macro endm endif ?debug S "main.cpp" ?debug T "main.cpp" _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' @A@mul$qqrv segment virtual align 2 @@A@mul$qqrv proc near ?live16385@0: ; ; void __fastcall A::mul(void) ; { ; z=x*y;//z=x+y неважно ; @1: fld dword ptr [eax] fmul dword ptr [eax+4] fstp dword ptr [eax+8] ; ; } ; @2: ret @@A@mul$qqrv endp @A@mul$qqrv ends _TEXT ends _TEXT segment dword public use32 'CODE' @f$qp1Bt1 segment virtual align 2 @@f$qp1Bt1 proc near ?live16386@0: ; ; void f(B* B1, B* B2) ; @3: push ebp mov ebp,esp mov eax,dword ptr [ebp+8] ; ; { ; B1->a->z=B1->a->x*B1->a->y; ; ?live16386@16: ; EAX = B1 mov edx,dword ptr [eax] fld dword ptr [edx] fmul dword ptr [edx+4] fstp dword ptr [edx+8] ; ; B2->a->mul(); ; ?live16386@32: ; mov eax,dword ptr [ebp+12] mov eax,dword ptr [eax] call @@A@mul$qqrv ; ; } ; @4: pop ebp ret @@f$qp1Bt1 endp @f$qp1Bt1 ends _TEXT ends _TEXT segment dword public use32 'CODE' _TEXT ends ?debug D "main.cpp" 13643 43119 end видим, что код идентичен за исключением call. а если A::mul() явно или неявно объявить inline то код будет абсолютно идентичен: Код (Text): .586p ifdef ??version if ??version GT 500H .mmx endif endif model flat ifndef ??version ?debug macro endm endif ?debug S "main.cpp" ?debug T "main.cpp" _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' @f$qp1Bt1 segment virtual align 2 @@f$qp1Bt1 proc near ?live16385@0: ; ; void f(B* B1, B* B2) ; @1: push ebp mov ebp,esp mov eax,dword ptr [ebp+8] ; ; { ; B1->a->z=B1->a->x*B1->a->y; ; ?live16385@16: ; EAX = B1 mov edx,dword ptr [eax] fld dword ptr [edx] fmul dword ptr [edx+4] fstp dword ptr [edx+8] ; ; B2->a->mul(); ; ?live16385@32: ; mov eax,dword ptr [ebp+12] mov eax,dword ptr [eax] fld dword ptr [eax] fmul dword ptr [eax+4] fstp dword ptr [eax+8] ; ; } ; @2: pop ebp ret @@f$qp1Bt1 endp @f$qp1Bt1 ends _TEXT ends _TEXT segment dword public use32 'CODE' _TEXT ends ?debug D "main.cpp" 13643 43387 end
Вот что наделал мой Builder: Код (Text): для T->z=0; for(int i=T->nn;i>=0;i--) { T->z+=T->x[i]*T->y[i]; } ?live16414@176: ; EBX = this, ESI = tt mov ecx,dword ptr [ebx+160772] xor eax,eax mov dword ptr [ecx+12],eax @88: mov eax,esi test eax,eax jl short @90 ?live16414@208: ; EBX = this, EAX = i @89: mov edx,dword ptr [ebx+160772] mov ecx,dword ptr [edx+4] fld dword ptr [ecx+4*eax] mov ecx,dword ptr [edx+8] fmul dword ptr [ecx+4*eax] fadd dword ptr [edx+12] fstp dword ptr [edx+12] @91: @92: dec eax test eax,eax jge short @89 @90: Код (Text): а для T->mul(); где mul(void) { z=0; for(int i=nn;i>=0;i++) z+=x[i]*y[i]; } ?live16391@16: ; EAX = this xor edx,edx mov dword ptr [eax+12],edx mov ebp,esp @15: mov edx,dword ptr [eax] test edx,edx jl short @17 ?live16391@64: ; EDX = i, EAX = this @16: mov ecx,dword ptr [eax+4] fld dword ptr [ecx+4*edx] mov ecx,dword ptr [eax+8] fmul dword ptr [ecx+4*edx] fadd dword ptr [eax+12] fstp dword ptr [eax+12] @18: dec edx test edx,edx jge short @16 @17: видно, что в первом случае в цикле добавляется обращение к вложенному объекту: Код (Text): mov edx,dword ptr [ebx+160772] тоесть, чем больше длина цикла, тем эффективней должен быть второй вариант. И даже если велики расходы на вызов функции(?), то при некоторой ,достаточно большой длине цикла эти расходы будут почти незаметны. На самом деле в этом примере так и происходит!(неудачный пример). На самом деле у меня более сложный вариант. Тройная вложенность объектов-слоистая структура(нейросеть). Там в потоке вытисляется градиент нейросети(обратное распростронение ошибки). И там все с точностью наоборот!? Хотя принципиальной разницы нет.
gradient Весь парадокс - в "тупости" компилятора, который в теле цикла грузит неизменяющиеся переменные, вместо того чтобы держать их в регистрах. Если речь идет о массивах, то лучше реализовать mul() на асме, исключив ненужные загрузки\выгрузки (в т.ч. z), да еще и распараллелить вычисление z в 2\4 раза Для одиночного же умножения z=x*y разумеется вызов функции (не inline) будет выполняться дольше прямого умножения PS: а разве в C++ нет оператора with, чтобы не пудрить мозги себе и компилятору такими нецензурными выражениями B->a->z=B->a->x*B->a->y ? ))
Конечно все это написано у меня на ассемблере. В z не перезаписывается. Компиляторы конечно тупят. Но вопрос остается. Ведь компилится все одним компилятором! А насчет with-я никогда им не пользуюсь. Зачем это нужно когда есть стуктура и класс. Я привел утрированный пример для простоты. Короче, я окончательно запутался. Может нужно как то выравнивать инструкции (вызовы функций)?