Algorithm level

Difference between revisions of "DCSC algorithm for finding the strongly connected components"

From Algowiki
Jump to navigation Jump to search
[quality revision][checked revision]
 
(36 intermediate revisions by one other user not shown)
Line 14: Line 14:
 
=== General description of the algorithm ===
 
=== General description of the algorithm ===
  
'''Алгоритм DCSC'''<ref>Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.</ref><ref>McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.</ref><ref>Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC'13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.</ref> (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой <math>O(|V| \ln |V|)</math> (при условии ограниченной констатой степени вершин).  
+
'''Алгоритм DCSC'''<ref>Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.</ref><ref>McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.</ref><ref>Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC'13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.</ref> (Divide and Conquer Strong Components) finds [[Связность в графах|strongly connected components]] of a directed graph with the expected complexity <math>O(|V| \ln |V|)</math> (assuming that the node degrees are bounded by a constant).  
  
Так же алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. <ref> Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. </ref>
+
In the literature on GPU implementations of the algorithm, it is typically called the  Forward-Backward algorithm (shortly, the FB-algorithm). <ref> Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. </ref>
  
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге так же может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.
+
From the outset, the algorithm was designed for parallel realization. Indeed, at each step, it finds a strongly connected component and selects up to three subsets in the graph that contain other connected components and can be processed in parallel. Moreover, the selection of these subsets and the strongly connected component can be done in parallel (using parallel breadth-first searches). It should be noted that the iterations of breadth-first searches need not to be synchronized because their aim is to determine reachable nodes rather than to find the distances to these nodes.  
  
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма так же значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу.  
+
The algorithm is well suitable for graphs that consist of a small number of large strongly connected components. A significant increase in the number of strongly connected components implies that the complexity of the algorithm also increases significantly (proportionally to the number of components). For this reason, the algorithm may become less efficient than Tarjan's algorithm, which selects strongly connected components in the course of one graph traversal.  
  
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма, производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.
+
The following modification of the algorithm was proposed to improve its efficiency for graphs with a large number of trivial (that is, of size 1 or 2) strongly connected components: prior to executing the classical algorithm, the so-called Trim step is performed. This step, described in a later section, allows one to select all the trivial strongly connected components. For instance, for RMAT graphs, an application of the Trim step leaves in the graph only a few large strongly connected components. For such components, the complexity of the algorithm is not large.
  
 
=== Mathematical description of the algorithm ===
 
=== Mathematical description of the algorithm ===
  
Пусть <math>v</math> – некоторая вершина графа. Определим следующие множества вершин:
+
Let <math>v</math> be a node of the graph. Define the following sets of nodes:
  
⎯ <math>Fwd(v)</math>  – вершины, достижимые из <math>v</math> .
+
⎯ <math>Fwd(v)</math>  – the nodes reachable from <math>v</math> .
  
⎯ <math>Pred(v)</math> – вершины, из которых достижима <math>v</math> (эквивалентно – вершины, достижимые из <math>v</math> в графе, полученном из <math>G</math> обращением всех рёбер).
+
⎯ <math>Pred(v)</math> – the nodes from which <math>v</math> is reachable (equivalently, the nodes reachable from <math>v</math> in the graph obtained by reverting all the arcs in <math>G</math>).
  
Используя эти множества, разобьём все вершины графа на четыре области:
+
By using these sets, all the nodes of the graph can be partitioned into four regions:
  
 
⎯ <math>V_1 =  Fwd(v) \cap Pred(v) </math>
 
⎯ <math>V_1 =  Fwd(v) \cap Pred(v) </math>
Line 42: Line 42:
 
⎯ <math>V_4 =  V \setminus Pred(v) \setminus Fwd(v)</math>
 
⎯ <math>V_4 =  V \setminus Pred(v) \setminus Fwd(v)</math>
  
Тогда можно утверждать следующее:
+
Then one can assert the following:
  
1. Область <math>V_1</math> является компонентой сильной связности.
+
1. The region <math>V_1</math> is a strongly connected component.
  
2. Любая другая компонента сильной связности полностью содержится в одной из областей <math>V_2</math>, <math>V_3</math>, или <math>V_4</math>.
+
2. Every other strongly connected component is completely contained in one of the regions <math>V_2</math>, <math>V_3</math>, or <math>V_4</math>.
  
 
=== Computational kernel of the algorithm ===
 
