Как оптимизировать для двух-, четырехъядерных процессоров и более высоких процессоров?

Люди, я программировал высокоскоростное программное обеспечение более 20 лет и знаю практически каждый трюк в книге из микро-скамьи, делающей совместную работу, профилирование, многозадачность пользовательского режима, рекурсию хвоста, вы называете ее очень высокоэффективной Linux, Windows и многое другое.

Проблема заключается в том, что я нахожусь в недоумении от того, что происходит, когда несколько потоков интенсивной работы с ЦП подвергаются воздействию многоядерных процессоров.

Результаты производительности в микро-тестах различных способов обмена датами между потоками (на разных ядрах), похоже, не следуют логике.

Ясно, что между ядрами существует некоторое "скрытое взаимодействие", которое не очевидно из моего собственного кода программирования. Я слышал о кеше L1 и других проблемах, но это непрозрачно для меня.

Вопрос: где я могу узнать этот материал? Я ищу подробный справочник о том, как работают многоядерные процессоры, как программировать для использования кэшей памяти или другой аппаратной архитектуры, а не наказывать их.

Любые советы или отличные сайты или книги? После большого Googling, я прихожу пустым.

С уважением, Wayne

Ответ 1

Эта книга научила меня многим вопросам такого рода, почему необработанная мощность процессора не является единственной вещью, на которую следует обратить внимание. Я использовал его в аспирантуре несколько лет назад, но я думаю, что все принципы все еще применяются:

http://www.amazon.com/Computer-Architecture-Quantitative-Approach-4th/dp/0123704901

По сути, основной проблемой в многопроцессорных конфигурациях является синхронизация доступа к основной памяти, и если вы не сделаете это правильно, это может стать реальным узким местом в производительности. Это довольно сложно с кешами, которые нужно синхронизировать.

Ответ 2

мой собственный вопрос с ответом на дочернем сайте stackoverflow: https://softwareengineering.stackexchange.com/questions/126986/where-can-i-find-an-overview-of-known-multithreading-design-patterns/126993# 126993

Я скопирую ответ, чтобы избежать необходимости переходов по ссылкам:

Цитата Борис:

Параллельное программирование в Microsoft.NET: шаблоны проектирования для декомпозиции и координации на многоядерных архитектурах https://rads.stackoverflow.com/amzn/click/0735651590

Это книга, я рекомендую всем сердцем.

Это:

Новое - опубликовано в прошлом году. Означает, что вы не читаете несколько устаревших практик.

Коротко о страницах 200+, насыщенных информацией. В наши дни слишком много для чтения и слишком мало времени для чтения 1000+ страниц книг.

Легко читается - он не только очень хорошо написан, но и вводит понятия, которые трудно понять, в действительно простом для чтения виде.

Предназначен для обучения - каждая глава дает упражнения для выполнения. Я знаю, что это всегда полезно, но редко делаю. Эта книга дает очень интересные и интересные задачи. Удивительно, но я сделал большинство из них и получил удовольствие от них.

Кроме того, если вы хотите узнать больше о низкоуровневых деталях, это лучший ресурс, который я нашел: " Искусство многопроцессорного программирования ". Он написан с использованием java в качестве примеров кода, что прекрасно сочетается с моим фоном С#.

PS: у меня около 5 лет опыта работы в параллельном программировании на основе "хардкор" (хотя и с использованием С#), поэтому надеюсь, что вы можете мне доверять, когда я скажу, что " Искусство многопроцессорного программирования " рушится

Ответ 4

Одной из конкретных причин неожиданных плохих результатов в параллельном коде является ложное использование, вы не увидите, что это произойдет, если вы не знаете, что происходит там (я этого не делал). Вот две статьи, в которых обсуждаются причины и способы устранения .Net:

http://msdn.microsoft.com/en-us/magazine/cc872851.aspx

http://www.codeproject.com/KB/threads/FalseSharing.aspx

Rgds GJ

Ответ 5

Существуют различные аспекты многопоточности, требующие разных подходов.

На веб-сервере, например, использование потоков-пулов широко используется, поскольку оно якобы "хорошо для" производительности. Такие пулы могут содержать сотни потоков, ожидающих выхода на работу. Использование этого большого количества потоков приведет к тому, что планировщик будет работать сверхурочно, что вредно для производительности, но в Linux-системах этого не избежать. Для Windows выбранным методом является механизм IOCP, который рекомендует несколько потоков, не превышающих количество установленных ядер. Это заставляет приложение запускать событие ввода-вывода (I/O), что означает, что никакие циклы не теряются при опросе. Несколько потоков привели к минимуму работы планировщика.

Если объект должен реализовать масштабируемую функциональность (больше ядер <= > более высокая производительность), то основной проблемой будет насыщение шины памяти. Насыщение будет происходить из-за выбора кода, чтения данных и записи данных. Некорректно реализованный код будет работать медленнее с двумя потоками, чем с одним. Единственный способ уменьшить нагрузку на шину памяти:

  • адаптация кода к минимальному объему памяти (= соответствует кешу кода) и который не вызывает другие функции или не перескакивает повсюду.
  • портняжная память считывает и записывает минимальный размер.
  • информирует механизм предварительной выборки текущей памяти.
  • адаптируя работу таким образом, чтобы отношение работы, выполняемой внутри основного кэша (L1 и L2), было максимально возможным по отношению к работе вне их (L3 и ОЗУ).

Поставить это по-другому: подберите применимый код и фрагменты данных как можно меньше строк кеша (по 64 байта), потому что в конечном итоге это будет определять масштабность. Если система кэша/памяти способна выполнять операции кэширования x каждую секунду, ваш код будет работать быстрее, если его требования - пять строк кэша на единицу работы (= > x/5), а не одиннадцать (x/11) или пятьдесят два (х/52).

Достижение этого не является тривиальным, поскольку оно требует более или менее уникального решения каждый раз. Некоторые компиляторы хорошо справляются с инструкциями по обучению, чтобы воспользоваться конвейерной обработкой хост-процессора. Это не обязательно означает, что это будет хороший порядок для нескольких ядер.

Эффективная реализация масштабируемого кода не обязательно будет довольно хорошей. Рекомендуемые методы кодирования и стили могут, в конце концов, препятствовать выполнению кода.

Мой совет - проверить, как это работает, написав простое многопоточное приложение на низкоуровневом языке (например, C), которое может быть настроено для запуска в одно- или многопоточном режиме, а затем профилирование кода для разных режимах. Вам необходимо проанализировать код на уровне инструкций. Затем вы экспериментируете с использованием различных конструкций кода (C), организации данных и т.д. Возможно, вам придется подумать нестандартно и переосмыслить алгоритм, чтобы сделать его более удобным для кэширования.

В первый раз потребуется много работы. Вы не узнаете, что будет работать для всех многопоточных решений, но вы, возможно, получите представление о том, что не делать и какие указания искать при анализе профилированного кода.

Ответ 6

Я нашел эту ссылку, которая специально объясняет проблемы многоядерная обработка кеша на процессорах, которые влияли на мои многопоточная программа.

http://www.multicoreinfo.com/research/intel/mem-issues.pdf

На сайте multicoreinfo.com есть много хорошего информация и ссылки о многоядерном программировании.