Gpseq

  • Что такое Gpseq?

  • Ссылки

Что такое Gpseq?

Gpseq - это библиотека предоставляющая параллелизм для Vala и GObject.

Она содержит следующий функционал:

  • Work-stealing и managed blocking планировщик задач аналогичные планировщику Go.

  • Обработка данных в стиле функциональной парадигмы с поддержкой параллельного выполнения: эквивалент потоков Java

  • Fork-Join параллелизм

  • Параллельная сортировка

  • 64-битные атомарные операции

  • Переполнение безопасных арифметических функций для целых чисел со знаком

  • ...

Ссылки

  • Еще не зарегистрирован на Valadoc.org

  • Локальная версия Valadoc

  • (TODO) pgi-docs (Python API)

  • (TODO) gjs-docs (JavaScript API)

Futures и promises

Содержание

  • Future.of()

  • Future.err()

  • wait() and wait_until()

  • value

  • map()

  • flat_map()

  • light_map()

  • map_err()

  • then()

  • and_then()

  • zip()

  • transform()

  • Цепочка фьючерсов

  • Зачем использовать Gpseq.Future вместо Gee.Future?

Библиотека Gpseq предоставляет два вида объектов Future и Promise.

Future - это read-only значение, которое становиться доступным в определенный момент. Значение (или исключение) обычно устанавливается с помощью Promise.

метод main() и using Gpseq;опущены

Пример 1. Futures и promises

Вывод: 123

Вывод: Oops!

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

Примеры методов

Future.of ()

Future.of () - это вспомогательный метод, который возвращает Future, заполненный заданным значением.

Вывод: 123

Future.err ()

Future.err () - это вспомогательный метод, который возвращает Future, заполненный заданным исключением.

wait() и wait_until()

Пример 2. wait()

Пример 3. wait_until()

value

Получает значение Future

Если значение не ещё готово, "получающая" значение сторона будет находиться в заблокированном состоянии, пока значение не будет вычислено. Если Future завершается исключением, получение значения вызовет GLib.error.

Вывод: ERROR: Oops!

map()

map - применяет функцию к каждомы елементу коллекции:

flat_map()

Работает как map, но с одним отличием — можно преобразовать один элемент в ноль, один или множество других.

Для того, чтобы один элемент преобразовать в ноль элементов, нужно вернуть null, либо пустой стрим. Чтобы преобразовать в один элемент, нужно вернуть Future из одного элемента, например, через Future.of(x). Для возвращения нескольких элементов, можно любыми способами создать Future с этими элементами.

light_map()

light_map() - это облегченная версия map(). Данная функция может быть вычислена повторно в любое время.

Пример 4. map () против light_map ()

Вывод:

map_err()

then()

Функция then() отложено запускает переданную в качестве аргумента функцию (принимающую future) - после вычисления текущего future, результатом чего может является значение или исключение.

Примечание автора перевода: Если по человечески -- С помощью функции then() можно создать цепочку ленивых вычислений, то есть поставить их в очередь, получая гарантию что следующие будут вычислены только после предыдущих.

Вывод:

exception .vala

Вывод:

and_then()

Функция and_then() отложено запускает переданную в качестве аргумента функцию (принимающую future) - после вычисления текущего future, результатом чего может является значение, а не исключение .

value.vala

output:

exception.vala

output: (none)

zip()

zip() объединяет значения двух фьючерсов в один.

Простой пример: zip [1,2,3] [3,2,1] - соеденяет списки [(1,3),(2,2),(3,1)]

output: 123

transform()

transform() создает новый future применяя переданную в качестве аргумента функцию к этому future, после того как этот future будет вычислен.

Все остальные mapping функции — map(), flat_map(), then(), etc. — кроме light_map() реализованы с помощью transform().

Вывод: 123

Цепочка фьючерсов

Вы можете поместить mapping методы в цепочку.

вывод: Hello 123!

Вывод:

Зачем использовать Gpseq.Future, вместо Gee.Future?

