El algoritmo de búsqueda en anchura (BFS) es una forma de explorar los vértices de un grafo capa por capa. Es un algoritmo básico en la teoría de grafos que puede utilizarse como parte de otros algoritmos de grafos. Por ejemplo, el algoritmo de Dinic utiliza BFS para encontrar el flujo máximo en un grafo. Además, BFS es también uno de los algoritmos de núcleo en el benchmark Graph500 , que es un benchmark para problemas de supercomputación intensivos en datos. [ 1 ] Este artículo analiza la posibilidad de acelerar BFS mediante el uso de computación paralela .
Búsqueda en amplitud serial
En el algoritmo BFS secuencial convencional, se crean dos estructuras de datos para almacenar la frontera y la siguiente frontera. La frontera contiene todos los vértices que tienen la misma distancia (también llamada "nivel") desde el vértice de origen; estos vértices deben explorarse en BFS. Se comprueba cada vecino de estos vértices; algunos de estos vecinos que aún no se han explorado se descubren y se añaden a la siguiente frontera. Al inicio del algoritmo BFS, un vértice de origen dado, s, es el único vértice en la frontera. Todos los vecinos directos de s se visitan en el primer paso, formando así la siguiente frontera. Tras cada recorrido de capa, la "siguiente frontera" se cambia a la frontera y los nuevos vértices se almacenan en la nueva siguiente frontera. El siguiente pseudocódigo describe la idea, donde las estructuras de datos para la frontera y la siguiente frontera se denominan FS y NS, respectivamente.
1 define bfs_sequential(graph(V,E), source s): 2 para todo v en V hacer 3 d[v] = -1; 4 d[s] = 0; nivel = 1; FS = {}; NS = {}; 5 empujes, FS); 6 mientras FS no esté vacío hacer 7 para u en FS hacer 8 para cada vecino v de u hacer 9 si d[v] = -1 entonces 10 push(v, NS); 11 d[v] = nivel; 12 FS = NS, NS = {}, nivel = nivel + 1;Primer paso de la paralelización
Como solución sencilla e intuitiva, el enfoque clásico de la Máquina de Acceso Aleatorio Paralelo (PRAM) es simplemente una extensión del algoritmo secuencial que se muestra arriba. Los dos bucles for (líneas 7 y 8) se pueden ejecutar en paralelo. La actualización de la siguiente frontera (línea 10) y el aumento de la distancia (línea 11) deben ser atómicos. Las operaciones atómicas son operaciones de programa que solo pueden ejecutarse completamente sin interrupciones ni pausas (es decir, "todo o nada").

Sin embargo, existen dos problemas en esta paralelización simple. En primer lugar, las operaciones de verificación de distancia (línea 9) y actualización de distancia (línea 11) introducen dos condiciones de carrera benignas. La razón de la condición de carrera es que un vecino de un vértice también puede ser vecino de otro vértice en la frontera. Como resultado, la distancia de este vecino puede ser examinada y actualizada más de una vez. Aunque estas condiciones de carrera desperdician recursos y generan una sobrecarga innecesaria, con la ayuda de la sincronización, no influyen en la corrección de BFS, por lo que estas condiciones de carrera son benignas. En segundo lugar, a pesar de la aceleración de cada recorrido de capa debido al procesamiento paralelo, se necesita una sincronización de barrera después de cada capa para descubrir completamente todos los vértices vecinos en la frontera. Esta sincronización capa por capa indica que los pasos de comunicación necesarios son iguales a la distancia más larga entre dos vértices, O(d) , donde O es la notación O grande y d es el diámetro del grafo .
La complejidad asintótica de esta sencilla paralelización es la misma que la del algoritmo secuencial en el peor de los casos. Se puede lograr una mejor paralelización de BFS con optimizaciones como las siguientes:
- Mitigación de la sincronización de barreras. La sincronización de barreras es necesaria después de cada recorrido de capa para garantizar la corrección. Reducir el costo de la sincronización de barreras es una forma eficaz de acelerar la búsqueda en amplitud paralela (BFS).
- Balanceo de carga para el descubrimiento de vecinos. Debido a la sincronización de barrera tras cada recorrido de capa, cada unidad de procesamiento debe esperar a que la anterior finalice su trabajo. Por lo tanto, la unidad de procesamiento con más vecinos determina el tiempo de procesamiento de esta capa. Mediante la optimización del balanceo de carga, se puede reducir el tiempo de recorrido de las capas.
- Mejorar la localidad de las referencias a memoria. En sistemas paralelos con memoria distribuida , las referencias a memoria remota acceden a datos de otras unidades de procesamiento, lo que suele generar un coste de comunicación adicional en comparación con el acceso a memoria local. Un diseño de estructura de datos más eficiente o una mejor organización de los datos pueden reducir la necesidad de acceso a memoria remota, disminuyendo así el coste total de comunicación.
BFS paralelo con memoria compartida
En comparación con BFS paralelo con memoria distribuida, la memoria compartida proporciona un mayor ancho de banda de memoria y una menor latencia debido al acceso directo de todos los procesadores. Por lo tanto, se evita la sobrecarga del paso de mensajes, que es necesaria para el acceso a memoria distribuida. [ 2 ]

