Рекомендации по запуску большого проекта многопоточного программирования - PullRequest
11 голосов
/ 14 декабря 2009

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

У нас есть коробка с 24 процессами, чтобы запустить это. У меня будет доступ к исходной программе (написанной на C ++, я думаю), но на данный момент я очень мало знаю о ее разработке.

Мне нужен совет, как справиться с этим. Я опытный программист (~ 30 лет, в настоящее время работаю в C # 3.5), но не имею опыта работы с несколькими процессорами / многопоточностью. Я желаю и хочу выучить новый язык, если это уместно. Я ищу рекомендации по языкам, учебным ресурсам, книгам, архитектурным руководствам. и т.д.

Требования: ОС Windows. Компилятор коммерческого класса с большой поддержкой и хорошими учебными ресурсами. Нет необходимости в необычном графическом интерфейсе - он, вероятно, будет запускаться из файла конфигурации и помещать результаты в базу данных SQL Server.

Edit: текущее приложение на C ++, но я почти наверняка не буду использовать этот язык для переписывания. Я удалил тег C ++, который кто-то добавил.

Ответы [ 16 ]

17 голосов
/ 14 декабря 2009

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

В высокопроизводительных вычислениях моделирование обычно распараллеливается с использованием либо MPI , либо OpenMP . MPI - это библиотека передачи сообщений с привязками для многих языков, включая C, C ++, Fortran , Python и C # . OpenMP - это API для многопроцессорной работы с общей памятью. В целом, MPI более сложен для кодирования, чем OpenMP, и гораздо более инвазивен, но также гораздо более гибок. OpenMP требует области памяти, разделяемой между процессорами, поэтому не подходит для многих архитектур. Гибридные схемы также возможны.

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

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

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

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

Отличным онлайн-обзором параллельных вычислений, который охватывает основные проблемы, является это введение из Ливерморской национальной лаборатории им. Лаврентия .

12 голосов
/ 14 декабря 2009

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

Отладка условий гонки явно неприятна.

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

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

7 голосов
/ 14 декабря 2009

Если вы можете разделить рабочую нагрузку на независимые куски работы (т. Е. Набор данных может быть обработан в битах, нет большого количества зависимостей данных), то я бы использовал механизм пула / задачи , Предположительно все, что есть в C #, эквивалентно Java java.util.concurrent. Я создавал рабочие единицы из данных, заключал их в задачу и затем выбрасывал задачи в пул потоков.

Конечно, производительность здесь может быть необходимой. Если вы можете сохранить исходное ядро ​​кода обработки как есть, то вы можете вызвать его из своего приложения на C #.

Если в коде много зависимостей от данных, разбить его на потоковые задачи может быть намного сложнее, но вы можете разбить его на последовательность действий. Это означает, что поток 1 передает данные в поток 2, который передает данные в потоки с 3 по 8, которые передают данные в поток 9 и т. Д.

Если в коде много математики с плавающей запятой, возможно, стоит подумать о переписывании в OpenCL или CUDA и запуске его на GPU вместо CPU.

3 голосов
/ 14 декабря 2009

Для 6-месячного проекта я бы сказал, что сначала стоит прочесть хорошую книгу на эту тему. Я бы предложил Параллельное программирование Джо Даффи в Windows . Это самая полная книга, которую я знаю о предмете, и она охватывает как .NET, так и нативную ветку Win32. Я написал многопоточные программы в течение 10 лет, когда обнаружил этот драгоценный камень и все еще находил то, чего не знал почти в каждой главе.

Кроме того, «моделирование риска природных катастроф» звучит как математика. Возможно, вам стоит взглянуть на библиотеку Intel IPP: она предоставляет примитивы для многих распространенных низкоуровневых алгоритмов обработки математики и сигналов. Он поддерживает многопоточность из коробки, что может значительно облегчить вашу задачу.

3 голосов
/ 14 декабря 2009

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

Самым общим и универсальным является просто «избегать общего состояния». По возможности копируйте ресурсы между потоками, а не заставляйте их обращаться к одной и той же общей копии.

Если вы пишете низкоуровневый код синхронизации самостоятельно, вы должны помнить, чтобы не делать абсолютно никаких предположений. И компилятор, и процессор могут переупорядочить ваш код, создавая условия гонки или взаимоблокировки, когда при чтении кода это не представляется возможным. Единственный способ предотвратить это - с помощью барьеров памяти. И помните, что даже самая простая операция может быть связана с проблемами потоков. Такие простые вещи, как ++i, обычно не являются атомарными, и если несколько потоков обращаются к i, вы получите непредсказуемые результаты. И, конечно, только потому, что вы присвоили значение переменной, это не гарантирует, что новое значение будет видно другим потокам. Компилятор может отложить запись в память. Опять же, барьер памяти заставляет его «сбрасывать» все ожидающие операции ввода-вывода памяти.

На вашем месте я бы использовал модель синхронизации более высокого уровня, чем простые блокировки / мьютексы / мониторы / критические секции, если это возможно. Существует несколько библиотек CSP , доступных для большинства языков и платформ, включая языки .NET и нативный C ++.

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

Другим подходом может быть Транзакционная память . Это легче вписать в традиционную структуру программы, но также имеет некоторые ограничения, и я не знаю многих библиотек производственного качества для этого (недавно был выпущен STM.NET, и, возможно, стоит проверить. Intel имеет C ++ компилятор со встроенными в язык расширениями STM)

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

Да, и , если вы пишете приложение на C ++, не стоит недооценивать язык. Вам нужно будет подробно выучить язык, прежде чем вы сможете писать надежный код, гораздо менее надежный многопоточный код.

2 голосов
/ 15 декабря 2009

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

Мой совет - получите и прочитайте вводный текст, чтобы ознакомиться с различными моделями параллелизма / параллелизма. Затем посмотрите на потребности вашего приложения и решите, какую архитектуру вы собираетесь использовать. После того, как вы знаете, какая архитектура вам нужна, вы можете обратиться к инструментам, которые помогут вам.

Книга с довольно высоким рейтингом, которая является введением в тему, называется «Искусство параллелизма: Руководство Обезьяны Нити для написания параллельных приложений».

2 голосов
/ 14 декабря 2009

Прочтите об Эрланге и, в частности, «Модель актера». Если вы сделаете все свои данные неизменяемыми, вам будет намного проще распараллелить их.

2 голосов
/ 14 декабря 2009

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

2 голосов
/ 14 декабря 2009

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

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

1 голос
/ 18 декабря 2009

Извините, я просто хочу добавить пессимистичный или более реалистичный ответ.

Вы находитесь под давлением времени. Крайний срок 6 месяцев, и вы даже не знаете наверняка, на каком языке эта система, что она делает и как она организована. Если это не тривиальный расчет, то это очень плохое начало.

Самое главное: вы говорите, что никогда не занимались многопоточным программированием. Здесь я получаю 4 звонка будильника одновременно. Многопоточность сложна и требует много времени, чтобы выучить ее, когда вы хотите сделать это правильно - и вам нужно сделать это правильно, когда вы хотите выиграть огромное увеличение скорости. Отладка чрезвычайно неприятна даже с такими хорошими инструментами, как отладчик Total Views или Intel VTune.

Затем вы говорите, что хотите переписать приложение в другом языке - ну, это не так плохо, как вы все равно должны его переписать. Шанс превратить однопотоковую программу в хорошо работающую многопоточную без полного перепроектирования практически равен нулю.

Но изучение многопоточности и нового языка (каковы ваши навыки в C ++?) С временной шкалой в 3 месяца (вы должны написать прототип, который я выбрасываю - так что я сократил временной промежуток на две половины) является чрезвычайно сложной задачей.

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

...