Оригинальная ссылка:A Java Fork/Join Framework(PDF) - Doug Lea
на основеСеть параллельного программирования — ifeve.comначальствоAlex/Сяо Хуанперевести,Фан ТэнфэйВыверенный перевод:Платформа соединения Java Fork
Переводы размещены вСеть параллельного программирования — ifeve.com:Java
Fork/Join
Рамка, 2017-11-02
заказ перевода
Doug LeaБог оJava 7
Представленный имFork/Join
Рамочный тезис.
реактивное программирование(Reactive Programming
/ RP
) как парадигма постепенно признается и внедряется во всей отрасли.Это краткое изложение усовершенствования модели системного технологического проектирования/архитектуры после понимания и определения бизнес-требований предыдущей системы.Java
Будучи зрелой платформой, она всегда была несколько стабильной в принятии и следовании тенденциям и обладает удивительной жизнеспособностью:
-
Java 7
при условииForkJoinPool
, поддерживаетсяJava 8
который предоставилStream
(Reactive Stream
даRP
основной компонент). - Кроме того
Java 8
также обеспечиваетLamda
(выражать и эффективно использоватьRP
необходимостьFP
языковые конструкции и идеи). - С этими постоянными, но вечными приготовлениями впереди, в
Java 9
предоставлено вRP
изFlow API
,заJava
Круг обеспечивает официальноеRP API
, маркировкаRP
Переход от фазы свободного исследования в рыночном стиле к унифицированному использованию в церковном стиле.
Из вышеприведенного описания видно, чтоForkJoinPool
принципиальное значение.
Кстати, еще одно упоминаниеJava 9
изFlow API
из@author
СлишкомDoug LeeО~
PS:
СвояПонимание поверхностное, а недочетов и неточностей в переводе точно будет много.Предложения приветствуются(Отправить вопрос) и исправления (Отправить код после форка)!
- 0. Аннотация
- 1. Введение
- 2. Дизайн
- 3. Реализация
- 4. Производительность
- 5. Заключение
- 6. Благодарности
- 7. Ссылки
0. Аннотация
В этой статье описываетсяFork/Join
Дизайн, реализация и производительность фреймворка, которые поддерживаются (рекурсивным) разделением задачи на подзадачи, параллельным выполнением этих подзадач и объединением окончательных результатов после завершения всех подзадач Программирование параллельных вычислений. Общий дизайн основан наCilk
Разработаноwork-stealing
Рамка. На уровне проектирования в основном речь идет о том, как эффективно создавать очереди задач и рабочие потоки и управлять ими. Данные тестов производительности показывают, что хорошая программа для параллельных вычислений улучшит большинство приложений, а также указывают на некоторые потенциальные возможности для улучшения.
Примечание 1:
Cilk
это IntelCilk
язык. ИнтелC++
Новые возможности редактораCilk
методы расширения языка дляC/C++
Язык добавляет поддержку мелких задач, упрощая добавление параллелизма к новому и существующему программному обеспечению, чтобы полностью использовать мощность нескольких процессоров.
1. Введение
Fork/Join
Параллелизм — один из самых простых и наиболее эффективных методов проектирования для получения хорошей производительности параллельных вычислений.Fork/Join
Параллельный алгоритм — это параллельная версия знакомого алгоритма «разделяй и властвуй».
Result solve(Problem problem) {
if (problem is small) {
directly solve problem
} else {
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from subresults
}
}
fork
операция начнет новую параллельFork/Join
Подзадачи.join
Операция будет ожидать завершения всех подзадач.Fork/Join
Алгоритмы, как и другие алгоритмы «разделяй и властвуй», всегда рекурсивно и неоднократно разделяют подзадачи до тех пор, пока эти подзадачи не смогут выполняться достаточно простым коротким последовательным способом.
Некоторые связанные методы программирования и примеры находятся в"Java
Параллельное программирование — принципы проектирования и шаблоны, второе издание[7]Это обсуждалось в разделе 4.4. В этой статье будет обсуждатьсяFJTask
Дизайн (раздел 2), реализация (раздел 3) и производительность (раздел 4)Java™
Рамка.FJTask
в видеutil.concurrent
часть пакета, в настоящее время доступного наgee.cs.oswego.edu/полученный.
2. Дизайн
Fork/Join
Программы могут работать в любой среде, которая поддерживает возможность распараллеливания выполнения подзадач сборки и имеет механизм ожидания завершения выполнения подзадач. Тем не мение,java.lang.Thread
класс (в том числеPOSIX pthread
, это тожеJava
основа, на которой базируется нить)Fork/Join
Не лучший выбор для программ:
-
Fork/Join
Задачи имеют простые и регулярные потребности в синхронизации и управлении. По сравнению с обычными нитями,Fork/Join
Схема вычислений, отображаемая задачей, приведет к более гибкой стратегии планирования. Например,Fork/Join
Помимо ожидания подзадач, задачи не нужно блокировать в других случаях. Таким образом, традиционные затраты на отслеживание заблокированных потоков в данном случае на самом деле пустая трата времени. - Для разумной детализации базовой задачи создание потока и управление им может быть даже дороже, чем само выполнение задачи. Хотя степень детализации должна быть скорректирована соответствующим образом, поскольку приложение работает на разных конкретных платформах. Но чрезвычайно крупномасштабные накладные расходы на потоки ограничивают параллелизм.
короче,Java
Пара стандартных резьбовых рамокFork/Join
Это слишком громоздко для программирования. Но поскольку потоки составляют основу многих других параллельных и параллельных программ, невозможно (или нецелесообразно) полностью исключить эти затраты или настроить планирование потоков для этой цели.
Хотя эта идея существует уже давно, первая опубликованная структура для систематического решения этих проблем былаCilk
[5].Cilk
и другие облегченные фреймворки основаны на базовых механизмах потоков и процессов операционной системы для поддержки специальных задач.Fork/Join
программа. Эта стратегия также применима кJava
,несмотря на то чтоJava
Потоки реализованы на основе низкоуровневых возможностей операционной системы. Основным преимуществом создания такой облегченной среды выполнения является возможность сделатьFork/Join
Программы написаны более интуитивно понятным способом, что позволяет имJVM
работает в системе.
FJTask
каркас основан наCilk
Эволюция дизайна. Другие подобные фреймворкиHood
[4],Filaments
[8],Stackthreads
[10]и некоторые связанные системы, которые полагаются на облегченное выполнение задач. Все эти платформы используют операционную систему для сопоставления потоков сCPU
Сопоставьте задачи с потоками так же, как описано выше. просто они будут использоватьFork/Join
Простота, регулярность и последовательность программы для выполнения этого сопоставления. Хотя эти фреймворки могут работать с неформально параллельными программами, они оптимизируютFork/Join
дизайн:
- Набор пулов рабочих потоков готов. Каждый рабочий поток является стандартным («тяжеловесным») потоком, который обрабатывает задачи, хранящиеся в очереди (это относится к
Thread
подкласс классаFJTaskRunner
объект экземпляра). Как правило, рабочие потоки должны соответствовать количеству процессоров в системе. Для некоторых собственных фреймворков, таких какCilk
, они сначала будут отображены в потоки ядра или облегченные процессы, а затем запущены на процессоре. существуетJava
, виртуальная машина и операционная система должны быть объединены друг с другом, чтобы выполнить сопоставление потоков и процессоров. Тогда для операций с интенсивными вычислениями это отображение является относительно простой задачей для операционной системы. Любая разумная стратегия отображения приведет к отображению потоков на разные процессоры. - все
Fork/Join
Задачи — это экземпляры облегченных классов выполнения, а не экземпляры потоков. существуетJava
, должны быть реализованы независимые исполняемые задачиRunnable
интерфейс и переопределениеrun
метод. существуетFJTask
framework эти задачи будут наследоваться как подклассыFJTask
вместоThread
, все они достигаютRunnable
интерфейс. (Для приведенных выше двух случаев класс также может реализоватьRunnable
Интерфейс, экземпляр объекта класса может выполняться как в задаче, так и в потоке. Поскольку на выполнение задачи влияетFJTask
Ограничения строгого правила метода, подклассыFJTask
Условно говоря, удобнее вызывать их напрямую. ) - Мы будем использовать специальную очередь и принцип планирования для управления задачами и их выполнения через рабочие потоки. Эти механизмы реализуются родственными методами, представленными в классе задач:
fork
,join
,isDone
(идентификатор конечного состояния) и некоторые другие удобные методы, такие как вызовcoInvoke
декомпозировать и объединить две или более задач. - Простой класс контроля и управления (здесь имеется в виду
FJTaskRunnerGroup
) для запуска пула рабочих потоков и первоначального выполнения вызова, инициированного обычным потокомFork/Join
задание (аналогичноJava
в программеmain
метод).
В качестве стандартного примера, демонстрирующего программисту, как работает фреймворк, это класс, вычисляющий функцию Фибоначчи.
class Fib extends FJTask {
static final int threshold = 13;
volatile int number; // arg/result
Fib(int n) {
number = n;
}
int getAnswer() {
if (!isDone())
throw new IllegalStateException();
return number;
}
public void run() {
int n = number;
if (n <= threshold) // granularity ctl
number = seqFib(n);
else {
Fib f1 = new Fib(n - 1);
Fib f2 = new Fib(n - 2);
coInvoke(f1, f2);
number = f1.number + f2.number;
}
}
public static void main(String[] args) {
try {
int groupSize = 2; // for example
FJTaskRunnerGroup group = new FJTaskRunnerGroup(groupSize);
Fib f = new Fib(35); // for example
group.invoke(f);
int result = f.getAnswer();
System.out.println("Answer: " + result);
} catch (InterruptedException ex) {
}
}
int seqFib(int n) {
if (n <= 1) return n;
else return seqFib(n − 1) + seqFib(n − 2);
}
}
Эта версия работает как минимум быстрее, чем любая задача на платформах, упомянутых в разделе 4.Thread
Класс работает в 30 раз быстрее. Сохраняя производительность, программа по-прежнему поддерживаетJava
Переносимость многопоточных программ. Для программистов обычно есть два значения параметра, которые их беспокоят:
- Количество создаваемых рабочих потоков обычно может быть таким же, как и количество процессоров на платформе (или меньше, для других связанных задач, или, в некоторых случаях, больше, для повышения производительности задач, не связанных с интенсивными вычислениями)).
- Параметр детализации представляет собой точку, в которой стоимость создания задач перевешивает потенциальный выигрыш в производительности от распараллеливания. Этот параметр больше зависит от алгоритма, чем от платформы. Переломный момент, который обычно хорошо работает на одном процессоре, также будет хорошо работать на многопроцессорной платформе. В качестве побочного эффекта этот подход можно комбинировать с
Java
Механизм динамической компиляции виртуальной машины — хорошее сочетание, и этот механизм лучше оптимизирует методы малых блоков, чем монолитные программы. Таким образом, в сочетании с преимуществами локализации данных,Fork/Join
Производительность алгоритма лучше, чем у других алгоритмов даже на одном процессоре.
2.1 work−stealing
Fork/Join
Ядром фреймворка является легкий механизм планирования.FJTask
УсыновленныйCilk
изwork-stealing
Принята базовая стратегия планирования:
- Каждый рабочий поток поддерживает свою собственную очередь отправки выполняемых задач.
- Очереди поддерживаются в виде двусторонних очередей (Примечание:
deques
Часто произносится как «колоды»), поддерживает не только LIFO —LIFO
изpush
иpop
Операция также поддерживает принцип "первым пришел - первым вышел" --FIFO
изtake
работать. - Для данного рабочего потока подзадачи, сгенерированные задачей, будут помещены в собственную очередь рабочего потока.
- Рабочие потоки используют LIFO --
LIFO
(последний элемент первым) упорядочить, выталкивая задачи для обработки задач в очереди. - Когда у рабочего потока нет локальных задач для запуска, он будет использовать FIFO.
FIFO
Правила пытаются случайным образом взять («украсть») задачу из другого рабочего потока для запуска. - когда рабочий поток касается
join
операция, которая будет обрабатывать другие задачи, если это возможно, до тех пор, пока целевой задаче не будет сообщено, что она завершена (черезisDone
метод). Все задачи будут выполнены без блокировки. - Когда рабочий поток больше не может получать задачи и не может обрабатываться другими потоками, он завершается (через
yield
,sleep
и/или настройку приоритета, см. раздел 3) и повторить попытку через некоторое время, пока всем рабочим потокам не будет сообщено, что они простаивают. В этом случае они будут блокироваться до тех пор, пока другие задачи не будут снова вызваны верхним уровнем.
Используйте ЛИФО—LIFO
Используется для обработки собственной задачи каждого рабочего потока, но с использованием FIFO --FIFO
Правила используются для получения других задач, что является широко используемой рекурсией.Fork/Join
Средство тюнинга дизайна. Цитировать[5]Детали подробно обсуждаются.
Если задачи захвата потоков выполняются с направления, противоположного владельцу очереди, это снижает конкуренцию за потоки. Он также отражает стратегию приоритета больших задач рекурсивного алгоритма «разделяй и властвуй». Следовательно, возможно, что более ранняя украденная задача предоставит более крупную единичную задачу, что позволит рекурсивно разложить украденный поток в будущем.
Как следствие приведенных выше правил, для некоторых основных операций задачи с относительно небольшой степенью детализации будут выполняться быстрее, чем те, которые используют только грубое разбиение на разделы и те, которые не используют рекурсивную декомпозицию. Хотя соответствующие несколько задач находятся в большинствеFork/Join
Кадры могут быть украдены другими рабочими потоками, но создание множества хорошо организованных задач означает, что пока рабочий поток находится в состоянии выполнения, эта задача потенциально может быть выполнена.
3. Реализация
Этот кадр состоит примерно из 800 строк чистогоJava
Состав кода, основной классFJTaskRunner
,этоjava.lang.Thread
подкласс .FJTask
Он поддерживает только логическое значение конечного состояния, а все остальные операции делегируются текущим рабочим потоком.JFTaskRunnerGroup
Классы используются для создания рабочих потоков, поддержания некоторого общего состояния (например, идентификаторов для всех рабочих потоков, необходимых для операций кражи), а также координации запуска и завершения работы.
Более подробную документацию по реализации можно найти по адресуutil.concurrent
Просмотр параллельных пакетов. Этот раздел фокусируется только на двух типах проблем и некоторых решениях, которые были сформированы при реализации этой структуры: поддержка эффективных операций с двусторонними списками (push
,pop
иtake
) и поддерживает протокол кражи, когда рабочие потоки пытаются получить новые задачи.
3.1 Дек
(Примечание: элементы в двухсторонней очереди могут быть извлечены с обоих концов, а операции вставки и удаления ограничены обоими концами очереди.)
Чтобы добиться эффективного и масштабируемого выполнения задач, управление задачами должно быть максимально быстрым. Создание, публикация и извлечение (или нечастое получение) задач в модели последовательного программирования влекут за собой накладные расходы на вызов процедур. Меньшие накладные расходы позволяют программистам создавать более мелкие задачи и в конечном итоге лучше использовать преимущества параллелизма.
Java
Виртуальная машина отвечает за выделение памяти для задач.Java
Сборщик мусора избавляет нас от необходимости писать специальный распределитель памяти для обслуживания задач. По сравнению с аналогичными фреймворками на других языках эта причина позволяет значительно сократить реализациюFJTask
сложность и объем необходимого кода.
Базовая структура дека использует очень обычную структуру — массив (хотя и переменной длины) для представления каждой очереди с двумя индексами:top
Индекс подобен указателю стека в массиве.push
иpop
операция по изменению.base
индекс может быть передан толькоtake
операция по изменению. с учетомFJTaskRunner
Операции плавно связаны с деталями двухсторонней очереди (например,fork
позвонить напрямуюpush
), поэтому эта структура данных размещается непосредственно внутри класса, а не как отдельный компонент.
Но доступ к элементам двухсторонней очереди будет осуществляться одновременно несколькими потоками при отсутствии достаточной синхронизации и единственногоJava
Элементы массива также не могут быть объявлены какvolatile
переменная (столкновение:объявлен какvolatile
массив, элементы которого не имеютvolatile
семантики), каждый элемент массива на самом деле является фиксированной ссылкой на массив, который поддерживает единуюvolatile
Ссылочный объект пересылки. Это решение было принято с самого начала в основном из-заJava
Непротиворечивость модели памяти. Но на этом уровне было показано, что требуемая косвенность повышает производительность на некоторых протестированных платформах. Вероятно, потому что доступ к соседним элементам снижает конкуренцию за кеш, косвенное обращение к памяти происходит немного быстрее.
Основная проблема реализации двухсторонней очереди связана с синхронизацией и ее отменой. Хотя вJava
Используя оптимизированный инструмент синхронизации на виртуальной машине, для каждогоpush
иpop
Должна ли операция получить блокировку или позволить всему этому стать узким местом в производительности? Затем, основываясь на следующих наблюдениях, мы можем изменитьClik
стратегии в , что дает нам допустимое решение:
-
push
иpop
Операции может вызывать только владелец рабочего потока. - правильно
take
Операция может быть легко скомпрометирована в определенный момент из-за кражи потока задачи.take
Операция заблокирована и ограничена. (Деки также можно отключить при необходимостиtake
работать. ) таким образом, конфликт управления будет сведен к двум уровням частичной синхронизации. -
pop
иtake
Операции будут конфликтовать, только если двухсторонняя очередь пуста, в противном случае очередь гарантированно будет работать с разными элементами массива.
Пучокtop
иbase
Индекс определяется какvolatile
Переменная может гарантировать, что если в очереди более одного элемента,pop
иtake
Операции могут выполняться без блокировки. Это делается черезDekker
Алгоритм достижения. когдаpush
предварительно уменьшить доtop
Время:
if (–top >= base) ...
иtake
предварительно уменьшить доbase
Время:
if (++base < top) ...
В каждом из приведенных выше случаев они проверяют, не приведет ли это к тому, что дек станет пустой очередью, сравнивая два индекса. Асимметричное правило будет использоваться для предотвращения потенциальных конфликтов:pop
перепроверит состояние и продолжит работу после получения блокировки (дляtake
держится) и не завершается до тех пор, пока очередь действительно не станет пустой. иtake
Операция завершится немедленно, особенно при попытке получить другую задачу. Используйте с другими подобнымиClik
изTHE
Как и в протоколе, эта асимметрия является единственным существенным изменением.
использоватьvolatile
переменный индексpush
Операции могут выполняться без синхронизации, если очередь не заполнена. Если очередь вот-вот переполнится, она сначала должна получить блокировку очереди, чтобы сбросить длину очереди. В остальных случаях просто убедитесь,top
Операция ставится в очередь для обновления после того, как слот массива очередей окажется в полосе подавления помех.
При последующей реализации инициализации было обнаружено, что существует несколькоJVM
не встречаетсяJava
Правильное чтение и запись в модели памятиvolatile
переменные правила. как рабочее место,pop
Условие для повторной попытки операции при удержании блокировки изменено на: если есть два или меньше элементов, иtake
Операция добавляет вторую блокировку для обеспечения эффекта барьера памяти, затем запускается повторная попытка. Это удовлетворительно, пока поток-владелец теряет не более одного индекса, и это приводит лишь к небольшому снижению производительности.
3.2 Перехваты и бездействие
В среде упреждающей работы рабочие потоки ничего не знают о требованиях синхронизации программ, которые они запускают. Они просто создают, публикуют, извлекают, получают, управляют состоянием и выполняют задачи. Эта простая схема делает ее эффективной, когда у всех потоков есть много задач для выполнения. Однако этот подход имеет свою цену, он будет полагаться на эвристику, когда работы недостаточно. То есть после запуска основной задачи до ее окончания, в некоторыхFork/Join
В алгоритме используются указатели синхронизации с точкой.
Основная проблема заключается в том, что делать, когда рабочий поток не имеет ни локальных задач, ни кражи задач из других потоков. Если программа работает на специализированном многоядерном процессоре, вы можете положиться на аппаратный цикл ожидания-занятия, чтобы попытаться украсть задачу. Однако даже в этом случае попытка кражи увеличит конкуренцию и даже приведет к неэффективности рабочих потоков, которые не простаивают (из-за протокола блокировки, в разделе 3.1). Кроме того, в сценарии, который больше подходит для запуска платформы, операционная система должна иметь возможность уверенно запускать несвязанные и работоспособные процессы и потоки.
Java
Нет очень надежной работы, чтобы гарантировать это, но на практике это часто приемлемо. Поток, которому не удалось украсть, понизит свой приоритет перед попыткой другого угона, выполняясь между попытками угона.Thread.yeild
операцию, а затем поместить ее состояние вFJTaskRunnerGroup
устанавливается в неактивное состояние. Они будут блокироваться до тех пор, пока не появится новый основной поток. В других случаях после определенного количества спинов потоки уйдут в сон и будут спать вместо того, чтобы отказаться от кражи. Усовершенствованный механизм сна может создавать иллюзию того, что разделение задач занимает много времени. Но это кажется лучшим и общим компромиссом. Будущие версии платформы могут поддерживать дополнительные методы управления, позволяющие программистам переопределять реализацию по умолчанию, если они почувствуют снижение производительности.
4. Производительность
Сегодня с компиляторами иJava
В связи с постоянным улучшением производительности виртуальных машин результаты тестов производительности применимы лишь некоторое время. Однако данные результатов испытаний, представленные в этом разделе, показываютFork/Join
Основные возможности фреймворка.
В следующей таблице кратко описывается наборFork/Join
тестовая программа. Эти программы изutil.concurrent
Пример кода в пакете адаптирован для демонстрацииFork/Join
Получены различия в производительности фреймворка при решении разных типов моделей задач, а также результаты тестирования фреймворка на некоторых общих параллельных тестовых процедурах.
Основные тесты, упомянутые ниже, тестовые программы выполняются вSun Enterprise 10000
На сервере на сервере 30CPU
, операционная система естьSolaris 7
система работаетSolaris
коммерческая версия1.2
JVM
(2.2.2_05
более ранняя версия релизной версии). в то же время,Java
Параметр среды виртуальной машины о сопоставлении потоков выбран как "bound threads
(Примечание переводчика:XX:+UseBoundThreads
, связывает потоки пользовательского уровня с потоками ядра, только сSolaris
связанных), а настройки параметров памяти виртуальной машины обсуждаются в разделе 4.2. Кроме того, следует отметить, что некоторые из упомянутых ниже тестов выполняются с 4CPU
изSun Enterprise 450
на сервере.
Чтобы уменьшить детализацию таймера иJava
Влияние факторов запуска виртуальной машины на результаты тестирования, программа тестирования использует огромное количество входных параметров. И некоторые другие факторы запуска мы маскируем, запуская задачу инициализации перед запуском таймера. Большинство полученных результатов тестов находятся в середине трех результатов тестов, но некоторые данные тестов получены только из результатов одного запуска (включая многие тесты в разделах 4.2 ~ 4.4), поэтому эти результаты тестов будут зашумлены.
4.1 Эффект ускорения
Тестируя один и тот же набор задач с разным количеством (1 ~ 30) рабочих потоков, он используется для получения результатов теста масштабируемости фреймворка. Хотя мы не можем гарантироватьJava
Всегда ли виртуальная машина может сопоставить каждый поток с другимCPU
В то же время у нас нет доказательств, подтверждающих это. Можно сопоставить новый поток сCPU
Задержка будет увеличиваться с увеличением количества потоков и может различаться в разных системах и разных тестовых программах. Однако полученные результаты тестирования показывают, что увеличение количества потоков увеличивает использованиеCPU
Количество.
Ускорение обычно выражается какTimen / Time1
. Как показано на рисунке выше, лучшее ускорение показывает программа, вычисляющая интегрирование (28,2 для 30 потоков), а худшее — программа матричной факторизации (30 потоков — это всего лишь 15,35 для ускорения).
Другой мерой масштабируемости является скорость выполнения задачи и среднее время, необходимое для выполнения одной задачи (здесь задача может быть задачей узла рекурсивной декомпозиции или задачей корневого узла). Приведенные ниже данные показывают данные о скорости выполнения задачи, полученные при выполнении каждой программы за один раз. Очевидно, что количество задач, выполняемых в единицу времени, должно быть фиксированной константой. Однако на самом деле по мере увеличения количества потоков полученные данные будут демонстрировать небольшое уменьшение, что также указывает на некоторые ограничения масштабируемости. Здесь необходимо пояснить, что огромная разница в производительности скорости выполнения задач в каждой программе вызвана разницей в степени детализации ее задач. Программа с наименьшей скоростью выполнения задачиFib
(последовательность Фибоначчи) с порогом, установленным на 13, в общей сложности было выполнено 2,8 миллиона модульных задач с 30 потоками.
Четыре фактора способствовали тому, что показатели выполнения задач для этих программ не отображались в виде горизонтальной линии. Три из них являются общими причинами для всех сред параллелизма, поэтому мы начнем сFJTask
рама (в отличие отCilk
и другие фреймворки), а именно сбор мусора.
4.2 Сбор мусора
В целом, производительность текущего механизма сборки мусора сравнима сFork/Join
Рамка соответствует:Fork/Join
Программы будут генерировать огромное количество блоков задач во время выполнения, но эти задачи быстро превратятся в мусор памяти после их выполнения. По сравнению с однопоточной программой, выполняемой последовательно в любое время, ее соответствующаяFork/Join
Программа требует максимальноp
умноженное на объем памяти (гдеp
это количество потоков). Сборщик мусора с копией полупространства на основе генерации (то есть тот, который используется в тестовой программе в этой статье)Java
Сборщик мусора, используемый виртуальной машиной, может очень хорошо справиться с этой ситуацией, потому что этот механизм сборки мусора копирует только блоки памяти, не являющиеся мусором, во время высвобождения памяти. Это позволяет избежать сложной проблемы с ручным параллельным управлением памятью, когда отслеживаются единицы памяти, выделенные одним потоком, но используемые другим потоком. Этому механизму сборки мусора не нужно знать источник выделения памяти, поэтому ему не нужно решать эту сложную проблему.
Типичное проявление преимуществ этого механизма сборки мусора: при использовании этого механизма сборки мусора выполняются четыре потокаFib
Процедура занимает всего 5,1 секунды, и еслиJava
Виртуальная машина настроена на отключение повторного использования копий генерации (в этом случае используется метка-очистка (mark−sweep
) механизм сборки мусора), который занимает 9,1 секунды.
Однако механизм сборки мусора становится фактором масштабируемости только в том случае, если использование памяти достигает высокого значения, потому что в этом случае виртуальная машина должна часто останавливать другие потоки для сборки мусора. Приведенные ниже данные показывают три различных параметра памяти (Java
Виртуальная машина поддерживает настройку параметров памяти через дополнительные параметры), разница показана в коэффициенте ускорения: по умолчанию полупространство 4M, полупространство 64M, и по количеству потоков по формуле (2 + 2p
) M задает полупространство. При меньшем полупространстве накладные расходы на остановку других потоков и сборку мусора начинают влиять на запечатывание в тех случаях, когда дополнительные потоки вызывают увеличение скорости сборки мусора.
Учитывая приведенные выше результаты, мы используем полупространство 64M в качестве рабочего стандарта для других тестов. На самом деле лучшей стратегией для установки размера памяти является определение фактического количества потоков на тест. (Как и в случае с тестовыми данными выше, мы обнаружили, что в этом случае ускорение будет более плавным). С другой стороны, установленный программой порог детализации задачи также должен увеличиваться пропорционально количеству потоков.
4.3 Распределение памяти и размер слова
Среди тестовых программ, упомянутых выше, четыре программы создают и манипулируют большим количеством общих массивов и матриц: числовая сортировка, матричное умножение/разложение на множители и релаксация. Среди них алгоритм сортировки должен быть операцией перемещения данных (переместить данные памяти вCPU
cache) и общая пропускная способность системной памяти, наиболее чувствительные. Чтобы определить характер этих влияющих факторов, мы ранжируем алгоритмsort
Переписан в четыре версии соответственноbyte
байтовые данные,short
тип данных,int
введите данные иlong
Сортировать данные. Все данные, которыми манипулируют эти программы, равны 0.
~ 255 для обеспечения равенства между этими сравнительными тестами. Теоретически, чем больше разрядность операционных данных, тем больше нагрузка на память.
Результаты тестов показывают, что увеличение нагрузки на память приводит к снижению ускорения, хотя мы не можем предоставить четких доказательств того, что это единственная причина такого поведения. Но разрядность данных влияет на производительность программы. Например, с помощью потока, сортирующего байтыbyte
Данные занимают 122,5 секунды, однако сортировкаlong
Данные занимают 242,5 секунды.
4.4 Синхронизация задач
Как обсуждалось в разделе 3.2, модель кражи задач часто сталкивается с проблемами синхронизации обработки задач.Если рабочий поток получает задачу, но в соответствующей очереди нет задач для получения, это вызовет конкуренцию. существуетFJTask
Во фреймворке эта ситуация иногда приводит к принудительному переходу потока в спящий режим.
отJacobi
Мы можем видеть такого рода проблемы в программе.Jacobi
Программа выполняется в течение 100 шагов, и ячейки вокруг соответствующих точек матрицы обновляются на каждом шаге. В программе есть глобальное барьерное разделение. Чтобы уточнить размер воздействия этой операции синхронизации. Мы делаем синхронизацию каждые 10 шагов в программе. Разница в масштабируемости, показанная на рисунке, иллюстрирует влияние этой стратегии параллелизма. Это также означает, что мы должны добавить дополнительные методы для переписывания программистами в последующих версиях этой структуры, чтобы настроить структуру для достижения максимальной эффективности в различных сценариях. (Обратите внимание, что этот график может немного преувеличивать влияние фактора синхронизации, так как версия с 10-этапной синхронизацией, скорее всего, должна управлять большей локальностью задач)
4.5 Локальность задачи
FJTask
, или другойFork/Join
Фреймворк максимально оптимизирован для распределения задач, чтобы рабочие потоки обрабатывали задачи, сгенерированные их собственной декомпозицией. Потому что, если вы этого не сделаете, производительность вашей программы пострадает по двум причинам:
- Кража задач из других очередей обходится дороже, чем выполнение их в своей очереди
pop
Операция дорогая. - В большинстве программ операции задачи работают с общей единицей данных, и лучший доступ к локальным данным можно получить, запустив только собственную часть задачи.
Как показано выше, в большинстве программ относительные данные о краже задач поддерживаются в лучшем случае на очень низком уровне. тогда в которомLU
иMM
По мере увеличения количества потоков программа будет создавать больший дисбаланс в рабочей нагрузке (относительно больший захват задач). Настроив алгоритм, мы можем уменьшить этот эффект, чтобы получить лучшее ускорение.
4.6 Сравнение с другими фреймворками
По сравнению с другими фреймворками на разных языках маловероятно получение каких-либо четких или значимых результатов сравнения. Однако с помощью этого метода, по крайней мере, можно узнатьFJTask
В отличие от других языков (здесь в основном имеется в видуC
иC++
), чтобы сравнить сильные стороны и недостатки аналогичных фреймворков, написанных на . В следующей таблице показаны несколько похожих фреймворков (Cilk
,Hood
,Stackthreads
а такжеFilaments
) проверенные данные о производительности. Участвующие тесты находятся в 4CPU
изSun Enterprise 450
Сервер работает в 4 потока. Чтобы избежать реконфигурации в другой среде или программе, все тестовые программы запускаются с немного меньшим набором задач, чем тесты выше. Полученные данные также являются лучшим значением из трех тестов, чтобы убедиться, что компилятор или конфигурация среды выполнения обеспечивают наилучшую производительность. вFib
Программа не указывает порог детализации задачи, что означает, что значение по умолчанию равно 1. (это установлено вFilaments
версияFib
Программа установлена на 1024, так что программа будет вести себя более последовательно с другими версиями).
В тесте на ускорение результаты тестов, полученные разными фреймворками на разных программах, очень близки, количество потоков 1 ~ 4, а коэффициент ускорения находится между (3,0 ~ 4,0). Поэтому следующий рисунок посвящен только абсолютной производительности различных фреймворков.Однако, поскольку все фреймворки очень быстры с точки зрения многопоточности, большинство различий больше связано с качеством самого кода, компилятора. вызванные оптимизацией элементов конфигурации или настройки параметров. В практических приложениях разные фреймворки выбираются в соответствии с реальными потребностями, чтобы компенсировать огромную разницу в производительности между разными фреймворками.
FJTask
Низкая производительность при вычислениях с массивами и матрицами с плавающей запятой. Несмотря на тоJava
Производительность виртуальной машины продолжает улучшаться, но по сравнению с теми,C
иC++
Мощный внутренний оптимизатор, используемый языком, недостаточно конкурентоспособен. Хотя это и не показано на приведенной выше диаграмме,FJTask
Версии всех программ по-прежнему быстрее, чем версии без оптимизации компиляции. И некоторые неофициальные тесты также показывают, что большинство различий в тестах связано с проверкой границ массива, обязательствами времени выполнения. Это тожеJava
Разработчики виртуальных машин и компиляторов были обеспокоены и продолжают решать проблемы.
Напротив, разница в производительности, вызванная качеством кодирования чувствительных к вычислениям программ, очень мала.
5. Заключение
В этой статье описывается использование чистогоJava
Реализация поддерживает портативные (portable
),Эффективный(efficient
) и масштабируемый (scalable
) возможность параллельной обработки и обеспечивает удобствоAPI
Позволяет программистам следовать нескольким правилам и шаблонам проектирования (ссылки[7]предложено и обсуждено), вы можете эффективно использовать структуру. Характеристики производительности, наблюдаемые и проанализированные в примерах программ в этом документе, также предоставляют дополнительные рекомендации для пользователей и подсказывают области, в которых сама структура потенциально может быть улучшена.
Хотя показанные результаты масштабируемости относятся к одномуJVM
, но эмпирически эти основные выводы должны оставаться в силе и в более общем случае:
- Хотя поколения
GC
(generational GC
) обычно хорошо работает с параллелизмом, но может быть форсирован при быстрой генерации мусораGC
Очень часто это мешает масштабируемости программы. в такомJVM
выше, эта основная причина, по-видимому, приводит кGC
Время, необходимое для остановки потока, примерно пропорционально количеству запущенных потоков. Поскольку чем больше запущено потоков, тем больше мусора генерируется в единицу времени, а увеличение накладных расходов примерно пропорционально квадрату числа потоков. При этом только вGC
Когда частота относительно высока, это окажет значительное влияние на производительность. Безусловно, этот вопрос требует дальнейших исследований и параллельных разработок.GC
алгоритм. Результаты этой статьи также иллюстрируют, что в многопроцессорныхJVM
Параметры оптимизации доступны на (tuning options
) и адаптивные механизмы (adaptive mechanisms
), так что память может быть активированаCPU
Необходимо расширение номера. - Большинство проблем с масштабируемостью возникают только тогда, когда запущенные программы используют
CPU
Доступно на большем количестве устройствCPU
появится когда.FJTask
(и другиеFork/Join
рама) в общем 2-х, 4-х и 8-х сторонниеSMP
Машина показывает почти идеальный эффект ускорения. заstock multiprocessor
предназначен для работы на более чем 16CPU
ВверхFork/Join
рамки, эта статья может быть первой статьей, в которой систематически сообщаются результаты. Сохраняется ли паттерн этого результата в других структурах, требует дальнейшего измерения. - Характеристики приложений, включая локальность памяти, локальность задач и использование глобальной синхронизации, часто сложнее, чем у фреймворков.
JVM
или нижний слойOS
Функции оказывают большее влияние на масштабируемость и абсолютную производительность. Например, как видно из неофициального тестирования, тщательно избегалиdeques
Операции синхронизации с повышением частоты (обсуждаемые в разделе 3.1) полезны для программ, которые генерируют относительно мало задач (таких какLU
) вообще не улучшилось. Однако минимизация накладных расходов на управление задачами может расширить область применения и полезность фреймворка и связанных с ним методов проектирования и программирования.
В дополнение к постепенным улучшениям платформы будущие возможности включают создание полезных приложений на платформе (а не демонстраций и тестов), последующую оценку под нагрузкой приложения в производственных средах и в различных средах.JVM
Измеряйте и разрабатывайте расширения для простоты использования в многопроцессорных кластерах.
6. Благодарности
Часть работы в этой статье была вдохновленаSun
Лаборатория поддерживается совместным исследовательским грантом. благодарныйSun
лабораторияJava
исследовательской группыOle Agesen,Dave Detlefs,Christine Flood,Alex GarthwaiteиSteve Hellerза советы, помощь и комментарии.David Holmes,Ole Agesen,Keith Randall,Kenjiro Tauraи полезные комментарии к проектам этой статьи рецензентами, имена которых мне неизвестны.Bill Pughуказывает на то, что обсуждалось в разделе 3.1.JVM
Ограничения чтения после записи (read−after−write limitations
). Особая благодарностьDave DiceУделите время тестированию модели предприятия с 30 участниками.
7. Ссылки
- [1]Агесен, Оле, Дэвид Детлефс и Дж. Элиот Б. Мосс Сборка мусора и локальная переменная — точность типов и живость в виртуальных машинах Java.Proceedings of 1998 ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), 1998.
- [2]Агесен, Оле, Дэвид Детлефс, Алекс Гартуэйт, Росс Книппель, Ю. С. Рамакришна и Дерек Уайт Эффективная метаблокировка для реализации вездесущей синхронизации.Материалы OOPSLA ’99, ACM, 1999.
- [3] Arora, Nimar, Robert D. Blumofe, and C. Greg Plaxton. Thread Scheduling for Multiprogrammed Multiprocessors. In Proceedings of the Tenth Annual ACM Symposium on Parallel Algorithms and Architectures (SPAA), Пуэрто Валларта, Мексика, 28 июня — 2 июля 1998 г.
- [4] Blumofe, Robert D. and Dionisios Papadopoulos. Hood: библиотека потоков пользовательского уровня для мультипрограммных мультипроцессоров. Technical Report, University of Texas at Austin, 1999.
- [5]Фриго, Маттео, Чарльз Лейзерсон и Кит Рэндалл Реализация многопоточного языка Cilk-5.Proceedings of 1998 ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), 1998.
- [6] Gosling, James, Bill Joy, and Guy Steele. The Java Language Specification, Аддисон-Уэсли, 1996.
- [7] Lea, Doug. Concurrent Programming in Java, second edition, Аддисон-Уэсли, 1999.
- [8]Ловенталь, Дэвид К., Винсент В. Фри и Грегори Р. Эндрюс Эффективный мелкозернистый параллелизм на машинах с общей памятью.Параллелизм — практика и опыт, 10, 3:157-173, 1998.
- [9] Simpson, David, and F. Warren Burton. Space efficient execution of deterministic parallel programs. IEEE Transactions on Software Engineering, December, 1999.
- [10] Taura, Kenjiro, Kunio Tabata, and Akinori Yonezawa. "Stackthreads/MP: Integrating Futures into Calling Standards." In Proceedings of ACM SIGPLAN Symposium on Principles & Practice of Parallel Programming (PPoPP), 1999.