Sin embargo, se ha demostrado que el número de vértices en cada capa y el número de vecinos de cada vértice son muy irregulares, lo que conlleva accesos a memoria y una distribución de trabajo en BFS también muy irregulares. En BFS paralelo, esta característica reduce los beneficios de la paralelización debido a la carga desequilibrada. Por lo tanto, es fundamental lograr un equilibrio de carga en BFS paralelo con memoria compartida. Además, explorar la localidad de los datos también puede acelerar la ejecución paralela.
Los algoritmos BFS paralelos con memoria compartida se pueden dividir en dos tipos: enfoques centrados en contenedores y enfoques centrados en vértices. [ 3 ] En el enfoque centrado en contenedores, se crean dos estructuras de datos para almacenar la frontera actual y la siguiente frontera de vértice. La siguiente frontera de vértice se cambia a la frontera actual al final de cada iteración. Existe una compensación entre el costo de la sincronización y la localidad de los datos según la ubicación de los datos. Estas dos estructuras de datos pueden mantenerse en cada unidad de procesamiento (como un hilo), lo que admite la localidad de los datos pero requiere mecanismos adicionales de equilibrio de carga. Alternativamente, pueden almacenarse globalmente para proporcionar un equilibrio de carga implícito, donde se utilizan estructuras de datos especiales para el acceso concurrente, lo que requiere un esfuerzo adicional para la sincronización.
Además, se puede optimizar la organización de datos de los contenedores. La estructura de datos típica en BFS serial y en algunos BFS paralelos es una cola FIFO , ya que es simple y rápida (las operaciones de inserción y eliminación tienen un coste de tiempo constante).
Otra alternativa es la estructura de bolsa. [ 4 ] La operación de inserción en una bolsa requiere un tiempo de O(logn) en el peor de los casos, mientras que solo requiere un tiempo amortizado constante , que es tan rápido como FIFO. Además, la unión de dos bolsas requiere un tiempo de Θ(lgn), donde n es el número de elementos en la bolsa más pequeña. La operación de división de bolsa también requiere un tiempo de Θ(lgn) . Con la ayuda de la estructura de bolsa, un cierto número de vértices (según el parámetro de granularidad) se almacenan en una bolsa y la estructura de bolsa se convierte en la entidad paralela básica. Además, el reductor se puede combinar con la estructura de bolsa para escribir vértices en paralelo y recorrerlos de manera eficiente.
El enfoque centrado en vértices trata a cada vértice como una entidad paralela, lo que permite la iteración paralela. Cada vértice se asigna a una entidad paralela. Este enfoque centrado en vértices solo funciona bien si la profundidad del grafo es muy baja. La profundidad del grafo en BFS se define como la distancia máxima desde cualquier vértice del grafo hasta el vértice de origen. Por lo tanto, el enfoque centrado en vértices es adecuado para GPU si cada hilo se asigna a un único vértice. [ 3 ]
BFS paralelo con memoria distribuida
En el modelo de memoria distribuida, cada unidad de procesamiento tiene su propia memoria. Por ello, las unidades de procesamiento deben comunicarse mediante el paso de mensajes para compartir sus datos locales y acceder a datos remotos.

