Совет по запуску большого многопоточного проекта программирования

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

Прочитайте о Erlang и "Actor Model" в частности. Если вы сделаете все свои данные неизменными, вам будет гораздо легче распараллелить его.

Ответ 9

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

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

Книга с очень высокой оценкой, которая работает как введение в эту тему, - "Искусство Concurrency: руководство обезьяны темы для написания параллельного приложения".

Ответ 10

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

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

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

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

Ответ 11

Я бы подумал об этом в .NET 4.0, так как у него появилось много новой поддержки, специально предназначенной для упрощения написания параллельного кода. Официальная дата релиза - 22 марта 2010 года, но до этого она, вероятно, будет RTM, и вы можете начать с достаточно стабильной Beta 2.

Вы можете использовать С#, с которым вы более знакомы, или использовать управляемый С++.

На высоком уровне попробуйте разбить программу на System.Threading.Tasks.Task, которые являются отдельными единицами работы. Кроме того, я бы минимизировал использование общего состояния и рассмотрел возможность использования Parallel.For (или ForEach) и/или PLINQ, где это возможно.

Если вы сделаете это, очень тяжелый подъем будет сделан для вас очень эффективным способом. Это направление, которое Microsoft будет все больше поддерживать.

2: я бы подумал об этом в .NET 4.0, так как у него есть много новой поддержки, специально предназначенной для написания параллельных код проще. Официальная дата релиза - 22 марта 2010 года, но до этого она, вероятно, будет RTM, и вы можете начать с разумно стабильной Beta 2. На высоком уровне попробуйте разбить программу на System.Threading.Tasks.Task, которые являются отдельными единицами работы. Кроме того, я бы свести к минимуму использование общего состояния и рассмотреть возможность использования Parallel.For и/или PLINQ, где это возможно. Если вы сделаете это, очень тяжелый подъем будет сделан для вас очень эффективным способом. 1: http://msdn.microsoft.com/en-us/library/dd321424%28VS.100%29.aspx

Ответ 12

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

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

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

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

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

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

Ответ 13

Если возможно, чтобы все потоки работали над несвязанными наборами данных процесса и имели другую информацию, хранящуюся в базе данных SQL, вы можете легко сделать это на С++ и просто создавать новые потоки для работы на своих собственных частях используя API Windows. SQL-сервер будет обрабатывать всю жесткую синхронизацию с транзакциями DB! И, конечно же, С++ будет выполнять намного быстрее, чем С#.

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

Ответ 14

Вы отметили этот вопрос как С++, но упомянули, что сейчас являетесь разработчиком С#, поэтому я не уверен, что вы будете заниматься этим заданием с С++ или С#. Во всяком случае, в случае, если вы собираетесь использовать С# или .NET(включая С++/CLI): у меня есть следующая статья MSDN, и я настоятельно рекомендую прочитать ее как часть вашей подготовительной работы.

Асинхронный вызов синхронных методов

Ответ 16

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

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

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