=== Computational kernel of the algorithm ===
  
Основными вычислительными операциями алгоритма является поиск вершин, достижимых из выбранной вершины <math>v</math>, а так же поиск вершин, из которых достижима выбранная вершина <math>v</math>. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:
+
The basic computational operations of the algorithm are the search for nodes reachable from the chosen node <math>v</math> and the search for nodes from which <math>v</math> is reachable. Both these operations can be implemented by using breadth-first searches in the following manner:
  
1.   Вершина <math>v_0</math> помещается в начало в очереди и помечается ее как посещенная
+
1. The node <math>v_0</math> is placed at the beginning of queue and marked as a visited node. 
  
2.   Верхняя вершина <math>v</math> извлекается из очереди. Для всех ребер <math>(v, u)</math>, исходящих из вершины <math>v</math>, проверяется, является ли посещенной вершина  <math>u</math>. В случае, если является, вершина <math>u</math> помещается в начало очереди.
+
2. Remove the front node <math>v</math> from the queue. For all the arcs <math>(v, u)</math> outcoming from <math>v</math>, verify whether the node <math>u</math> was visited. If it was, then <math>u</math> is placed at the beginning of queue.
  
3.   Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.
+
3. As long as the queue is not empty, go to step 2.
  
 
=== Macro structure of the algorithm ===
 
=== Macro structure of the algorithm ===
  
Алгоритм DCSC состоит в следующем:
+
The DCSC algorithm consists of the following actions:
  
1. Поместить в очередь множество <math>V</math>.
+
1. Place the set <math>V</math> to a queue.
  
2. Параллельно обрабатывать очередь. Для каждого элемента очереди <math>V</math>:
+
2. Process the queue in parallel. For each element in <math>V</math>:
  
а) Выбрать произвольную ведущую вершину <math>v \in V</math>.
+
(a) Choose an arbitrary pivot node <math>v \in V</math>.
  
б) Вычислить множества <math>Fwd(v)</math>, <math>Pred(v)</math> (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).
+
(b) Compute the sets <math>Fwd(v)</math> and <math>Pred(v)</math> (these two computations can be executed in parallel; in addition, as indicated above, each of them is well parallelizable).
  
в) Добавить множество <math> V_1</math> в список компонент сильной связности.
+
(c) Add the set <math> V_1</math> to the list of strongly connected components.
  
г) Добавить множества <math> V_2</math>, <math>V_3</math> и <math>V_4</math> в очередь.
+
(d) Add the sets <math> V_2</math>, <math>V_3</math>, and <math>V_4</math> to the queue.
  
3. Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.
+
3. The algorithm terminates when the queue is empty and there are no active processes left.  
  
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.
+
To improve the balance of loading at the first steps of the algorithm, one can simultaneously choose several pivot nodes rather than a single node. If these pivots belong to different strongly connected components, then the result is a partition of the graph into numerous regions, which thereafter are processed in parallel.  
  
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:
+
An important modification of the DCSC algorithm consists in executing a Trim step before the basic calculations. The Trim step can be described as follows:
  
1.     Пометить все вершины из  <math>v \in V</math> активными.
+
1. Mark all <math>v \in V</math> as active nodes.
  
2.     Для каждой вершины <math>v</math>вычислить число входящих (<math>in(v)</math>) и исходящих (<math>out(v)</math>) дуг <math>(v, u) \in E</math>, таких, что вершина <math>u</math> - активная.
+
2. For each node <math>v</math>, calculate the number <math>in(v)</math> of incoming arcs and the number <math>out(v)</math> of outcoming arcs <math>(v, u) \in E</math> such that <math>u</math> is an active node.
  
3.   Все вершины <math>v \in V</math> , для которых <math>in(v)</math> или <math>out(v)</math> равно нулю, пометить как неактивные.
+
3. Mark as nonactive all the nodes <math>v \in V</math> for which at least one of the numbers <math>in(v)</math> and <math>out(v)</math> is zero.
  
