В настоящее время я пытаюсь заняться программированием с несколькими ядрами. Я хочу написать / реализовать параллельное умножение матриц с помощью C ++ / Python / Java (я предполагаю, что Java будет самым простым).

Но один вопрос, на который я не могу ответить сам, - это то, как доступ к ОЗУ работает с несколькими процессорами.

Мои мысли

У нас есть две матрицы A и B. Мы хотим вычислить C = A * B:

enter image description here

Параллельное выполнение будет быстрее, только если n, m или p большие. Итак, предположим, что n, m и p> = 10,000. Для простоты предположим, что n = m = p = 10,000 = 10 ^ 4.

Мы знаем, что можем вычислить каждый $ c_ {i, j} $, не глядя на другие записи C. Таким образом, мы можем вычислить все c_ {i, j} параллельно:

enter image description here

Но для всех c_ {1, i} (i \ in 1, ..., p) нужна первая строка A. Поскольку A представляет собой массив с 10 ^ 8 двойными, ему требуется 800 МБ. Это определенно больше, чем кеш процессора. Но одна строка (80кБ) поместится в кеш процессора. Поэтому я думаю, что будет хорошей идеей назначить каждую строку C ровно одному процессору (как только процессор освободится). Таким образом, этот процессор будет иметь как минимум A в кэше и извлекать из этого выгоду.

Мой вопрос

Как осуществляется доступ к ОЗУ для разных ядер (на обычном ноутбуке Intel)?

Я предполагаю, что должен быть один «контроллер», который дает монопольный доступ к одному ЦП за раз. У этого контроллера есть особое имя?

Случайно двум или более процессорам может потребоваться одна и та же информация. Могут ли они получить его одновременно? Является ли доступ к оперативной памяти узким местом в проблеме умножения матриц?

Сообщите мне также, когда вы узнаете несколько хороших книг, знакомящих вас с многоядерным программированием (на C ++ / Python / Java).

5
Martin Thoma 19 Окт 2013 в 17:18
Вы также можете узнать о согласованности кеша.
 – 
Chris O
19 Окт 2013 в 17:25
Также существует разница (с точки зрения управления памятью) между многоядерными и многопроцессорными процессорами, поскольку несколько ядер на одном физическом процессоре будут совместно использовать (по крайней мере, часть) кэш-память. Все ядра могут читать из ОЗУ, хотя это не может быть «буквально» одновременно. Это типичный современный процессор с несколькими ядрами, который реализует общий кеш верхнего уровня для всех ядер.
 – 
Leigh
19 Окт 2013 в 17:38
1
Зачем изобретать колесо? :) Почему бы не взять что-то вроде OpenBLAS и посмотреть реализацию?
 – 
Vladislav Rastrusny
19 Окт 2013 в 18:54
Потому что я считаю OpenBLAS слишком сложным. Когда я смотрю на репозиторий (github.com/xianyi/OpenBLAS), я не знаю, куда Начните.
 – 
Martin Thoma
19 Окт 2013 в 18:56
Я думаю, это обычно используется для умножения компьютерных матриц: en.wikipedia.org/wiki/LU_decomposition
 – 
v.oddou
20 Мар 2014 в 05:38

1 ответ

Лучший ответ

Вы должны разделить вопросы распараллеливания умножения матриц удобным для кеша способом (для этого есть много методов - ищите "tiling". вот хорошее объяснение из Беркли ), из вопроса о том, как несколько ядер совместно используют доступ к некоторым ресурсам, таким как общий кеш и память. Первый относится к тому, как можно избежать перегрузки кеша и обеспечить эффективное повторное использование данных (в заданной иерархии кеша), второй относится к использованию полосы пропускания памяти. Это правда, что они связаны, но в большинстве случаев они исключают друг друга, поскольку хорошее кэширование уменьшит вашу исходящую пропускную способность (что, конечно, желательно как для производительности, так и для мощности). Однако иногда это невозможно сделать, если данные не могут быть повторно использованы или алгоритм не может быть изменен для размещения в кэше. В этих случаях BW памяти может стать вашим узким местом, и другое ядро ​​просто должно будет разделить ее как можно лучше.

Большинство современных процессоров имеют несколько ядер, совместно использующих кеш последнего уровня (я не уверен, что это так в некоторых сегментах смартфонов, но для ноутбуков / настольных компьютеров / серверов это обычно применимо). Этот кеш, в свою очередь, взаимодействует с контроллером памяти (который раньше находился на другом чипе, называемом северным мостом, но несколько лет назад был интегрирован в большинство процессоров для более быстрого доступа). Через контроллер памяти весь ЦП может общаться с DRAM и сообщать ему, что нужно получить. MC обычно достаточно умен, чтобы комбинировать доступы таким образом, что они требуют минимального времени и усилий для выборки (имейте в виду, что выборка «страницы» из DRAM - долгая задача, часто требующая сначала удалить текущую страницу, буферизованную в усилителях смысла. ).

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

Теперь примечание о совместном использовании - если 2 (или более) ядра нуждаются в одних и тех же данных одновременно, вам повезло - либо они уже находятся в кеше (в этом случае оба доступа будут обслуживаться по очереди путем отправки копий данных каждому ядру). , и пометив их как «общие»), или, если данные не существуют, оба будут ждать, пока MC не сможет их принести (один раз), а затем продолжат, как и в случае совпадения. Однако единожды исключением является ситуация, когда одному или нескольким ядрам необходимо записать новые данные в эту строку или ее часть. В этом случае модификатор выдаст запрос на чтение для владения (RFO), что предотвратит совместное использование строки и сделает недействительными все копии в других ядрах, в противном случае вы рискуете потерять согласованность или согласованность кеша (поскольку одно ядро ​​может использовать устаревшие данные или воспринимать неправильный порядок памяти). Это известно как состояние гонки в параллельных алгоритмах и является причиной сложных механизмов блокировки / ограждения. Опять же - обратите внимание, что это ортогонально фактическому доступу к ОЗУ и в равной степени может применяться для доступа к кешу последнего уровня.

3
Leeor 20 Окт 2013 в 19:49