Libgee уже предоставляет фьючерсы и промисы. Однако с некоторыми функциями есть проблемы, а некоторых не хватает.

  • Gee версия переопределяет исключения с помощью FutureError.EXCEPTION в map функциях — map(), flat_map(), и zip(). FutureError.EXCEPTION вместо реального исключения не несет никакой информации и нежелателен. libgee#23

  • Gee.Future не поддерживает цепочки вычислений фьючерсов. map() on an already completed future doesn’t return and blocks the program. libgee#31

  • Exceptions can’t be thrown in the map functions, and there is no way to map exceptions. libgee#22 libgee#24

Code refactoring may be required to solve the problems, and it will break backward compatibility of libgee. Instead of waiting for the problems to be fixed, I chose to implement Gpseq’s own Future.

Compare Gpseq.Future with Gee.Future to see the difference.

Seq

Seq это последовательность элементов, поддерживающая паралельные и последовательные операции. Эквивалент Java’s streams.

Seq pipelines

Seq поддерживает различные методы обработки данных. В Seq есть 2 типа операций: промежуточных и терминальных. Seq последовательность может состоять из нуля и более промежуточных операций и терминальной операции.

Последовательность вызовов (ориг. A seq pipeline )

Промежуточные Операции

Промежуточные операции всегда выполняются лениво. Это означает что вычисление seq не начнется пока не будет выполен хотя бы один терминальный оператор.

Промежуточные операции также делятся на те у которых есть состояния и те у которых их нет(stateless и stateful ). Операции без сохранения состояния не сохраняют состояние предыдущего элемента при обработке нового элемента. Поэтому каждый элемент может обрабатываться независимо от других элементов. В операциях с отслеживанием состояния(stateful) учитываются состояния предыдущих операций над элементами при обработке новых элементов, из-за чего обработка может быть выполнена только последовотельн(Например вычисление чисел Фибоначчи).

Следовательно, конвейеры seq, содержащие промежуточные stateful операции, могут потребовать нескольких проходов или буферизации важных промежуточных вычислений. Напротив, конвейеры seq, содержащие только stateless операции, могут обрабатываться за один проход.

Stateless операции:

filter, flat_map, map, parallel, sequential, etc.

Stateful операции:

chop, chop_ordered, distinct, limit, limit_ordered, order_by, skip, skip_ordered, etc.

Терминальные операции

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

Терминальные операции:

foreach, all_match, any_match, collect, collect_ordered, count, find_any, find_first, fold, group_by, iterator, max, min, none_match, partition, reduce, spliterator, etc.

Укороченные (Short-circuiting) операции

Промежуточные укороченные операции могут привести к тому что seq итерирующий по бесконечному источнику(например генератору четных чисел) приведет к результату за конечное время .

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

Short-circuiting операции:

all_match, any_match, chop, chop_ordered, find_any, find_first, limit, limit_ordered, none_match, etc.

Parallelism

При запуске терминальной операции конвейер seq выполняется последовательно или параллельно в зависимости от режима seq, в котором он вызывается. Последовательный режим является режимом по умолчанию. Он может быть изменен с помощью промежуточных операций sequential() или parallel().

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

Возвращаемый результат любой терминальной операции уже является вычисленным, поэтому вам не нужно ждать future для получения результата

Примеры методов

Main method, using Gpseq;, and using Gee; omitted.

foreach()

output:

filter()

Промежуточный оператор filter отбирает только те строки, длина которых не превышает 3.

output:

map()

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

output:

flat_map()

Работает как map, но с одним отличием — можно преобразовать один элемент в ноль, один или множество других.

Для того, чтобы один элемент преобразовать в ноль элементов, нужно вернуть null, либо пустой стрим. Чтобы преобразовать в один элемент, нужно вернуть стрим из одного элемента, например, через Seq.of(x). Для возвращения нескольких элементов, можно любыми способами создать стрим с этими элементами.

output:

skip(), limit(), and chop()

Example 1. skip()

output:

Example 2. limit()

