В настоящее время я пытаюсь заняться программированием с несколькими ядрами. Я хочу написать / реализовать параллельное умножение матриц с помощью C ++ / Python / Java (я предполагаю, что Java будет самым простым).
Но один вопрос, на который я не могу ответить сам, - это то, как доступ к ОЗУ работает с несколькими процессорами.
Мои мысли
У нас есть две матрицы A и B. Мы хотим вычислить C = A * B:
Параллельное выполнение будет быстрее, только если n, m или p большие. Итак, предположим, что n, m и p> = 10,000. Для простоты предположим, что n = m = p = 10,000 = 10 ^ 4.
Мы знаем, что можем вычислить каждый $ c_ {i, j} $, не глядя на другие записи C. Таким образом, мы можем вычислить все c_ {i, j} параллельно:
Но для всех c_ {1, i} (i \ in 1, ..., p) нужна первая строка A. Поскольку A представляет собой массив с 10 ^ 8 двойными, ему требуется 800 МБ. Это определенно больше, чем кеш процессора. Но одна строка (80кБ) поместится в кеш процессора. Поэтому я думаю, что будет хорошей идеей назначить каждую строку C ровно одному процессору (как только процессор освободится). Таким образом, этот процессор будет иметь как минимум A в кэше и извлекать из этого выгоду.
Мой вопрос
Как осуществляется доступ к ОЗУ для разных ядер (на обычном ноутбуке Intel)?
Я предполагаю, что должен быть один «контроллер», который дает монопольный доступ к одному ЦП за раз. У этого контроллера есть особое имя?
Случайно двум или более процессорам может потребоваться одна и та же информация. Могут ли они получить его одновременно? Является ли доступ к оперативной памяти узким местом в проблеме умножения матриц?
Сообщите мне также, когда вы узнаете несколько хороших книг, знакомящих вас с многоядерным программированием (на C ++ / Python / Java).
1 ответ
Вы должны разделить вопросы распараллеливания умножения матриц удобным для кеша способом (для этого есть много методов - ищите "tiling". вот хорошее объяснение из Беркли ), из вопроса о том, как несколько ядер совместно используют доступ к некоторым ресурсам, таким как общий кеш и память. Первый относится к тому, как можно избежать перегрузки кеша и обеспечить эффективное повторное использование данных (в заданной иерархии кеша), второй относится к использованию полосы пропускания памяти. Это правда, что они связаны, но в большинстве случаев они исключают друг друга, поскольку хорошее кэширование уменьшит вашу исходящую пропускную способность (что, конечно, желательно как для производительности, так и для мощности). Однако иногда это невозможно сделать, если данные не могут быть повторно использованы или алгоритм не может быть изменен для размещения в кэше. В этих случаях BW памяти может стать вашим узким местом, и другое ядро просто должно будет разделить ее как можно лучше.
Большинство современных процессоров имеют несколько ядер, совместно использующих кеш последнего уровня (я не уверен, что это так в некоторых сегментах смартфонов, но для ноутбуков / настольных компьютеров / серверов это обычно применимо). Этот кеш, в свою очередь, взаимодействует с контроллером памяти (который раньше находился на другом чипе, называемом северным мостом, но несколько лет назад был интегрирован в большинство процессоров для более быстрого доступа). Через контроллер памяти весь ЦП может общаться с DRAM и сообщать ему, что нужно получить. MC обычно достаточно умен, чтобы комбинировать доступы таким образом, что они требуют минимального времени и усилий для выборки (имейте в виду, что выборка «страницы» из DRAM - долгая задача, часто требующая сначала удалить текущую страницу, буферизованную в усилителях смысла. ).
Обратите внимание, что эта структура означает, что MC не должен общаться с несколькими ядрами по отдельности, он просто извлекает данные в кеш последнего уровня. Ядрам также не нужно будет напрямую взаимодействовать с контроллером памяти, поскольку доступы фильтруются через кеш последнего уровня (с некоторыми исключениями, такими как некэшируемые доступы, которые будут проходить мимо него, и доступы ввода-вывода, которые имеют другой контроллер). Все ядра будут совместно использовать это хранилище кешей в дополнение к своим собственным частным кешам.
Теперь примечание о совместном использовании - если 2 (или более) ядра нуждаются в одних и тех же данных одновременно, вам повезло - либо они уже находятся в кеше (в этом случае оба доступа будут обслуживаться по очереди путем отправки копий данных каждому ядру). , и пометив их как «общие»), или, если данные не существуют, оба будут ждать, пока MC не сможет их принести (один раз), а затем продолжат, как и в случае совпадения. Однако единожды исключением является ситуация, когда одному или нескольким ядрам необходимо записать новые данные в эту строку или ее часть. В этом случае модификатор выдаст запрос на чтение для владения (RFO), что предотвратит совместное использование строки и сделает недействительными все копии в других ядрах, в противном случае вы рискуете потерять согласованность или согласованность кеша (поскольку одно ядро может использовать устаревшие данные или воспринимать неправильный порядок памяти). Это известно как состояние гонки в параллельных алгоритмах и является причиной сложных механизмов блокировки / ограждения. Опять же - обратите внимание, что это ортогонально фактическому доступу к ОЗУ и в равной степени может применяться для доступа к кешу последнего уровня.
Похожие вопросы
Новые вопросы
parallel-processing
Параллельная обработка, в отличие от просто параллельной обработки, гарантирует запуск / выполнение / завершение всех задач уровня потока и / или уровня команд, выполняемых параллельно, и обеспечивает гарантированное завершение одновременно выполняемых путей кода.