Многоядерные процессоры и проблемы параллельной обработки

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

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

Васудеван Тьягараян, архитектор, Royal Caribbean Cruises Ltd.

Васу Тьягараян (Vasu Thiyagarajan) работает ИТ-архитектором в компании Royal Cruises. До этого был сотрудником IBM Software Labs в Бангалоре. Интересуется построением высокомасштабируемых систем и распределенными вычислениями.



02.04.2013

Закон Мура — сделанное в 1965 году Гордоном Муром предсказание, что число компонентов интегральных схем будет удваиваться каждые 18-24 месяца — оказался верным, и ожидается, что он будет соблюдаться до 2015-2020 года (см. раздел Ресурсы). До 2005 года тактовая частота процессоров тоже постоянно повышалась, и этого было достаточно для повышения производительности всех приложений, выполняемых этими процессорами. Сообщество разработчиков приложений получало дотацию на повышение производительности, мало или вообще ничего не вкладывая в улучшение алгоритмов.

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

Куда мы идем?

О том, почему параллелизм на основе акторов становится все более популярной и необходимой альтернативой традиционному Java-параллелизму, ― в учебном курсе Использование параллелизма на основе акторов при создании Java-приложений (developerWorks, май 2012 г.).

В этом подкасте developerWorksЭнди Гловер берет интервью у эксперта в области параллельной обработки Алекса Миллера.

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

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

Модель программирования на основе потоков

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

Параллелизм на основе потоков имеет следующие преимущества:

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

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

Глубина стека вызовов

Стек вызовов ― это внутренняя структура, поддерживаемая операционной системой или виртуальной машиной для обработки всех вызовов методов. Каждый вызов метода в рамках выполнения потока добавляет один кадр стека (состоящий из сведений о текущем вызове метода, таких как параметры, обратный адрес и локальные переменные).

Внутренние детали процесса вызова метода показаны на рисунке 1.

Рисунок 1. Внутренняя структура и рост стека вызовов
Внутренняя структура и рост стека вызовов

Как бы приложение ни разбивалось на логические уровни (уровень контроллера, фасада, компонентов, объектов доступа к данным [DAO] и т.п.), во время выполнения все это сводится в один поток, у которого только один стек. Стек вызовов - это потрясающее изобретение с точки зрения управления модульностью исходного кода во время выполнения. Но по мере повышения степени сложности приложения и нагрузки на систему существующая модель структуры стека вызовов ограничивает масштабируемость приложений, и ей присущи проблемы, связанные с размером памяти и достижимостью объектов.

Достижимость объектов

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

Реализация стека вызовов Java™ рассчитана на автоматическое исключение всех своих ссылок при возврате вызова метода. Это приемлемо, когда JVM работает без высокой нагрузки. Но может стать проблемой, когда JVM имеет дело с большим числом активных потоков. Например, если каждый поток занимает до 5 MB неиспользуемых действительных ссылок в стеке вызовов, и работает 100 потоков, то JVM не сможет собрать 500 МБ мусора пространства кучи, потому что на них ссылаются переменные и параметры стека вызовов. На 32-разрядной машине это может составить 25% всей памяти, доступной для JVM.


Общие объекты

Еще одна критическая проблема параллелизма на основе потоков заключается в усилиях по синхронизации ввиду изменчивости объектов, совместно используемых несколькими потоками, как показано на рисунке 2.

Рисунок 2. Общая память
Общая память

Хотя концепция синхронизации не нова и широко распространена, она отрицательно влияет на производительность приложения, потому что заблокированная последовательность может заставить поток ожидать ее освобождения, которое вызовет внутреннее переключение контекста потока. Переключение контекста в целом замедляет выполнение потока. К тому же при этом сбрасываются все инструкции конвейера и кэш ядра. В JVM с большим количеством параллельных потоков синхронизация может привести к частым переключениям контекста потоков.


Последовательное программирование

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

