Параллельный Scala Итератор

Обратите внимание: Это не дублирующий вопрос, так как этот вопрос указывает на все методы Iterator имеет, а не только map и flatMap. Поэтому Future.traverse не является хорошим ответом.

Скажем, у меня есть это простое утверждение:

(1 to 100).toSet.subsets.find(f)

Он отлично работает. Он ленив, не использует много памяти и возвращается, как только будет найден один элемент. Проблема начинается, когда вы хотите распараллелить ее. Вы могли бы сказать, что это Scala, должно быть .par или Iterator, но этого нет.

Предлагаемое решение в Интернете - использовать .grouped, но это не так хорошо, как хотелось бы. Почему?

val it = (1 to 100).toSet.subsets.grouped(1000000).map(_.par.find(f)).flatten
if (it.hasNext) Some(it.next) else None
  • Использует гораздо больше памяти. Я знаю, что это все еще O (1), но позвольте быть идеальным здесь:)

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

  • Производит более сложный/более длинный код (см. пример). Это немного сократило бы код, если Iterator имел .nextOption, но все же.

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

Ответ 1

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

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

Итераторы слишком низкоуровневые, чтобы распараллелить. Но вам фактически не нужен параллельный итератор, а параллельный обход итератора, вы можете Future.traverse из стандартной библиотеки.