Для повышения производительности многоядерных и многопроцессорных систем, в которые уже инвестированы огромные средства, нам осталось одно – программировать параллельно, но остается все еще открытым вопрос – как? Действительно, развитие вычислительных архитектур уже поставлено на параллельные рельсы, однако эффективность созданных параллельных процессоров и систем весьма далека от ожидаемой. Рецептов, способных коренным образом переломить ситуацию, основными игроками на рынке параллельных систем не предложено. «Официальный параллелизм» продолжает развиваться путями, которые не ведут к качественным изменениям.
До сегодняшнего дня параллельное программирование развивалось на фоне успехов программирования последовательного, хотя на самом деле успехи последнего были связаны с развитием архитектур и совершенствованием технологий производства процессоров. Программирование как было, так и остается последовательным, и даже увлечение автоматическим распараллеливанием можно рассматривать как нежелание что-то менять.
Тем не менее к мечте, возникшей хоть однажды, возвращаются вновь и вновь. И здесь желание программировать параллельно сравнимо с мечтой пещерного человека об арифметике. Вождь племени, получив весть, что один из соплеменников убил двух мамонтов, а другой – трех, хотел бы знать, сколько всего они «завалили» вместе. «Обычный» вождь прикажет на полянке разложить мамонтов, где их и пересчитает, а другой, просвещенный и с абстрактным мышлением, возьмет столько камешков, сколько мамонтов убил каждый охотник, и пересчитает получившуюся кучку. Но подобные действия не решают проблему в целом – мамонтов таскать с места на место тяжело, да и нужного числа камешков под рукой может не оказаться. Рано или поздно приходит понимание того, что необходимы иные инструменты.
Кто-то ограничится обычными счетами, но более логично создание формального математического инструментария. Нашим предкам достаточно было простейшей арифметики. Можно даже предложить, что пещерный мир перестал быть таковым с приходом в него арифметики.
Далеко не все вожди от программирования знают, что алгоритмами/программами, как и мамонтами, можно оперировать подобно числам. Но что мы получим, перемножив два алгоритма? Как можно продемонстрировать возможности соответствующей программной арифметики? И столь ли она необходима?
Нынешнее параллельное программирование – это пещерный мир без арифметики, а мы – пещерные программисты. Не согласны? А знаете ли вы тех, кто может складывать-вычитать и умножать-делить программы? Пока мы не поймем, что же такое параллельное программирование, мы не только не научимся программировать параллельно, но и не создадим арифметику программирования.
Пещерное состояние параллельного программирования – одна из причин его многочисленных проблем: сложность кода, трудности синхронизации процессов, непредсказуемость результата, ненадежность функционирования и т.п. И все это усугубляется отсутствием осмысленной теории параллельных процессов. А то, что есть, – латание дыр. Но лишь знать о подобных вещах мало – их надо прочувствовать. Как объем отличается от плоскости, так параллельное программирование в сравнении с последовательным представляет собой иной мир.
Пока параллельные программы и компьютеры – это все еще порожденные нами же чудища. Кластеры своими габаритами и энергопрожорливостью вызывают ужас. Между тем существуют задачи и не такие уж объемные, перед которыми пасуют новейшие многоядерные системы и системы их программирования. Их не то что сложно, а просто нельзя решить в рамках нынешних параллельных подходов.
Но самое печальное, что мы тратим сегодня огромные ресурсы в гонке по созданию суперкомпьютерных «чудищ», применение которых весьма ограничено. Безусловно, расчет погоды, моделирование крэш-тестов, расшифровка ДНК, объемные и сложные экономические расчеты и т.д. – все это важные задачи, требующие колоссальных ресурсов. Но это лишь небольшая доля параллельных задач. И уж совсем неэффективно обобщать методы решения подобных задач на реализацию параллельных процессов вообще, которые в общем случае имеют более тесное взаимодействие и большую связность.
Справедливо гордиться попаданием в список Тop 500, но не это определяет реальное состояние науки параллельного программирования, диапазон ее практического распространения и реальные достижения. И подобно тому как наличие самых мощных ракетоносителей не гарантирует качества дорог, развития автомобилестроения и отсутствия дураков, так и успехи в «кластеростроении» не гарантируют прорывов в параллельном программировании.
Среди упомянутых «маленьких» задач – моделирование RS-триггера. Триггер – это два логических элемента, соединенные перекрестными связями, которые, при переходе к программной модели, можно описать в форме двух параллельных операторов:
y1 = !(x1&y2); y2 = !(x2&y1).
Здесь каждый параллельный оператор соответствует логическому элементу (в данном случае И-НЕ), а перекрестные связи реализованы через переменные: x1, x2 – входные переменные, а y1, y2 – выходные переменные созданной простейшей параллельной системы.
Утекло много воды с тех пор, как моделирование триггера было предложено в качестве теста на параллелизм, но воз и ныне там – простейшая параллельная задача по-прежнему не по зубам существующим технологиям параллельного программирования. Почему? Да потому что, если пойти «очевидным путем» и поместить каждый из операторов в отдельный поток, то не получить нужного результата. Причина этого – так называемые гонки сигналов/переменных в потоках. И эту проблему не поможет решить даже реализация операторов на отдельных ядрах. Правильный результат дает «бумажный анализ», проведенный в блоге (software.intel.com/ru-ru/blogs/2008/12/10/389).
Однако как должно выглядеть решение и доказательство его правильности за пределами «пещерного программирования»? Реализация остается прежней: создаем процессы-элементы, устанавливаем между ними связи и смотрим на результат. Все, как мы это и представили в рамках многопоточного программирования, но только параметров у процессов и связей между ними должно быть ровно столько, сколько определяет постановка задачи. В нашем случае отдельный процесс должен иметь три параметра – два входных канала и один выходной. А доказательство? Это должна быть формальная операция, которая позволяла бы создать один оператор, эквивалентный параллельным операторам, подобно тому как, например, вычисляется общее сопротивление двух параллельно соединенных сопротивлений.
Так можно ли доверять результатам работы «пещерной программы»? Пример реализации триггера на потоках говорит – нет. Очень неубедительно выглядят «бумажные обоснования» ожидаемых результатов работы параллельной программы. Понятно ведь, что в общем случае программа будет сложнее триггера. А сколько при этом бумаги понадобится? Но самое печальное, что других доказательств никто не предлагает – своеобразный «счет на палочках», «бумажный анализ» и им подобные «теории» часто служат единственным доказательством правильности современных параллельных программ. И это даже тогда, когда обсуждаются так называемые средства высокоуровневого параллельного программирования.
А что хотелось бы видеть в рамках нормальной математики параллельных программ? Во-первых, параллельную модель и, во-вторых, ее «арифметику». Пример одной из таких моделей – сети Петри. Правда, попробовав создать в ее рамках модель триггера и обосновать формальными средствами результаты работы модели, вы на собственном опыте поймете, почему данная модель не имеет реального распространения. А дойдет ли дело до программной реализации подобного решения?
В чем причина того, что мы часто останавливаемся на полпути? Почему, когда есть все условия для «арифметизации» параллельного программирования, оно остается пещерным?
Прежде всего, это образование. В основе преподавания программирования сегодня лежат такие модели и такая теория, что в дальнейшем уже сложно перестроиться на что-то новое. Безусловно, виновата и лень. Скажем, объектному программированию, давно доказавшему свою эффективность, уже столько лет, а все еще можно столкнуться и с непониманием его роли и просто с отрицанием. А все потому, что нужны определенные усилия для его освоения. Параллельное мышление, как и мышление объектное, требует качественно иного подхода к программированию, на что решиться пока, к сожалению, могут немногие.
Сегодня все пока сводится к рассуждениям о высокоуровневости языков программирования, о выборе чего-то, скромно называемого системой кодирования, к мечтам о все большем числе ядер – как будто это что-то изменит. Нас уже завалили «параллельным железом», производители которого буквально вынуждают его приобретать, но «параллельное дело», кроме рассуждений о его предполагаемой эффективности, застыло в мертвой точке. Потому-то при увеличении ядер в разы мы имеем в лучшем случае прирост производительности в проценты. Да и то, если вникнуть, то больше за счет совершенствования окружения процессора: увеличения объема и скорости доступа к оперативной памяти, повышения быстродействия шин, совершенствования жестких дисков и графической подсистемы.
Поэтому, если уж говорить прямо – а такая необходимость назрела уже давно, – сейчас нужно сосредоточить основное внимание и резервы не на создании очередного «монстра», а на развитии теории параллельных систем. Необходимы новые параллельные модели, языки программирования, среды их поддержки. А если будет эффективная теория, для которой понятие «арифметики» не будет выглядеть экзотическим, то появятся и соответствующие аппаратные архитектуры. И тогда можно будет говорить не только, например, об автоматном программировании, но и об автоматной архитектуре. А пока не редки ситуации, когда параллельная программа работает хуже своего последовательного аналога. Пример – уже рассмотренный нами RS-триггер.
Я сознательно не привожу строгие рецепты создания арифметики программ, оставляя простор для действий и конструктивных споров. Здесь мы помечтали на тему, что следует ожидать от той или иной «системы кодирования». И уж хотя бы в простых случаях пора доказывать свойства программы не на бумажке, не на палочках, а привлекая возможности математического инструментария.
Пока же дело обстоит так, что с рекламой аппаратных параллельных систем и средств их программирования все хорошо, а вот с их применимостью – плохо. Но нет такой ситуации, из которой бы не было выхода. В противном случае мы так и остались бы пещерными людьми. Еще в памяти времена, когда параллельным программированием занимались не только на бумажке, есть и модели, есть и их математика. Только не следует их забывать, а если они чем-то сегодня не устраивают, необходимо их совершенствовать. И тогда – держитесь, «мамонты».
Вячеслав Любченко (sllubch@mail.ru) – руководитель проекта компании «ПараДип» (Москва).