Это можно сравнить с производственными процессами в эпоху до введения Генри Фордом сборочного конвейера. Тогда всё изделие создавал один рабочий или бригада. Сборочный конвейер позволил рабочим сосредоточиться на конкретных подзадачах в рамках общего процесса производства. Это многократно повысило производительность труда, экономя время, которое рабочие тратили на переходы от одного этапа производства к другому.

Современный аналог сборочного конвейера ― это обработка заказов клиентов в ресторане быстрого обслуживания. Заказ выполняется определенным числом работников, каждый из которых специализируется на своих операциях, выполняя лишь часть общей работы. Когда эта часть работы выполнена, полуфабрикат передается следующему работника в цепи, и так далее до конечного блюда. В противоположность этому рассмотрим систему, в которой каждый работник обслуживает только одного клиента, от начала и до конца. Оба способа выполнения заказов допустимы, но система фастфуд продуктивнее. Один работник, обслуживающий весь заказ, тратит слишком много времени на перемещение с места на место, вместо того чтобы заниматься собственно блюдом. Передача заказа между работниками создает другие проблемы, такие как состязание за пространство и ожидание.

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

Рисунок 3. Один поток
Один поток

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


Заключение

Многопоточность ― отличный способ эффективного использования ресурсов центрального процессора. Но по мере развития системы сообщества разработчиков приложений и OС распространяют многопоточность на параллелизм уровня приложения. Разработчики приложений начали использовать программирование на основе потоков для последовательного выполнения всей логики приложения. Между тем тенденция в направлении многоядерных процессоров набирает силу, число ядер неуклонно растет, и последовательное программирование на основе потоков становится все менее эффективным.

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

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

Отказ от ответственности

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

Ресурсы

  • Оригинал статьи: Multicore CPUs and the concurrency changes they bring.
  • New life for Moore's Law (Michael Kanellos, CNET, апрель 2005 г.): прогнозы отраслевых экспертов в отношении развития технологии микропроцессоров на ближайшее и отдаленное будущее.
  • Multicore Processors — A Necessity (Bryan Schauer, 2008 г.): обзор технологии многоядерных процессоров и задач, которые они ставят перед программистами.
  • Is Parallel Programming Hard, And, If So, What Can You Do About It? (Paul E. McKenney, ed., 2011 г.): эта книга помогает разработчикам понять, как программировать параллельные машины с общей памятью.
  • Java, Python, Ruby, Linux, Windows are all Doomed (Russel Winder, 2010 г.): в этом докладе исследуются некоторые вопросы, связанные с операционными системами, прикладным ПО, программированием и языками программирования, которые ставит перед разработчиками следующий этап многоядерной революции.
  • Concurrent Programming for Scalable Web Architectures (Benjamin Erb, апрель 2010 г.): диссертация, содержащая обзор конкурирующих подходов к реализации параллелизма и их использования.
  • Software Pipelines and SOA: Releasing the Power of Multi-Core Processing (Cory Isaacson, Addison-Wesley, 2009 г.): книга, знакомящая читателя с подходом к реализации параллельной обработки, называемым "каналами программного обеспечения" (Software Pipelines).
  • The Future of Computing Performance: Game Over or Next Level? (Samuel H. Fuller and Lynette I. Millett, eds., The National Academies Press, 2011 г.): книга о факторах, которые привели к ограничениям развития отдельных микропроцессоров, проблемах параллельных вычислений и архитектуры, а также о необходимости исследований, практической работы и обучения для преодоления этих трудностей.
  • Software and the Concurrency Revolution (Herb Sutter and James Larus, AMC Queue, сентябрь 2005 г.): авторы статьи доказывают, что для использования всей мощи многоядерных процессоров от индустрии программного обеспечения требуются новые инструменты и новое мышление. (Полный текст предоставляется только подписчикам).
  • Java concurrency (developerWorks, август 2011 г.): о многопоточном программировании на языке Java и об альтернативных подходах к платформе Java, нацеленных на использование оборудования современных многоядерных процессоров.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=863566
ArticleTitle=Многоядерные процессоры и проблемы параллельной обработки
publish-date=04022013