gradient А в чем собс-но вопрос ? Во-первых, ты же сам говоришь - "На самом деле в этом примере так и происходит!(неудачный пример)". Но других примеров ты не приводишь - только намеки на "более сложные варианты". О чем тут говорить ? Во-вторых, компилятор то один, но туповатый В варианте с mul() в функцию передается указатель this, который так и сидит в регистре EAX. А в первом варианте EBX это никакой не this, а вообще фиг знает что (похоже на линуксовую базу кода), а this по тупому грузится из [ebx+160772] на каждой итерации цикла, вместо того чтобы загрузиться один раз перед циклом (в дельфях кстати так и делается, а почему в билдере борманы так облажались не понятно Если тебе нужна скорость пиши свои циклы на асме, неизменяемые адреса храни в регистрах, копи z в регистре FPU и сохраняй один раз после выхода из цикла, еще лучше копи две частичные суммы (по четным и нечетным i) - будет в два раза быстрее (при 4-х суммах - в 3-4 раза быстрее). PS: Не знаю о каком "ненужном" with ты говоришь, но в паскале with упрощает работу со структурами и классами. Вместо того, чтобы писать B->a->z=B->a->x*B->a->y пишем with B->a do z:=x*y; А если поставить with перед циклом, то видимо и компилятору проще сообразить держать указатель на класс в регистре, а не грузить его на каждой итерации (хотя дельфи в отличие от билдера и так соображает
Вот часть оригинального кода. В нем заремарены места с вызовом функций, соответствующих незаремареным местам , помеченным "так лучше". _numl и _mn- массивы объектов. Код (Text): for(int l=n-1;l>1;l--) { for(int k=_NN[l-1]-1;k>=0;k--) { lay=&_numl[l]; //_s=lay->_mn[0].pa(k); так хуже _s=lay->_mn[0]._p[k]*lay->_mn[0].a[k]; так лучше for(int i=_NN[l]-1;i>0;i--) { // _s+=lay->_mn[i].pa(k); так хуже ee=&lay->_mn[i];//Здесь можно было бы поставить with _s+=ee->_p[k]*ee->a[k];так лучше } ee=&_numl[l-1]._mn[k]; ee->__c+=_konf*(ee->_c=ee->__dn*(_s+ee->_n));так лучше // ee->Cset(_konf,_s); так хуже p=ee->_p; pp=ee->__p; _s=ee->_c; for(int j=_NN[l-2]-1;j>=0;j--) { pp[j]+= _konf*(p[j]=_s*ee->neuleyer[j]._n); так лучше } // ee->Pset( _konf,_s); так хуже } } Код (Text): for(int j=_NN[l-2]-1;j>=0;j--) { pp[j]+= _konf*(p[j]=_s*ee->neuleyer[j]._n); так лучше } // ee->Pset( _konf,_s); так хуже } } Пусть не пугает сложность, ведь смыл тот же. Вопрос остается, потому что это работает быстрее, чем заремаренный вариант. Вместо того, чтобы писать with B->a do z:=x*y; пишем B->a->mul(); Насчет разворачивания циклов. Я это делал , ноничего заметного не получил. Я даже делал вариант с xmm регистрами, они быстро складывают и умножают, но очень долго загружаются из памяти!
Вот к стати мой asm-вариант скалярного произведения -линейная функция переделка Фога. Код (Text): float __fastcall neu::_fn(float *_x) { float v1; // float s=this.c; // for(int i=amax-1;i>=0;i--) s+=a[i]*_x[i]; // v1=this.vo*s; asm{ DSIZE = 4 // ; //data size mov edi, this mov eax, [edi].amax //MOV EAX, _NN //; number of elements mov esi, [[edi].a] // MOV ESI, [_a] //; pointer to a mov edi, [_x] // MOV EDI, [_x] //; pointer to x XOR ECX, ECX LEA ESI, [ESI+DSIZE*EAX] //; point to end of a SUB ECX, EAX //; -NN LEA EDI, [EDI+DSIZE*EAX] //; point to end of x JZ SHORT L3 //; test for amax = 0 FLD DSIZE PTR [EDI+DSIZE*ECX] //; start first calc. FMUL DSIZE PTR [ESI+DSIZE*ECX] //; A[0] * X[0] JMP SHORT L2 //; jump into loop ALIGN 16 L1: FLD DSIZE PTR [EDI+DSIZE*ECX] FMUL DSIZE PTR [ESI+DSIZE*ECX] //; A[i] * X[i] FADD } Код (Text): L2: INC ECX //; increment index JNZ L1 //; loop mov esi,this FLD DSIZE PTR [esi].c FADD FLD DSIZE PTR [ESI].vo FMUL FSTP v1 L3: } Если кто улучшит, благодарю.
Начет тупости С++Builder' a http://www.citforum.ru/programming/C_comp/ Что меня приятно(неприятно) удивило, так это то, что он быстрее всех вызывает функции-члены.
gradient А чего тут удивительного ? В предыдущем "неудачном примере" цикл выполнялся внутри функции mul, поэтому задержка вызова функции была незаметна на фоне общего времени выполнения цикла. Здесь же ты пытаешься вызывать функции внутри циклов для выполнения "элементарных" действий и ес-но из-за ненужных затрат на call\ret это работает "хуже", чем непосредсвенные вычисления. Полные латентности call\ret составляют соответсвенно 3\5 тактов на атлонах и до 5\8 тактов на P4 (NetBurst). В действительности эффективная латентность получается меньше (по А.Фогу ~2 такта), т.к. за счет внеочередного исполнения следующие за call\ret команды могут начинаться до их полного завершения. В частности командам, не работающим со стеком и с памятью, незачем ждать микрооперации push ret_addr при call и pop ret_addr при ret (кстати это тоже неявные операции обращения к памяти, которых ты так боишься Плюс вопрос как передавать float-параметры при fastcall - если по ссылке, то в eax,edx помещаются их адреса и сами переменные грузятся непосредственно в FPU по этим адресам, если же по значению, то эти переменные пушатся в стек и грузятся в FPU уже из стека -> возможны доп.задержки. А вот задержки "лишних" обращений к памяти ты сильно преувеличиваешь, т.к. во-первых, как ты уже убедился компилятор хоть и "тупой", но не настолько, чтобы для каждой переменной выполнять все цепочки B->a->x - общую часть адреса он все-таки соображает загружать один раз. Во-вторых, латентность загрузки целочисленного операнда (адреса) из кэша составляет ~3 такта, но независимые загрузки могут выполняться на пеньках по одной в каждом такте, а на атлонах аж две за такт, а латентность зависимых загрузок может быть скрыта (нивелирована) за счет внеочередного спекулятивного исполнения (например загрузка загадочного [ebx+160772] в следующей итерации может происходить одновременно с fmul или fadd предыдущей итерации). Простой разворот это одно, а разворот с распараллеливанием это несколько другое. Латентнось операции fadd составляет не менее 4-5 тактов, поэтому обычный разворот тут мало что дает, т.к. операции управления циклом и так выполняются параллельно c fadd, а следующее сложение z+ не может начаться прежде чем закончится предыдущее. Нужно распараллеливать вычисление z, т.е. копить не одно значение z, а 2 или 4 независимых значения (независимые fadd могут выполняться каждый такт). Примерно так Код (Text): fldz ;z1 fldz ;z2 @@: fld dword ptr [edi+ecx*4] fmul dword ptr [esi+ecx*4] fld dword ptr [edi+ecx*4+4] fmul dword ptr [esi+ecx*4+4] faddp st(2),st(0) ;z2+ faddp st(2),st(0) ;z1+ add ecx,2 jl @B faddp st(1),st(0) ;z=z1+z2
В том то и дело, что не такие они и элементарные. В случае Код (Text): // _s+=lay->_mn[i].pa(k); так хуже ee=&lay->_mn[i];//Здесь можно было бы поставить with _s+=ee->_p[k]*ee->a[k];так лучше Да это всего лишь одно умножение Но в случае Код (Text): for(int j=_NN[l-2]-1;j>=0;j--) { pp[j]+= _konf*(p[j]=_s*ee->neuleyer[j]._n); так лучше } // ee->Pset( _konf,_s); так хуже Pset( _konf,_s)-это целый цикл с опционным размером(параметр класса ee)! Спасибо за информацию. Теперь буду передовать ссылки. Этого не знал. Спасибо! Попробую.