Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Аппаратное обеспечение

Что каждый программист должен знать о памяти, часть 7. Инструменты для повышения производительности памяти

Оригинал: Memory part 7: Memory performance tools
Автор: Ulrich Drepper
Дата публикации: ноябрь 2007 г.
Перевод: М.Ульянов
Дата перевода: 5 мая 2010 г.

Инструменты для повышения производительности памяти

7.2 Моделирование кэшей ЦПУ

В то время как техническое описание работы кэша относительно легко понять, гораздо тяжелее разобраться, как именно с ним взаимодействует конкретная программа. Программисты не работают напрямую с адресами, ни с абсолютными, ни с относительными. Адреса определяются, во-первых, компоновщиком; а во-вторых - уже во время выполнения программы, ядром и динамическим компоновщиком. Ожидается, что полученный в результате компоновки код сможет работать со всеми возможными адресами; в исходном же коде нет ни намека на то, какие адреса использовать. Вот поэтому понять, как именно программа работает с памятью, и бывает довольно тяжело. {При программировании на низком уровне, близком к аппаратному, ситуация может отличаться - но такой вариант не является типичным для нормального программирования и вообще возможен только в особых случаях, например с устройствами, отображаемыми в память.}

Разобраться в использовании кэша могут помочь инструменты профилирования уровня ЦПУ, вроде oprofile, описанного в разделе 7.1. Предоставляемые ими результаты привязаны к фактически используемому оборудованию, и могут быть собраны относительно быстро, если нет необходимости в большей детализации. Но если углубленная детализация все-таки потребуется, oprofile выходит из игры: слишком часто придется прерывать работу потока, что недопустимо. Более того, чтобы протестировать поведение программы на разном оборудовании, придется как-то находить другие машины и выполнять программу на них. Иногда (а скорее - очень и очень часто) это попросту невозможно. Возьмем пример - рисунок 3.8. Чтобы собрать такие данные с помощью oprofile, потребовалось бы 24 компьютера разных конфигураций, многих из которых вообще не существует на данный момент.

На деле те данные были получены с использованием программы моделирования кэша. Программа носит имя cachegrind и использует инструментарий valgrind, который первоначально разрабатывался для отладки работы с памятью. Valgrind моделирует выполнение программы и при этом поддерживает различные расширения, вроде cachegrind, позволяющие в ней копаться. Утилита cachegrind использует эти возможности для перехвата всех адресных обращений к памяти; затем она моделирует работу кэшей L1i, L1d и L2 с заданными объемами, размерами строк кэша и соответствующей ассоциативностью.

Для применения утилиты необходимо запустить программу, вызвав её через valgrind:

valgrind --tool=cachegrind command arg

Это простейшая форма запуска: программа command запускается с параметром arg и моделированием трех кэшей, в которых используются размеры и ассоциативность, аналогичные таковым на используемом процессоре. Часть результатов выводится при стандартной ошибке во время выполнения программы; показывается общая статистика использования кэша, как показано на рисунке 7.5.

==19645== I   refs:      152,653,497
==19645== I1  misses:         25,833
==19645== L2i misses:          2,475
==19645== I1  miss rate:        0.01%
==19645== L2i miss rate:        0.00%
==19645==
==19645== D   refs:       56,857,129  (35,838,721 rd + 21,018,408 wr)
==19645== D1  misses:         14,187  (    12,451 rd +      1,736 wr)
==19645== L2d misses:          7,701  (     6,325 rd +      1,376 wr)
==19645== D1  miss rate:         0.0% (       0.0%   +        0.0%  )
==19645== L2d miss rate:         0.0% (       0.0%   +        0.0%  )
==19645==
==19645== L2 refs:            40,020  (    38,284 rd +      1,736 wr)
==19645== L2 misses:          10,176  (     8,800 rd +      1,376 wr)
==19645== L2 miss rate:          0.0% (       0.0%   +        0.0%  )

Рисунок 7.5. Общие выходные данные cachegrind

Дано общее число команд и обращений к памяти, количество промахов кэшей L1i/L1d и L2, коэффициенты промахов и т.п. Утилита может даже разделять обращения к L2 на запросы инструкций и запросы данных, а все обращения к данным - на запросы чтения и запросы записи.

А еще интереснее становится, если изменить параметры моделируемых кэшей и сравнить результаты. Чтобы заставить cachegrind игнорировать параметры кэшей используемого процессора и применять указанные в командной строке, используются параметры --I1, --D1 и --L2. Ну например, строка

  valgrind --tool=cachegrind --L2=8388608,8,64 command arg

