К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 67
Текст из файла (страница 67)
процессоры Рб и Кб могут дожидаться загрузки ячейки из оперативной памяти параллельно с ее записью), но гарантирует, что содержимое буферов к моменту обращения к ним не будет вытеснено дальше кэша первого уровня. А кэш первого уровня — он всегда "под рукой" и его чтение не займет много времени. Как всегда, здесь не обходится без тонкостей. При загрузке данных в неиспользуемую переменную оптимизирующий компилятор может проигнорировать бессмысленное с его точки зрения присвоение и тогда у нас ничего не получится. Один из способов запретить компилятору самовольничать— ЭТО ОбЪЯВИТЬ ПЕРЕМЕННУЮ КВК оо1аах1е. Экспериментальное подтверждение самопроизвольной выгрузки буферов.
Теперь, после надлежащей теоретической подготовки, имеет смысл исследовать процесс выгрузки буферов, что называется, "в живую". Конкретно нас будет интересовать какой именно промежуток времени записываемые данные проводят В буферах, в каком порядке и с какой скоростью они вытесняются оттуда. Но ведь буферы записи полностью прозрачны для программиста и нам не предоставлено абсолютно никаких рычагов управления! Хорошо, будем рассматривать буфер как "черный ящик" со входом и выходом. Как узнать, что у него внутри? Непосредственно задачу решить невозможно, но мы вполне В состоянии посылать этому ящику запросы и засекать время их выполнения. Останется лишь сопоставить несколько очевидных фактов и прийти к определенным заключениям.
Попросту говоря; если данные считываются практически мгновенно, то они безусловно все еще находятся в буфере. Чуть большая задержка укажет на то, что данных в буфере уже нет и их следует искать в кэше первого уровня. Наконец, резкое увеличение времени доступа означает, что данные выгружены непосредственно в кэш второю уровня. Как мы будем действовать? Последовательно записывая все большее и большее количество ячеек с последующим обращением к первой из них, мы рано или поздно столкнемся с внезапным паданием производительности. Кэш Это и будет обозначать, что ячейка, содержимое который мы пытаемся прочесть, по тем или иным причинам, покинула "застенки" буферов и "отошла в мир иной". Так мы узнаем стратегию выгрузки буферов: выгружаются ли они в "фоновом" режиме или выгрузка происходит лишь при переполнении буферов. Вообще-то, ~сотовую программу можно было бы написать и на чистом С, но на этом пути притаилось множество трудностей.
Во-первых, компилятор С не поддерживает циклических макросов, а значит, не позволяет автоматически дублировать команды записи заданное число раз. Если же выполнять запись в цикле, мы сразу проиграем в точности измерений. Кроме того, накладные расходы на организацию цикла сравнимы со временем загрузки данных из кэша первого уровня. Во-вторых, нельзя быть уверенным, что код, сгенерированный компилятором, не содержит лишних обращений к памяти.
И, в-третьих, параллельно с обработкой ветвлений могут вьпружаться и буферы. Да простят меня прикладные программисты, но все-таки я остановлю свой выбор на ассемблере. К слову сказать, приведенные далее листинги (листинги 3.14, 3.!5)„достаточно подробно комментированы и разобраться в алгоритме их работы навряд ли будет стоить большого труда. (Полный исходный текст программы (листинг 3.14) читатель найдет в файле )(згс(ДЗ).сас()е'(5(оге Ьцбхп), который находится на прилагаемом компакт-диске.) И 1ТЕЕ ЕЯС )// <-- (аито Чеп! /*- иакроо, аатоиатитвоки аубаир)екав< оков тело И рае -*/ 5ТОЕЕ ВСГГ МАСРО И ИН1ЬЕ А ИЕ И МОЧ (ЕВХ+32* Ю,ЕСХ) <" *(Епь *) ((ьпт)р + 32 * А) = к) ЕЛОМ /* Глава 3 З4г дензнстищия Вьетузки Вуееюя ВО врззшг заяятссти шивы ВТОВЕ ВОГГ МОЧ ЕОХ, ~ЕВХ~ ; Ь = «р; <- ...мы обращаемся к саыому первому записанноыу буФеру; если он еше не выгружен, — его содержимое считается максиыально быстро; в противном случае возникнет задержка смешаем указатель на след.
буферы АСС ЕВХ,32*И 1ТЕР« (Полный исходный текст программы (листинг 3.15) читатель найдет в файле 15гс'1(3).сас(те15гоге Ьиг )ч(ОР.хгп, который находится на прилагаемом ком- пакт-диске.) и Ттее ВОО >// < — !апго деп! /*-- ВтОРЕ ВОГГ МАсао И <- ТЕЛО МАКРОСА ИОР А= А*1 ЕИОМ деьзэнстрация ВИГРузки ВуееРОВ ВО Вреиеня ПРссттзг шины И 1ТЕВ ; «р+00 = а1 *р+32 а; «р+64 а/ и = и А = 0 ИИ1ЬЕ А ИЕ И заполняем буферы записи, записывая каждый раз ячейку в новый буфер. Буферы выгружактся параллельно с записью.
чтобы доказать зто ьм.... Кзш 343 ИОЧ (ЕВХН ЕСХ ; р а; <- тут иы записываем в *р некое значение <- записываемое значение в первую очередь <- попадает в буфер записи (ввозе Ьп(1егз( <- один или несколько НОР Н 1ТЕН 5ТОВЕ ВСГГ параллельно с ик выполнением содержнмое буферов вытесняется в кзш первого (Нищ или второго (1пте1( уровней ИОЧ ЕОХ, (ЕВХ! ; Ь *р( <- читаем содержимое ячейки -р если к атому моменту соответствуквай ей буфер еие не вытеснен, то она прочтется иаксимально быстро; в противном же случае возникнет задержка ию евх, 32 ; (епырез2; <- смеиаем указатель на след. буфер Результаты прогонов программы на процессорах Р-1П и АМР А(1(!Оп представлены на диаграмме (рис. 3.37).
Наше обсуждение мы начнем с характера кривой зависимости времени загрузки данных от количества команд записи. Кривая Р-1И изображена жирной линией, обозначенной на рисунке цифрой !. Смотрите — после семи команд записи время загрузки данных без всяких видимых причин возрастает с 35 до 150 тактов, т. е, в четыре с небольшим раза. Это говорит о том, что первая из записанных ячеек уже покинула буфер и "отлетела" в кэш второго уровня. Она сделала это несмотря на то„ что свободные буферы еше не были исчерпаны! Тем самым, мы убедительно доказали, что буферы могут выгружаться и самопроизвольно, а не только при переполнении их.
Приняв за время выполнения операции записи один такт, мы сможем оценить приблизительное время выгрузки содержимого перового из буферов. Оно, как нетрудно установить, составляет 7+1 тактов. Последуюшие три замера показывают практически идентичное время прогона, но затем кривая делает легкий взмах вверх, образуя своеобразную ступеньку.
О чем она говорит? По всей видимости, к этому моменту завершает свою выгрузку второй буфер и, вследствие занятости шины, чтение ячеек из кэша второго уровня испытывает некоторые задержки. Глава 3 344 Рис. З.З7. Демонстрация выгрузки буферов записи Следующая ступенька наблюдается на четырнадцати операциях записи, что и неудивительно, т. к, с этого момента начинается острая нехватка свободных буферов (на процессорах Р-П/Р-Ш всего 12 буферов плюс два уже освободившихся — итого четырнадцать) и каждая последующая запись обходится приблизительно в семь дополнительных тактов, требующихся для выгрузки содержимого хотя бы одного из буферов.
Неудивительно, что производительность стремительно падает, "ну прямо как рубль в печально памятные дни августовского кризиса". Теперь запустим второй вариант программы, который выполняет всего одну-единственную запись, затем выдерживает короткую паузу, "скармливая" процессору некоторое количество команд-пустышек, после чего проверяет наличие записанных данных в буфере. Оказывается, как это подтверждает линия, обозначенная цифрой 2, опорожнение буферов происходит и в данном случае, причем, приблизительно за то же самое время, что и в предыдущей программе 1процессоры Р-П/Р-1П способны выполнять до трех машинных команд нов за каждый такт, поэтому результаты замеров следует разделить на три).
Кэш 345 Поскольку время записи данных в кзш второго уровня на процессоре Р-Ш составляет всего лишь два такта, напрашивается интересный вывод: содержимое буферов выгружается отнюдь не при первой же возможности (при первом же освобождении шины), а согласно внутреннему таймеру. Я не уверен, что продолжительность "проживания" данных в буферах записи на всех процессорах идентична, но во всяком случае мы установили порядок этой величины.
Как нетрудно увидеть, он заметно короче времени выполнения многих вычислительных команд, поэтому наше интуитивное предположение о нежелательности разделения команд записи и чтения полностью подтвердилось. Рассмотрим теперь, как реализован механизм буферизации записи в процессоре АМР Аг!з!оп (коричневая кривая).
Сразу же бросается в глаза, что за счет выгрузки содержимого буферов в кзш первого, а не второго (как на Р-П/Р-П1) уровня, процессор А!)т!оп не имеет проблем с обвальным падением производительности. За счет этого сокращено и время выгрузки буферов. Причем, процессор А!в!оп, судя по всему, не выгружает буферы вплоть до тех пор, пока в этом не возникнет насущной необходимости.
Правда, наблюдается трудно объяснимый "пик" кривой, отражающий значительное увеличение времени доступа при объединении семнадцати операций записи. Именно семнадцати! Обработка шестнадцати или восемнадцати операций записи не вызывает никаких проблем и "послушно" ложится на гладкую кривую. Почему так происходит — трудно сказать, Требуются дополнительные исследования (быть может позже — во втором издании настоящей книги — вы и встретите объяснение, а пока же спишем это на ошибку разработчиков процессора). Комбинирование операций записи с вычислительными операциями Буферизация записи позволяет откладывать физическую выгрузку данных в сверхоперативную и/или основную оперативную память до того момента, когда процессору зто будет наиболее "удобно". До тех пор пока в наличии имеется, по крайней мере, один свободный буфер„такой прием существенно увеличивает производительность.