|
| 1 | +--- |
| 2 | +layout: overview-large |
| 3 | +title: Архитектура библиотеки параллельных коллекций |
| 4 | + |
| 5 | +disqus: true |
| 6 | + |
| 7 | +partof: parallel-collections |
| 8 | +language: ru |
| 9 | +num: 5 |
| 10 | +--- |
| 11 | + |
| 12 | +Как и обычная библиотека коллекций Scala, библиотека параллельных коллекций содержит большое количество операций, для которых, в свою очередь, существует множество различных реализаций. И так же, как последовательная, параллельная библиотека избегает повторений кода путем реализации большинства операций посредством собственных "шаблонов", которые достаточно объявить один раз, а потом наследовать в различных реализациях параллельных коллекций. |
| 13 | + |
| 14 | +Преимущества этого подхода сильно облегчают **поддержку** и **расширяемость**. Поддержка станет простой и надежной, когда одна реализация операции над параллельной коллекцией унаследуется всеми параллельными коллекциями; исправления ошибок в этом случае сами распространятся вниз по иерархии классов, а не потребуют дублировать реализации. По тем же причинам всю библиотеку проще расширять-- новые классы коллекций наследуют большинство имеющихся операций. |
| 15 | + |
| 16 | + |
| 17 | +## Ключевые абстракции |
| 18 | + |
| 19 | +Упомянутые выше "шаблонные" трейты реализуют большинство параллельных операций в терминах двух ключевых абстракций -- разделителей (`Splitter`) и компоновщиков (`Combiner`). |
| 20 | + |
| 21 | +### Разделители |
| 22 | + |
| 23 | +Задача разделителя `Splitter`, как и предполагает имя, заключается в том, чтобы разбить параллельную коллекцию на непустые разделы. А основная идея-- в том, чтобы разбивать коллекцию на более мелкие части, пока их размер не станет подходящим для последовательной обработки. |
| 24 | + |
| 25 | + trait Splitter[T] extends Iterator[T] { |
| 26 | + def split: Seq[Splitter[T]] |
| 27 | + } |
| 28 | + |
| 29 | +Что интересно, разделители `Splitter` реализованы через итераторы-- `Iterator`, а это подразумевает, что помимо разделения, они позволяют перебирать элементы параллельной коллекции (то есть, наследуют стандартные методы трейта `Iterator`, такие, как `next` и `hasNext`.) Уникальность этого "разделяющего итератора" в том, что его метод `split` разбивает текущий объект `this` (мы помним, что `Splitter`, это подтип `Iterator`а) на другие разделители `Splitter`, каждый из которых перебирает свой, **отделенный** набор элементов когда-то целой параллельной коллекции. И так же, как любой нормальный `Iterator`, `Splitter` становится недействительным после того, как вызван его метод `split`. |
| 30 | + |
| 31 | +Как правило, коллекции разделяются `Splitter`ами на части примерно одинакового размера. В случаях, когда требуются разделы произвольного размера, особенно в параллельных последовательностях, используется `PreciseSplitter`, который является наследником `Splitter` и дополнительно реализует точный метод разделения, `psplit`. |
| 32 | + |
| 33 | +### Компоновщики |
| 34 | + |
| 35 | +Компоновщик `Combiner` можно представить себе как обобщенный `Builder` из библиотеки последовательных коллекций Scala. У каждой параллельной коллекции есть свой отдельный `Combiner`, так же, как у каждой последовательной есть свой `Builder`. |
| 36 | + |
| 37 | +Если в случае с последовательными коллекциями элементы можно добавлять в `Builder`, а потом получить коллекцию, вызвав метод `result`, то при работе с параллельными требуется вызвать у компоновщика метод `combine`, который берет аргументом другой компоновщик и делает новый `Combiner`. Результатом будет компоновщик, содержащий объединенный набор. После вызова метода `combine` оба компоновщика становятся недействительными. |
| 38 | + |
| 39 | + trait Combiner[Elem, To] extends Builder[Elem, To] { |
| 40 | + def combine(other: Combiner[Elem, To]): Combiner[Elem, To] |
| 41 | + } |
| 42 | + |
| 43 | +Два параметра-типа в примере выше, `Elem` и `To`, просто обозначают тип элемента и тип результирующей коллекции соответственно. |
| 44 | + |
| 45 | +_Примечание:_ Если есть два `Combiner`а, `c1` и `c2` где `c1 eq c2` равняется `true` (то есть, они являются одним и тем же `Combiner`ом), вызов `c1.combine(c2)` всегда ничего не делает, а просто возвращает исходный `Combiner`, то есть `c1`. |
| 46 | + |
| 47 | + |
| 48 | +## Иерархия |
| 49 | + |
| 50 | +Параллельные коллекции Scala во многом созданы под влиянием дизайна библиотеки (последовательных) коллекций Scala. На рисунке ниже показано, что их дизайн фактически отражает соответствующие трейты фреймворка обычных коллекций. |
| 51 | + |
| 52 | +[<img src="{{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png" width="550">]({{ site.baseurl }}/resources/images/parallel-collections-hierarchy.png) |
| 53 | + |
| 54 | +<center><b>Иерархия библиотеки Scala: коллекции и параллельные коллекции</b></center> |
| 55 | +<br/> |
| 56 | + |
| 57 | +Цель, конечно же, в том, чтобы интегрировать параллельные коллекции с последовательными настолько тесно, насколько это возможно, так, чтобы можно было без дополнительных усилий заменять последовательные коллекции параллельными (и наоборот). |
| 58 | + |
| 59 | +Чтобы можно было получить ссылку на коллекцию, которая может быть либо последовательной, либо параллельной (так, чтобы было возможно "переключаться" между параллельной и последовательной коллекции вызовами `par` и `seq` соответственно), у обоих типов коллекций должен быть общий предок. Этим источником "обобщенных" трейтов, как показано выше, являются `GenTraversable`, `GenIterable`, `GenSeq`, `GenMap` и `GenSet`, которые не гарантируют того, что элементы будут обрабатываться по-порядку или по-одному. Отсюда наследуются соответствующие последовательные и параллельные трейты; например, `ParSeq` и `Seq` являются подтипами общей последовательности `GenSeq`, а не унаследованы друг от друга. |
| 60 | + |
| 61 | +Более подробное обсуждение иерархии, разделяемой последовательными и параллельными коллекциями, можно найти в техническом отчете. \[[1][1]\] |
| 62 | + |
| 63 | + |
| 64 | +## Ссылки |
| 65 | + |
| 66 | +1. [On a Generic Parallel Collection Framework, Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011][1] |
| 67 | + |
| 68 | +[1]: http://infoscience.epfl.ch/record/165523/files/techrep.pdf "flawed-benchmark" |
0 commit comments