Автоматическая параллелизация

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

Взгляните на код ниже:

for(int i=0;i<100;i++)
   sum1 += rand(100)
for(int j=0;j<100;j++)
   sum2 += rand(100)/2

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

Вы считаете это полезным проектом? есть что-нибудь подобное?

Ответ 1

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

Это не значит, что это было бы полезно. Рассмотрим следующее:

  • Прежде всего, чтобы сделать это во время компиляции, вам нужно проверить все пути кода, которые вы можете потенциально достичь внутри конструкции, которую вы хотите распараллелить. Это может быть сложно для чего угодно, кроме просто вычислений.
  • Во-вторых, вам нужно как-то решить, что является параллелизуемым, а что нет. Например, вы не можете тривиально разбить цикл, который, например, изменяет одно и то же состояние на несколько потоков. Это, вероятно, очень сложная задача, и во многих случаях вы не уверены: две переменные могут ссылаться на один и тот же объект.
  • Даже если бы вы могли достичь этого, это закончилось бы запутанным для пользователя. Было бы очень трудно объяснить, почему его код не был параллелизирован и как его следует изменить.

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

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

Ответ 2

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

Можно автоматически разбить ваш пример на несколько потоков, но не так, как вы думаете. Некоторые современные методы пытаются запустить каждую итерацию цикла for в своем потоке. Один поток получит четные знаки (i = 0, я = 2,...), другой - нечетные индексы (i = 1, я = 3,...). Как только этот цикл будет выполнен, можно запустить следующий. Другие методы могут стать более сумасшедшими, выполняя приращение i++ в одном потоке и rand() в отдельном потоке.

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

Если вы действительно заинтересованы в этой теме и не возражаете просеивать через исследовательские работы:

Ответ 3

Это практически невозможно.

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

Хотя можно было бы распараллелить очень простые циклы, даже тогда есть риск. Например, ваш вышеприведенный код можно было бы распараллелить, если rand() является потокобезопасным - и многие подпрограммы генерации случайных чисел не являются. (Java Math.random() синхронизирован для вас - однако.)

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

Ответ 4

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

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

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

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

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

Но на самом деле это не так, как вы хотели бы распараллелить код. Похоже, что каждая итерация цикла зависит от предыдущей, поскольку sum1 + = rand (100) совпадает с sum1 = sum1 + rand (100), где sum1 в правой части является значением из предыдущей итерации. Однако единственной операцией является добавление, которое ассоциативно, поэтому мы переписываем сумму много разных способов.

sum1 = (((rand_0 + rand_1) + rand_2) + rand_3) ....
sum1 = (rand_0 + rand_1) + (rand_2 + rand_3) ...

Преимущество второго заключается в том, что каждое отдельное добавление в скобках может быть вычислено параллельно всем остальным. Как только у вас будет 50 результатов, их можно объединить в еще 25 дополнений и т.д. Вы больше работаете таким образом 50 + 25 + 13 + 7 + 4 + 2 + 1 = 102 дополнения по сравнению с 100 в оригинале, но там всего 7 последовательных шагов, так что помимо параллельного разветвления/соединения и связи накладные расходы он выполняется в 14 раз быстрее. Это дерево дополнений называется операцией сбора в параллельных архитектурах и, как правило, является дорогостоящей частью вычисления.

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

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

Ответ 5

Есть несколько проектов, которые пытаются упростить распараллеливание - например, Cilk. Однако это не всегда хорошо работает.

Ответ 6

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