Particionamiento 1-D
La partición 1D es la forma más sencilla de combinar la búsqueda en amplitud paralela con memoria distribuida. Se basa en la partición de vértices. El equilibrio de carga sigue siendo un aspecto importante para la partición de datos, ya que determina cómo podemos aprovechar la paralelización. En otras palabras, cada unidad de procesamiento con memoria distribuida debe encargarse de aproximadamente el mismo número de vértices y sus aristas salientes. Para la implementación del almacenamiento de datos, cada procesador puede almacenar una matriz de adyacencia de sus vértices locales, donde cada fila para cada vértice es una fila de aristas salientes representadas por los índices de los vértices de destino.
A diferencia de la búsqueda en anchura (BFS) con memoria compartida, el vértice vecino de una unidad de procesamiento puede almacenarse en otra. Por lo tanto, cada unidad de procesamiento es responsable de informar a las demás sobre el estado del recorrido mediante el envío de mensajes. Además, cada unidad de procesamiento debe gestionar los mensajes de todas las demás para construir su frontera local del siguiente vértice. Obviamente, es necesaria una comunicación bidireccional (es decir, cada unidad tiene mensajes diferentes para las demás) en cada paso al intercambiar la frontera actual y la del siguiente vértice.
El siguiente pseudocódigo de un BFS de memoria distribuida unidimensional [ 5 ] fue diseñado originalmente para sistemas IBM BlueGene/L , que cuentan con una arquitectura de red toroidal tridimensional . Dado que la sincronización representa el principal costo adicional para un BFS paralelizado, los autores de este artículo también desarrollaron una comunicación escalable de todo a todo basada en comunicaciones punto a punto . Posteriormente, redujeron el número de comunicaciones punto a punto, aprovechando el alto ancho de banda de su red toroidal.
Los pasos principales del recorrido BFS en el siguiente algoritmo son:
- Vista del procesador (línea 8): construir el sistema de archivos frontera con vértices del almacenamiento local.
- Vista global (líneas 10-11): finalice el recorrido si los sistemas de archivos de todos los procesadores están vacíos.
- Vista del procesador (línea 13): construye la siguiente frontera basándose en los vértices vecinos de su FS, aunque algunos de sus vecinos pueden estar almacenados en otros procesadores.
- Vista global (líneas 15-18): ejecute una comunicación de todos a todos para que cada procesador sepa qué vértices locales deben colocarse en su siguiente frontera local.
- Vista del procesador (líneas 20-22): recibe mensajes de todos los demás procesadores, actualiza el valor de distancia de sus vértices locales en la frontera actual, cambia su NS a FS.
1 define 1_D_distributed_memory_BFS( graph(V,E), source s): 2 //inicialización normal 3 para todos los v en V hacer 4 d[v] = -1; 5 d[s] = 0; nivel = 0; FS = {}; NS = {}; 6 //comienza el recorrido BFS 7 mientras sea verdadero hacer : 8 FS = {el conjunto de vértices locales con nivel} 9 //todos los vértices recorridos 10 si FS = {} para todos los procesadores entonces : 11. Finalizar el bucle while 12 // Construye la red de Nash basada en los vértices locales en la frontera actual. 13 NS = {vecinos de los vértices en FS, tanto vértices locales como no locales} 14 //sincronización: comunicación de todos a todos 15 para 0 <= j < p hacer : 16 N_j = {vértices en NS propiedad del procesador j} 17 enviar N_j al procesador j 18 reciben N_j_rcv del procesador j 19 //combina el mensaje recibido para formar la frontera del siguiente vértice local y luego actualiza el nivel para ellos. 20 NS_rcv = Unión(N_j_rcv) 21 para v en NS_rcv y d[v] == -1 hacer 22 d[v] = nivel + 1Combinado con el procesamiento multihilo, el siguiente pseudocódigo de BFS de memoria distribuida 1D también especifica la pila de hilos y la barrera de hilos, que proviene del artículo. [ 6 ]
Con el multihilo, los vértices locales en el sistema de archivos frontera se pueden dividir y asignar a diferentes hilos dentro de un mismo procesador, lo que paraleliza aún más el recorrido BFS. A diferencia de los métodos anteriores, se necesitan más estructuras de datos para cada hilo individual. Por ejemplo, la pila de hilos, que se prepara para guardar los vértices vecinos de los vértices de este hilo. Cada hilo tiene p-1 de almacenamiento local, donde p es el número de procesadores. Esto se debe a que cada hilo debe separar los mensajes para todos los demás procesadores. Por ejemplo, colocarán sus vértices vecinos en su j-ésima pila para formar el mensaje que enviarán al procesador j, si este es el propietario de dichos vértices. Además, la barrera de hilos también es necesaria para la sincronización. Como resultado, aunque la memoria distribuida con multihilo puede beneficiarse de una mejor paralelización, también introduce un coste de sincronización adicional para los hilos.
Los pasos principales del recorrido BFS en el siguiente algoritmo son:
- Vista de hilo (líneas 19-22): basándose en los vértices asignados a sí mismo, encuentra el procesador propietario de los vértices vecinos y colócalos en la pila de hilos en función de sus propietarios.
- Vista del procesador (línea 23): ejecute una barrera de subprocesos, espere hasta que todos los subprocesos (del mismo procesador) terminen su trabajo.
- Vista del procesador (líneas 25-26): fusionar todas las pilas de subprocesos de todos los subprocesos que tengan el mismo propietario (aquellos tienen el destino para el siguiente paso).
- Vista global (líneas 28-30): ejecute una comunicación de todos a todos con el hilo maestro para informar a cada procesador qué vértices locales deben colocarse en la siguiente frontera.
- Vista del procesador (línea 31): ejecutar una barrera de subprocesos, esperar hasta que finalice la comunicación (del subproceso maestro).
- Vista del procesador (línea 33): asigna vértices de la siguiente frontera a cada hilo.
- Vista de hilo (líneas 34–36): si el vértice no se visita, actualiza el valor de distancia para sus vértices y colócalo en la pila de hilos para la siguiente frontera NS.
- Vista del procesador (línea 37): ejecute una barrera de subprocesos, espere hasta que todos los subprocesos (del mismo procesador) terminen su trabajo.
- Vista del procesador (línea 39): agrega pilas de subprocesos para la siguiente frontera desde cada subproceso.
- Vista del procesador (línea 40): ejecute una barrera de subprocesos, espere hasta que todos los subprocesos envíen todos sus vértices en su pila.
1 define 1_D_distributed_memory_BFS_with_threads(graph(V,E), source s): 2 // inicialización normal 3 para todos los v en V hacer 4 d[v] = -1; 5 nivel = 1; FS = {}; NS = {}; 6 // encuentra el índice del procesador propietario del vértice de origen s 7 pu_s = encontrar_propietario(s); 8 si pu_s = index_pu entonces 9 empujes(s,FS); 10 d[s] = 0; 11 // inicialización del mensaje 12 para 0 <= j < p hacer 13 sendBuffer_j = {} // p búferes de mensajes compartidos 14 recvBuffer_j = {} // para comunicación MPI 15 thrdBuffer_i_j = {} //pila local del hilo para el hilo i 16 // comenzar el recorrido BFS 17 mientras FS != {} hacer 18 // recorrer vértices y encontrar propietarios de vértices vecinos 19 para cada u en FS en paralelo hacer 20 para cada vecino v de u hacer 21 pu_v = find_owner(v) 22 push(v, thrdBuffer_i_(pu_v)) 23 Barrera de subprocesos 24 // combinar la pila de subprocesos para formar sendBuffer 25 para 0 <= j < p hacer 26 fusionar thrdBuffer_i_j en paralelo 27 // comunicación de todos a todos 28 Paso colectivo de todos a todos con hilo maestro: 29 1. enviar datos en sendBuffer 30 2. Recibir y agregar los vértices recién visitados en recvBuffer 31 Barrera de hilos 32 // nivel de actualización para vértices recién visitados 33 para cada u en recvBuffer en paralelo hacer 34 si d[u] == -1 entonces 35 d[u] = nivel 36 push(u, NS_i) 37 Barrera de Hilo 38 // agregar NS y formar un nuevo FS 39 FS = Unión(NS_i) 40 Barrera de Hilo 41 nivel = nivel + 1fParticionamiento 2D
Dado que el algoritmo BFS siempre utiliza la matriz de adyacencia como representación del grafo, la descomposición bidimensional natural de la matriz también puede ser una opción a considerar. En la partición bidimensional, cada procesador tiene un índice bidimensional (i,j). Las aristas y los vértices se asignan a todos los procesadores mediante una descomposición en bloques bidimensional, donde se almacena la matriz de subadyacencia.
Si en total hay P=R·C procesadores, entonces la matriz de adyacencia se dividirá como se muestra a continuación:

Tras esta división, quedan C columnas y R·C filas de bloques. Cada procesador se encarga de C bloques; es decir, el procesador (i,j) almacena A i,j (1) a A i,j (C) bloques. La partición convencional unidimensional es equivalente a la partición bidimensional con R=1 o C=1.
En general, el procesamiento paralelo de bordes basado en particionamiento 2D se puede organizar en 2 fases de comunicación, que son la fase de "expansión" y la fase de "plegado". [ 6 ]
En la fase de "expansión", si la lista de aristas para un vértice dado es la columna de la matriz de adyacencia, entonces para cada vértice v en la frontera, el propietario de v es responsable de informar a los demás procesadores en su columna de procesador que v ha sido visitado. Esto se debe a que cada procesador solo almacena listas parciales de aristas de los vértices. Después de esta comunicación, cada procesador puede recorrer la columna según los vértices y encontrar sus vecinos para formar la siguiente frontera. [ 5 ]
En la fase de "plegado", los vértices de la siguiente frontera resultante se envían a sus procesadores propietarios para formar la nueva frontera localmente. Con la partición 2D, estos procesadores están en la misma fila de procesadores. [ 5 ]
Los pasos principales del recorrido BFS en este algoritmo de particionamiento 2D son (para cada procesador):
- fase de expansión (líneas 13-15): basándose en vértices locales, solo se envían mensajes a los procesadores en la columna de procesadores para indicarles que estos vértices están en la frontera, y se reciben mensajes de estos procesadores.
- (líneas 17-18): fusionar todos los mensajes recibidos y formar la frontera de red N. Nótese que no todos los vértices de los mensajes recibidos deben colocarse en la siguiente frontera, ya que algunos podrían haber sido visitados. La siguiente frontera solo contiene vértices con un valor de distancia de -1.
- fase de plegado (líneas 20-23): basándose en los vértices locales en la siguiente frontera, envíe mensajes a los procesadores propietarios de estos vértices en la fila del procesador.
- (líneas 25–28): fusionar todos los mensajes recibidos y actualizar el valor de distancia de los vértices en la siguiente frontera.
El pseudocódigo que aparece a continuación describe más detalles del algoritmo BFS 2D, que proviene del artículo: [ 5 ].
1 define 2_D_distributed_memory_BFS( graph(V,E), source s): 2 // inicialización normal 3 para todos los v en V hacer 4 d[v] = -1; 5 d[s] = 0; 6 // comienza el recorrido BFS 7 para l = 0 hasta infinito hacer : 8 F = {el conjunto de vértices locales con nivel l} 9 // todos los vértices recorridos 10 si F = {} para todos los procesadores entonces : 11. Finalizar el bucle while 12 // Recorre los vértices enviando un mensaje al procesador seleccionado 13 Para todos los procesadores q en esta columna de procesadores, haz lo siguiente : 14 Enviar F al procesador q 15 Reciba F q r de q 16 // procesar la información recibida después del recorrido de la frontera 17 F r = Unión{F q r } para todo q 18 N = {vecinos de los vértices en F r usando listas de aristas en este procesador} 19 // Transmite los vértices vecinos enviando un mensaje a su procesador propietario 20 para todos los procesadores q en esta fila de procesadores hacer : 21 N q = {vértices en N propiedad del procesador q} 22 Enviar N q al procesador q 23 Reciba N q r de q 24 // forma la siguiente frontera utilizada para el recorrido de la siguiente capa 25 N r = Unión{N q r } para todo q 26 // actualización de la distancia de la capa 27 para v en N r y d(v) = -1 hacer : Nivel 28 = l + 1En la partición 2D, solo las columnas o filas de procesadores participan en la comunicación durante las fases de "expansión" o "reducción", respectivamente. [ 5 ] Esta es la ventaja de la partición 2D sobre la partición 1D, ya que en esta última todos los procesadores participan en la comunicación entre todos. Además, la partición 2D ofrece mayor flexibilidad para un mejor equilibrio de carga, lo que facilita un enfoque más escalable y eficiente en cuanto al almacenamiento.
Implementación de estrategias de optimización
Además de los conceptos básicos de la búsqueda en amplitud paralela (BFS paralela), se pueden utilizar algunas estrategias de optimización para acelerar el algoritmo y mejorar su eficiencia. Ya existen varias optimizaciones para la BFS paralela, como la optimización direccional, el mecanismo de equilibrio de carga y la mejora de la estructura de datos, entre otras.
Optimización de la dirección
En el BFS descendente original, cada vértice examina todos los vecinos del vértice en la frontera. Esto a veces no es eficiente, cuando el grafo tiene un diámetro bajo . [ 7 ] Pero algunos vértices dentro tienen grados mucho más altos que el promedio, como un grafo de mundo pequeño . [ 8 ] Como se mencionó antes, una condición de carrera benigna en BFS paralelo es que, si más de un vértice en la frontera tiene vértices vecinos comunes, la distancia de los vértices vecinos se verificará muchas veces. Aunque la actualización de la distancia sigue siendo correcta con la ayuda de la sincronización, el recurso se desperdicia. De hecho, para encontrar los vértices para la siguiente frontera, cada vértice no visitado solo necesita verificar si alguno de sus vecinos está en la frontera. Esta es también la idea central para la optimización de la dirección. Mejor aún, cada vértice encontraría rápidamente un padre al verificar sus aristas entrantes si un número significativo de sus vecinos están en la frontera.
En el artículo [ 8 ] , los autores introducen una búsqueda en anchura (BFS) ascendente donde cada vértice solo necesita comprobar si alguno de sus padres se encuentra en la frontera. Esto se puede determinar de forma eficiente si la frontera se representa mediante un mapa de bits . En comparación con la BFS descendente, la BFS ascendente reduce la comprobación de fallos al autoexaminar el padre para evitar la contención.
Sin embargo, la búsqueda en anchura (BFS) ascendente requiere serializar el trabajo de un vértice y solo funciona mejor cuando una gran fracción de vértices se encuentra en la frontera. Por lo tanto, una BFS optimizada direccionalmente debería combinar la BFS descendente y la ascendente. En particular, la BFS debería comenzar con la dirección descendente y cambiar a la ascendente cuando el número de vértices supere un umbral determinado, y viceversa. [ 8 ]
Balance de carga
El balanceo de carga es fundamental no solo en la búsqueda en amplitud paralela (BFS paralela), sino en todos los algoritmos paralelos, ya que una distribución equilibrada del trabajo optimiza los beneficios de la paralelización. De hecho, prácticamente todos los diseñadores de algoritmos BFS paralelos deberían observar y analizar la partición del trabajo de su algoritmo e implementar un mecanismo de balanceo de carga.
La aleatorización es una forma útil y sencilla de lograr el equilibrio de carga. Por ejemplo, un grafo puede recorrerse reordenando aleatoriamente todos los identificadores de vértices antes de la partición. [ 6 ]
Estructura de datos