4.   Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.
+
4. Go to step 2 until the number of active nodes ceases to very.
  
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина <math>v</math> в вычислительном ядре алгоритма.
+
Moreover, the chosen scheme for storing the graph may require that the transpose of this graph be first constructed. This may make more efficient the implementation of the Trim step and the search (within the computational kernel of the algorithm) for nodes from which the given node <math>v</math> is reachable.
  
 
=== Implementation scheme of the serial algorithm ===
 
=== Implementation scheme of the serial algorithm ===
  
Последовательный алгоритм реализуется следующим псевдокодом:
+
The serial algorithm is implemented by the following pseudocode:
  
  '''Входные данные''':
+
  '''Input data''':
   граф с вершинами ''V'', рёбрами ''E'';
+
   graph with nodes ''V'' and arcs ''E'';
  '''Выходные данные''': номера компонент сильной связанности ''c(v)'' до каждой вершины ''v'' ∈ ''V''.
+
  '''Output data''': indices of the strongly connected components ''c(v)'' for each node ''v'' ∈ ''V''.
  
 
<source lang="C++">
 
<source lang="C++">
Line 146: Line 146:
 
</source>
 
</source>
  
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины <math> p </math> в графе <math> G </math>
+
Here, bfs are conventional breadth-first searches described in the corresponding section. Such a search results in finding all the nodes of the graph <math> G </math> that are reachable from the given node <math> p </math>.
  
 
=== Serial complexity of the algorithm ===
 
=== Serial complexity of the algorithm ===
  
Ожидаемая последовательная сложность алгоритма составляет <math>O(|V| \ln(|V|))</math> при условии, что степень вершин ограничена сверху константой.
+
Assume that all the nodes of a graph have degrees bounded by a constant. Then the expected serial complexity of the algorithm is <math>O(|V| \ln(|V|))</math>.
  
 
=== Information graph ===
 
=== Information graph ===
  
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основным частями.
+
The information graph of the DCSC algorithm shown in figure 1 demonstrates relations between the basic parts of the algorithm.
  
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]
+
[[file:DCSC_full_ig.png|thumb|center|500px|Figure 1. Information graph of the DCSC algorithm (Forward-Backward-Trim).]]
  
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработке результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а так же рекурсивных вызовах FB-step. Далее будут приведены более подробные информационные графы для каждой из частей, а так же дана подробная расшифровка соотвесвующих операций, а в следующем разделе и оценим ресурс параллелизма для каждой части алгоритма.  
+
These basic parts are: initial Trim step; choice of the pivot node [1]; breadth-first searches performed in the direct graph and its transpose (forward и backward BFS); handling the results of searches [2]; test for the necessity to continue the recursion [3]; and recursive calls of the FB-step. Below, we present more detailed information graphs for each of the parts, as well as the transcript of the corresponding operations. In the next section, we assess the parallelization resource of each part.  
  
На рисунке 2 представлен информационный граф шага Trim.  
+
The information graph of the Trim step is shown in figure 2.  
  
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]
+
[[file:DCSC_trim_step.png|thumb|center|500px|Figure 2. Information graph of the Trim step in the DCSC algorithm.]]
  
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а так же активных вершин
+
[1] - memory allocation for the arrays storing the numbers of incoming and outcoming arcs and the array of active nodes
  
[2] - инциализация переменной наличия изменений
+
[2] - initialization of the variable "change indicator"
  
[3] - инициализация массива сильно связанных компонент, а так же массива активных вершин.
+
[3] - initialization of the array for strongly connected components and the array of active nodes
  
[4] - установка переменной наличия изменений в позицию "нет изменений"
+
[4] - assignment of the value "no changes" to the change indicator
  
[5] - обнуление массивов числа входящих и исходящих дуг
+
[5] - zeroing out the arrays for the numbers of incoming and outcoming arcs
  
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменно наличия изменений
+
[6] - verification whether the nodes at the ends of each arc are active; increasing the number of incoming/outcoming arcs for the corresponding nodes; update the value of the change indicator
  
[7] - проверка активности всех вершин графа
+
[7] - activity test for all the nodes of the graph
  
[8] - присваивание номеров сильно связанных компонент с нулевой степенью входящих или исходящих вершин
+
[8] - numbering of the strongly connected components with zero degree of incoming or outcoming arcs
  
