К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 66
Текст из файла (страница 66)
3.35)! На процессоре Р-4 (если верить его разработчикам) величина пенальти еще больше. Намного больше, вот цитата из руководства: "ТЬе ре(уогп(апсе репа1(у угот х(о(а((пя 5(огеуо(тяог(((пц лез(пс((опз (таз Ргезеп( (п (Ье Репйпт П ап(( Реп((ип( П1 ргосеиогь, Ьи( (Ье репо((у м 1агяег оп (Ье Реп((пт 4 ргосеиог". Приятное исключение составляет чтение маленькой порции данных после записи большой. Если их адреса совпадают, время доступа к ячейке увеличивается "всего" в полтора раза. Рис. 3.36. Возникновение задержек при обработке данных различной разрядности, находящихся в основной памяти Вие кэша.
При выходе за пределы кэш-памяти второго уровня картина существенно изменяется. Штрафные санкции снижаются до не таких уж зна- 337 чительных полутора-трех крат, а короткое чтение после длинной записи на процессоре Р-Ш (и, предположительно, на Р-П и Р-4) исполняется и вовсе без издержек! Впрочем, не стоит обольщаться — АМП А(!)!оп не прости~ вам подобных вольностей и накажет двухкратным падением производительности (рис. З.Зб). С другой стороны, преобразование ланных к единому типу путем расширения их до наибольшей разрядности обернется еще большими потерями— ведь удельное время доступа к ячейкам стремительно растет с увеличением размера обрабатываемого блока. Таким образом: универсальной стратегии райотьс с разнотинными данными нет. Решайте сами, что лучше в каждом конкретном случае; мириться со штрафными задержками или возросшей потребностью в памяти. "Волчьи яаяыа опережающей записи И Итак, как мы уже знаем, что при попытке записи в ячейку, отсутствующую в кэш-памяти первого уровня, процессор временно сохраняет записываемые данные в одном из свободных буферов (конечно, если таковые есть), а затем при первой же возможности выгружает их в кэш первого и/или второго уровня.
Чтение данных, находящихся в буфере, осуществляется по крайней мере на один такт быстрее, чем обращение к кэшу первого уровня, к тому же буферы имеют значительно больше портов, чем кэш, и могут обрабатывать более двух запросов одновременно !хотя, буферы записи процессора АМ0 К5 имели всего лишь один-единственный порт). Как это можно использовать на практике? На процессорах Рб и Кб следующий код будет исполняться предельно быстро независимо от того, присутствует ли ячейка *р в сверхоперативной памяти или нет: «р а; ь - р*; Тем не менее, использование буферов записи таит в себе одну очень коварную опасность. Рассмотрим следующий пример, на первый взгляд как будто бы полностью повторяющий предылуший: :«р а; Е (ааа(х) «аоп(З)) / а; ь-р ( Да, команды записи и чтения данных уже не прижаты друг к другу, а разделены некоторым количеством "посторонних" инструкций. Предположим, ззв Глава 3 что компилятор сгенерировал "наиглупейший" код, сохраняющий результаты всех четырех вычислений в промежуточных переменных.
Предположим, что все переменные (включая г) отсутствуют в кзше и претендуют на различные буферы записи. Тогда, между записью ячейки *р и чтением ее содержимого происхолит заполнение всего лишь пяти буферов, и судя по всему ячейка "р еще находится в буфере. А вот и нет! Кто вам это обещал?! Разработчики процессора? Отнюдь! Буфера записи, в отличие от кэш-памяти, склонны к самопроизвольному опорожнению с переносом /именно переносом, а не «опированиел4 своего содержимого в кэш первого и/или второго уровня.
Рассматриваемый нами пример кода неустойчив, поскольку скорость его выполнения варьируется в зависимости от того, успел ли процессор выгрузить буферы или нет. Попросту говоря, производительность такого кода определяется "настроением" процессора и различные прогоны могут показать весьма неодинаковые результаты. Причем, если на процессоре Кб содержимое буферов выгружается в кэш первого уровня, откуда данные могут быть считаны всего за один такт, на Рб в этой ситуации возникает кэш-промах и процессор вынужден обращаться к кэшу второго уровня, что будет стоить многих тактов. В данном случае проблемы легко избежать перегруппировкой команд, переместив вычислительную операцию на одну строчку вверх или вниз, мы добьемся спаривания команд записи/чтения и гарантированно избежим преждевременного вытеснения буферов.
Но такое решение не всегда достижимо. Команды могут иметь зависимость по данным или вообще находиться в различных функциях, а то и потоках. Как быть тогда? Откроем, например, уже упомянутое руководство по оптимизации от Агнера Фога Г" Нон го орйпите Гог гНе Репйшп Гапп!у оГ пз1сгоргоссззогз" Ьу Аяпег Роя) и найдем в главе, посвященной кэш-памяти следующие строки; ''йгйеп уои игйе (о ап аИгезз нЬ(сЬ (з по! (п йе (ете! 1 спсле, йеп йе га!ие ич!! яо г(яЬ( (Ьгоияй (о йе (ете! 2 спсйе ог (о йе йАМ Гдерепсвпя оп Ьов йе !ете! 2 сасле й зег ир) оп йе РР1тп апс! РММХ.
ТЫз (п(сез прргох(тп(е!у (00 пз. 1( уои нг((е е(еЬ( ог тоге йтез (о йе лате 32 Ьуге Иос(г о/ тетогу тйои( а!зо геад(пз)азот ((, апс( йе Иос(с (з по( и йе !еге! опе сопле, йеп 0 тау Ье пс(рпп(пяеоиз (о тп(се п дитту геас(/гот йе Ъ(ос(с /(гзг (о (оаг( (( и(о а сасле 1ие. А0 зиЬзес(иеп( вг((ез го йе зате Иос(с ич!1 йеп яо (о йе спсле изтепд, мйгсЬ (п(сез оп(у опе с(ос(г сус(е. Оп РР(пи апс( РММХ, йеге ц зотейтез п зтп(( репа!(у (ог ит((и» гереп(ед!у (о йе зате пг(с(гезз айпи! гепдие (и Ье(и ееп. Оп РРю, РП апг( Р(11, а иг!ге т(зз п(!1 погтаИу (оаг( а сасйе !(пе, Ьиг (г (з розз!Ые го зегир ап агеа о/' тетогу го рет(огт г(блеген!(у, /ог ехатр(е пйео .йАМ Где "Реп((ит Рго РатИу Реге!орегз Мании(, го!. 3: Орегагупа оузгет (гг(гегз Си(с(е ".) Кэш (" Когда на Репйит-просто или Репйит ММХ вы залисывпете данные, отсутствующие в кэш-памяти первого уровня, они будут помещены в кзш второгп уровня или основную оперативную память (в зависимости от того: нпличествует ли кэш второго уровня или нет).
Эта операция зпнимает приблизительно 700 нс. Если вы обращпетесь к записи восемь или более раз (именно раз, а не байт, кпк скпзпнп в популярном перевпде )(митрия Померанцева — К. К.) к пдному и тому же 32-байтовому блоку ппмяти без чтения чего бы то ни было оттуда, и данный блок памяти отсутствует в кэше первпго урпвня, было бы недурственно предварительно прочитать любую ячейку блока, загружая тем самым его в кэш первого уровня. Все последующие операции записи дпнного блока будут записывпться в кэш первого уровня, что зайл|ет всего один такт. На Репйит-просто и Реп|шт ММХ при многокрптной записи данных по одному и тому же адресу иногда возникают небольшие задержки, если эти данные не будут востребованы. На Репттт Рго, Реийит-П и Репе(ит-И! промах записи обычно загружает соответствующую кэш-линейку, но если это возможно, установите область памяти для предотвращения различий, например видеопамять. (См.
"Семейство Релйит Рго. Справочник рпзработчика. Том 3. Руководство создателям оперпционных систел|'.) Выделенное курсивом предложение написано довольно неуверенным тоном (похоже Агнер Фог и сам его не понимал). Итак, начинаем лексический анализ. ")х!оппа!!у )оад а сасйе !)пе" — можно перевести двояко, а именно: "нормально загружает" (т. е. самостоятельно загружает "без дураков") или же "обычно загружает" (т. е. может загрузить, а может и нет). Судя по всему, Агнер Фог подразумевал последний вариант. Действительно, в зависимости от состояния соответствующих атрибутов страницы, кэширование записи может быть как разрешено, так и нет.
Вот например, в области видеопамяти оно уж точно запрещено, ведь в противном случае обновление изображения происхолило бы не в момент записи, а спустя неопределенное время после вытеснения данных из каша первого уровня, что вряд ли кого могло бы устроить. Вот Фог и советует: убедитесь, что обрабатываемая область памяти разрешает кэширование. Между тем, это только часть истины, — Агнер Фог совсем забыл о буферизации. На самом деле, и на процессорах Р-рго, и на Р-Т), и нп Р-(П промах записи не загружает кэш-линейку! (Исключение составляет запись расщепленных данных). На Кб/Аг)з!оп промах записи так же не приводит к немедленной загрузке кэш-линейки, но поскольку содержимое буферов вытесняется в кэш первого уровня, с некоторой натяжкой можно сказать, что такая загрузка все-таки происходит.
Поэтому к современным процессорам применимы те же самые рекомендации, что и к Репбшп-просто и Реп!(цгп ММХ. Глава 3 Покажем их "живое" воплощение на практике: ео1аеа1е аоааЬ! ааааь - *р; *р = а: ~аховы + ооо(у~ 1 у а! Ь = *р; Что изменилось? Обратите внимание на выделенную жирным шрифтом строку, загружающую содержимое записываемой ячейки в неиспользуемую переменную. Такой трюк практически не снижает производительности (т. к.