Опубликован: 12.07.2010 | Уровень: специалист | Доступ: платный | ВУЗ: Алтайский государственный университет
Лекция 16:

Приемы и технологии программирования многоядерных процессоров

< Лекция 15 || Лекция 16: 12 || Лекция 17 >
Аннотация: В лекции дается краткая характеристика проблем, возникающих при параллельном программировании и при программировании многоядерных процессоров, в частности. Кратко описаны основные модели программирования многоядерных процессоров, лежащие в основе различных инструментов разработки и управлении процессорными ядрами в операционных системах.
Ключевые слова: ПО, синхронизация процессов, когерентность, диск, представление, приложение, расходы, очередь, закон Амдала, shared memory, память, Дополнение, Write, back, разделяемая память, параллельная обработка, конвейеризация, операции, массив, математический сопроцессор, грани, распараллеливание, hyper-threading, альтернатива, исполнение, ISO, Си, параллелизм, реинжиниринг, микроядерная архитектура, openmp, explicit parallelism, программирование, software radio, lab, implicit parallelism, performance, primitive, math, kernel, library, логический, зависимость компонент, опыт, поток команд, компромисс, расширяемость, переносимость, MPI, message passing, interface, множества, system software, масштабируемость, Windows, mac os, gigabit, Ethernet, infiniband, OPEN, processing, IBM, SUN, поддержка, Visual Studio 2005, GCC, pragma, API, переменная среды, директива компилятора, компилятор, POSIX, portable, system interface, интерфейс, IEEE, международный стандарт, integrity, Green, потоковая модель, мультипроцессор, целый, разбиение, запрос, контроль, light-weighted process, thread, стек, поток, планировщик , реакция, запрос подтверждения, round-robin, многозадачность, гранулярность, управление безопасностью, синтаксис, семантика, запуск, пользователь, программа, производительность, затраты, жизненный цикл, вероятность, взаимоблокировка, checker, память данных, блок памяти, сеть, значение, preemptive multitasking, model, отношение, процессор, быстродействие, программное обеспечение, ядро, асимметрия, процессорное ядро, Instruction, set, architecture, ISA, SSE4, gate, FPGA, application, ASIC, поиск, technology, AMPS, memory access, NUMA, архитектура, stream processing, x86, основная модель, acceleration

Проблемы параллельного программирования

При переходе с одноядерных процессоров на многоядерные системы приходится принимать во внимание проблему последовательного выполнения. В многоядерной системе выполнение считается последовательным, когда одно или более ядер в какой-то момент не могут выполнять код параллельно с другими ядрами. Эта ситуация может возникнуть по разным причинам: ситуации блокировки при доступе к ресурсам, необходимость синхронизации процессов или потоков на различных ядрах, поддержание когерентности кэш-памяти и неравномерность загрузки [90].

Блокировки возникают из-за невозможности одновременного доступа приложений на разных ядрах к таким ресурсам, как жесткий диск, некоторые устройства ввода/вывода, прикладные данные в определенных ситуациях (например, в момент "сборки мусора").

Программист, пишущий физически параллельную программу, обязательно должен иметь представление о следующих вещах:

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

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

При переходе на многоядерную архитектуру возникает необходимость поддержания когерентности (согласованности) кэш-памяти для всех ядер при использовании так называемой разделяемой памяти (shared memory). Для ускорения доступа к разделяемой памяти может применяться кэш-память. Теперь в дополнение к поддержанию когерентности между основной памятью и кэш-памятью каждого отдельного ядра (с помощью методов write through, write back и других) необходимо в каждый момент времени поддерживать когерентность основной памяти и кэш­памяти всех ядер, использующих эту разделяемую память при любых операциях чтения-записи. Как правило, эта проблема решается на аппаратном уровне.

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

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

Идея распараллеливания еще проще. Ее можно пояснить с помощью простой аналогии: если одному рабочему требуется 10 часов на изготовление 10 деталей, то 10 рабочим для этого нужен всего час. К сожалению, несмотря на мощь и простоту идей физического параллелизма, установка 10 вычислительных модулей вместо одного не означает десятикратного увеличения вычислительной мощности системы. Если в задаче нельзя выделить участки, допускающие параллельную обработку, или массив однотипных операндов для конвейеризации, то сократить время ее выпол­нения не удастся.

Кроме конвейеризации и физического распараллеливания используются следующие методы:

  • специализация (применение внутри процессоров блоков, оптимизированных под определенный вид вычислений, например математических сопроцессоров);
  • кэширование;
  • спекулятивные вычисления.

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

Архитектурные усовершенствования привели относительно однородную ИТ-индустрию к грани разделения на более мелкие области. Это вызвано тем, что физическое распараллеливание не работает в произвольном классе задач: увеличение разрядности процессора, применение гиперпотоковости (Hyper-Threading) и многоядерности могут дать эффект, противоположный ожидаемому, т. е. после распараллеливания время исполнения программы даже увеличится.

