Неэффективный доступ к памяти, пожалуй, одна из наиболее частых проблем производительности программ. Скорость загрузки данных из памяти традиционно отстаёт от скорости их обработки процессором. Для уменьшения времени доступа к данным в современных процессорах реализуются специальные блоки и многоуровневые системы кэшей, позволяющие сократить время простоя процессора при загрузке данных, однако, в некоторых случаях, процессорная логика работает не эффективно. В этом посте поговорим о том, как можно исследовать работу с памятью вашего приложения с помощью нового профиля Memory Access в VTune Amplifier XE.
![](https://habrastorage.org/files/06f/cbd/30e/06fcbd30e74445ba8da23c0fae109a25.png)
Метрики, относящиеся к памяти, уже давно доступны в VTune Amplifier. Новый тип анализа Memory Access не только собрал их в одном месте, но и добавил несколько серьёзных улучшений.
Было раньше в других типах анализа:
Появилось в Intel VTune Amplifier XE 2016:
Итак, запускаем анализ Memory Access, сразу включаем отслеживание объектов данных. Эта возможность пока доступна только на Linux.
![](https://habrastorage.org/files/3bb/8f1/d86/3bb8f1d868e246a686eec0f4cff49693.png)
Или из командной строки:
![](https://habrastorage.org/files/06f/cbd/30e/06fcbd30e74445ba8da23c0fae109a25.png)
Итак, посмотрим на профиль, полученный после профилирования бенчмарка stream. В верхней половине окна Bottom-up отражён трафик, созданный нашим приложением:
Ниже представлена таблица, отражающая основные аппаратные метрики и счётчики обращений к памяти:
В Memory Bound входит отношение количества обращений к удалённой и локальной оперативной памяти, и метрики по тем же категориям, что и промахи кэша верхнего уровня (LLC):
![](https://habrastorage.org/files/bfc/68b/497/bfc68b4976a64e7586b25e93d43e1637.png)
Метрики Memory Bound это формулы, включающие количество тех или иных событий (например, промахов LLC) и их оценочной стоимости в циклах. Величина метрики сравнивается с некоторым эталонным значением, и подсвечивается розовым, если она превышает заданный порог. На выходе пользователь получает подсказку о том, на что стоит обратить внимание – большое количество промахов кэша может говорить о неэффективности расположения объектов данных, возможно, самые «горячие» объекты можно вынести в отдельный блок, который сможет дольше удерживаться в кэше.
Проблемы работы с памятью неразрывно связаны с объектами данных, которые раньше приходилось вычислять самому. Теперь можно просто выбрать группировку с Memory Object, и наблюдать, на какие конкретно объекты приходятся большая часть обращений, промахи кэша и т.д.:
![](https://habrastorage.org/files/311/a6f/bf4/311a6fbf4e064e9fa6ea6ac931e08fdf.png)
При этом, если дважды кликнуть на функцию над объектом, то попадём на строку кода, осуществлявшую интересующую нас операцию с памятью:
![](https://habrastorage.org/files/e54/be3/2cf/e54be32cfa7247b688fef075381fcf8a.png)
А если сгруппироваться по Memory Object Allocation Source и дважды кликнуть на сам объект, то можно определить место его создания:
![](https://habrastorage.org/files/48a/1aa/414/48a1aa414d7d4efc816751dba2b2573b.png)
![](https://habrastorage.org/files/742/3d7/dcd/7423d7dcd05a4f179b6c420b9ea70ea5.png)
VTune Amplifier распознаёт динамически создаваемые объекты языков С и C++ и статические объекты С, С++ и Fortran.
На NUMA машинах обращения к локальной оперативной памяти выполняются быстрее, чем к удалённой, т.к. к оперативной памяти другого сокета приходится обращаться через QPI шину, которая медленнее, чем шина доступа к локальной оперативной памяти. Большое количество обращений к оперативной памяти или кэшу другого сокета, вкупе с высокими значениями Average Latency, может свидетельствовать о неэффективной локализации данных приложения. Если, например, глобальный объект данных создан в главном потоке, а другие потоки, возможно работающие на другом сокете, активно обращаются к нему, возможны простои процессора, обусловленные удалённым обращением к данным. Подобные проблемы можно решать с помощью локализации горячих данных в потоке, “пиннингом” (pinning) потоков, применением разных NUMA-aware библиотек.
Обнаружить NUMA проблемы в VTune Amplifier теперь достаточно просто. Для начала, смотрим все метрики со словом Remote, например, Remote/Local DRAM ratio – относительное количество удалённых обращений:
![](https://habrastorage.org/files/ac1/cf7/6f4/ac1cf76f427b4fbb9199d3129786de40.png)
Можно отфильтровать функции и объекты с высоким QPI трафиком. На вкладке Summary в Bandwidth Utilization Histogram двигаем ползунки, определяя, какие значения мы считаем Low, Medium и High:
![](https://habrastorage.org/files/4f3/852/35b/4f385235b6eb4a91aea0679bf9405706.png)
В Bottom-up группируемся по Bandwidth Domain, и смотрим, какие объекты использовались (или какой код исполнялся) во времена высокой QPI нагрузки:
![](https://habrastorage.org/files/88a/37b/e15/88a37be1564e4c5e858f735a00f87fca.png)
Ну и традиционно для Bandwidth анализа, всплески трафика бывают наглядно видны на временной шкале. Выделяем такой участок и фильтруемся (клик правой кнопкой, Filter In by selection). Список функций в таблице внизу отразит только код, исполнявшийся в выбранном промежутке времени:
![](https://habrastorage.org/files/2d5/e17/4eb/2d5e174eb9f649eb9bc7a7dd0b1a1178.png)
Ниже приведён результат профиля другого бенчмарка – Intel Memory Latency Checker:
![](https://habrastorage.org/files/4e8/f32/fc2/4e8f32fc203441c6996e788cad42e161.png)
Выделенный фрагмент имеет большой трафик на чтение и низкий на запись из локальной оперативной памяти сокета 1. Т.е. сокет 1 что-то активно читает. Также сокет 1 имеет большой исходящий трафик QPI, т.е. он активно что-то шлёт сокету 0 (больше некому, их всего два. Если сокетов 4 и больше, можно тоже определить направление по UNIT-ам, конкретным QPI линкам). При этом на сокете 0 наблюдается высокая активность процессора. Всё это наталкивает на мысль, что сокет 0 активно обращается к данным, которые расположены в оперативной памяти сокета 1, что подкрепляется данными о количестве удалённых обращений в таблице. Дальше можно разбивать таблицу до уровня функций и находить конкретные места в коде, ответственные за выявленный шаблон доступа.
Новый тип анализа Memory Access помогает увидеть, как исполнение кода приложения соотносится с физической топологией памяти машины. Какие уровни памяти задействованы (кэши, DRAM, удалённая DRAM), как распределялся трафик памяти. И, что самое главное, какой код исполнялся во время долгих обращений к памяти, и к каким объектам данных эти обращения происходили.
Да, и если кто не слышал – Intel Parallel Studio можно скачать бесплатно для разных некоммерческих нужд – детали здесь.
![](https://habrastorage.org/files/06f/cbd/30e/06fcbd30e74445ba8da23c0fae109a25.png)
Метрики, относящиеся к памяти, уже давно доступны в VTune Amplifier. Новый тип анализа Memory Access не только собрал их в одном месте, но и добавил несколько серьёзных улучшений.
Было раньше в других типах анализа:
- Промахи кэшей всех уровней
- Данные о трафике в локальную оперативную память (DRAM bandwidth)
- Количество обращений к локальной и удалённой оперативной памяти на NUMA машинах
Появилось в Intel VTune Amplifier XE 2016:
- Отслеживание объектов данных
- Данные о трафике в удаленную оперативную память на NUMA машинах (QPI bandwidth)
- Среднее время исполнения (Аverage Latency) инструкций обращения к памяти или загрузки данных из объектов приложения
Итак, запускаем анализ Memory Access, сразу включаем отслеживание объектов данных. Эта возможность пока доступна только на Linux.
![](https://habrastorage.org/files/3bb/8f1/d86/3bb8f1d868e246a686eec0f4cff49693.png)
Или из командной строки:
amplxe-cl -c memory-access -knob analyze-mem-objects=true -knob mem-object-size-min-thres=1024 -- ./my_app
Метрики подсистемы памяти
![](https://habrastorage.org/files/06f/cbd/30e/06fcbd30e74445ba8da23c0fae109a25.png)
Итак, посмотрим на профиль, полученный после профилирования бенчмарка stream. В верхней половине окна Bottom-up отражён трафик, созданный нашим приложением:
- DRAM bandwidth – чтение (зелёным), запись (красным), и общий трафик в локальную оперативную память по каждому сокету (package).
- QPI outgoing bandwidth – исходящий трафик от одного сокета к другому.
Ниже представлена таблица, отражающая основные аппаратные метрики и счётчики обращений к памяти:
- Average Latency – среднее количество циклов процессора, затраченное на выполнение инструкций обращения к памяти или загрузку данных из объектов приложения. Это статистическое значение, может быть неточным на коротких замерах. Однако, если значение велико, стоит обратить внимание на остальные метрики.
- LLC Miss Count – количество промахов кэша верхнего уровня (обычно L3) – подразделяются на обращения к локальной оперативной памяти, удалённой оперативной памяти и удалённому кэшу, т.к. загружаемые данные могут находится в кэше другого процессора.
- Loads и Stores – количество исполненных инструкций чтения и записи данных
- Memory Bound – метрики эффективности операций с памятью
В Memory Bound входит отношение количества обращений к удалённой и локальной оперативной памяти, и метрики по тем же категориям, что и промахи кэша верхнего уровня (LLC):
![](https://habrastorage.org/files/bfc/68b/497/bfc68b4976a64e7586b25e93d43e1637.png)
Метрики Memory Bound это формулы, включающие количество тех или иных событий (например, промахов LLC) и их оценочной стоимости в циклах. Величина метрики сравнивается с некоторым эталонным значением, и подсвечивается розовым, если она превышает заданный порог. На выходе пользователь получает подсказку о том, на что стоит обратить внимание – большое количество промахов кэша может говорить о неэффективности расположения объектов данных, возможно, самые «горячие» объекты можно вынести в отдельный блок, который сможет дольше удерживаться в кэше.
Объекты данных
Проблемы работы с памятью неразрывно связаны с объектами данных, которые раньше приходилось вычислять самому. Теперь можно просто выбрать группировку с Memory Object, и наблюдать, на какие конкретно объекты приходятся большая часть обращений, промахи кэша и т.д.:
![](https://habrastorage.org/files/311/a6f/bf4/311a6fbf4e064e9fa6ea6ac931e08fdf.png)
При этом, если дважды кликнуть на функцию над объектом, то попадём на строку кода, осуществлявшую интересующую нас операцию с памятью:
![](https://habrastorage.org/files/e54/be3/2cf/e54be32cfa7247b688fef075381fcf8a.png)
А если сгруппироваться по Memory Object Allocation Source и дважды кликнуть на сам объект, то можно определить место его создания:
![](https://habrastorage.org/files/48a/1aa/414/48a1aa414d7d4efc816751dba2b2573b.png)
![](https://habrastorage.org/files/742/3d7/dcd/7423d7dcd05a4f179b6c420b9ea70ea5.png)
VTune Amplifier распознаёт динамически создаваемые объекты языков С и C++ и статические объекты С, С++ и Fortran.
NUMA проблемы
На NUMA машинах обращения к локальной оперативной памяти выполняются быстрее, чем к удалённой, т.к. к оперативной памяти другого сокета приходится обращаться через QPI шину, которая медленнее, чем шина доступа к локальной оперативной памяти. Большое количество обращений к оперативной памяти или кэшу другого сокета, вкупе с высокими значениями Average Latency, может свидетельствовать о неэффективной локализации данных приложения. Если, например, глобальный объект данных создан в главном потоке, а другие потоки, возможно работающие на другом сокете, активно обращаются к нему, возможны простои процессора, обусловленные удалённым обращением к данным. Подобные проблемы можно решать с помощью локализации горячих данных в потоке, “пиннингом” (pinning) потоков, применением разных NUMA-aware библиотек.
Обнаружить NUMA проблемы в VTune Amplifier теперь достаточно просто. Для начала, смотрим все метрики со словом Remote, например, Remote/Local DRAM ratio – относительное количество удалённых обращений:
![](https://habrastorage.org/files/ac1/cf7/6f4/ac1cf76f427b4fbb9199d3129786de40.png)
Можно отфильтровать функции и объекты с высоким QPI трафиком. На вкладке Summary в Bandwidth Utilization Histogram двигаем ползунки, определяя, какие значения мы считаем Low, Medium и High:
![](https://habrastorage.org/files/4f3/852/35b/4f385235b6eb4a91aea0679bf9405706.png)
В Bottom-up группируемся по Bandwidth Domain, и смотрим, какие объекты использовались (или какой код исполнялся) во времена высокой QPI нагрузки:
![](https://habrastorage.org/files/88a/37b/e15/88a37be1564e4c5e858f735a00f87fca.png)
Ну и традиционно для Bandwidth анализа, всплески трафика бывают наглядно видны на временной шкале. Выделяем такой участок и фильтруемся (клик правой кнопкой, Filter In by selection). Список функций в таблице внизу отразит только код, исполнявшийся в выбранном промежутке времени:
![](https://habrastorage.org/files/2d5/e17/4eb/2d5e174eb9f649eb9bc7a7dd0b1a1178.png)
Ниже приведён результат профиля другого бенчмарка – Intel Memory Latency Checker:
![](https://habrastorage.org/files/4e8/f32/fc2/4e8f32fc203441c6996e788cad42e161.png)
Выделенный фрагмент имеет большой трафик на чтение и низкий на запись из локальной оперативной памяти сокета 1. Т.е. сокет 1 что-то активно читает. Также сокет 1 имеет большой исходящий трафик QPI, т.е. он активно что-то шлёт сокету 0 (больше некому, их всего два. Если сокетов 4 и больше, можно тоже определить направление по UNIT-ам, конкретным QPI линкам). При этом на сокете 0 наблюдается высокая активность процессора. Всё это наталкивает на мысль, что сокет 0 активно обращается к данным, которые расположены в оперативной памяти сокета 1, что подкрепляется данными о количестве удалённых обращений в таблице. Дальше можно разбивать таблицу до уровня функций и находить конкретные места в коде, ответственные за выявленный шаблон доступа.
Резюме
Новый тип анализа Memory Access помогает увидеть, как исполнение кода приложения соотносится с физической топологией памяти машины. Какие уровни памяти задействованы (кэши, DRAM, удалённая DRAM), как распределялся трафик памяти. И, что самое главное, какой код исполнялся во время долгих обращений к памяти, и к каким объектам данных эти обращения происходили.
Да, и если кто не слышал – Intel Parallel Studio можно скачать бесплатно для разных некоммерческих нужд – детали здесь.