Existen algunas estructuras de datos especiales de las que puede beneficiarse la búsqueda en amplitud paralela (BFS), como CSR (Fila Dispersa Comprimida), estructura de bolsa, mapa de bits , etc.
En el CSR, todas las adyacencias de un vértice se ordenan y almacenan de forma compacta en un bloque contiguo de memoria, con la adyacencia del vértice i+1 junto a la adyacencia de i. En el ejemplo de la izquierda, hay dos matrices, C y R. La matriz C almacena las listas de adyacencia de todos los nodos. La matriz R almacena el índice en C; la entrada R[i] apunta al índice inicial de las listas de adyacencia del vértice i en la matriz C. El CSR es extremadamente rápido porque el acceso a la adyacencia de un vértice solo requiere un tiempo constante. Sin embargo, solo es eficiente en cuanto a espacio para particiones 1D. [ 6 ] Se puede encontrar más información sobre CSR en. [ 9 ] Para particiones 2D, DCSC (Columnas Dispersas Doblemente Comprimidas) para matrices hiperdispersas es más adecuado. [ 10 ]
En el artículo [ 4 ] , los autores desarrollan una nueva estructura de datos llamada bag-structure. Bag-structure se construye a partir de la estructura de datos pennant. Un pennant es un árbol de 2k nodos , donde k es un entero no negativo. Cada raíz x en este árbol contiene dos punteros, x.left y x.right, a sus hijos. La raíz del árbol tiene solo un hijo izquierdo, que es un árbol binario completo de los elementos restantes. [ 4 ]
La estructura de la bolsa es la colección de banderines con una matriz base S. Cada entrada S[i] en S es un puntero nulo o un puntero a un banderín de tamaño s i . La operación de inserción en una bolsa toma tiempo amortizado y la unión de dos bolsas toma tiempo . La división de la bolsa también lleva tiempo. Con esta estructura de bolsa, se permite que BFS paralelo escriba los vértices de una capa en una única estructura de datos en paralelo y luego los recorra eficientemente en paralelo. [ 4 ]
Además, el mapa de bits también es una estructura de datos muy útil para memorizar qué vértices ya se han visitado, independientemente de si se ha realizado una búsqueda en amplitud (BFS) de abajo hacia arriba. [ 11 ] o simplemente para comprobar si se han visitado vértices en la búsqueda en amplitud (BFS) de arriba hacia abajo. [ 9 ]
Puntos de referencia
Graph500 es el primer benchmark para problemas de supercomputación intensivos en datos. [ 1 ] Este benchmark genera inicialmente una tupla de aristas con dos extremos. A continuación, el kernel 1 construye un grafo no dirigido, en el que no se asigna peso a las aristas si solo se ejecuta el kernel 2 posteriormente. Los usuarios pueden optar por ejecutar BFS en el kernel 2 y/o Single-Source-Shortest-Path en el kernel 3 sobre el grafo construido. El resultado de estos kernels se verifica y se mide el tiempo de ejecución.
Graph500 también proporciona dos implementaciones de referencia para los kernels 2 y 3. En el BFS de referencia, la exploración de vértices consiste simplemente en enviar mensajes a los procesadores de destino para informarles de los vecinos visitados. No se utiliza ningún método adicional de balanceo de carga. Para la sincronización, la barrera AML (Active Messages Library, una biblioteca de comunicación SPMD basada en MPI3 , diseñada para aplicaciones de grano fino como Graph500) garantiza un recorrido consistente después de cada capa. El BFS de referencia se utiliza únicamente para verificar la corrección de los resultados. Por lo tanto, los usuarios deben implementar su propio algoritmo BFS en función de su hardware. La elección del BFS no está restringida, siempre que el árbol BFS de salida sea correcto.
La corrección del resultado se basa en la comparación con el resultado de la búsqueda en amplitud (BFS) de referencia. Dado que solo se muestrean 64 claves de búsqueda para ejecutar el kernel 2 y/o el kernel 3, el resultado también se considera correcto si difiere del resultado de referencia únicamente porque la clave de búsqueda no está incluida en las muestras. Estas 64 claves de búsqueda también ejecutan el kernel secuencialmente para calcular la media y la varianza, con las que se mide el rendimiento de una sola búsqueda.
A diferencia de TOP500 , la métrica de rendimiento en Graph500 es el número de aristas recorridas por segundo (TEPS).
Véase también
- Algoritmo distribuido : algoritmo que se ejecuta en hardware construido a partir de procesadores interconectados.
- Algoritmo paralelo : Algoritmo que puede realizar múltiples operaciones en un tiempo determinado.
Referencias
- 1 2 Gráfico500
- ↑ "Diseño de algoritmos multihilo para búsqueda en amplitud y conectividad st en el Cray MTA-2." , Bader, David A. y Kamesh Madduri. Conferencia Internacional de Procesamiento Paralelo de 2006 (ICPP'06). IEEE, 2006.
- 1 2 "Algoritmos de búsqueda en anchura paralelos síncronos por niveles para sistemas multinúcleo y multiprocesador." , Rudolf y Mathias Makulla. FC 14 (2014): 26-31.]
- 1 2 3 4 "Un algoritmo de búsqueda en amplitud paralelo eficiente en términos de trabajo (o cómo lidiar con el no determinismo de los reductores)." , Leiserson, Charles E. y Tao B. Schardl. Actas del vigésimo segundo simposio anual de la ACM sobre paralelismo en algoritmos y arquitecturas. ACM, 2010.
- 1 2 3 4 5 "Un algoritmo de búsqueda en anchura distribuido, paralelo y escalable en BlueGene/L." , Yoo, Andy, et al. Actas de la conferencia ACM/IEEE de 2005 sobre supercomputación. IEEE Computer Society, 2005.
- 1 2 3 4 "Búsqueda paralela en amplitud en sistemas de memoria distribuida." , Buluç, Aydin y Kamesh Madduri. Actas de la Conferencia Internacional de 2011 sobre Computación de Alto Rendimiento, Redes, Almacenamiento y Análisis. ACM, 2011.
- ↑ "Dinámica colectiva de redes de 'mundo pequeño'." , Watts, Duncan J. , y Steven H. Strogatz . nature 393.6684 (1998): 440.
- 1 2 3 "Búsqueda en amplitud optimizada por dirección." , Beamer, Scott, Krste Asanović y David Patterson . Scientific Programming 21.3-4 (2013): 137-148.
- 1 2 "Recorrido de grafos escalable en GPU" , Merrill, Duane, Michael Garland y Andrew Grimshaw. Acm Sigplan Notices. Vol. 47. Núm. 8. ACM, 2012.
- ↑ "Sobre la representación y multiplicación de matrices hiperdispersas." Buluc, Aydin y John R. Gilbert. Simposio Internacional IEEE de Procesamiento Paralelo y Distribuido de 2008. IEEE, 2008.
- ↑ "Búsqueda en amplitud con memoria distribuida en grafos masivos." Buluc, Aydin, et al. arXiv preprint arXiv:1705.04590 (2017).
- Algoritmos de grafos
- Computación paralela