Pull to refresh

Comments 30

Я думаю, читателям было бы интересно узнать, как именно обеспечивается когерентность кешей на нескольких ядрах (MESI протокол?).
MESI — это базовый протокол синхронизации кэшей, ранее использовавшийся в микроархитектурой с общей шиной (FSB). Сейчас, с переходом на QPI используют модифицированный протокол MESIF. Программистам, даже тем, кто оптимизирует для микроархитектуры, подробности реализации алгоритма будут в основном скучны — мне так кажется. Но если действительно интересно, то можно почитать интересную статейку, где сравниваются несколько протоколов, актуальных на данный момент.
Может быть с точки зрения программиста разницу между MESI и MESIF понимать не нужно. Но не рассказав про тот же MESI крайне сложно объяснить, в чем проблема с false sharing.
Если честно, я не согласен, что для понимания проблемы false sharing необходимо объяснять весь принцип работы протокола когерентности кэша. Я просто упомянул следствие его работы — перевод кэш-линии в состояние невалидной при модификации ее элементов. И далее, заметил, что для восстановления идентичности данных в кэшах ядер (синхронизация) требуется некоторое время (которое теряет наша программа).

Понять прицип работы протокола можно, пойдя по ссылкам. А загружать свой пост такими подробностями было бы жестоко по отношению к читателям. Меня и так слегка пинают за излишнюю избыточность и сложность моих обзоров. Поэтому я попробовал соблюсти разумный баланс.
UFO just landed and posted this here
Открыл топик и первым делом стал искать подобные комментарии :)
UFO just landed and posted this here
Конкурентная read-read производительность при обращение к одной линейки же ок.
В статье это не указано явно, и может создаться неверное впечатление, что false sharing это всегда плохо, и нужно его лечить.
Кстати, меня всегда волновал вопрос актуальности оптимизации операций с кэшем в современных не-RT осях. Там же запущенно 100500 процессов в фоне, которые вполне вероятно обращаются к данным в памяти, и мусорят в L1/L2 кэше и портят производительность оптимизированным программам!
Обращение к одной кэш-линии по чтению всеми потоками с высокой производительностью — это, собственно, главное назначение и сама суть системы организации памяти, поэтому я даже не догадался об этом написать. Но замечание верное.

Что касается 100500 потоков в системе — нас это не должно сильно волновать, так как эти потоки принадлежат разным процессам, которым операционная система предоставляет доступ, переключая контекст. Это все-рано приводит к вытеснению данных из кэшей, если все процессы активно работают и используют память. С этим ничего не поделать. Другое дело — одни процесс, наша программа, которую мы оптимизируем. Мы можем иметь сколько угодно потоков (число ограничено лимитами ресурсов ОС), но они должны «спать», а активными, т.к. готовыми к исполнению, должно быть количество потоков, равное числу логических процессоров в системе — тогда баланс будет соблюден. Дальше уже требуется работать над оптимизацией расположения данных в кэш-памяти.
> Это все-рано приводит к вытеснению данных из кэшей, если все процессы активно работают и используют память

Дык о том и речь, что может получиться так что оптимизация по кэшу срабатывать будет крайне редко, ибо есть другие процессы, которые портят кэш. Вот поэтому и возникает вопрос, насколько это эффективно в реальных, а не эталонных, системах.
Правда, с другой стороны само переключение контекстов занимает сотни, а то и тысячи тактов, и можно, наверно, забить на потери при cache-miss'ах при возобновлении потока.
В случае системы, в которой работают много процессов, нагружающих память, оптимизация будет работать в рамках кванта времени, выделенного на наш процесс. Но это тоже важно, так как в относительных величинах программа бутет все-рано работать быстрее.
Вы правы, переключение контекстов занимает больше тысячи тактов, и забить можно на потери при cache-miss'aх которые возникнут при заполнении кэшей данными заново, а вот на остальные cache-miss'ы забивать не надо )
Проблема, которую вы описываете, играет существенную роль для систем с общим LLC, на которых на разных ядрах исполняются RT и GPOS, с или без виртуализации. Если цикл для RT системы порядка сотен микросекунд с толерантностью в десятки микросекунд, вытеснения данных RT процесса из LLC действительно будет критичным.

В ARM есть Cache lock down, которые это решает, в x86 (пока) нет.
Так на сколько, в результате, увеличивается скорость?
Подскажите пожалуйста, кто ответственен за распределение потоков по ядрам на процессоре с HyperThreading (для уменьшения борьбы за L1/L2) — программист или ОС?
По-умолчанию, ОС сама назначает исполнение потоков в логических процессорах. Но в случаях, когда это оправдано с точки зрения производительности, программист может сам привязывать потоки к процессорам с помощью механизма аффинитизации — наиболее часто применяется для NUMA-платформ, где бывает важно данные локализовать в «своей» памяти процессора. Привязка к процессорам для улучшения работы кэшей неэффективна.
Вот есть статейка про HT, как его использовать эффективно.
> P.S. Попробуйте мой примерчик, и расскажите, на сколько процентов увеличилось быстродействие теста на вашей платформе.

Ну, для этого полную готовую программу выложить было бы неплохо, наверное? Хотя бы исходник, про бинарник уж молчу.
Да, было бы неплохо…

«Так вы что, и конфеты за меня есть будете?!.. — АГА!!!»
[Вовка в Тридевятом царстве]
Это был ответ на:
>Ну, для этого полную готовую программу выложить было бы неплохо, наверное?
Т.к. начинаю понемногу баловаться с OpenCL, то написал простенький kernel (в OpenCL так называется код, который выполняется на устройстве в несколько потоков) для заполнения матрицы размером 16*16. Запустил 5 млн итераций. Процессор Intel Core i5 650 (3.2 GHz), OS Ubuntu 10.04 x64

1) Кол-во потоков: 256, время выполнения: 69 сек
2) Кол-во потоков: 16, время выполнения: 63 сек
Оптимальное количество потоков обычно берут как 2 * количество логических процессоров.
Больше — редко когда эффективно для вычислительных задач.
Оптимальное количество _активных_ потоков, все же, мы считаем = количество логических процессоров.
Работаю с SoC имеющей 2 ядра с кешем, но с отсутствующим механизмом когерентности кешей. Основные правила:
1. Не использовать глобальные переменные
2. Если все же без глобальных переменных не обойтись, то выравнить их по длине кеш линии (16 байт в моем случае).
3. Попытаться сгруппировать глобальные переменные в структуры, чтобы избежать траты памяти из-за padding байт.
3. Использовать некешируемый доступ к этой переменной/структуре.

осталость выяснить, сохранится ли выигрыш в производительности, если пользователь запустит эту программу под Atom, AMD Fusion или вообще под виртуальной машиной
Под Atom не думаю, что будет сильный прирост, а вообще, для любой сисиемы актуально, где используется кэширование памяти. Особенно если это многопроцессорная система.
перестали работать картинки
The server encountered an internal error or misconfiguration and was unable to complete your request.
Sign up to leave a comment.