cppasm Других нет Во-первых, в P4 только два порта запуска fpu-команд - один для fld\fst и т.п., другой для всего прочего, к тому же эти порты являются общими с целочисленными операциями, а в атлонах все fpu\mmx\sse выделены в отдельный конвеер с тремя портами запуска для умножений\делений, сложений\вычитаний и пересылок. Поэтому, например, в P4 нельзя одновремено запустить fmul и fadd, а в АМД можно. Во-вторых, латентности основных операций в P4 завышены до 1.5-2 раз по сравнению с АМД
Ясно, вроди со всем разобрался. Спасибо за помощ. PS: leo ты был прав - на стаканчик не хватило. свернул всё назад.
Так же как и в ring0 mov eax,esp Или ты хотел eip? Тогда только через call, по другому никак (ring0 или ring3 значения не имеет).
Так пойдёт? Код (Text): eip_value:mov eax,eip_value Вот поинтереснее вопрос. В мануале AMD есть пример. Код (Text): #define CACHEBLOCK 400h // QWORDs in a block, (8K bytes) int* storedest char buffer[CACHEBLOCK * 8] // in-cache temporary storage mov esi, [src1] // source array one mov ebx, [src2] // source array two mov edi, [dst] // destination array mov ecx, [len] // number of Floats (8 bytes) // (assumes len /CACHEBLOCK = integer) lea esi, [esi+ecx*8] lea ebx, [ebx+ecx*8] lea edi, [edi+ecx*8] mov [storedest], edi // save the real dest for later mov edi, [buffer] // temporary in-cache buffer... lea edi, [edi+ecx*8] // stays in cache from heavy use neg ecx mainloop: mov eax, CACHEBLOCK / 16 prefetchloop1: // block prefetch array #1 mov edx, [esi+ecx*8] mov edx, [esi+ecx*8+64] // (this loop is unrolled 2X) add ecx, 16 dec eax jnz prefetchloop1 sub ecx, CACHEBLOCK mov eax, CACHEBLOCK / 16 prefetchloop2: // block prefetch array #2 mov edx, [ebx+ecx*8] mov edx, [ebx+ecx*8+64] // (this loop is unrolled 2X) add ecx, 16 dec eax jnz prefetchloop2 sub ecx, CACHEBLOCK mov eax, CACHEBLOCK / 8 processloop: // this loop read/writes all in cache! fld qword ptr [esi+ecx*8+56] fadd qword ptr [ebx+ecx*8+56] ... fld qword ptr [esi+ecx*8+0] fadd qword ptr [ebx+ecx*8+0] fstp qword ptr [edi+ecx*8+0] ... fstp qword ptr [edi+ecx*8+56] add ecx, 8 dec eax jnz processloop emms sub ecx, CACHEBLOCK mov edx, [storedest] mov eax, CACHEBLOCK / 8 writeloop: // write buffer to main mem movq mm0, qword ptr [edi+ecx*8] ... movq mm7, qword ptr [edi+ecx*8+56] movntq qword ptr [edx+ecx*8], mm0 ... movntq qword ptr [edx+ecx*8+56], mm7 add ecx, 8 dec eax jnz writeloop or ecx, ecx jge exit sub edi, CACHEBLOCK * 8 // reset edi back to start of // buffer sfence emms jmp mainloop exit: Откуда такая уверенность, что при fstp qword ptr [edi+ecx*8+0] будет произведена запись во временный буфер в кеше, а не в память? По-моему в цикле writeloop: производится лишняя работа т.к. данные окажутся в памяти после fstp.
murder Не совсем понял, что имеется ввиду. Есть буфер записи. А есть кеш. Две разные, хотя и связаные сущности. Практически во всех современных процессорах используется Write-Back кеш. Запись всегда производится в него. Если запись производится в строку, которой в кеше нет, онасначала в него загружается, если не используется команда типа MOVNTQ. А она как раз здесь используется. Смысл этого примера в том, чтобы обрабатывать данные во временной области памяти, которая находится в кеше, а потом сбрасывать всю порцию в память, минуя кеш.
Ustus MOVNTQ копирует данные в буфер записи, затем SFENCE копирует буфер записи в память. Это понятно. movq mm0, qword ptr [edi+ecx*8] читает кешированые данные и тут всё в порядке. Почему процессор не начнёт копировать данные из кеша в память параллельно с циклом processloop не дожидаясь writeloop?
murder Процессор сбрасывает обычные WB-данные из кэша в ОЗУ, только когда "рак на горе свистнет" - т.е. либо возникнет конфликт адресов или данных в кэше, либо принудительно командами CLFLUSH, INVD\WBINVD. Конфликт адресов для данной строки кэша возникает при поступлении запроса на чтение\запись по адресу, проецируемому на тот же набор кэша, в котором находится данная строка и эта строка является наиболее "старой" в наборе. В современных процах ассоциативность кэша L2 составляет 8 или 16-way, поэтому чтобы некоторая строка была вытеснена другой нужно, чтобы после последнего обращения к этой строке произошло как минимум 8 или 16 обращений по разным физическим адресам, проецируемым на тот же набор. Конфликты данных могут возникать при использовании некэшируемой записи movnt по адресам, совпадающим с адресом модифицированной линейки кэша (в этом сл.сначала происхдит сброс линейки в ОЗУ), либо в многопроцессорных системах с раздельными кэшами данных при одновременном обращении разных потоков к модифицированным линейкам кэша с одинаковыми физ.адресам Поскольку в приведенном коде - ничего подобного нет, то данные на достаточно коротком интервале времени могут быть вытеснены из кэша только "чудом" - другим потоком, интенсивно работающим с памятью
leo Э-э-э тогда какая польза от writeloop? Ведь мы тратим лишнее время на копирование данных из кеша в память, в то время как эта операция может быть выполнена параллельно с другим кодом когда "рак на горе свистнет".
murder Отличие некэшируемой записи movnt от обычной WB-записи заключается в том, что при movnt данные копятся в спец.WC-буферах (write combining) и затем целыми линейками по 64 байта пишутся прямо в ОЗУ, минуя кэш. А при обычной кэшируемой записи для обеспечения когерентности памяти данные сначала считываются из ОЗУ (RFO - read for ownership), затем изменяются в кэше и пишутся обратно в ОЗУ когда "рак на горе свистнет", т.е. когда случится конфликт адресов. Поэтому при кэшируемой записи больших объемов данных, во-первых, имеем лишний бесполезный "поток" чтения RFO, как минимум вдвое снижающий проп.способность шины памяти, и, во-вторых, сброс данных в ОЗУ идет "бесконтрольно" во время их вытестнения новыми RFO, что добавляет лишних тормозов из-за чередования чтения и записи в ОЗУ по разным далеко отстающим адресам. Поэтому суть приведенного примера заключается в том, что выделяется сравнительно небольшой блок памяти, умещающийся в быстром кэше L1, один раз производится его чтение из ОЗУ (мусора из стека) и затем этот блок постоянно находится в кэше, т.к. к нему идут частые обращения в цикле и соотв-но доступ к даным на чтение\запись произв-ся очень быстро. После заполнения блока он сбрасывается в ОЗУ через movntq - при этом рулит один "поток" записи по последовательным адресам и соотв-но шина памяти работает на макс.проп.способности, т.к. никаие RFO "не путаются под ногами"
leo Ну вроде понял А вот допустим вышло так, что в виртуальном пространстве адресов я работаю с единым блоком, а в физическом он сильно фрагментирован. Это сильно скажется на производительности?
murder Во-первых, физ.адреса выделяются страницами по 4К, поэтому "непоследовательность" проявляется только при переходах от одной 4К страницы к другой. Во-вторых, говорить о влиянии "фрагментации" физ.адресов на производительность не имеет практического смысла, т.к. сравнивать не с чем Физ.память это вотчина ОС и думать о ее фрагментации должны разработчики ОС и железа, а нам остается только принцип "ешь, что дают"
Люди добрые, подскажите как правильно разруливать циклы? Некоторые свои циклы я разруливаю на 4 операции, некоторые на 8, а некоторые аж на 32... Чем нужно руководствоваться при выборе степени разруливания?
Появился тут у меня вопрос по зависимостям. Т.е. в каком случае команды считаются зависимыми? К примеру Код (Text): mov reg1,[mem1] add reg1,reg2 тут понятно что есть зависимость по регистру reg1. А вот здесь Код (Text): mov [mem1],reg1 mov reg1,[mem2] или Код (Text): mov [mem1],reg1 add reg1,reg2 не очень понятно. По-моему во втором варианте зависимости нет. Или я ошибаюсь?
В варианте "или" - вообще никаких зависимостей нет, т.к. кажущаяся зависимость по reg1 в современных процах устраняется за счет переименования регистров В варианте "А вот здесь" - зависимости по reg1 нет, но на "осторожных" атлонах может быть квазизависимость по порядку вычисления адресов mem1 и mem2, т.е. операция mov reg1,[mem2] не может быть выполнена раньше mov [mem1],reg1 пока проц не вычислит адрес mem1 и не убедится в том, что mem2 != mem1 (AGI - address generation interlock)
leo А я почему-то подумал, что AGI - это просто долгое вычисление составного адреса (в примере ниже eax+ebx). Код (Text): MOV ECX, DWORD PTR [EAX+EBX] ;inst 3 (slow address calc.) MOV EDX, DWORD PTR [24h] ;this load is stalled from ; accessing data cache due ; to long latency for ; generating address for ; inst 3
leo спасибо Я собственно так и думал, но в мануалах нигде точного описания этого момента не нашёл. Да, насчёт этого я в AMD Athlon x86 Code Optimization Manual читал. Интересовала именно зависимость по регистрам. По сути получается что зависимости есть при записи в регист и последующих операциях с ним. А при чтении из регистра и последующих операциях зависимостей нет. Ну так leo про это и написал. Просто в моих "записях" под mem1 и mem2 могут скрываться разные выражения.
murder Не обязательно долгое и не обязательно составного. Например: Код (Text): inc ebx ;(1) mov ecx,[ebx] ;(2) - явная зависимость от (1) по ebx mov edx,[24h] ;(3) - независимая команда Загрузка edx не зависит по данным от команд (1) и (2) и соотв-но на пеньках м.б. выполнена одновременно или даже раньше inc ebx. А на атлонах все адреса вычисляются строго последовательно, поэтому в лучшем случае mov edx может поступить на исполнение одновременно с mov ecx, но не раньше. Т.е. AGI по сути приводит к тому, что зависимость от изменения регистра распространяется не только на команды, в которые этот регистр явно входит в операнд памяти, но и на все последующие команды, работающие с памятью
Что-то я зачастил Вопрос к тем кто разбирался с архитектурой AMD. В соответствии с ихними мануалами процессор может одновременно выполнять до 4 команд fpu. У меня вопрос - почему 4, исполнительных устройства ведь 3: FADD, FMUL, FSTORE. На практике мне это надо для упорядочивания инструкций (scheduling). Т.е. к примеру если у FMUL латентность 4 такта, результаты раньше этого времени желательно не использовать. Вопрос в том как эти такты перевести в число инструкций, т.е. через сколько команд можно ипользовать результаты. Плюс не очень понятно - если латентность 4 такта, FMUL будет занят все 4 такта, или один? По-моему один, но я могу и ошибаться