Уважаемые программисты! Столкнулся с такой проблемой: есть код на Си, вычисляющий факториал. Код таков: Код (Text): #include <stdio.h> unsigned short array[1000000] = {1}; unsigned int len= 1; void main() { unsigned int i; unsigned long L; unsigned long cn; int N; printf("Input N:"); scanf("%d",&N); cn= 0; for(L= 1; L<=N; L++) { i=0; do {cn+= array[i]*L; array[i]= (unsigned short)(cn%10000); cn/= 10000;} while (++i<len || cn); len= i; } printf("%d", array[len-1]); for(i= len-1; i--;) { printf("%04d", array[i]); } printf("\n"); } Я решил переписать его на ассемблере, получив следующее: Код (Text): format PE console include 'win32axp.inc' .data dllka db 'msvcrt.dll',0 name1 db 'scanf',0 scanf dd ? name2 db 'printf',0 printf dd ? inp db 'Input N:',0 d db '%d',0 d4 db '%04d',0 crlf db 13,10,0 N dd ? len dd 1 array dw 1 dw 999999 dup 0 .code puck: invoke LoadLibrary,dllka push eax invoke GetProcAddress,eax,name1 mov [scanf],eax pop eax invoke GetProcAddress,eax,name2 mov [printf],eax cinvoke printf,inp cinvoke scanf,d,N mov ebx,[N] test ebx,ebx jl konets mov edi,[len] xor esi,esi inc esi xor eax,eax cmp ebx,esi jb mtk1 mtk0: xor ecx,ecx mtk: movzx edx,word [array+ecx*2] imul edx,esi add eax,edx xor edx,edx mov ebp,2710h div ebp mov word[array+ecx*2],dx inc ecx cmp ecx,edi jb mtk test eax,eax jnz mtk inc esi cmp esi,ebx mov edi,ecx jbe mtk0 mov [len],edi mtk1: movzx eax,word [array-2+edi*2] cinvoke printf,d,eax mov esi,[len] dec esi jz konets mtk2: dec esi movzx ecx,word[array+esi*2] cinvoke printf,d4,ecx test esi,esi jnz mtk2 konets: cinvoke printf,crlf invoke ExitProcess,0 .end puck Скомпилировав сишный исходник с помощью MS VC++ Toolkit 2003 с максимальной оптимизацией, я сравнил время его работы с ассемблерным кодом. Замерял примитивно, с помощью обычных часов Итог: при вычислении чисел типа 150000! ассемблерный вариант секунд на 10 отстаёт от сишного! Я, конечно, понимаю, что в MS не дураки сидят и умеют писать оптимизаторы, но чтобы так... И это при том, что сишный компилятор даже не все регистры занимает! Господа программисты! Подскажите, где теряется производительность в ассемблерном варианте и, если это возможно, подскажите пути исправления Советовать VTune не стоит - пока я его ещё не раздобыл, да и толку - проц у меня AMD
ну так посмотри что сгенерил Си. а твой код неотформатирован, коряво читается. но по-крайне мере деление можно было и не юзать.
Нда, нет слов Adrax, зачем тебе здесь умножение? Индекс массива увеличивается линейно, на постоянную величину, так не проще ли завести указатель на массив и просто инкрементировать его? Вообще код очень корявый, пропадает все желание в нем разбираться, но даже навскидку видно нерациональное распределение регистров(хватит и четырех) и лишние проверки ((++i<len || cn) можно свести к одной).
Adrax Возможно ещё влияет выравнивание. Неоднократно наблюдал прикольную картину: добавляешь в цикл какую-нибудь бесполезную команду, и скорость существенно возрастает, хотя по идее должно бы наоборот. Ещё встечалось, что было достаточно компильнуть более новым компиллером, и тоже в несколько раз возрастала скорость. Так что флаг в руки и вперёд, анализировать сгенерённый ассемблерный листинг.
Насчет умножения был неправ, там никак не заменишь Посмотрел, что мне борландовский компиль выдал, и упал под стол, такой код никак не может быть быстрее, нежели представленный Adrax Что касается оптимизации, то, не изменяя самого алгоритма, напрашивается избавление от одного условного перехода в цикле while (++i<len || cn), например: Код (Text): ; ebx - i ; ecx - cn ; ebp - len lea eax, [ebx+1] sub eax, ebp inc ebx or eax, ecx jnz __do Что касается использования регистров, то cn желательно хранить в eax, поскольку отпадают ненужные перестановки: Код (Text): movzx eax, [array+ebx*2] imul xxx mov ecx, 10000 xor edx, edx div ecx mov [array+ebx*2], dx ; cn в eax Если же нужна более эффективная оптимизация, то необходимо развернуть цикл, заменив mov [array+ebx*2], dx на mov [array+ebx*2], edx. Теоретически должно добавить скорости, хотя бы за счет сокращения обращений к памяти. ЗЫ: Adrax, попробуй. Если не получится, тогда уж помогу.
Adrax, да незачто Сейчас протестил малость. Не дало ни грамма приросту То ли предсказатель ветвлений умнее, чем я думал, то ли одно из двух В общем попробуй, для начала, тупой, построчный перевод: Код (Text): mov esi, 1 ; esi = len mov ecx, esi ; ecx = L xor edi, edi ; edi = cn mov ebp, 10000 __loop: xor ebx, ebx ; ebx = i __do: ; здесь можно и упростить, но очень уж не хочется диапозон для imul ограничивать movzx eax, [word array+ebx*2] imul ecx add eax, edi ; eax = cn+array[i]*L xor edx, edx div ebp inc ebx mov edi, eax ; edi = cn/10000 mov [word array+ebx*2-2], dx cmp ebx, esi jb __do or eax, eax jnz __do inc ecx mov esi, ebx ; esi = len cmp ecx, [N] jbe __loop Билдер делает почти в два раза, хотя и не оптимизирован ни грамма PS: а вообще было бы неплохо взглянуть на код, сгенерированный VC. У кого есть возможность, выложите плиз PPS: будет время - разверну цикл, хотя эффект будет только на больших итерациях.
Если факториал - это промежуточный результат (для дальнейших вычислений), то лучше считать его в хексе. Не будет тяжёлых операций / и %. В любом случае, лучше оперировать ulong вместо ushort (используя uint64).
Наверное имелось в виду, что вместо 10000 лучше взять 16384, отбросив деление. Только как потом результат в десятичную систему переводить?
KeSqueer в 16-ричной системе исчисления. Быстрее будет - в 2 раза меньше итераций во внутреннем цикле. Если факториал - это промежуточный результат, то лучше считать в hex. Но если это вся задача - посчитать факториал и вывести его, то, наверно, надо сразу в dec(10-й) считать - выигрыш теряется из-за необходимости перевода в dec при выводе (будет то же деление на степень 10).
KeSqueer Просто заменяем основание системы счисления: с 10000 на 65536. Это, с одной стороны, значительно ускорит алгоритм за счёт избавления от деления. Но, с другой стороны, при выводе таких чисел их наверняка придётся переводить обратно в десятичную систему, т.е. выполнять кучу длинных делений.
Это я уже пробовал. Выигрыш немалый, но это не то. Единственное, что приходит в голову, это использовать, вместо 10000, более крупную степень десятки, чтобы отлавливать переполнение через флаги. Осталось только решить, какое. Наверное лучше всего перейти на 32-х разрядные.