К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 79
Текст из файла (страница 79)
3.51. График зависимости производительности функции турбо-копирования от размера предвыбираемого блока (Репбигпцв 733/133(100) Оптимизация заполнения (инициализации) памяти Техника оптимизации копирования памяти в целом применима и к ее инициализации — заполнению блока памяти некоторым значением (чаще всего нулями). Эта операция обычно осуществляется либо стандартной функцией языка С ь, либо туш32-функцией г пн у.
(Впрочем, на самом деле это одна и та же функция — в заголовочном файле %1Х)ЧТ.)т макрос г ынеисгу определен как ас гщтн у, а аттгщтне гу на х86-платформе определен как функция ы) ПОЛВВЛВЮщЕЕ бОЛЬШИНСТВО рсаЛИЗацИй фуНКИИИ петзес ИСПОЛЬЗуст ИНСтрукцию циклической записи в память явг зтозо, инициализирующей одно двойное слово за одну итерацию. Но, в отличие от инструкции вкг моузо, она требует совсем другого выравнивания. Причем, об этом обстоятельстве не упоминает ни 1пге1, ни АМ1з, ни сторонние руководства по оптимизации! Кэш 401 Достаточно неожиданным эффектом инициализации ячеек памяти, уже находящихся в каше первого (а на Р-Н!ч- и второго) уровня, является существенное увеличение производительности при выравнивании начального адреса по границе 8 байт. На процессорах Р-11 и Р-1П в этом случае за 32 такта выполняются 42 итерации записи двойных слов.
Рекомендуемое же документацией выравнивание по границе 4 байт дает гораздо худший результат — за 32 такта выполняются всего лишь ! 2 итераций, т. е. в гари с нолавинай раза меньше! Это объясняется тем, что в первом случае не тратится время на выравнивание внутренних буферов и строк каша — сброс данных происходит по мере заполнения буфера и не интерферирует с операциями выравнивания. Поскольку разрядность шины (и буфера) — 64 бита (8 байт), выбор начального алреса, не кратного 8, приводит к образованию "дыры" в 4 байта и, прежде чем "свалить" данные, потребуется выровнять буфер к кэщу и "закрасить" недостающие 4 байта. Л это — время.
Сказанное справедливо не только для инструкции квг зтоз, но и вообще для любой циклической записи — не важно — слов, двойных слов или даже байт. Поэтому многократно инициализируемые структуры данных, на момент инициализации уже находящиеся в каше, целесообразно выравнивать по адресам, кратным восьми. Циклическая запись в область памяти, отсутствующую в каше, — это совсем другое дело. Выравнивание начального адреса по границе 8 байт ничем не предпочтительнее четырех.
Причем, на процессоре Р-П1 начальный адрес можно вообще не выравнивать, т. к. выигрыш измеряется долями процента. Правда, на процессоре Р-П цикл записи, начинающийся с алреса, не кратного четырем, замедляется более чем в два раза. Такой существенный проигрыш никак нельзя не брать в расчет, лаже в свете того, что парк Р-П с каждым годом будет все сильнее и сильнее истощаться. Сказанное наглядно иллюстрируют графики, приведенные на рис.
3.52 и 3.53, изображающие зависимость скорости инициализации блоков памяти различного размера от кратности начального адреса на процессорах Реп!!ппз-П и Реп!!цп1-Н! (см. программу тещмоге а!!8п). Скорость записи ячеек памяти, отсутствующих в каше, крайне непостоянна и зависит в первую очередь от состояния внутренних буферов процессора. Время инициализации небольших блоков порядка 4 — 8 Кбайт может отличаться в лва и более раз, особенно если операции записи следуют друг за другом всплошную, т. е. без пауз на сброс буферов. Отсутствие пауз при инициализации большого количества блоков памяти приводит к образованию "затора" — переполнению кэгда второго уровня и, как следствие, значительным "тормозам".
И хотя средне взятый разброс скорости записи при Глава 3 402 этом существенно уменьшается (составляя порялка 5%), на графике появляются высокие пики и глубокие провалы, причем пики традиционно предшествуют провалам. Их происхождение связано с переключением задач многозадачной операционной системой, — если остальные задачи не слишком плотно "налегают" на шину (что чаще всего и случается), буферы (или хотя бы часть из них) успевают выгрузиться и полготовигь себя к эффективному приему следующей порции записываемых данных (рис.
3.54). Рио. 3.32. График зависимое~и времени инициализации блоков памяти различного размера от кратности начального адреса (Реп1агп-П! 733/133/100) Непостоянство скорости записи создает проблемы профилирования приложений — разные участки программы поставлены в разные условия. Медленная работа одного из участков вполне может объясняться тем, что предшествующий ему код до отказа заполнил все буферы и теперь инициализация происходит крайне не эффективно, т, е., выражаясь словами одного сказочного героя, "когда болит горло — лечи хвост". Кзш 403 Рис. З.ба.
График зависимости времени инициализации блоков памяти различно~о размера от кратности начального адреса (Се!егоп-ЭООА/бб/бб) Серьезные проблемы наблюдаются и при оптимизации функции инициализации памяти — большой разброс замеров скорости выполнения затрудняет оценку эффективности оптимизации. Приходится делать множество прогонов лля вычисления "средневзвешенного" времени выполнения. В отличие от копирования, инициализировать память всегда лучше в прямом направлении, независимо от того, как обрабатывается проинициализированный блок — с начала или с конца.
Объясняется это тем, что запись ячейки, отсутствующей в кэше, не приводит к загрузке этой ячейки в кэш первого уровня — данные попадают в буферы, откуда выгружаются в кэш второго уровня. Поэтому на блоках, не превышающих размера кэша второго уровня, никакого выигрыша заведомо не получится. Блоки, в несколько раз превосходящие 1.2-кэш, действительно, быстрее обрабатываются будучи проинициализированными сзаду наперед, но выигрыш этот столь несущественен, что о нем не стоит и говорить. Обычно он составляет 5 — 10% и "тонет" на фоне непостоянства скорости инициализации (рис.
3.55). Глава 3 Рис. 3.54. График иллюстрирует непостоянность скорости записи ячеек памяти, отсутствующих в кеше. В данном примере последовательно обрабатывается 512 4-килобайтовых блоков памяти Рис. 3.55. Диаграмма иллюстрирует относительное время инициализации блоков памяти различного размера с последующей обработкой (Репбцпь)П 733/133/100) Кэш 405 Оптимизация инициализации памяти в старших моделях процессоров Рент(ипт.
Инструкция некэшируемой записи восьмеричных слов еря, уже рассмотренная ранее, практически втрое ускоряет инициализацию памяти, при этом не влияя на кэш второго уровня. Это идеально подходит для инициализации больших массивов данных, которые все равно не помещаются в кэше, а вот инициализация компактных структур данных с их последующей обработкой — дело другое. На компактных блоках инструкция .
грз заметно отстает от штатной функции, проигрывая ей в полтора-два раза, а на блоках большого размера инструкция птрк хотя и лидирует, но обгоняет функцию -... г всего на 25 — 30%, что ставит под сомнение целесообразность ее применения (ведь на процессоре Р-П и более ранних процессорах ее нет!) (рис.
3.56, 3.57). Рис. 3.56. Диаграмма иллюстрирует относительное время инициализации блоков памяти различного размера. За 100% взято время инициализации штатной функции нет*ею С нею состязаются инструкция копирования учетверенных слов исчч (светло-серые столбики) и инструкция некзшируюшей записи восьмеричных слов истпг рз (темно-серые столбики) 40б Глава 3 Рис. 3.57. Использование инструкции нсчс на АМ0 АФ!оп Глава 4 Машинная оптиыизация Сравнительный анализ оптимизирующих компиляторов языка С~С++ Лучший способ оптимизации — ие оптимизировать вообще, а изначально производить хороший код. Джон Своей Количество С/С++-компиляторов огромно — какой же из них выбрать? Точнее, на какой критерий (критерии) следует обращать внимание при выборе компилятора? Сшоимость? Отнюдь, — для профессиональных разработчиков ценовой фактор вторичен, — вложенные в компилятор средства они с лихвой окупают одной — максимум двумя программами, а непрофессионалы в своей массе лицензионных продуктов вообще не приобретают.
Степень соответствия АИБ1 стандартом С/С++? Да большинство разработчиков с этими стандартами знакомы лишь понаслышке! И потом, кто из нас удерживался от использования нестандартных расширений или библиотек, специфичных исключительно для данного компилятора? Качество олзлимизаяии кода — один из тех критериев, значимость которого разделяет подавляющее большинство программистов. Даже существует поверье, что "крутой" компилятор способен исправить "кривой от рождения" код. Отчасти это действительно так — оптимизатор устраняет многие небрежности и ляпы программиста, но вот вопрос — какие именно? Техника оптимизации — тайна за семью печатями.
Далеко не каждый разработчик знает, что конкретно умеет оптимизировать его любимый компилятор и чем именно он отличается от своих конкурентов. Штатная документация об этом обычно умалчивает, ограничиваясь рекламными лозунгами, не несущими никакой информации. Доступной литературы, посвященной вопросам оптимизации (насколько известно автору), не существует и един- Глава 4 408 Сводная таблица основных методов оптимизации В тестах принимали участие три популярнейших компилятора: М!сгозой Ч!ьца! С++ 6.0, Вот!апг! С++ 5.0 и ЧУАТСОМ С 10,0, результаты тестирова- ния прелставлены в табл.