Не всякая задача допускает физическое распараллеливание [6]. Целые классы задач (например, быстрое Фурье-преобразование, обработка запроса к базе данных, быстрые алгоритмы сортировок и т. п.) не получают заметного выигрыша от распараллеливания либо не распараллеливаются в принципе. У разработчиков появляется альтернатива — вместо изменения структуры программы уделять больше внимания локальной оптимизации кода. До некоторого предела это окажется рабочим вариантом, причем будет поддержано ростом интереса к средствам разработки, ориентированным на высокоскоростное исполнение в рамках однопроцессорной архитектуры [89, 90].

Возникает проблема совместимости старых библиотек, написанных для однопроцессорных архитектур. Если библиотеки физически не поддерживают параллельное исполнение, то распараллеливание алгоритма может привести к ошибке. Не случайно Intel расширяет программистскую составляющую своей деятельности, в частности — для создания безопасных библиотек, которые предполагают и гибкую настройку на конкретную вычислительную платформу. По-видимому, в ближайшем будущем речь пойдет о динамически реконфигурируемых библиотеках, конечный вид которых будет определяться при загрузке задачи и выявлении текущей топологии системы.

Не вполне ясен вопрос объектно-ориентированного программирования: нет исследований по совместимости объектно-ориентированного программирования с многоядерной архитектурой процессора. Текущий стандарт ISO на Си++ обходит стороной физический параллелизм (Intel пишет специализированные библиотеки на "чистом" Си).

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

Необходимо отметить видимые преимущества микроядерных архитектур ОС, которые (в отличие от монолитных, оптимизированных для однопроцессорных систем) допускают физическое распараллеливание.

Главной тенденцией программирования в ближайшее время станет создание новых языков и техник, совмещающих удобство разработки и физический параллелизм исполнения. Этого удастся добиться только за счет решения вопросов, связанных с физическим параллелизмом. Существующие решения типа pthread и OpenMP можно расценивать лишь как паллиатив. Ручное управление физическим параллелизмом (explicit parallelism) отбрасывает программирование назад, в эпоху машинных кодов и ассемблеров, реанимируя привязанность структуры программы к физической архитектуре вычислительной платформы. Следует сказать, что для встраиваемых систем, особенно связанных с обработкой сигналов, мультимедийной информации, так называемых "software radio" систем и для других подобных задач ручное распараллеливание дает неплохие результаты. Это возможно благодаря использованию конфигурируемых программных модулей, привязанных к определенному ядру или группе ядер, благодаря связке компиляторов для многоядерных процессоров со средами визуального программирования и средствами визуального представления процессов (например, Lab View, Ratitional Rapsody).

Автоматическое распараллеливание (implicit parallelism) для многоядерных архитектур в большинстве случаев обеспечивает ускорение лишь с некоторой вероятностью. Промежуточным решением могут быть реконфигурируемые библиотеки (Integrated Performance Primitives, Math Kernel Library).

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

Другая возможность — попытаться адаптировать к многоядерным ар­хитектурам существующие техники и языки многопоточной организации программ. Опыт применения языка Рефлекс в многопроцессорных системах показывает, что такое вполне допустимо. Правда, появляющиеся при этом сущности означают достаточно радикальное изменение программ на уровне машинных команд. Например, происходит очевидный отказ от использования стека при организации структуры программы. При создании программ меняется и восприятие процесса программирования: классические функции дополнены процессами, которые при их запуске порождают отдельный независимый поток команд и данных [90, 91].

Существуют проекты, нацеленные на создание языковых средств и методик обеспечения переносимых параллельных программ. Например, проект "Пифагор", цель которого — обеспечить разумный компромисс между быстродействием алгоритма и приемлемым уровнем его сопровождаемости (эволюционная расширяемость, переносимость, платформенная независимость). Однако этот подход имеет свои особенности: программирование предполагает применение функционального языка, что непривычно для большинства практикующих программистов.

При разработке параллельных программ в рамках модели параллелизма задач используются специализированные библиотеки и системы параллельного программирования, такие, как PVM, LAM и некоторые другие. Наметилось три основных подхода к реализации этих систем, отличающихся методами взаимодействия параллельных задач. Один подход строится на основе концепции обмена сообщениями, второй — на задействовании разделяемой памяти. Третий подход — на основе стандарта POSIX — объединяет два предыдущих.