[9] - проверка, происходи ли ли изменения на шаге 6, переход к следующей итерации
+
[9] - verification whether any changes occurred at step 6; transition to the next iteration
  
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]).
+
The information graph of the BFS stage (Forward и Backward BFS) is shown at the corresponding section ([[Breadth_first_search_(BFS)]]).
  
 
=== Parallelization resource of the algorithm ===
 
=== Parallelization resource of the algorithm ===
  
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма.  
+
From the outset, the algorithm was designed for parallel realization. It has several levels of parallelism.  
  
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до <math> 2*u/log|u|</math> параллельных потоков, где <math> u </math> - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.
+
At the upper level, the DCSC algorithm is able to execute in parallel the FB-steps shown in figure 1. Thus, at each step, the algorithm can recursively generate up to three new parallel processes; their total number is equal to the number of strongly connected components. Moreover, the breadth-first searches in the original graph and its transpose are independent and can also be performed in parallel. Thus, up to
 +
<math> 2*u/log|u|</math> parallel processes can be executed at the upper level, where <math> u </math> is the number of strongly connected components in the graph. It is important to note that the FB-steps of the same level (as well as the subsequent recursive steps) are completely independent in terms of data because they concern disjoint sets of graph nodes. Consequently, they can even be performed on different cores. Moreover, the composite steps of the algorithm  (the breadth-first search and the Trim step) also have a considerable parallelization potential, which is described below.  
  
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет <math>O(|V|)</math>. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно <math>O(E)</math>. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет <math>|V|</math> или <math>|E|</math>. Учитывая, что величины <math>|V|</math> и <math>|E|</math> для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы.  
+
The Trim stage, whose information graph is shown in figure 2, has a considerable parallelization resource: the only serial steps are the initializations of arrays and variables [(1),(2)] and test (9) for loop termination. Operations (3),(5),(7), and (8) are completely independent and can be performed in parallel. The number of these operations is <math>O(|V|)</math>. Tests and data updatings in operation (6) can also be executed in parallel; however, the data updatings must be done in the atomic manner. The number of operations in (6) is <math>O(E)</math>. Thus, almost all steps of the Trim stage can be performed in the parallel mode, and the number of operations executed in parallel is  <math>|V|</math> or <math>|E|</math>. For large graphs, the values <math>|V|</math> and <math>|E|</math> are fairly large. Hence, the parallelization potential of this stage in the algorithm is very significant; it is sufficient for the full loading of modern computational cores/coprocessors. In addition, operations (3),(5),(7), and (8) can be successfully vectorized.  
  
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае).  
+
The parallelization resource of the breadth-first search is described in detail in the corresponding section ([[Breadth_first_search_(BFS)]]). Depending on the choice of a format for storing graphs, the parallel implementation of the breadth-first search may have linear or (in the worst case) quadratic complexity.  
  
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться <math>O(n)</math> параллельных операций, где <math>n</math> - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет <math> r </math>, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет <math> O(|V|)/r </math>. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.
+
In the case of linear parallelization, the breadth-first search performs at each case <math>O(n)</math> parallel operations, where <math>n</math> is the number of nodes added for inspection at the preceding step. Prior to the start of the search, the source node is placed for inspection; at the first step, all the not yet visited nodes adjacent to the source node are added; at the second step, all the not yet visited nodes adjacent to the nodes of the first step are added; etc. It is difficult to estimate the maximum (or even average) number of nodes at each step because the number of steps in the algorithm and the number of reachable nodes, as well as the pattern of their relations, are only determined by the structure of the input graph. If all the nodes are reachable and the number of steps in the breadth-first search is <math> r </math>, then the average number of nodes processed at each step in parallel is <math> O(|V|)/r </math>. It is important to note that the maximum level of parallelism is attained at the middle steps of the algorithm, whereas the number of nodes processed at the initial and final steps may be small.
  
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя <math>O(|E|)</math> параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.
+
In the case of quadratic parallelization, the breadth-first search traverses at each step all the arcs of the graph, thus performing <math>O(|E|)</math> parallel operations. This is identical to the similar estimate for the trim step. Moreover, if the implementation uses lists of graph nodes, then the breadth-first search can be vectorized.
  