output:

Example 3. chop()

output:

skip_ordered(), limit_ordered(), and chop_ordered()

They are the ordered version of skip/limit/chop. They always respect encounter order regardless of sequential/parallel execution. They are, however, quite expensive on parallel execution.

order_by()

output:

distinct()

output:

all_match() and any_match()

You should read the documentation of Future first.Example 4. all_match()

output:

Example 5. any_match()

output:

(!) is the explicit non-null cast operator in Vala. (Currently not necessary, except when using --enable-experimental-non-null option)

count()

output:

find_any() and find_first()

Example 6. find_any()

output:

Example 7. find_first()

output:

Example 8. find_any() vs find_first() on parallel execution

max() and min()

output:

Seq.iterate()

Each element is generated by the given next function applied to the previous element, and the initial element is the seed.Example 9. For loop

output:

reduce() and fold()

Example 10. reduce()

output:

Example 11. fold()

output:

group_by()

output:

partition()

output:

iterator()

output:

spliterator()

Spliterator is an object for traversing and partitioning elements of a data source, used in Seq.

You should read the documentation of Spliterator first.

output:

collect() and collect_ordered()

See the section Collectors.

Both perform a mutable reduction operation on the elements of a seq.collect()

collect() performs a concurrent reduction if the seq is in parallel mode and the collector is CONCURRENT.collect_ordered()

collect_ordered() preserves encounter order even though the seq is in parallel mode, if the collector is not CONCURRENT or not UNORDERED. If, however, the seq is in parallel mode and the collector is CONCURRENT and UNORDERED, it performs an unordered concurrent reduction.

Error handling

Most seq operations take delegates which can throw errors. If an error occurs in the delegate, the returned future is completed with the error.Example 12. Error handling

output:

Collectors

Collector is an object for mutable reduction operation that accumulates input elements into a mutable accumulator, and transforms it into a final result. It is used with collect() or collect_ordered().

Gpseq.Collectors namespace provides various useful collector implementations. Here are some examples:

Main method, using Gpseq;, using Gpseq.Collectors;, and using Gee; omitted.Example 13. Collectors.to_list()

output:

Example 14. Collectors.to_generic_array()

output:

GenericArray (Vala) == GPtrArray (C-lang)

Example 15. Collectors.sum_int()

output:

The arithmetic wraps around on overflow; e.g. the sum of int.MAX and 1 ⇒ int.MIN

Example 16. Collectors.count()

output:

Example 17. Collectors.join()with_default_delimiter.vala

output:

delimiter_specified.vala

output:

Example 18. Collectors.partition_with()

partition_with.vala

output:

Seq.partition(pred) is equivalent to collect( partition<G>(pred) ). And partition<G>(pred) is equivalent to partition_with<Gee.List<G>,G>(pred, to_list<G>()).

Example 19. Collectors.wrap()

wrap() takes a collector and returns a collector will produce a Wrapper object containing the result of the given collector.

output:

Example 20. Collectors.tee()

tee.vala

output:

Custom collectors

A collector implements four methods: create_accumulator, accumulate, combine, and finish.

  1. create_accumulator() - Creates a new accumulator, such as Gee.Collection.

  2. accumulate() - Incorporates a new element into a accumulator.

  3. combine() - Combines two accumulators into one.

  4. finish() - Transforms the accumulator into a final result.

The methods must satisfy an identity and an associativity constraints. The identity constraint means that combining any accumulator with an empty accumulator must produce an equivalent result. i.e. an accumulator a must be equivalent to collector.accumulate(a, collector.create_accumulator()).

The associativity constraint means that splitting the computation must produce an equivalent result. i.e.:Associativity constraint

Collectors also have a property, features. it provides hints that can be used to optimize the operation.

Here is the collector implementation of Collectors.to_collection<G>():CollectionCollector.vala

Worker pools

TODO

References

  • More detailed description about seq pipelines can be found on the Seq documentations: Valadoc, GtkDoc

Last updated

Was this helpful?