Как я пишу задачи? (параллельный код) - PullRequest
11 голосов
/ 12 мая 2010

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

Я хотел узнать больше о написании задания. Мне нравится видео «Многопоточность на основе задач - как программировать для 100 ядер» здесь http://www.gdcvault.com/sponsor.php?sponsor_id=1 (в настоящее время вторая последняя ссылка. ВНИМАНИЕ, это не «здорово»). Моя любимая часть заключалась в том, что «решение лабиринта лучше выполнять параллельно», что составляет около 48 минут (вы можете нажать на ссылку слева. Эта часть - действительно все, что вам нужно смотреть, если есть).

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

Ответы [ 2 ]

7 голосов
/ 29 мая 2010

В Java есть структура параллельных задач, похожая на Thread Building Blocks - она ​​называется средой Fork-Join. доступно для использования с текущей Java SE 6 и будет включено в будущую Java SE 7.

В дополнение к документации по классу javadoc доступны ресурсы для начала работы с платформой. На странице jsr166 упоминается, что «Существует также вики, содержащая дополнительную документацию, заметки, советы, примеры и т. Д. Для этих классов».

Хорошие примеры для начала - примеры , такие как умножение матриц.

Я использовал инфраструктуру fork-join для решения некоторых проблем Intel с потоками 2009 года . Фреймворк является легковесным и с минимальными издержками - моя была единственной записью Java для задачи Kight's Tour, и она превзошла другие записи в конкурсе. Исходные тексты java и рецензии доступны для загрузки на сайте испытаний.

РЕДАКТИРОВАТЬ:

Понятия не имею, как класс или кусочки кода может выглядеть после нажатия на бассейн [...]

Вы можете создать свою собственную задачу, создав подклассы одного из подклассов ForKJoinTask , таких как RecursiveTask . Вот как можно вычислить последовательность Фибоначчи параллельно. (Взято из RecursiveTask javadocs - комментарии мои.)

 // declare a new task, that itself spawns subtasks. 
 // The task returns an Integer result.
 class Fibonacci extends RecursiveTask<Integer> {
   final int n;      // the n'th number in the fibonacci sequence to compute
   Fibonnaci(int n) { this.n = n; } // constructor
   Integer compute() {   // this method is the main work of the task
     if (n <= 1)         // 1 or 0, base case to end recursion
        return n;
     Fibonacci f1 = new Fibonacci(n - 1);  // create a new task to compute n-1
     f1.fork();                            // schedule to run asynchronously
     Fibonacci f2 = new Fibonacci(n - 2);  // create a new task to compute n-2
     return f2.invoke() + f1.join();       // wait for both tasks to compute.
       // f2 is run as part of this task, f1 runs asynchronously. 
       // (you could create two separate tasks and wait for them both, but running
       // f2 as part of this task is a little more efficient.
   }
 }

Затем вы запустите эту задачу и получите результат

// default parallelism is number of cores
ForkJoinPool pool = new ForkJoinPool(); 
Fibonacci f = new Fibonacci(100);
int result = pool.invoke(f);

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

[...] или как может выглядеть странный код, когда вам нужно сделать копию всего и сколько всего толкается в бассейн.

Только сами задачи помещаются в пул. В идеале вы не хотите копировать что-либо: чтобы избежать вмешательства и необходимости блокирования, которое могло бы замедлить вашу программу, ваши задачи в идеале должны работать с независимыми данными. Данные только для чтения могут совместно использоваться всеми задачами, и их не нужно копировать. Если потоки должны сотрудничать при создании какой-то большой структуры данных, лучше всего, чтобы они строили части по отдельности, а затем объединяли их в конце. Объединение может быть выполнено как отдельная задача, или каждая задача может добавить свою часть головоломки к общему решению. Это часто требует некоторой формы блокировки, но это не является существенной проблемой производительности, если работа задачи намного больше, чем работа, обновляющая решение. Решение My Knight's Tour использует этот подход для обновления общего репозитория туров на доске.

Работа с задачами и параллелизмом является довольно серьезным изменением парадигмы от обычного однопоточного программирования. Часто можно решить несколько задач, но только некоторые из них подойдут для резьбового решения. Может потребоваться несколько попыток, чтобы понять, как преобразовывать знакомые проблемы многопоточным способом. Лучший способ научиться - это посмотреть на примеры, а затем попробовать сами. Всегда профилируйте и измеряйте эффекты от изменения количества потоков. Вы можете явно указать количество потоков (ядер) для использования в пуле в конструкторе пула. Когда задачи разбиваются линейно, вы можете ожидать почти линейного ускорения по мере увеличения числа потоков.

0 голосов
/ 27 августа 2010

Игра с «фреймворками», которые утверждают, что решают неразрешимые задачи (оптимальное планирование задач - трудная задача NP), совсем не поможет вам - чтение книг и статей по параллельным алгоритмам. Так называемые «задачи» - это не более чем причудливое название для определения отделимости проблемы (части, которые могут быть вычислены независимо друг от друга). Класс разделяемых задач очень мал - и они уже описаны в старых книгах.

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

Когда вы вводите высокий параллелизм, вы даже не можете поддерживать очередь, у вас даже нет шины памяти - представьте, что 100 процессоров пытаются синхронизировать только с одним общим int или пытаются выполнить арбитраж шины памяти , Вы должны заранее спланировать и предварительно сконфигурировать все, что будет работать, и по существу доказать правильность на белой доске. Многопоточные строительные блоки Intel - маленький ребенок в этом мире. Они предназначены для небольшого количества ядер, которые могут совместно использовать шину памяти. Выполнение разделяемых задач - это легкая задача, которую вы можете обойтись без какой-либо «структуры».

Итак, вы вернулись к необходимости читать как можно больше разных параллельных алгоритмов. Обычно требуется 1-3 года, чтобы исследовать приблизительно оптимальную структуру барьера данных для одной проблемы. Это становится компоновкой, когда вы выбираете, скажем, 16+ ядер на одном чипе, поскольку только первые соседи могут эффективно обмениваться данными (в течение одного цикла барьера данных). Таким образом, вы действительно узнаете гораздо больше, взглянув на CUDA, а также на документы и результаты с экспериментальным 30-ядерным процессором IBM, чем на коммерческую презентацию Intel или какую-нибудь игрушку на Java.

Остерегайтесь демонстрационных проблем, для которых размер ресурсов, потраченных впустую (количество ядер и память), намного больше, чем ускорение, которого они достигают. Если для решения задачи в 2 раза быстрее требуется 4 ядра и 4x оперативной памяти, решение не будет масштабируемым для распараллеливания.

...