Таким образом, на начальном этапе алгоритма может выполняться <math>O(|V|)</math> или <math>O(|E|)</math> параллельных операций. Далее следует <math>O(log(u))</math> шагов, в каждом из которых в среднем присутствует <math>2*u/O(log(u))</math> поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет <math>O(|E|)</math> параллельных операций на каждом шаге при квадратическом подходе к параллелизации.
+
Thus, at the initial stage, the algorithm can perform <math>O(|V|)</math> or <math>O(|E|)</math> parallel operations. Then <math>O(log(u))</math> steps are carried out; on the average, each step involves <math>2*u/O(log(u))</math> breadth-first searches, which also have an inherent parallelization resource. Namely, for a quadratic implementation, each breadth-first search performs at each step <math>O(|E|)</math> parallel operations.  
  
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.
+
The height and width of the parallel form depend on the structure of graph (that is, on the number and location of strongly connected components) because the FB steps depend in a similar way on the input data.
  
 
=== Input and output data of the algorithm ===
 
=== Input and output data of the algorithm ===
  
'''Входные данные''': граф <math>G(V, E)</math>, <math>|V|</math> вершин <math>v_i</math> и <math>|E|</math> рёбер <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math>.
+
'''Input data''': graph <math>G(V, E)</math> with <math>|V|</math> nodes <math>v_i</math> and <math>|E|</math> arcs <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math>.
  
'''Объём входных данных''': <math>O(|V| + |E|)</math>.
+
'''Size of the input data''': <math>O(|V| + |E|)</math>.
  
'''Выходные данные''': для каждой вершины <math>v</math> исходного графа – номер компоненты сильной связанности, в которую входит данная вершина <math> c(v) </math>, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера
+
'''Output data''': for each node <math>v</math> of the original graph, the index
 +
<math> c(v) </math> of the strongly connected component to which this node belongs. Nodes belonging to the same strongly connected component produce identical indices.
  
'''Объём выходных данных''': <math>O(|V|)</math>.
+
'''Size of the output data''': <math>O(|V|)</math>.
  
 
=== Properties of the algorithm ===
 
=== Properties of the algorithm ===
  
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.
+
The computational kernel of the algorithm is based on breadth-first searches; consequently, many of the properties (locality, scalability) of the former and latter algorithms are similar.
  
 
== Software implementation of the algorithm ==
 
== Software implementation of the algorithm ==
Line 220: Line 222:
 
=== Implementation peculiarities of the serial algorithm ===
 
=== Implementation peculiarities of the serial algorithm ===
  
=== Locality of data and computations ===
 
==== Locality of implementation ====
 
===== Structure of memory access and a qualitative estimation of locality =====
 
===== Quantitative estimation of locality =====
 
 
=== Possible methods and considerations for parallel implementation of the algorithm ===
 
=== Possible methods and considerations for parallel implementation of the algorithm ===
  
=== Scalability of the algorithm and its implementations ===
+
=== Run results ===
==== Scalability of the algorithm ====
 
 
 
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).
 
 
 
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.
 
 
 
==== Scalability of of the algorithm implementation ====
 
 
 
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере "Ломоносов-2 Суперкомпьютерного комплекса Московского университета.
 
 
 
Набор и границы значений изменяемых параметров запуска реализации алгоритма:
 
 
 
число процессоров [1 : 28] с шагом 1;
 
размер графа [2^20 : 2^27].
 
 
 
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.
 
 
 
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.
 
 
 
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]
 
 
 
=== Dynamic characteristics and efficiency of the algorithm implementation ===
 
 
=== Conclusions for different classes of computer architecture ===
 
=== Conclusions for different classes of computer architecture ===
=== Existing implementations of the algorithm ===
 
 
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция <code>[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]</code>), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].
 
  
 
== References ==
 
== References ==

Latest revision as of 15:11, 6 July 2022


Алгоритм DCSC поиска компонент сильной связности
Sequential algorithm
Serial complexity [math]O(|V| \ln(|V|))[/math]
Input data [math]O(|V| + |E|)[/math]
Output data [math]O(|V|)[/math]
Parallel algorithm
Parallel form height [math]C * O(ln(u))[/math]
Parallel form width [math]O(|E|)[/math]


Primary author of this description: I.V.Afanasyev.

1 Properties and structure of the algorithm

1.1 General description of the algorithm

