"Оптимизация для PENTIUM процессора" - читать интересную книгу авторана 32:
AGAIN: MOV EAX, [ESI] MOV EBX, [ESI + 13*4096 + 4] MOV ECX, [ESI + 20*4096 + 28] DEC EDX JNZ AGAIN Все три адреса имеют одинаковую установочную величину, поскольку в усеченном виде они кратны 4096. Этот цикл будет исполняться крайне медленно. Как только мы попытеамся прочесть данные в ECX процессор обнаружит, что нет возможных строк, для кеширования данных, следовательно он удалит из кеша наиболее давно используемые данные, т.е. строку кеширующую данные, которые мы читали в EAX и заполнит ее данными с [ESI + 20*4096] по [ESI +20*4096 + 31], а затем прочтет данные в ECX. Затем, когда мы попытаемся прочесть данные в EAX, процессор снова обнаружит, что в кеше нет строки для кеширования EAX и нет места, что бы ее добавить, значит ему придется снова отвергнуть наиболее старую строку (для EBX), затем проведет кеширование для EAX и, наконец, прочтет данные. Теперь тоже самое произойдет с EBX, и т.д... Таким образом мы имеем постоянные промахи кеша, и на каждый проход по циклу тратиться около 60 тактов. Но если мы заменим третью строку на: MOV ECX, [ESI + 20*4096 + 32] Все! Мы пересекли 32 байтную границу, значит третья строка будет иметь другую промахов. Время затрачиваемое на один проход по циклу сократится до 3 тактов (за исключением первого прохода, конечно) - очень значительное улучшение! Конечно, определить это весьма не просто, особенно если данные разбросаны по разным сегментам. Лучший способ, которым вы можете решить эту проблему - хранить данные, использующиеся в критической части вашей программы, в пределах одного блока длиной 8Кб максимум, или двух блоков, по 4Кб максимум (например один блок для статических переменных, а другой для стека). Это гарантирует, что все линии кеша будут использованы оптимально. Если критическая часть вашей программы работает с большими структурами данных или работает с произвольными адресами, то не плохим решением будет хранить все часто используемые переменные (счетчики, указатели, контрольные переменные, и т.д.) в одном непрерывном блоке, до 4Кб длиной, тогда у вас останется полный набор строк кеша, для доступа к произвольным данным. Поскольку вам скорее всего потребуется память в стеке, для параметров подпрограмм или адресов возврата, то лучше будет скопировать все часто используемые статические данные в динамические переменные, в стеке, а затем, если необходимо, копировать их обратно, за пределами критической части программы. Во время чтения данных, не находящихся в L1, строка кеша сначала заполняется из L2, скорость доступа которой примерно 200 ns (что требует 20 тактов в 100 MHz системе), но первые байты из запрашиваемой строки обычно доступны |
|
|