16 июля 2010 г.

JSR-166y -- fork/join

Ух ты блин! Читал "Scala 2.8.0: what's new" и наткнулся на загадочную фразу: "Actors can be configured to use the efficient JSR166y fork/join pool, resulting in significant performance improvements on 1.6 JVMs". Поскольку я никогда не слышал о fork/join в 1.6, полез копать. Оказывается, группа разработки JSR-166 (который java.util.concurrent) не угомонилась, когда их работа была включена в jdk. Нет, эти гарные хлопцы продолжили свои бесчеловечные экспериментыразработки. Они разработали расширение для JSR-166, называется JSR-166y, иначе Fork/Join framework. Это фреймворк, упрощающий разработку кода для многоядерных систем. В целом можно сказать, что это что-то в духе (отдаленно) map/reduce идеологии, когда код пишется в терминах элементарных операций, независимых друг от друга, которым совершенно по барабану, на каком ядре/узле сети они выполняются, а уже специальный управляющий модуль раскидывает куски данных по вычислительным узлам, и собирает результаты работы.

Статья Дога Ли с описанием исходного Fork/Join фраймворка здесь. Вкратце: вычисления обертываются в наследника ForkJoinTask (который, в свою очередь, наследник Future) и помещаются в ForkJoinPool (менеджер потоков). Выполняемый в пуле ForkJoinTask может внутри себя создать и запустить (асинхронно) другой ForkJoinTask, используя метод fork(). И может дождаться его завершения методом join() (близкий аналог Future.get()). В такой семантике легко записываются алгоритмы типа "разделяй и властвуй" -- в примерах обычно показывается, как удобно реализовывать merge sort в такой нотации.

Основной бонус перед ExecutorService/Future, как я его сейчас понял -- не нужно явно указывать ExecutorService каждый раз при создании новой задачи. То есть вместо
final ExecutorService es = ...;
final Future<?> f = es.submit( task );
final Object res = f.get();

мы имеем

final ForkJoinTask task = ...;
task.fork();
final Object res = task.join();


Разумеется, "верхний" ForkJoinTask все равно нужно явно поместить выполняться в какой-то пул -- но внутренняя реализация ForkJoinTask.exec не имеет никакой информации о пуле -- она вообще пул не видит, она видит только задачи (ForkJoinTask). На мой взгляд, это очень удобно. Последний раз, когда я реализовывал асинхронный рекурсивный обход дерева, я сильно ругался на то, что мне все время приходится тащить с собой ссылку на ExecutorService. Т.е. именно для рекурсивных алгоритмов, как я вижу, такой подход становится особенно удобным.

Дополнение можно просто скачать как отдельную библиотеку. Возможно, оно будет включено в jdk 1.7. Я бы приветствовал :)

1 комментарий:

  1. какая-то странная ссылка: http://artisans-serverintellect-com.si-eioswww6.com/default.asp?W9

    ОтветитьУдалить