Алгоритм DCSC[1][2][3] (Divide and Conquer Strong Components) finds strongly connected components of a directed graph with the expected complexity [math]O(|V| \ln |V|)[/math] (assuming that the node degrees are bounded by a constant).

In the literature on GPU implementations of the algorithm, it is typically called the Forward-Backward algorithm (shortly, the FB-algorithm). [4]

From the outset, the algorithm was designed for parallel realization. Indeed, at each step, it finds a strongly connected component and selects up to three subsets in the graph that contain other connected components and can be processed in parallel. Moreover, the selection of these subsets and the strongly connected component can be done in parallel (using parallel breadth-first searches). It should be noted that the iterations of breadth-first searches need not to be synchronized because their aim is to determine reachable nodes rather than to find the distances to these nodes.

The algorithm is well suitable for graphs that consist of a small number of large strongly connected components. A significant increase in the number of strongly connected components implies that the complexity of the algorithm also increases significantly (proportionally to the number of components). For this reason, the algorithm may become less efficient than Tarjan's algorithm, which selects strongly connected components in the course of one graph traversal.

The following modification of the algorithm was proposed to improve its efficiency for graphs with a large number of trivial (that is, of size 1 or 2) strongly connected components: prior to executing the classical algorithm, the so-called Trim step is performed. This step, described in a later section, allows one to select all the trivial strongly connected components. For instance, for RMAT graphs, an application of the Trim step leaves in the graph only a few large strongly connected components. For such components, the complexity of the algorithm is not large.

1.2 Mathematical description of the algorithm

Let [math]v[/math] be a node of the graph. Define the following sets of nodes:

[math]Fwd(v)[/math] – the nodes reachable from [math]v[/math] .

[math]Pred(v)[/math] – the nodes from which [math]v[/math] is reachable (equivalently, the nodes reachable from [math]v[/math] in the graph obtained by reverting all the arcs in [math]G[/math]).

By using these sets, all the nodes of the graph can be partitioned into four regions:

[math]V_1 = Fwd(v) \cap Pred(v) [/math]

[math]V_2 = Fwd(v) \setminus Pred(v) [/math]

[math]V_3 = Pred(v) \setminus Fwd(v)[/math]

[math]V_4 = V \setminus Pred(v) \setminus Fwd(v)[/math]

Then one can assert the following:

1. The region [math]V_1[/math] is a strongly connected component.

2. Every other strongly connected component is completely contained in one of the regions [math]V_2[/math], [math]V_3[/math], or [math]V_4[/math].

1.3 Computational kernel of the algorithm

The basic computational operations of the algorithm are the search for nodes reachable from the chosen node [math]v[/math] and the search for nodes from which [math]v[/math] is reachable. Both these operations can be implemented by using breadth-first searches in the following manner:

1. The node [math]v_0[/math] is placed at the beginning of queue and marked as a visited node.

2. Remove the front node [math]v[/math] from the queue. For all the arcs [math](v, u)[/math] outcoming from [math]v[/math], verify whether the node [math]u[/math] was visited. If it was, then [math]u[/math] is placed at the beginning of queue.

3. As long as the queue is not empty, go to step 2.

1.4 Macro structure of the algorithm

The DCSC algorithm consists of the following actions:

1. Place the set [math]V[/math] to a queue.

2. Process the queue in parallel. For each element in [math]V[/math]:

(a) Choose an arbitrary pivot node [math]v \in V[/math].

(b) Compute the sets [math]Fwd(v)[/math] and [math]Pred(v)[/math] (these two computations can be executed in parallel; in addition, as indicated above, each of them is well parallelizable).

(c) Add the set [math] V_1[/math] to the list of strongly connected components.

(d) Add the sets [math] V_2[/math], [math]V_3[/math], and [math]V_4[/math] to the queue.

3. The algorithm terminates when the queue is empty and there are no active processes left.

To improve the balance of loading at the first steps of the algorithm, one can simultaneously choose several pivot nodes rather than a single node. If these pivots belong to different strongly connected components, then the result is a partition of the graph into numerous regions, which thereafter are processed in parallel.

An important modification of the DCSC algorithm consists in executing a Trim step before the basic calculations. The Trim step can be described as follows:

1. Mark all [math]v \in V[/math] as active nodes.