приведет к моделированию кэша L2 объемом 8 Мб с 8-канальной ассоциативностью и строкой кэша размером 64 байта. Обратите внимание, что параметр --L2 в командной строке идет перед именем моделируемой программы.

Этим возможности cachegrind не исчерпываются. Перед завершением процесса cachegrind создает файл с именем cachegrind.out.XXXXX, где XXXXX - идентификатор процесса (PID). В файле содержится как общая суммарная информация, так и подробности использования кэша каждой функцией исходного кода. Данные можно просмотреть с помощью программы cg_annotate.

Выходные данные, предоставляемые программой, содержат общую информацию об использовании кэша, которая была показана при завершении процесса, а также подробности использования строк кэша каждой функцией программы. Для связи данных с соответствующими функциями необходимо, чтобы cg_annotate могла сопоставить адреса функциям. То есть, для наилучшего результата должна быть доступна информация отладки. Если таковая отсутствует, то могут помочь таблицы идентификаторов ELF, но, поскольку внутренние идентификаторы отсутствуют в динамической таблице, результаты будут неполными. На рисунке 7.6 показана часть выходных данных для того же прогона программы, что и на рисунке 7.5.

--------------------------------------------------------------------------------
        Ir  I1mr I2mr         Dr  D1mr D2mr        Dw D1mw D2mw  file:function
--------------------------------------------------------------------------------
53,684,905     9    8  9,589,531    13    3 5,820,373   14    0  ???:_IO_file_xsputn@@GLIBC_2.2.5
36,925,729 6,267  114 11,205,241    74   18 7,123,370   22    0  ???:vfprintf
11,845,373    22    2  3,126,914    46   22 1,563,457    0    0  ???:__find_specmb
 6,004,482    40   10    697,872 1,744  484         0    0    0  ???:strlen
 5,008,448     3    2  1,450,093   370  118         0    0    0  ???:strcmp
 3,316,589    24    4    757,523     0    0   540,952    0    0  ???:_IO_padn
 2,825,541     3    3    290,222     5    1   216,403    0    0  ???:_itoa_word
 2,628,466     9    6    730,059     0    0   358,215    0    0  ???:_IO_file_overflow@@GLIBC_2.2.5
 2,504,211     4    4    762,151     2    0   598,833    3    0  ???:_IO_do_write@@GLIBC_2.2.5
 2,296,142    32    7    616,490    88    0   321,848    0    0  dwarf_child.c:__libdw_find_attr
 2,184,153 2,876   20    503,805    67    0   435,562    0    0  ???:__dcigettext
 2,014,243     3    3    435,512     1    1   272,195    4    0  ???:_IO_file_write@@GLIBC_2.2.5
 1,988,697 2,804    4    656,112   380    0    47,847    1    1  ???:getenv
 1,973,463    27    6    597,768    15    0   420,805    0    0  dwarf_getattrs.c:dwarf_getattrs

Рисунок 7.6: Выходные данные cg_annotate

Столбцы Ir, Dr и Dw показывают общее использование кэша, а не промахи, которые, в свою очередь, показаны в оставшихся двух столбцах. Эти данные можно использовать для поиска области кода, приводящей к большинству промахов кэша. В первую очередь, пожалуй, стоит сконцентрироваться на промахах L2, а затем уже переходить к оптимизации промахов L1i/L1d.

cg_annotate может детализировать данные еще сильнее. Если дано имя исходного файла, утилита сможет пометить каждую строку (кроме имени программы) количеством промахов и попаданий кэша, соответствующих данной строке. Такая информация позволит программисту точно выявить строку, приводящую к промахам. Интерфейс программы сыроват: на момент написания данной работы, файл данных cachegrind и исходный файл должны находиться в одной директории.

А теперь снова следует повторить: cachegrind - утилита моделирования, которая не использует аппаратные измерения процессора. Фактическая реализация кэшей процессора может очень и очень сильно отличаться. Cachegrind моделирует алгоритм вытеснения LRU (Least Recently Used), который для кэшей с большим уровнем ассоциативности явно слишком дорог. Более того, при моделировании не принимаются во внимание системные вызовы и контекстные переключения, а ведь и те и другие могут занять большую часть L2 и переполнить L1i/L1d. В итоге число промахов кэша при модуляции оказывается меньше, чем оно есть на самом деле в реальных условиях. Но тем не менее, cachegrind - отличная утилита для изучения работы программы с памятью и сопутствующих проблем.


Назад Оглавление Вперед

Замечания и предложения по переводу принимаются по адресу michael@right-net.ru. М.Ульянов.