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#23Gee.Future не поддерживает цепочки вычислений фьючерсов.
map()
on an already completed future doesn’t return and blocks the program. libgee#31
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 последовательность может состоять из нуля и более промежуточных операций и терминальной операции.
Промежуточные Операции
Промежуточные операции всегда выполняются лениво. Это означает что вычисление 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.
create_accumulator() - Creates a new accumulator, such as Gee.Collection.
accumulate() - Incorporates a new element into a accumulator.
combine() - Combines two accumulators into one.
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
Last updated