2. For each node [math]v[/math], calculate the number [math]in(v)[/math] of incoming arcs and the number [math]out(v)[/math] of outcoming arcs [math](v, u) \in E[/math] such that [math]u[/math] is an active node.

3. Mark as nonactive all the nodes [math]v \in V[/math] for which at least one of the numbers [math]in(v)[/math] and [math]out(v)[/math] is zero.

4. Go to step 2 until the number of active nodes ceases to very.

Moreover, the chosen scheme for storing the graph may require that the transpose of this graph be first constructed. This may make more efficient the implementation of the Trim step and the search (within the computational kernel of the algorithm) for nodes from which the given node [math]v[/math] is reachable.

1.5 Implementation scheme of the serial algorithm

The serial algorithm is implemented by the following pseudocode:

Input data:
  graph with nodes V and arcs E;
Output data: indices of the strongly connected components c(v) for each node vV.
compute_scc_with_trim(G)
{
	G = transpose(G)
	G = trim(G)
	compute_scc()
}

compute_scc(V, G, G)
{
	p = pivot(V)

	fwd = bfs(G, p);
	pred = bfs(G, p);

	compute_scc(fwd / pred, G, G)
	compute_scc(pred / fwd, G, G)
	compute_scc(V / pred / fwd, G, G)
}

pivot(V)
{
	return random v in V
}

trim(G)
{
	for each v in V(G)
		active[v] = true

	changes = false
	do
	{
		for each v in V
			in_deg(v) = compute_in_degree(v)
			out_deg(v) = compute_out_degree(v)
		
		for each v in V
			if (in_deg[v] == 0 || out_deg[v] == 0)
				active[v] = false
				changes = true
	} while (!changes)
	return active
}

Here, bfs are conventional breadth-first searches described in the corresponding section. Such a search results in finding all the nodes of the graph [math] G [/math] that are reachable from the given node [math] p [/math].

1.6 Serial complexity of the algorithm

Assume that all the nodes of a graph have degrees bounded by a constant. Then the expected serial complexity of the algorithm is [math]O(|V| \ln(|V|))[/math].

1.7 Information graph

The information graph of the DCSC algorithm shown in figure 1 demonstrates relations between the basic parts of the algorithm.

Figure 1. Information graph of the DCSC algorithm (Forward-Backward-Trim).

These basic parts are: initial Trim step; choice of the pivot node [1]; breadth-first searches performed in the direct graph and its transpose (forward и backward BFS); handling the results of searches [2]; test for the necessity to continue the recursion [3]; and recursive calls of the FB-step. Below, we present more detailed information graphs for each of the parts, as well as the transcript of the corresponding operations. In the next section, we assess the parallelization resource of each part.

The information graph of the Trim step is shown in figure 2.

Figure 2. Information graph of the Trim step in the DCSC algorithm.

[1] - memory allocation for the arrays storing the numbers of incoming and outcoming arcs and the array of active nodes

[2] - initialization of the variable "change indicator"

[3] - initialization of the array for strongly connected components and the array of active nodes

[4] - assignment of the value "no changes" to the change indicator

[5] - zeroing out the arrays for the numbers of incoming and outcoming arcs

[6] - verification whether the nodes at the ends of each arc are active; increasing the number of incoming/outcoming arcs for the corresponding nodes; update the value of the change indicator

[7] - activity test for all the nodes of the graph

[8] - numbering of the strongly connected components with zero degree of incoming or outcoming arcs

[9] - verification whether any changes occurred at step 6; transition to the next iteration

The information graph of the BFS stage (Forward и Backward BFS) is shown at the corresponding section (Breadth_first_search_(BFS)).

1.8 Parallelization resource of the algorithm

From the outset, the algorithm was designed for parallel realization. It has several levels of parallelism.

At the upper level, the DCSC algorithm is able to execute in parallel the FB-steps shown in figure 1. Thus, at each step, the algorithm can recursively generate up to three new parallel processes; their total number is equal to the number of strongly connected components. Moreover, the breadth-first searches in the original graph and its transpose are independent and can also be performed in parallel. Thus, up to [math] 2*u/log|u|[/math] parallel processes can be executed at the upper level, where [math] u [/math] is the number of strongly connected components in the graph. It is important to note that the FB-steps of the same level (as well as the subsequent recursive steps) are completely independent in terms of data because they concern disjoint sets of graph nodes. Consequently, they can even be performed on different cores. Moreover, the composite steps of the algorithm (the breadth-first search and the Trim step) also have a considerable parallelization potential, which is described below.