Наиболее известным представителем первой группы является спецификация MPI (Message Passing Interface) для языков программирования C и Фортран, первый вариант которой был разработан в 1994 году. Спецификация MPI включает около 200 функций и реализована для множества компиляторов и операционных систем. Одной из наиболее распространенных реализаций MPI является библиотека MPICH. Кроме того, на рынке представлены несколько коммерческих реализаций MPI, например, MPI/Pro производства компании Verari Systems Software (http://www.verari.com). MPI/Pro оптимизирует время работы параллельных приложений, обеспечивает их масштабируемость, осуществляя балансировку параметров производительности и использования ресурсов. Компания Verari предлагает версии MPI/Pro для различных операционных систем, включая Windows, Linux, Mac OS X, LynxOS, а также таких интерконнектов, как Gigabit Ethernet, Myrinet и InfiniBand.

Представителем второй группы является спецификация OpenMP (Open specifications for Multi-Processing). Первая версия спецификации OpenMP (http://www.openmp.org) появилась в 1997 году и предназначалась для языка программирования Фортран. У истоков OpenMP стояли такие известные компании, как IBM, Intel, Sun и Hewlett-Packard. В 1998 году появились варианты OpenMP для языков C/C++, и к настоящему моменту последней является версия 2.5. Поддержка спецификации OpenMP имеется во всех компиляторах Intel (начиная с шестой версии), в Microsoft C/C++ (начиная с Visual Studio 2005), в GCC. Очень интересна сама концепция OpenMP как набора специальных директив компилятору (pragma), библиотечных (API) функций и переменных среды. Наиболее оригинальны здесь pragma-директивы компилятору. Они используются для обозначения областей (regions) в коде и могут выполняться параллельно. Компилятор, поддерживающий OpenMP, преобразует исходный код и вставляет соответствующие вызовы функций для выполнения этих обла стей кода параллельно.

Третьим широко используемым стандартом, который применяется для реализации параллельных вычислений, является POSIX (Portable Operating System interface for unIX — переносимый интерфейс операционных систем на уровне исходных текстов). Первое описание POSIX (http://www.pasc.org) было опубликовано в 1986 году. Основная спецификация разработана как IEEE 1003.1 и одобрена как международный стандарт ISO/IEC 9945-1:1990. С точки зрения организации параллельных вычислений наибольший интерес представляют три части стандарта: 1003.1a (OS Definition), 1003.1b (Realtime Extensions) и 1003.1c (Threads). В рамках POSIX возможно реализовать параллельные вычисления как на основе обмена сообщениями аналогично MPI, так и на основе разделяемой памяти, как в OpenMP. Естественно, в POSIX возможна и любая комбинация этих методов. В настоящее время в наибольшей степени стандарту POSIX соответствуют (сертифицированы) две ОСРВ: LynxOS компании LynuxWorks (http://www.lynuxworks.com) и Integrity компании Green Hills (http://www.ghs.com).

При программировании многоядерных процессоров могут применяться следующие модели или их комбинации [89-90-91-92-93-94]:

  • модель выгрузки функций;
  • модель ускорения вычислений;
  • потоковые модели;
  • модель мультипроцессора с разделяемой памятью;
  • модель ассиметричных потоков.

Модель выгрузки функций (function offload model)

Модель выгрузки функций является наиболее простой в реализации.

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

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

  1. Алгоритм должен допускать распараллеливание.
  2. При вычленении параллельных участков, как правило, приходится придавать алгоритму специальную форму. Например, если необходимо определить сумму массива, при распараллеливании на первом шаге одновременно суммируются соседние четные и нечетные элементы массива, а на втором попарно суммируются результаты, полученные на первом шаге, и т. д. Компактно записать параллельный вариант на языке Си невозможно. В общем случае приведение алгоритма к форме, позволяющей сократить время вычислений, означает отход от формы, обеспечивающей наиболее наглядное представление.
  3. В многопроцессорной системе разбиение на слишком крупные части не позволяет равномерно загрузить процессоры и добиться минимального времени вычислений, а излишне мелкая "нарезка" означает рост непроизводительных расходов на связь и синхронизацию.
  4. Физическому параллелизму присуща зависимость глобальной структуры алгоритма от топологии вычислительной платформы. Процесс создания максимально эффективного алгоритма практически не автоматизируется и связан с большими трудозатратами на поиск специфической структуры алгоритма, оптимальной для конкретной топологии целевой системы. Найденная структура обеспечит минимальное время получения результата, но, скорее всего, будет неэффективна для другой конфигурации. Более суровое следствие зависимости структуры алгоритма от вычислительной платформы — тотальная непереносимость не только исполняемых кодов, но и самого исходного текста.

Физический параллелизм предназначен для получения результата по входным данным за кратчайшее время.

< Лекция 15 || Лекция 16: 12 || Лекция 17 >
Сергей Горбунов
Сергей Горбунов

 

прошел курс и сдал экзамен   Многоядерные процессоры   

система сертификат не выдала. почему?

Зарина Каримова
Зарина Каримова
Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008
Филипп Шишкин
Филипп Шишкин
Россия, Пенза, Пензенский Государственный Университет, 2015