The Trim stage, whose information graph is shown in figure 2, has a considerable parallelization resource: the only serial steps are the initializations of arrays and variables [(1),(2)] and test (9) for loop termination. Operations (3),(5),(7), and (8) are completely independent and can be performed in parallel. The number of these operations is [math]O(|V|)[/math]. Tests and data updatings in operation (6) can also be executed in parallel; however, the data updatings must be done in the atomic manner. The number of operations in (6) is [math]O(E)[/math]. Thus, almost all steps of the Trim stage can be performed in the parallel mode, and the number of operations executed in parallel is [math]|V|[/math] or [math]|E|[/math]. For large graphs, the values [math]|V|[/math] and [math]|E|[/math] are fairly large. Hence, the parallelization potential of this stage in the algorithm is very significant; it is sufficient for the full loading of modern computational cores/coprocessors. In addition, operations (3),(5),(7), and (8) can be successfully vectorized.

The parallelization resource of the breadth-first search is described in detail in the corresponding section (Breadth_first_search_(BFS)). Depending on the choice of a format for storing graphs, the parallel implementation of the breadth-first search may have linear or (in the worst case) quadratic complexity.

In the case of linear parallelization, the breadth-first search performs at each case [math]O(n)[/math] parallel operations, where [math]n[/math] is the number of nodes added for inspection at the preceding step. Prior to the start of the search, the source node is placed for inspection; at the first step, all the not yet visited nodes adjacent to the source node are added; at the second step, all the not yet visited nodes adjacent to the nodes of the first step are added; etc. It is difficult to estimate the maximum (or even average) number of nodes at each step because the number of steps in the algorithm and the number of reachable nodes, as well as the pattern of their relations, are only determined by the structure of the input graph. If all the nodes are reachable and the number of steps in the breadth-first search is [math] r [/math], then the average number of nodes processed at each step in parallel is [math] O(|V|)/r [/math]. It is important to note that the maximum level of parallelism is attained at the middle steps of the algorithm, whereas the number of nodes processed at the initial and final steps may be small.

In the case of quadratic parallelization, the breadth-first search traverses at each step all the arcs of the graph, thus performing [math]O(|E|)[/math] parallel operations. This is identical to the similar estimate for the trim step. Moreover, if the implementation uses lists of graph nodes, then the breadth-first search can be vectorized.

Thus, at the initial stage, the algorithm can perform [math]O(|V|)[/math] or [math]O(|E|)[/math] parallel operations. Then [math]O(log(u))[/math] steps are carried out; on the average, each step involves [math]2*u/O(log(u))[/math] breadth-first searches, which also have an inherent parallelization resource. Namely, for a quadratic implementation, each breadth-first search performs at each step [math]O(|E|)[/math] parallel operations.

The height and width of the parallel form depend on the structure of graph (that is, on the number and location of strongly connected components) because the FB steps depend in a similar way on the input data.

1.9 Input and output data of the algorithm

Input data: graph [math]G(V, E)[/math] with [math]|V|[/math] nodes [math]v_i[/math] and [math]|E|[/math] arcs [math]e_j = (v^{(1)}_{j}, v^{(2)}_{j})[/math].

Size of the input data: [math]O(|V| + |E|)[/math].

Output data: for each node [math]v[/math] of the original graph, the index [math] c(v) [/math] of the strongly connected component to which this node belongs. Nodes belonging to the same strongly connected component produce identical indices.

Size of the output data: [math]O(|V|)[/math].

1.10 Properties of the algorithm

The computational kernel of the algorithm is based on breadth-first searches; consequently, many of the properties (locality, scalability) of the former and latter algorithms are similar.

2 Software implementation of the algorithm

2.1 Implementation peculiarities of the serial algorithm

2.2 Possible methods and considerations for parallel implementation of the algorithm

2.3 Run results

2.4 Conclusions for different classes of computer architecture

3 References

  1. Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.
  2. McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.
  3. Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC'13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.
  4. Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic.