Departamento de Arquitectura de Computadores y Automática

Anuncio
Departamento de Arquitectura de Computadores y
Automática
Universidad Complutense de Madrid
Gestión Eficiente de Transacciones en
Máquinas de Búsqueda para la Web
Carolina Bonacic Castro
Madrid, España
Resumen
Las máquinas de búsqueda para la Web son sistemas diseñados para alcanzar un rendimiento eficiente frente a situaciones de tráfico de consultas muy intenso y dinámico. Este
objetivo se consigue a través de composiciones de estrategias de indexación distribuida
y caching, y procesamiento paralelo de consultas, las cuales son desplegadas en grandes
clusters de nodos procesadores. Los clusters forman sistemas de memoria distribuida entre nodos, y cada nodo es un sistema de memoria compartida que permite la ejecución
concurrente de muchos threads.
El foco de atención en investigación ha estado centrado sólo en los sistemas de memoria
distribuida. El problema de cómo administrar eficientemente los threads en cada nodo no
ha sido mencionado en la literatura del área. Para máquinas de búsqueda convencionales,
donde la solución a consultas de usuarios genera operaciones de sólo lectura, este problema no es relevante puesto que cualquier estrategia estándar de gestión de threads puede
alcanzar un rendimiento razonablemente eficiente.
Sin embargo, el problema de la sincronización eficiente de threads lectores y escritores
que acceden concurrentemente a la memoria compartida del nodo, se ha vuelto relevante
debido a que las máquinas de búsqueda han comenzado a permitir que sus contenidos
sean actualizados de manera on-line. Este trabajo de tesis se ha dedicado al estudio de
este problema en el contexto de nodos con procesadores multi-core.
Se proponen estrategias de procesamiento de transacciones de lectura y escritura para nodos de máquinas de búsqueda. Las estrategias desarrolladas realizan paralelismo
sincrónico a nivel de threads como una alternativa más eficiente al enfoque convencional
basado en concurrencia asincrónica y sincronización vı́a locks. Las ideas propuestas son
validadas tanto con experimentación en base a implementaciones reales como con modelos
de simulación discreta.
Índice general
1. Introducción
1
1.1. Organización de la Tesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2. Estado del Arte
5
8
2.1. Clusters de Servicios de Indice . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.2. Índices Invertidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.1. Distribución por documentos . . . . . . . . . . . . . . . . . . . . . . 11
2.2.2. Distribución por términos . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.3. Diferencias entre ambas estrategias . . . . . . . . . . . . . . . . . . . 12
2.3. Ranking de documentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4. Trabajo Relacionado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.1. Índices invertidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.2. Compresión de ı́ndices invertidos . . . . . . . . . . . . . . . . . . . . 16
2.5. Caches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6. Arquitectura de una máquina de búsqueda . . . . . . . . . . . . . . . . . . . 20
2.7. Procesamiento Round-Robin de Consultas . . . . . . . . . . . . . . . . . . . 23
3. Paralelismo Hı́brido
26
3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1
3.2. Estrategia Hı́brida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3. Experimentos sobre procesadores Intel . . . . . . . . . . . . . . . . . . . . . 32
3.3.1. Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.4. Experimentos sobre procesadores Niagara . . . . . . . . . . . . . . . . . . . 34
3.4.1. Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.4.2. Aritmética de punto fijo: Impacto en el rendimiento y validación . . 40
3.5. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4. Transacciones Concurrentes
44
4.1. Planteamiento del Problema . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.2. Solución Propuesta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.2.1. Query Solver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.2.2. Algoritmo Bulk Processing (BP Local) . . . . . . . . . . . . . . . . . 51
4.2.3. Alzas bruscas en el tráfico de consultas
. . . . . . . . . . . . . . . . 53
4.3. Un Caso Especial: BP global . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.4. Sobre la Administración de los Caches . . . . . . . . . . . . . . . . . . . . . 58
4.4.1. Paralelización con Nt threads . . . . . . . . . . . . . . . . . . . . . . 61
4.5. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5. Comparación de Estrategias
64
5.1. Estrategias alternativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.2. Evaluación utilizando un Servicio de Indice . . . . . . . . . . . . . . . . . . 68
5.2.1. Sobre el tamaño de bloque
. . . . . . . . . . . . . . . . . . . . . . . 70
5.2.2. Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.3. Evaluación utilizando un Servicio de Click-Through . . . . . . . . . . . . . . 76
5.3.1. Diseño de los experimentos . . . . . . . . . . . . . . . . . . . . . . . 78
2
5.3.2. Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.3.3. Resultados para la Cola de Prioridad . . . . . . . . . . . . . . . . . . 81
5.4. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6. Análisis de Escalabilidad
6.1. El modelo Multi-BSP
86
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
6.2. Análisis Caso Promedio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
6.3. Simulador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.4. Simulaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.4.1. Configuración de simuladores . . . . . . . . . . . . . . . . . . . . . . 100
6.4.2. Protocolo optimista de timestamps . . . . . . . . . . . . . . . . . . . 105
6.4.3. Resultados para Intel y Niagara . . . . . . . . . . . . . . . . . . . . . 106
6.5. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
7. Conclusiones Finales
113
A. Arquitecturas y Modelos
116
A.1. Arquitecturas Multi-Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
A.1.1. Procesador UltraSPARC T1 . . . . . . . . . . . . . . . . . . . . . . . 116
A.1.2. Procesador Intel Xeon Quad-Core . . . . . . . . . . . . . . . . . . . 118
A.2. Modelos de computación paralela . . . . . . . . . . . . . . . . . . . . . . . . 118
A.2.1. Bulk-Synchronous Parallel Model (BSP) . . . . . . . . . . . . . . . . 118
A.2.2. Multi-BSP
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
A.2.3. MPI y OpenMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
B. Estrategias Alternativas
125
3
Índice de figuras
2.1. Estructura de un ı́ndice invertido. . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2. Estrategia de Distribución por Documento. . . . . . . . . . . . . . . . . . . 12
2.3. Estrategia de Distribución por Término. . . . . . . . . . . . . . . . . . . . . 13
2.4. Enfoque base del procesamiento de una consulta. . . . . . . . . . . . . . . . 22
2.5. Arquitectura para el proceso de una consulta.
. . . . . . . . . . . . . . . . 23
2.6. Procesamiento round-robin sincrónico para siete consultas.
. . . . . . . . . 25
3.1. Speed-up obtenidos en dos nodos para tráfico alto y moderado de consultas. 35
3.2. Benchmark sintético que intenta imitar el tipo de cómputo realizado en el
proceso de ranking utilizando aritmética de punto flotante y punto fijo.
. . 36
3.3. Rendimiento para el modo Sync. . . . . . . . . . . . . . . . . . . . . . . . . 37
3.4. Tiempo promedio (ms) por consulta utilizando el modo Async. . . . . . . . 39
3.5. Rendimiento para los modos Async y Sync con tráfico alto de consultas. . . 39
3.6. Throughput para diferentes representaciones numéricas: punto flotante (columna gris) y punto fijo (columna negra). . . . . . . . . . . . . . . . . . . . . . 40
4.1. Arquitectura de un Nodo de Búsqueda. . . . . . . . . . . . . . . . . . . . . 45
4.2. Camino seguido por las consultas.
. . . . . . . . . . . . . . . . . . . . . . . 47
4
4.3. Organización del ı́ndice invertido donde cada lista invertida es almacenada
en un número de bloques y cada bloque se encuentra lógicamente dividido
en trozos que son asignados a cada threads. Cada trozo está compuesto por
un número de ı́temes de la lista invertida, (doc id, freq). En este ejemplo,
el primer bloque el término term 0 es procesado en paralelo por los threads
th0, th1, th2 and th3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.4. Inserción/Actualización de documentos. . . . . . . . . . . . . . . . . . . . . 51
4.5. Pasos ejecutados por cada thread en paralelo con los otros Nt − 1 threads.
52
4.6. Sincronización relajada ejecutada por cada thread con identificador my tid.
54
4.7. Pasos ejecutados por cada thread en paralelo con los otros Nt − 1 threads.
57
4.8. Cola de prioridad para la administración de caches.
. . . . . . . . . . . . . 59
5.1. (a) Largo de listas invertidas ordenadas de mayor a menor largo, y largos
mostrados cada 50 listas (Web UK). (b) Distribución de número de términos
en los documentos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.2. (a) Frecuencia de ocurrencia de los términos del log de consultas en los
documentos de la base de texto. (b) Distribución del número de términos
en las consultas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
5.3. Throughput alcanzado por las diferentes estrategias con distinto tamaño de
bloque.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.4. Throughput (transacciones por segundo) alcanzado por las estrategias para
diferentes tasas de escritura y consultas OR, y un total de 40 mil transacciones.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.5. Throughput (transacciones por segundo) alcanzado por las estrategias para
diferentes tasas de escritura y consultas AND, y un total de 40 mil transacciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.6. Throughput alcanzado por las estrategias para diferentes tasas de escritura
y consultas OR y AND, y un total de 40 mil transacciones. Trazas con alto
grado de coincidencia entre términos de transacciones consecutivas.
5.7. Tiempo en segundos para transacciones de lectura/escritura.
5
. . . . 75
. . . . . . . . 76
5.8. Estructuras de datos y secuencia de operaciones. La secuencia (1), (2) y
(3) indica que a partir de un término dado (1) es posible llegar a un nuevo
término (2), que a su vez conduce a un nuevo conjunto de URLs (3), los
cuales se incluirán en el proceso de ranking. Para cada ı́tem (URL, freq) del
ı́ndice invertido, esta secuencia se repite para cada elemento de la lista del
segundo ı́ndice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.9. Escalabilidad de las diferentes estrategias para Workload B y suponiendo
que los usuarios hacen un click (gráfico izquierdo) en un enlace en cada
búsqueda o hacen clicks en 5 enlaces (gráfico derecho), los cuales se transforman en 5 transacciones de escritura.
. . . . . . . . . . . . . . . . . . . . 80
5.10. Escalabilidad de las diferentes estrategias para Workload A (gráfico izquierdo) y Workload B (gráfico derecho) para 8 threads y varios clicks indicados
en el eje x.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.11. Tiempo individual de transacciones. Parte superior se muestran los resultados de ejecución del tiempo promedio para 1, 2, 4 y 8 threads. En la parte
inferior se muestra el tiempo máximo observado cada 1000 transacciones
procesadas para 8 threads.
. . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.1. Ejemplo de arquitectura multi-core según Multi-BSP. . . . . . . . . . . . . . 89
6.2. Una de las co-rutinas (concurrent routines) simulando los pasos seguidos
por un thread para procesar una consulta y generando costo en el tiempo
de simulación.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.3. Pasos principales ejecutados durante la simulación de una operación relevante de las transacciones. Las co-rutinas del simulador, las cuales simulan
los threads del procesador, ejecutan esta función run(...) a medida que ejecutan los pasos asociados al procesamiento de cada transacción según la
lógica de las estrategias BP, RTLP o TLP2.
. . . . . . . . . . . . . . . . . 98
6.4. Simulación de locks de acceso exclusivo. . . . . . . . . . . . . . . . . . . . . 99
6.5. Simulación de barrera de sincronización.
. . . . . . . . . . . . . . . . . . . 100
6.6. Throughput alcanzado por las estrategias para diferentes tasas de escritura
y consultas OR, y un total de 40 mil transacciones en un procesador Intel i7. 101
6
6.7. (a) Valores observados en las operaciones de locks y barreras. (b) Razón
entre el tiempo de acceso a los caches L2 y L1 en función del tamaño de los
datos y la disperción de los accesos.
. . . . . . . . . . . . . . . . . . . . . . 103
6.8. Valores promedio para g1 y g2 en unidades de 10−9 segundos. . . . . . . . . 105
6.9. Throughputs que el modelo de simulación predice para las estrategias BP,
RTLP y RTLP con Rollbacks (RTLP-RB), en el procesador Intel.
. . . . . 107
6.10. Tiempos de respuesta de transacciones recolectados a intervalos regulares
del tiempo de simulación para 8 threads. . . . . . . . . . . . . . . . . . . . . 108
6.11. Tiempos totales de ejecución normalizados a 1 para un caso en que se simula
granularidad fina haciendo que el costo del ranking de documentos sea 100
veces menor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
6.12. Throughputs que el modelo de simulación predice para las estrategias BP,
RTLP, RTLP-RB y BP-Global (BPG), en el procesador Niagara T1. . . . . 111
A.1. Ejemplo de arquitectura dual-core. . . . . . . . . . . . . . . . . . . . . . . . 117
A.2. Modelo BSP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
A.3. Modelo Multi-BSP para 8 núcleos. . . . . . . . . . . . . . . . . . . . . . . . 121
B.1. Pseudo-Código CR. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
B.2. Pseudo-Código TLP1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
B.3. Pseudo-Código TLP2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
B.4. Pseudo-Código RTLP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
B.5. Pseudo-Código RBLP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7
Índice de cuadros
3.1. Resultados para P procesos MPI con T threads cada uno para el caso en
que todos los núcleos se ocupan para resolver las consultas activas. . . . . . 34
5.1. Estrategias de sincronización de transacciones. . . . . . . . . . . . . . . . . 65
5.2. Resultados para la Cola de Prioridad. . . . . . . . . . . . . . . . . . . . . . 83
6.1. Razones de la pérdida de rendimiento de las estrategias RTLP-RB y RTLP. 108
6.2. Total de aciertos (cache hits) en los caches L1 y L2. Valores en millones de
aciertos cada 10 mil transacciones. . . . . . . . . . . . . . . . . . . . . . . . 109
A.1. Caracterı́sticas del procesador SUN UltraSPARC-T1. . . . . . . . . . . . . . 117
A.2. Caracterı́sticas principales del procesador Intel Quad-Xeon. . . . . . . . . . 118
8
Capı́tulo 1
Introducción
A principios de los años noventa se propuso el modelo BSP de computación paralela [100] como una metodologı́a de paralelización de problemas clásicos en computación
cientı́fica de alto rendimiento. Entre las principales ventajas declaradas para BSP estaban la simplicidad y la portabilidad del software, y la existencia de un modelo de costo
que permitı́a hacer el diseño e ingenierı́a de algoritmos paralelos de manera sencilla y
precisa. Durante esa década, otros investigadores fueron mostrando que BSP también era
capaz de abordar eficientemente la paralelización de otras aplicaciones de Ciencia de la
Computación, y problemas fundamentales en algoritmos y estructuras de datos discretas.
El objetivo fue proponer soluciones escalables a sistemas de gran tamaño, y por lo tanto
la atención estuvo centrada en sistemas de memoria distribuida.
La contribución del modelo BSP al estado del arte de la época no fue proponer una
forma de computación paralela completamente nueva. En realidad, ese tipo de paralelismo
ya existı́a informalmente en numerosas soluciones a problemas variados (e.g., [29]). La
contribución de BSP fue su formalización en un modelo de computación paralela completo, sobre el cual el diseñador de algoritmos y software puede desarrollar soluciones sin
necesidad de recurrir a otros métodos de paralelización o explotar caracterı́sticas del hardware tales como la topologı́a de interconexión de procesadores. Posteriormente, el modelo
pareció desaparecer.
Sin embargo, las ideas detrás de BSP — procesamiento sincrónico por lotes — han
prevalecido puesto que actualmente se les ve claramente presentes en sistemas de amplia
difusión para procesamiento paralelo en grandes clusters de memoria distribuida. Ellos
son Map-Reduce y Hadoop los cuales, aunque son formas restringidas de BSP, mantienen
1
la caracterı́stica principal de BSP, es decir, su simplicidad, donde un aspecto clave en la
concreción de esa simplicidad es que el modelo no aleja radicalmente al diseñador de lo
que es una solución secuencial intuitiva al mismo problema.
Lo anterior a nivel macroscópico, es decir, sistemas de memoria distribuida, lo que
para la aplicación de interés en esta tesis toma la forma de clusters de nodos procesadores
que se comunican entre sı́ mediante una red de interconexión. A nivel microscópico, la
tendencia actual indica que dichos nodos están formados por un grupo de procesadores
multi-core que comparten la misma memoria principal. Dichos procesadores permiten la
ejecución eficiente de muchos threads en el nodo, donde modelos de programación tales
como OpenMP están especialmente diseñados para explotar esta caracterı́stica. OpenMP
ha alcanzado gran difusión en los últimos años en aplicaciones de cómputo cientı́fico.
Curiosamente, una de las formas que promueve OpenMP para organizar la computación realizada por los threads también tiene mucha similitud con BSP. Tal es ası́ que
se propuso recientemente una extensión de BSP para procesadores multi-core (Multi-BSP
[84, 101]). La extensión desde el modelo para memoria distribuida es casi directa puesto
que los procesadores multi-core están construidos en base a una jerarquı́a de memorias.
Esto se puede ver como una distancia entre los núcleos y la memoria principal, lo cual se
puede modelar en BSP como una latencia de comunicación entre ambas unidades.
La paradoja es que mientras para personas no expertas en computación paralela parece
resultar incluso natural organizar sus códigos en el estilo sincrónico por lotes promovido
por BSP en los noventas, al menos para la aplicación que se estudia en este trabajo de
tesis — máquinas de búsqueda para la Web — los especialistas en computación paralela
tienden a organizar sus soluciones como sistemas completamente asincrónicos. Lo habitual
es encontrar soluciones que destinan un thread asincrónico a resolver secuencialmente una
determinada tarea y donde los mecanismos principales de control de acceso a los recursos
compartidos son de tipo locks. Desde la experiencia adquirida en las implementaciones
desarrolladas en esta tesis, se puede afirmar que dichas soluciones resultan ser bastante
más intrincadas y difı́ciles de depurar, que las desarrolladas utilizando el estilo BSP.
La pregunta que responde este trabajo de tesis es si el estilo BSP aplicado a nivel
microscópico viene también acompañado de un rendimiento eficiente. Para lograr este
objetivo fue necesario diseñar, analizar, implementar y evaluar estrategias sincrónicas de
procesamiento de transacciones y compararlas con sus contrapartes asincrónicas.
El requerimiento de eficiencia, tanto en rendimiento como en uso de recursos de hardware es ineludible en las máquinas de búsqueda para la Web. Se trata de sistemas com2
puestos por decenas de miles de nodos procesadores, los cuales reciben centenas de miles
de consultas de usuario por segundo. Entonces, es imperativo ser eficientes en aspectos de
operación tales como consumo de energı́a y cantidad de nodos desplegados en producción.
Respecto de calidad de servicio, la métrica a optimizar es la cantidad consultas resueltas
por unidad de tiempo pero garantizando un tiempo de respuesta individual menor a una
cota superior. Por lo tanto, si se diseña una estrategia de procesamiento de consultas que
le permita a cada nodo alcanzar una mayor tasa de solución de consultas que otra estrategia alternativa, la recompensa puede ser una reducción del total de nodos desplegados
en producción.
No obstante, aun cuando el estilo BSP alcance la misma eficiencia que la mejor estrategia asincrónica, la recompensa proviene desde la simplificación del desarrollo de software
requerido para implementar los distintos servicios que componen una máquina de búsqueda. Tı́picamente, éstas contienen servicios de front-end (broker), servicios de cache de
resultados, servicios de ı́ndices y servicios de datos para ranking de documentos basado en
aprendizaje automático. Entre éstos, el servicio de ı́ndice es el de mayor costo en tiempo
de ejecución puesto que dicho costo es proporcional al tamaño de la muestra de la Web
almacenada en cada nodo del cluster de procesadores.
Para consultas normales enviadas por los usuarios a la máquina de búsqueda, no es
difı́cil demostrar que, en el caso promedio, tanto un algoritmo completamente asincrónico
como uno sincrónico por lotes como BSP puede alcanzar un rendimiento muy similar. Sin
embargo, las máquinas de búsqueda han dejado de ser sistemas en que las transacciones
(consultas) son de sólo lectura. Estas han comenzado a posibilitar la actualización online de sus ı́ndices, lo cual implica desarrollar estrategias que sean eficientes tanto para
el procesamiento concurrente o paralelo de transacciones de lectura como de escritura.
Esto demanda desafı́os más exigentes para estas estrategias, puesto que deben resolver el
problema de los posibles conflictos de lectura y escritura cuando ocurren periodos de alto
tráfico de transacciones que contienen términos o palabras en común. Este trabajo propone
soluciones eficientes a este problema en el contexto de servicios implementados utilizando
el enfoque BSP de paralelización de threads ejecutados en procesadores multi-core.
Como ejemplos de aplicaciones de máquinas de búsqueda que requieren de la actualización en tiempo real de sus ı́ndices se pueden mencionar las siguientes.
En sistemas Q&A como Yahoo! Answers, miles de usuarios por segundo presentan
preguntas sobre temas variados a la base de texto, las cuales son resueltas eficientemente con la ayuda de ı́ndices invertidos. Al mismo tiempo, otros usuarios responden
3
con textos pequeños a las preguntas de usuarios anteriores, donde dichos textos contienen palabras que coinciden con las de las respectivas preguntas, originando de
esta manera posibles conflictos entre threads lectores y escritores al momento de
actualizar el ı́ndice invertido. Ciertamente, a los usuarios les gustarı́a que sus textos
sean parte de las respuestas a las preguntas lo antes posible. Estos sistemas entregan incentivos a los usuarios que proporcionan respuestas pertinentes a preguntas
de otros usuarios, aplicando votaciones sobre la calidad de las respuestas.
En sistemas para compartir fotos como Flickr, los usuarios suben constantemente nuevas fotos que son etiquetadas por ellos con pequeños textos y un conjunto
pre-definido de tags describiendo el contenido. Esto permite que esas fotografı́as
aparezcan en los resultados de las búsquedas que realizan otros usuarios en el sistema en forma concurrente. Se estima que en Flickr se suben decenas de millones
de fotografı́as por dı́a. Si alguien sube una fotografı́a que es pertinente a un tema
que ha capturado el interés repentino de muchos usuarios, lo deseable es que dicha
imagen sea visible a las búsquedas tan pronto como sea posible.
Aunque aún incipiente, un caso muy exigente de procesamiento concurrente o paralelo de transacciones de lectura y escrituras surge cuando se desea incluir en el proceso
de ranking de documentos, los efectos de las preferencias de usuarios anteriores por
documentos seleccionados para consultas similares, pero anteriores en la escala de
los últimos segundos o minutos. En este caso, los clicks realizados por los usuarios
sobre los URLs presentados como respuestas a sus consultas, deben ser enviados
de vuelta a la máquina de búsqueda e indexados de manera on-line. El objetivo es
utilizar estos clicks para refinar el ranking de los resultados de consultas posteriores.
Los clicks pueden ser indexados usando un ı́ndice invertido y ahora la concurrencia
aparece entre la actualizaciones sobre el ı́ndice y las operaciones necesarias para ejecutar tareas tales como la determinación de los términos de consultas similares y los
clicks en los documentos (URLs) relacionados con dichos términos.
Recientemente, las principales máquinas de búsqueda para la Web han comenzado
a incluir en los resultados de consultas a documentos recuperados de sistemas online con cientos de millones de usuarios, muy activos en generación de nuevos textos
pequeños, es decir, con gran probabilidad de coincidencia de palabras entre ellos,
tales como Twitter. Esto fuerza a las máquinas de búsqueda a incluir estos textos
en sus ı́ndices invertidos en tiempo real de manera que puedan ser incluidos en el
proceso de ranking de documentos para las consultas que se recepcionan desde sus
usuarios.
4
Claramente el requerimiento de actualizaciones en tiempo real de los ı́ndices no puede
ser satisfecho por las técnicas convencionales de realizar la indexación de manera off-line
y luego reemplazar la copia en producción del ı́ndice por una nueva. Generalmente la
indexación off-line se hace utilizando sistemas como Map-Reduce ejecutados en cluster de
computadores y sistemas de archivos distintos al cluster o los clusters en producción. La
latencia entre ambos sistemas de archivos es grande y el proceso de copiado a todos los
nodos del cluster de producción puede tomar varios minutos o incluso decenas de minutos.
El enfoque abordado en este trabajo de tesis es indexación on-line, es decir, los nuevos
documentos son enviados directamente a los nodos del cluster ejecutando el servicio de
ı́ndice en producción, y cada nodo indexa localmente los documentos recibidos al mismo
tiempo que procesa las consultas que recibe desde el servicio de front-end. Por lo tanto, el
nodo debe realizar estas operaciones de manera eficiente para evitar degradar el tiempo
de respuesta a las consultas de usuario o transacciones de lectura.
1.1.
Organización de la Tesis
Este trabajo está organizado de la siguiente manera:
Capı́tulo 2 Describe conceptos básicos en máquinas de búsqueda para la Web y revisa
la bibliografı́a relevante para este trabajo de tesis.
Capı́tulo 3 Presenta un estudio de la factibilidad de software del estilo BSP en procesadores multi-core. Se propone un modelo de programación hı́brida utilizando implementaciones C++ y una combinación de las bibliotecas MPI y OpenMP.
Capı́tulo 4 Contiene la contribución principal de este trabajo de tesis. Propone algoritmos de procesamiento sincrónico de transacciones y gestión de caches.
Capı́tulo 5 Presenta una comparación de las estrategias propuestas en el Capı́tulo 4 con
estrategias asincrónicas conocidas. El estudio utiliza implementaciones en C++ y la
biblioteca Posix-Threads.
Capı́tulo 6 Presenta un análisis basado en simulación discreta para validar los resultados
obtenidos con las ejecuciones reales.
Capı́tulo 7 Se presentan las conclusiones finales y se plantea trabajo futuro.
5
Parte del trabajo desarrollado en el transcurso de esta tesis ha sido publicado en los
siguientes lugares:
C. Bonacic, C. Garcia, M. Marin, M. Prieto-Matias and F. Tirado, “Building Efficient Multi-Threaded Search Nodes”, In 19th ACM Conference on Information and
Knowledge Management (CIKM 2010), Toronto, Canada, Oct 26-30, 2010.
C. Bonacic, M. Marin, C. Garcia, M. Prieto and F. Tirado, “Exploiting Hybrid
Parallelism in Web Search Engines”, In 14th European International Conference on
Parallel Processing (Euro-Par 2008), Gran Canaria, Aug. 26-29, Spain, Lecture Notes
in Computer Science 5168, pp.414-423, Springer, 2008.
C. Bonacic, C. Garcia, M. Marin, M. Prieto and F. Tirado, “On-Line Multi-Threaded
Processing of Web User-Clicks on Multi-Core Processors”, In 9th International Meeting High Performance Computing for Computational Science (VECPAR 2010), Berkeley, California, June 22-25, 2010.
C. Bonacic, C. Garcia, M. Marin, M. Prieto and F. Tirado, “Improving Search
Engines Performance on Multithreading Processors”, In 8th International Meeting on
High Performance Computing for Computational Science (VECPAR 2008), Revised
Selected Papers, LNCS 5336, pp. 201-213, Toulouse, France, June 24-27, 2008.
C. Bonacic, M. Marin, “Comparative Study of Concurrency Control on Bulk Synchronous Parallel Search Engines”, In International Conference on Parallel Computing
(ParCo 2007), Aachen, Germany, Sep. 4-7, Advances in Parallel Computing Vol. 15,
pp. 389-396, ISBN 978-1-58603-796-3, IO Press, 2007.
Algunos de los resultados de este trabajo de tesis fueron adaptados a sistemas de
memoria distribuida y fueron publicados en colaboración con otros grupos de investigación
en los siguientes lugares:
M. Marin, V. Gil-Costa, C. Bonacic, R. Baeza-Yates, I.D. Scherson, “Sync/Async
Parallel Search for the Efficient Design and Construction of Web Search Engines”,
In Parallel Computing 36(4): 153-168, 2010
M. Marin, R. Paredes and C. Bonacic, “High-Performance Priority Queues for Parallel Crawlers”, In 10th ACM International Workshop on Web Information and Data
Management (WIDM 2008), California, US, Oct. 30, 2008.
6
M. Marin and C. Bonacic, “Bulk-Synchronous On-Line Crawling on Clusters of
Computers”, In 16th Euromicro International Conference on Parallel, Distributed
and Networkbased Processing (Euro-PDP 2008), Toulouse, France, Feb. 13-15, pp.
414-421, IEEE-CS Press, 2008.
M. Marin, C. Bonacic, V. Gil-Costa, C. Gomez, “A Search Engine Accepting OnLine Updates”, In 13th European International Conference on Parallel Processing
(Euro-Par 2007), IRISA, Rennes, France, Aug. 28-31, Lecture Notes in Computer
Science 4641, pp. 340-349, Springer, 2007.
7
Capı́tulo 2
Estado del Arte
Las máquinas de búsqueda para la Web alcanzan un rendimiento eficiente mediante
la combinación de varias técnicas de indexación, caching, multi-threading, heurı́sticas,
métodos de ranking de documentos, entre otras optimizaciones. En este capı́tulo se revisa
la bibliografı́a y conceptos básicos que sirven de base para este trabajo de tesis.
2.1.
Clusters de Servicios de Indice
Las máquinas de búsqueda para la Web están construidas sobre clusters de nodos,
los cuales ejecutan los distintos servicios que componen el sistema. Los nodos del cluster
están interconectados entre sı́ mediante una red de alta eficiencia que les permite enviarse
mensajes entre ellos. Estos mensajes se utilizan para recolectar la información necesaria
para resolver una determinada tarea, como por ejemplo la solución a una consulta de
un usuario. Cada nodo del cluster tiene su propia memoria principal y discos locales para
almacenar información. Cada nodo puede leer y escribir información en su propia memoria
y si necesita información almacenada en otro nodo debe enviarle un mensaje y esperar la
respuesta. Una descripción simplificada de lo que hacen las máquinas de búsqueda para
resolver las consultas de usuario es la siguiente.
En un cluster utilizado como máquina de búsqueda, cada nodo almacena una parte
de la información del sistema completo. Por ejemplo, si se tiene una colección de texto
que ocupa n bytes y se tiene un cluster con P nodos, entonces se puede asignar a cada
uno de los P nodos una fracción n/P del tamaño de la colección. En la práctica si la
8
colección completa tiene nd documentos o páginas Web, entonces a cada nodo del cluster
se le asignan nd /P documentos.
Las consultas de los usuarios llegan a un nodo recepcionista llamado broker, el cual
distribuye las consultas entre los P nodos que forman el cluster.
Dado que cada nodo del cluster tiene un total de nd /P documentos almacenados en
su memoria, lo que hacen las máquinas de búsqueda más conocidas es construir un ı́ndice
invertido (detalles en la siguiente sección) en cada nodo con los documentos almacenados
localmente en cada uno de ellos. Un ı́ndice invertido permite acelerar de manera significativa las operaciones requeridas para calcular las respuestas a las consultas de los usuarios.
Cada vez que el broker recibe una consulta de un usuario, éste envı́a una copia de la
consulta a todos los nodos del cluster. En el siguiente paso, todos los nodos en paralelo leen
desde su memoria las listas invertidas asociadas con las palabras o términos de la consulta
del usuario. Luego se realiza la intersección de las listas invertidas para determinar los
documentos que contienen todas las palabras de la consulta. Al finalizar este paso todos
los nodos tienen un conjunto de respuestas para la consulta. Sin embargo, la cantidad de
respuestas puede ser inmensamente grande puesto que las listas invertidas pueden llegar
a contener miles de identificadores de documentos que contienen todas las palabras de
la consulta. Es necesario entonces hacer un ranking de los resultados para mostrar los
mejores K resultados al usuario como solución a la consulta.
Para realizar el ranking de documentos es necesario colocar en uno de los nodos del
cluster los resultados obtenidos por todos los otros. Esto con el fin de comparar esos
resultados unos con otros y determinar los mejores K. Sin embargo, enviar mensajes conteniendo una gran cantidad de resultados entre dos nodos puede consumir mucho tiempo.
Es deseable reducir la cantidad de comunicación entre nodos.
Ahora, si cada nodo ha calculado los mejores resultados para la consulta considerando
los documentos (listas invertidas) que tiene almacenados en su memoria local, entonces no
es necesario enviarlos todos al nodo encargado de realizar el ranking final. Basta con enviar
a este nodo los K mejores de cada uno de los P − 1 nodos restantes. Es decir, el ranking
final se puede hacer encontrando los K mejores entre los K × P resultados aportados por
los P nodos.
Pero esto se puede mejorar más aun y ası́ reducir al máximo la cantidad de comunicación entre los nodos [59]. Dado que los documentos están uniformemente distribuidos en
los P nodos es razonable pensar que cada nodo tendrá más o menos una fracción K/P de
9
los mejores K resultados mostrados al usuario. Entonces se puede trabajar en iteraciones.
En la primera iteración todos los nodos envı́an sus mejores K/P resultados al nodo encargado de hacer el ranking final. Este nodo hace el ranking y luego determina si necesita más
resultados desde los otros nodos. Si es ası́, entonces pide nuevamente otros K/P resultados
y ası́ hasta obtener los K mejores. En el peor caso podrı́a ocurrir que para una consulta
en particular uno de los nodos posea los K mejores resultados que se le van a entregar al
usuario, caso en que se necesitan P iteraciones para calcular la respuesta al usuario. Pero
es muy poco probable que esto ocurra para todas las consultas que se procesan en una
máquina de búsqueda grande. En la práctica se ha observado [59] que se requieren una o
a lo más dos iteraciones para la inmensa mayorı́a de las consultas, lo cual permite reducir
considerablemente el costo de comunicación entre los nodos del cluster.
Una manera de explotar al máximo la capacidad de los nodos del cluster es hacerlos
trabajar en paralelo. Esto se puede lograr asignando los nodos de manera circular para
hacer el ranking final de las consultas. Por ejemplo, el nodo broker elige al nodo 0 para
hacer el ranking de la consulta q0 , al nodo 1 para la consulta q1 , ..., el nodo P − 1 para
la consulta qp−1 , el nodo 0 para la consulta qp , y ası́ sucesivamente de manera que en
un instante dado del tiempo se pueda tener a P nodos haciendo el ranking de P o más
consultas distintas en paralelo.
2.2.
Índices Invertidos
Consiste en una estructura de datos formada por dos partes. La tabla del vocabulario
que contiene todas las palabras (términos) relevantes encontradas en la colección de texto.
Por cada palabra o término existe una lista de punteros a los documentos que contienen
dichas palabras junto con información que permita realizar el ranking de las respuestas
a las consultas de los usuarios tal como el número de veces que aparece la palabra en el
documento. Ver figura 2.1. Dicho ranking se realiza mediante distintos métodos [9]. En
este trabajo de tesis se utiliza el llamado método del vector [9, 80, 81].
Para construir un ı́ndice invertido secuencial [31, 38, 88], es necesario procesar cada
documento para extraer las palabras o términos de importancia, registrando su posición
y la cantidad de veces que éste se repite. Una vez que se obtiene el término, con su
información correspondiente, se almacena en el ı́ndice invertido.
El mayor problema que se presenta en la práctica, es la memoria RAM. Para solventar
esta limitación se suele guardar de forma explı́cita en disco ı́ndices parciales cuando se
10
azul
casa
perro
rojo
doc 2 1
doc 1 2
doc 3 1
doc 1 1
doc 3 1
doc 2 1
Figura 2.1: Estructura de un ı́ndice invertido.
superan ciertos umbrales, liberándose de este modo la memoria previamente utilizada. Al
final de esta operación, se realiza un merge de los ı́ndices parciales, el cual no requiere
demasiada memoria por ser un proceso en que se unen las dos listas invertidas para cada
término y resulta relativamente rápido.
Respecto de la paralelización eficiente de ı́ndices invertidos existen varias estrategias.
Las más utilizadas consisten en (1) dividir la lista de documentos en p nodos y procesar
consultas de acuerdo a esa distribución y en (2) distribuir cada término con su lista
completa uniformemente en cada nodo.
2.2.1.
Distribución por documentos
Los documentos se distribuyen uniformemente al azar en los nodos. El proceso para
crear un ı́ndice invertido aplicando esta estrategia (figura 2.2), consiste en extraer todos los
términos de los documentos asociados a cada nodo y con ellos formar una lista invertida por
nodo. Es decir, las listas invertidas se construyen basándose en los documentos que cada
nodo posee. Cuando se resuelve una consulta, ésta se debe enviar a cada nodo (broadcast).
2.2.2.
Distribución por términos
Consiste en distribuir uniformemente entre los nodos, los términos del vocabulario
junto con sus respectivas listas invertidas. Es decir, la colección completa de documentos
es utilizada para construir un único vocabulario para luego distribuir las listas invertidas completas uniformemente al azar entre todos los nodos. En esta estrategia, no es
conveniente ordenar lexicográficamente las palabras de la tabla vocabulario, ya que si se
mantienen desordenadas, se obtiene un mejor balance de carga durante el procesamiento
de las consultas. Ver figura 2.3. En este caso la consulta es separada en los términos que
la componen, los cuales son enviados a los nodos que los contienen.
11
N
o
d
e
1
N
o
d
e
2
Figura 2.2: Estrategia de Distribución por Documento.
2.2.3.
Diferencias entre ambas estrategias
En el ı́ndice particionado por términos la solución de una consulta es realizada de
manera secuencial por cada término de la consulta, a diferencia de la estrategia por documentos donde el resultado de la consulta es construido en paralelo por todos los nodos. La
estrategia por documentos exhibe un paralelismo de grano más fino ya que cada consulta
individual se resuelve en paralelo. En la estrategia por términos sólo puede explotarse
paralelismo si se resuelven dos o más consultas concurrentemente.
Por otra parte, la estrategia de indexación por documentos permite ir ingresando nuevos documentos de manera fácil, el documento se envı́a a la máquina id doc mod P , y se
modifica la lista local de ese nodo. Sin embargo, para el caso de ranking mediante el método del vector (descrito más abajo), se requiere que las listas se mantengan ordenadas por
frecuencia, entonces no es tan sencillo modificar la lista. No obstante, para la estrategia
por términos, también es necesario hacer esa actualización cuando se modifican las listas.
La gran desventaja del ı́ndice particionado por términos está en la construcción del ı́ndice
debido a la fase de comunicación global que es necesario realizar para distribuir las listas
invertidas. Inicialmente el ı́ndice puede ser construido en paralelo como si fuese un ı́ndice
particionado por documentos para luego re-distribuir los términos con sus listas invertidas.
12
N
o
d
e
1
N
o
d
e
2
Figura 2.3: Estrategia de Distribución por Término.
2.3.
Ranking de documentos
El método vectorial [9] es bastante utilizado en recuperación de información para
hacer ranking de documentos que satisfacen una consulta. Las consultas y documentos
tienen asignado un peso para cada uno de los términos (palabras) de la base de texto
(documentos). Estos pesos se usan para calcular el grado de similitud entre cada documento
almacenado en el sistema y las consultas que puedan hacer los usuarios. El grado de
similitud calculado, se usa para ordenar de forma decreciente los documentos que el sistema
devuelve al usuario, en forma de clasificación (ranking).
Se define un vector para representar cada documento y consulta:
El vector dj está formado por los pesos asociados de cada uno de los términos en el
documento dj .
El vector q está compuesto por los pesos de cada uno de los términos en la consulta
q.
Ası́, ambos vectores estarán formados por tantos pesos como términos se hayan encontrado
en la colección, es decir, ambos vectores tendrán la misma dimensión.
13
El modelo vectorial evalúa el grado de similitud entre el documento dj y la consulta
q, utilizando una relación entre los vectores dj y q. Esta relación puede ser cuantificada.
Un método muy habitual es calcular el coseno del ángulo que forman ambos vectores.
Cuanto más parecidos sean, más cercano a 0 será el ángulo que formen y en consecuencia,
el coseno de este ángulo se aproximará más a 1. Para ángulos de mayor tamaño el coseno
tomará valores que irán decreciendo hasta −1, ası́ que cuanto más cercano de 1 esté el
coseno, más similitud habrá entre ambos vectores, luego más parecido será el documento
dj a la consulta q.
La frecuencia interna de un término en un documento, mide el número de ocurrencias
del término sobre el total de términos del documento y sirve para determinar cuan relevante
es ese término en ese documento. La frecuencia del término en el total de documentos,
mide lo habitual que es ese término en la colección, ası́, serán poco relevantes aquellos
términos que aparezcan en la mayorı́a de documentos de la colección. Invirtiéndola, se
consigue que su valor sea directamente proporcional a la relevancia del término.
Una de las fórmulas utilizadas para el ranking vectorial es la siguiente:
Sea {t1 ...tn } el conjunto de términos y {d1 ...dn } el conjunto de documentos, un
documento di se modela como un vector:
di → d~i = (w(t1 , di ), ..., w(tk , di ))
donde w(tr , di ) es el peso del término tr en el documento di .
En particular una consulta puede verse como un documento (formada por esas palabras) y por lo tanto como un vector.
La similitud entre la consulta q y el documento d está dada por:
0 <= sim(d, q) =
P
t (wq,t
∗ wd,t )/W d <= 1.
Se calcula la similitud entre la consulta q y el documento d como la diferencia coseno,
que geométricamente corresponde al coseno del ángulo entre los dos vectores. La
similitud es un valor entre 0 y 1. Notar que los documentos iguales tienen similitud
1 y los ortogonales (si no comparten términos) tienen similitud 0. Por lo tanto esta
fórmula permite calcular la relevancia del documento d para la consulta q.
El peso de un término para un documento es:
14
0 <= wd,t = fd,t /maxk ∗ idft <= 1.
En esta fórmula se refleja el peso del término t en el documento d (es decir qué tan
importante es este término para el documento).
fd,t /maxk es la frecuencia normalizada. fd,t es la cantidad de veces que aparece el
término t en el documento d. Si un término aparece muchas veces en un documento,
se supone que es importante para ese documento, por lo tanto fd,t crece. maxk es
la frecuencia del término más repetido en el documento d o la frecuencia más alta
de cualquier término del documento d. En esta fórmula se divide por maxk para
normalizar el vector y evitar favorecer a los documentos más largos.
idft = log10(N/nt), donde N es la cantidad de documentos de la colección, nt es
el número de documentos donde aparece t. Esta fórmula refleja la importancia del
término t en la colección de documentos. Le da mayor peso a los términos que
aparecen en una cantidad pequeña de documentos. Si un término aparece en muchos
documentos, no es útil para distinguir ningún documento de otro (idft decrece). Lo
que se intenta medir es cuanto ayuda ese término a distinguir ese documento de los
demás. Esta función asigna pesos altos a términos que son encontrados en un número
pequeño de documentos de la colección. Se supone que los términos raros tienen un
alto valor de discriminación y la presencia de dicho término tanto en un documento
como en una consulta, es un buen indicador de que el documento es relevante para
la consulta.
P
2 ))1/2 . Es utilizado como factor de normalización. Es el peso del doW d = ( (wd,t
cumento d en la colección de documentos. Este valor es precalculado y almacenado
durante la construcción de los ı́ndices para reducir las operaciones realizadas durante
el procesamiento de las consultas.
wq,t = (fq,t/maxk ) ∗ idft , donde fq,t es la frecuencia del término t en la consulta q
y maxk es la frecuencia del término más repetido en la consulta q, o dicho de otra
forma, es la frecuencia mas alta de cualquier término de q. Proporciona el peso del
término t para la consulta q.
15
2.4.
Trabajo Relacionado
2.4.1.
Índices invertidos
Existe abundante literatura que describe como resolver de manera eficiente consultas
sobre ı́ndices distribuidos sobre un conjunto de P nodos [4, 15, 17, 32, 33, 37, 50, 86, 87]. En
esos trabajos se discute la eficiencia de los métodos de indexación basados en partición por
documentos y partición por términos. Para cada una de estas estrategias, se han propuesto
diferentes técnicas para resolver consultas sobre el ı́ndice distribuido y, técnicas hı́bridas
para mejorar el rendimiento y/o balance de carga (ver por ejemplo [6, 19, 28, 61, 65, 66,
73, 74, 79, 96, 98, 104]).
2.4.2.
Compresión de ı́ndices invertidos
En la compresión de ı́ndices invertidos [72, 75, 76, 102, 105], y en particular de la lista
de posteos o invertida, la literatura actual habla de Variable-Byte [91], S9 [3], S16 [90] y
PForDelta [111] como métodos modernos que ofrecen la mejor velocidad de descompresión
sin dejar de lado la tasa de compresión [14, 90, 106]. Es por esto que han sido seleccionados
para presentar una breve descripción de cada uno de ellos a modo de estado del arte. Para
ello se considerará una lista de posteos ordenada por IDsdoc, donde la lista comienza con
el IDdoc del primer documento y de allı́ en adelante cada número representa la diferencia
con el IDdoc del documento anterior.
Variable-Byte: Expresa un número en la menor cantidad de bytes posibles. Para ello
ocupa el bit más significativo de cada byte como un bit de marca, que le indica si el byte
actual es el último de la secuencia, el último byte de la secuencia es marcado con un 0
en el bit más significativo. Los restantes 7 bits se ocupan para representar el número a
codificar en formato binario. Ası́ por ejemplo el número 513, que en binario es 1000000001,
codificado con Variable-Byte queda 10000100 00000001.
Es un método muy sencillo y también eficiente a la hora de descomprimir. El inconveniente que éste presenta para las listas de posteo, es que cuando son muy largas y
las diferencias se hacen pequeñas, Variable-Byte representa en un byte números que sólo
requieren un par de bits, desperdiciando gran cantidad de espacio.
S9: Este método trata de guardar la mayor cantidad posible de números en una palabra
de 32 bits. Para hacer esto, los cuatro primeros bits son ocupados como bits de estado,
16
donde se especifica como son ocupados los restantes 28 bits. S9 obtiene su nombre de los
9 posibles casos que se dan al dividir 28 bits en cantidades iguales; 1 número de 28 bits
(y su recı́proco 28 números de 1 bit), 2 números de 14 bits (y su recı́proco), 3 números
de 9 bits (y su recı́proco, desperdiciando 1 bit), 4 números de 7 bits (y su recı́proco) y 5
números de 5 bits (desperdiciando 3 bits).
Para diferencias entre números consecutivos que son muy pequeñas, con este método
se logra empaquetar una gran cantidad de números en una palabra de 32 bits, y además
se logra una buena velocidad de descompresión. Sin embargo, estas ventajas se pierden
cuando entre las diferencias entre números consecutivos existen valores grandes. En este
caso el método no se comporta bien respecto al ahorro de espacio.
S16: Variante de S9, que toma algunos casos especiales que no dividen los 28 bits en
forma regular, logrando con esto completar 16 casos (de ahı́ su nombre). De esta manera se
pretende ocupar la mayor cantidad de bits posibles en forma eficiente, eliminando algunos
casos que desperdiciaban bits, como por ejemplo 5 números de 5 bits, se reemplaza por 2
casos; el primero es 3 números de 6 bits y 2 números de 5 bits, el segundo es 2 números
de 5 bits y 3 números de 6 bits.
En compresión este método logra resultados levemente mejores a S9, manteniendo su
velocidad de descompresión, sin embargo aún mantiene las debilidades del método original.
PForDelta: Este método deriva originalmente del método de compresión FOR [35]
(Frame Of Reference). Para un cierto rango de números, FOR toma ambos extremos, observa cuantos casos posibles existen entre el menor y el mayor de los extremos, codificando
cada caso en binario desde 0 para el menor hasta el número binario que sea necesario para
cubrir todo el rango, ocupando ası́ la menor cantidad posible de bits para representar
cada caso. Por ejemplo, se tiene un rango en la lista de posteos donde se repiten muchas
diferencias entre 2 y 5, se sabe que los posibles casos son {2,3,4,5}, para diferenciar entre
ellos nos basta con usar 2 bits, ası́ cada caso quedarı́a; 00 para 2, 01 para 3, 10 para 4 y
11 para 5.
El método FOR presenta problemas al tener rangos con diferencias demasiado grandes,
PForDelta ataca ese problema. Para esto, PFordelta separa la mayorı́a (aproximadamente
el 90 % según el autor [111]) de los números que pueden ser representados en b bits (coded),
de los que no pueden ser representados en b bits (exceptions). De esta manera los números
codificables son guardados con FOR y las excepciones se guardan descomprimidas.
17
2.5.
Caches
Inicialmente los motores de búsqueda para la Web mantenı́an los resultados de las
consultas frecuentes a lo largo del tiempo en un cache estático. Posteriormente, en [69] se
muestra que el rendimiento del cache estático es deficiente, ya que muchas consultas que
llegan a la máquina de búsqueda son frecuentes por periodos cortos de tiempo y por lo
tanto, el cache estático no las considera. Dado esto, técnicas de caching dinámico basadas
en polı́ticas de reemplazo como LRU presentan mejor rendimiento. En [53] se calcula un
valor de relevancia para las consultas y se estima la probabilidad de que una consulta se
repita en el futuro cercano, esta técnica es llamada Probability Driven Caching (PDC).
En [27] se propone una estrategia de caching donde se mantiene en el cache de resultados una sección estática y otra dinámica, obteniendo buenos resultados, Static-Dynamic
Caching (SDC). En SDC, la parte estática mantiene las consultas más frecuentes en largos
periodos del tiempo, y la parte dinámica es implementada con polı́ticas de reemplazo como
LRU. Esta estrategia, logra una tasa de aciertos superior al 30 % en experimentos basados
en el log de Altavista. En [54] se muestra que al almacenar intersecciones de pares de
términos frecuentes, determinados por la co-ocurrencia en el log de consultas, es posible
mejorar el rendimiento significativamente. En [8] se muestra que al tener un cache de listas
invertidas, se logran mejores resultados que al tener un cache de resultados únicamente.
En [30] se estudia el cache de resultados con ponderación (Weighted Result Caching), donde el costo de procesar una consulta se considera en el momento de decidir su admisión
en el cache de resultados.
Las técnicas anteriores entregan una respuesta exacta a las consultas a través de la
información que existe en el cache. El uso de técnicas de aproximación mediante análisis
semántico puede ayudar a sacar mejor utilidad del contenido en el cache de resultados.
En [34] se utiliza esta técnica donde se proporciona una pauta para identificar los distintos
casos surgen al considerar distintas relaciones entre los términos de la nueva consulta y
los términos de las consultas almacenadas en el cache. Esto permite obtener resultados
aproximados para una consulta que, aún cuando no está exactamente en el cache, comparte
términos con otras consultas. Esta técnica se puede utilizar para permitir a la máquina de
búsqueda seguir respondiendo consultas incluso en casos en que sus nodos están saturados
debido a subidas bruscas en el tráfico de consultas.
En [20, 21] se presentan nuevos métodos semánticos para resolver consultas. Se propone mantener clusters de respuestas co-ocurrentes identificadas en una región, cada uno
de ellos asociados a una firma. Las nuevas consultas frecuentes también son identifica18
das con una firma, donde las regiones con firmas similares son capaces de entregar una
respuesta. Los resultados experimentales muestran que el rendimiento de los diferentes
métodos semánticos dependen del tipo de solapamiento. En [2] se estudia el problema de
escalabilidad para métodos de caching semántico.
En [83] se propone un método que utiliza un log de consultas para formar P clusters
de documentos y Q clusters de consultas. Los clusters son formados mediante el uso de un
algoritmo de co-clustering. Esto permite la creación de una matriz en la máquina broker
donde cada elemento indica que tan pertinente es un cluster de consultas a un cluster
de documentos. Además, para cada cluster de consultas, se mantiene un archivo de texto
que contiene todos los términos presentes en las consultas del cluster. Cuando la máquina
broker recibe una consulta q, ésta calcula que tan pertinente es q a uno de los clusters de
consultas mediante el uso de métodos de similitud. Estos valores de similitud se utilizan
en combinación con los elementos de la matriz de co-clustering, para calcular un ranking
de clusters de documentos. Los primeros lugares del ranking representan los nodos que
son capaces de responder a una consulta con mayor precisión. Es decir, son los nodos
que tienen mayor probabilidad de aportar documentos, y formar parte de los resultados
top-k globales de una consulta. La idea es enviar la consulta a los nodos más probables de
aportar documentos para la respuesta.
Los métodos presentados en [28, 71, 77] persiguen un objetivo similar pero utilizando
técnicas diferentes. Estos métodos están basados en el uso de un cache de aplicación llamado Location Cache (LCache) el cual registra los nodos que aportan los top-K documentos
para consultas almacenadas en cache. Los métodos propuestos utilizan técnicas semánticas y de aprendizaje sobre la LCache. Siempre el objetivo final es enviar la consulta a
un conjunto reducido de nodos con el fin de evitar saturar al motor de búsqueda frente a
situaciones de alzas en el tráfico de consultas.
El trabajo presentado en [28] propone usar el LCache como un cache semántico. El
método semántico para la evaluación de las nuevas consultas, utiliza la frecuencia inversa
de los términos de las consultas almacenadas en cache, que determinan cuando los resultados recuperados desde el cache son buenas aproximaciones al resultado exacto. La
precisión de los resultados varı́a en función de la coincidencia semántica de los casos analizados. En [60] se propone una combinación de LCache y RCache (cache de resultados
estándar) usando estrategias dinámicas de caching, obteniendo un desempeño eficiente en
escenarios de tráfico variable de consultas, opuesta a la estrategia que sólo utiliza RCache
la cual se satura ya que no es capaz de trabajar de manera eficiente con variaciones de
19
tráfico. El trabajo en [71] aplica métodos de máquinas de aprendizaje sobre el LCache
para determinar cual es el mejor nodo de búsqueda.
Una idea similar, pero basada en el modelo vectorial se aplica en [77] que predice los
nodos de búsqueda para las consultas que no se encuentran en la LCache. En todos los
casos, la motivación es reducir la carga sobre los nodos de búsqueda frente a cambios bruscos en el tráfico de consultas. La idea es entregar una buena aproximación de respuestas
a las consultas utilizando pocos nodos de búsqueda por cada una y evitar de esta manera
la saturación y el comportamiento inestable del motor de búsqueda.
2.6.
Arquitectura de una máquina de búsqueda
Los centros de datos para motores de búsqueda en la Web, contienen miles de nodos
intercomunicados, formando diferentes clusters. Por lo general, cada cluster se especializa
en una sola operación relacionada con el procesamiento de una consulta. La operación que
más tiempo demanda en el proceso de resolver una consulta, es determinar los mejores K
(top-K [42, 52]) documentos (IDs) que mejor coinciden con una consulta dada. Hay otras
operaciones atendidas por otros clusters, tales como la construcción de la página Web de
resultados de los mejores K documentos y la publicidad relacionada con los términos de
la consulta. Determinar los top-K documentos, es la parte más costosa [25] del proceso
dado el gran tamaño de la colección de texto de la Web.
Para el conjunto top-K, una arquitectura estándar de cluster es un arreglo (array) de
P × D nodos, donde P indica el nivel de partición de la colección de texto y D el nivel de
replicación de la colección.
El tiempo de respuesta de una consulta es proporcional al tamaño de la colección de
texto y para obtener tiempos de respuesta por debajo de un cierto lı́mite, la colección de
texto es dividida en P particiones diferentes. Cada consulta es enviada a las P particiones
y, en paralelo, se determinan los top-K locales a cada partición. A partir de los K locales,
se generan los K globales de la consulta. Existe una máquina aparte llamada broker que se
encarga de enviar las consultas a los nodos de búsqueda, para luego recopilar los resultados.
El nivel de replicación indica que cada una de las P particiones son replicadas D
veces. El propósito de replicar es por dos motivos. Entregar un soporte tolerante a fallos
y alanzar una tasa de solución de consultas por unidad de tiempo (throughput) eficiente.
20
En cualquier instante de tiempo, diferentes consultas, pueden ser resueltas por diferentes
réplicas. En cada partición una de las réplicas es seleccionada uniformemente al azar.
La implementación de una máquina de búsqueda en un arreglo logra un desempeño
eficiente utilizando técnicas de indexación [26, 56, 99, 110] y caching [5, 7, 11, 16, 64, 68,
78, 94, 107, 108]. Con respecto a la indexación, cada nodo de búsqueda contiene una lista
invertida que es usada para mapear de manera eficiente los términos de la consulta con los
documentos relevantes [61, 74]. Las listas invertidas asociadas a los términos de la consulta,
rápidamente entregan un conjunto de documentos relevantes que son ordenados mediante
técnicas de ranking. Las listas invertidas pueden ser muy grandes y por lo general sólo se
hace referencia a un subconjunto de ellas. Por este motivo, muchas de las listas invertidas
son almacenadas en memoria secundaria o en formato comprimido en memoria principal,
y sólo un subconjunto de ellas se mantienen descomprimidas en memoria principal, a esto
se llama cache de listas invertidas (posting list cache). Este cache es administrada bajo el
estándar LRU.
Un segundo cache, llamado cache de resultados (results cache), se mantiene en la
máquina broker. Este se encarga de recibir las consultas, enviarlas al arreglo de nodos
para que sean resueltas, y recolectar los resultados. El cache de resultados, almacena las
respuestas a las consultas más frecuentes. Las polı́ticas de administración para el cache de
resultados, se basa en la estrategia SDC [27]. Se mantiene una sección de sólo lecturas para
almacenar las respuestas de las consultas más frecuentes por un periodo largo de tiempo.
Una primera parte se administra como un cache estática, con la polı́tica LFU. La segunda
parte, representa el cache dinámico, administrado con LRU, se encarga de las consultas
más populares por un corto periodo de tiempo. Experimentos presentados en [27] sugieren
destinar un 80 % para la parte estática y un 20 % para la parte dinámica.
Recientemente [30] mostró que la parte dinámica es mejor administrarla utilizando
una polı́tica llamada Landlord, la cual es una generalización de LRU. Esta polı́tica de
administración, considera el costo de procesar una consulta cuando se decide si se encuentra
o no en cache. Inicialmente la prioridad de entrada a el cache está dada por el costo
promedio L de procesar una consulta en cada nodo de búsqueda. Cuando la entrada es
re-usada por la misma consulta (cache hit) la prioridad se incrementa por L unidades.
Cuando una entrada es sustituida por una nueva consulta, todas las entradas que quedan
en la memoria cache disminuyen su prioridad en L unidades. Una implementación eficiente
de esta estrategia es utilizando colas de prioridad. Cada nueva entrada recibe una prioridad
s + L con un valor inicial s = 0. La siguiente entrada que va ser sustituida es aquella que
tiene el menor valor numérico v en la cola de prioridad y se hace s = v para la prioridad
21
(a) Jerarquı́a estándar de Cache
(b) Camino de una consulta en un search node
Figura 2.4: Enfoque base del procesamiento de una consulta.
s+L del nuevo elemento que se almacena en la entrada. Para la cache estática se considera
f · L para los valores de prioridad donde f es la frecuencia de ocurrencia de las consultas
en un log.
Alternativamente, una tercera cache es mantenida en este esquema. Esta es la cache
de intersecciones (intersection cache) [54] la cual mantiene en cada nodo de búsqueda la
intersección de las listas invertidas que están asociadas a los términos más frecuentes de las
consultas. La operación de intersección es útil para detectar los documentos que contienen
todos los términos de la consulta, requerimiento tı́pico para encontrar los top-K resultados
en los principales motores de búsqueda.
De esta manera, las tres caches definen una jerarquı́a que va desde los datos más especı́ficos pre-computados para las consultas (cache de resultados) a los datos más genéricos
para resolver una consulta (cache de listas invertidas).
La Figura 2.4.a ilustra el estado del arte para una jerarquı́a de caches para máquinas de
búsqueda, donde un cache de intersecciones y listas invertidas son mantenidas en cada uno
de los P × D nodos de búsqueda. La Figura 2.4.b muestra qué ocurre cuando una consulta
no encuentra los resultados en el cache de resultados de la máquina broker. En este caso,
la consulta es enviada a un nodo de búsqueda por cada columna. En cada columna, el
respectivo nodo de búsqueda es seleccionado al azar de manera uniforme.
22
202
Answer 214
Broker 204
201
Rankers
212c
208
212b
S
Fetchers
S
212a
206(1)
206(2)
206(3)
206(i)
206(P−2) 206(P−1)
206(P)
Figura 2.5: Arquitectura para el proceso de una consulta.
2.7.
Procesamiento Round-Robin de Consultas
Es un método diseñado para optimizar el uso de los recursos del cluster de servicio de
ı́ndice, en situaciones con un alto tráfico de consultas. El proceso de resolver una consulta
utilizando P nodos se ve reflejado en la Figura 2.5: (202) las consultas llegan a la máquina
broker (204). Cada nodo 206(1) a 206(P) tiene 2 roles: fetcher y ranker. En general, el
proceso llamado fetcher puede simplemente reunir datos (por ejemplo, desde memoria
secundaria) y también puede ejecutar algunos pre-procesos. El proceso llamado ranker
recibe los resultados parciales desde el fecther y calcula la respuesta final para la consulta
(204).
En la Figura 2.5, el doble rol se ve reflejado en la linea 208. En este ejemplo, para el
proceso fetching, cada nodo (206) reúne los datos desde el ı́ndice para una consulta en
particular. Para el caso del ranking, cada nodo (206) realiza una unión (merge) de los
datos asociados a la consulta. El ranker envı́a la consulta (201) a T fetchers (212), donde
cada T trabaja de acuerdo al tipo de partición del ı́ndice. En relación al tráfico, el ranker
puede trabajar con varias consultas en paralelo en un perı́odo de tiempo.
El broker es el encargado de enviar las consultas a los nodos del cluster y recibir las
respectivas respuestas. La distribución de las consultas a través de los nodos, es mediante
heurı́sticas de balance de carga. En particular, la heurı́stica depende de la partición del
ı́ndice invertido con que se esté trabajando.
La máquina de búsqueda puede cambiar dinámicamente su modo de operación de
acuerdo al tráfico de consultas observado.
23
Modo Asincrónico (Async). Un tráfico bajo de consultas activa el modo Async.
Cada consulta es atendida por un único thread maestro encargado de resolver la
consulta. El thread maestro se puede comunicar con otros P threads esclavos; cada
uno se ubica en un nodo del cluster.
Modo Sincrónico (Sync). Un tráfico alto de consultas activa el modo Sync. Todos
los threads activos son bloqueados y solo un thread tiene el control del proceso de
consultas. En este caso los mensajes son almacenados en el buffer de todos los nodos
del cluster y enviados al comienzo de cada iteración, punto en el cual los nodos
son sincronizados. En este modo operacional, los recursos del sistema son utilizados
de mejor manera dada la forma de distribuir los threads. Se reducen los costos de
sincronización y comunicación, que son ejecutados en bloques. Se le llama superstep
al periodo que transcurre entre dos sincronizaciones en forma de barrera de los
procesos que participan en la solución de una o más consultas.
El procesamiento de una consulta aplicando el modelo round-robin, se realiza en cualquier de los modos de funcionamiento de una máquina de búsqueda. La Figura 2.6 ilustra,
para P = 2 nodos y en cada nodo T = 2 procesos o threads, el proceso iterativo de 7 consultas etiquetas como A, B, C, D, E, F y G. El proceso es distribuido a través de 2 nodos y
8 supersteps donde existe uso exclusivo de los supersteps para las operaciones de fetching
y ranking. En la figura, las consultas son procesadas secuencialmente en cada nodo. Por
ejemplo, el thread del nodo 0 procesa por completo la consulta A y, a continuación, se
otorga el quantum a la consulta B.
El mismo esquema de la Figura 2.6 puede ser usado para operar en el modo asincrónico.
La diferencia es que los periodos de ranking y fetching se van a solapar entre los procesos
o threads, pero la tendencia global de sincronización de operaciones se va a mantener.
24
Ranking
K/P
K
A
A
A
G
B
B
E
E
C
C
F
F
D
D
D
D
Proc 0
Proc 1
K
K/P
Superstep 1
2
3
4
5
6
7
8
Fetching
Figura 2.6: Procesamiento round-robin sincrónico para siete consultas.
25
Capı́tulo 3
Paralelismo Hı́brido
La arquitectura de software de los motores de búsqueda está compuesta de un conjunto
de servicios que son alojados en los nodos del cluster del centro de datos. La comunicación
entre servicios es vı́a paso de mensajes puesto que para alcanzar un rendimiento eficiente
es necesario asignar un servicio por nodo. Los servicios tı́picos son módulos de software
tales como servicio de broker, servicio de cache de resultados, servicio de ı́ndice y servicio de datos para soporte al ranking de documentos basado en aprendizaje automático.
Normalmente el servicio de ı́ndice está compuesto de un conjunto de P × D sub-servicios,
los cuales coinciden con el nivel P de partición del ı́ndice y el nivel D de replicación del
ı́ndice, y donde también se asigna un sub-servicio por nodo del cluster.
Cuando los nodos son procesadores multi-core se debe decidir cómo administrar los
diferentes núcleos (cores) para explotar el paralelismo disponible [1, 40, 109]. Básicamente
existen dos alternativas. La primera es la que dicta la evolución natural desde un sistema
en producción diseñado originalmente para procesadores con un solo núcleo y consiste en
alojar en cada procesador tantos sub-servicios como núcleos tenga. Cada uno de estos
sub-servicios mantiene sus propias estructuras de datos locales para alojar sus propios
ı́ndices y caches. Es decir, sistema cuyos nodos integran T núcleos, es necesario mantener
la sección del ı́ndice que le corresponde al nodo particionada en T partes. Entonces para
un cluster con P nodos encargados de procesar consultas en P particiones generales del
ı́ndice se tendrán en realidad T · P particiones. No obstante, se pueden emplear bibliotecas
de memoria compartida con el fin de mantener sólo una partición del ı́ndice en cada nodo.
La segunda alternativa consiste en intervenir la implementación de los servicios, transformándolos en sistemas con múltiples hebras de ejecución (multi-threading) [97] y asig-
26
nando cada thread a un núcleo del procesador. Esto implica un cambio importante en el
diseño del software que implementa el servicio, y por lo tanto su adopción se justifica sólo
si es posible alcanzar una mejora significativa en rendimiento.
En este capı́tulo se investiga la factibilidad de esta alternativa con respecto al rendimiento que es posible alcanzar en dos arquitecturas conocidas de procesadores multi-core.
La experimentación es realizada utilizando un método de indexación y ranking particular
pero representativo de las cargas de trabajo en este tipo de aplicaciones. El estudio se
concentra en el servicio de ı́ndice, puesto que es el componente que demanda la mayor
cantidad de cómputo en una máquina de búsqueda. La razón es que, al contrario de los
otros servicios, el tiempo de ejecución del servicio de ı́ndice es proporcional a la muestra
de la Web almacenada en la máquina de búsqueda.
También, sin pérdida de generalidad, el estudio se hace en el contexto de motores de
búsqueda Sync/Async descritos en el Capı́tulo 2. La estrategia Sync/Async fue diseñada
bajo el paradigma de P procesadores mono núcleo con memoria distribuida, y donde la
existencia de más núcleos en un nodo del cluster es tratada de manera transparente considerando que las T núcleos adicionales equivalen a nuevos procesadores hasta completar
un total de T · P procesadores mono núcleo con memoria distribuida. Es decir, se realiza
paso de mensajes incluso entre procesadores ubicados en el mismo nodo. Una contribución
de este capı́tulo es la adaptación de la estrategia Sync/Async a clusters equipados con
procesadores multicore utilizando de forma explı́cita modelos de programación basados en
memoria compartida.
Como se verá en el resto de los capı́tulos, varias de las técnicas propuestas son posibles sólo cuando la implementación de los servicios se realiza mediante multi-threading.
Por tanto, es de interés cuantificar la mejora en rendimiento que es factible alcanzar al
menos para transacciones de sólo lectura como lo es el procesamiento de consultas de
usuarios utilizando el servicio de ı́ndice presentado en este capı́tulo. Los resultados experimentales sobre dos arquitecturas diferentes constituyen una evidencia preliminar de que
la aplicación de paralelismo sincrónico por lotes (bulk-synchronous parallelism) a nivel de
procesador multi-core es una alternativa que produce mejor rendimiento que la alternativa más tradicional que consiste en desplegar sub-servicios asincrónicos en los núcleos del
procesador.
La base de software utilizada en la experimentación destinada a evaluar ambos enfoques
para el despliegue de servicios en los nodos, la constituyen las bibliotecas de comunicación
y sincronización MPI, BSPonMPI y OpenMP. Para este tipo de aplicaciones se ha observa-
27
do que tanto OpenMP como PThreads alcanzan el mismo rendimiento y se pueden utilizar
intercambiablemente. No obstante, una aplicación de producción con certeza utiliza mecanismos de más bajo nivel para la comunicación entre procesos y la coordinación entre
threads. No obstante, dada la alta granularidad del procesamiento de consultas, nuestra
conjetura es que los rendimientos alcanzados por el sistema propietario y las bibliotecas
escogidas para nuestra experimentación serán similares.
3.1.
Introducción
En el Capı́tulo 2 se comentó que para lograr un uso eficiente de los recursos de hardware asignados al procesamiento de las consultas, es conveniente variar dinámicamente
el método de procesamiento de consultas entre los modos sincrónicos y asincrónicos de
operación. A esto se le llama modos Sync/Async de operación [62] y el objetivo es poder
destinar menos nodos a servir una determinada carga de trabajo en régimen permanente.
El modo Sync es particularmente eficiente cuando existen muchas consultas que se están
resolviendo puesto que puede amortizar costos del manejo de threads y mensajes, procesando las consultas por lotes. Con esta estrategia es posible mantener operando los nodos
a una mayor utilización y la máquina de búsqueda se pasa al modo Sync para absorber
subidas repentinas en la intensidad de tráfico de consultas.
El modo Async (asincrónico) es el modo estándar de operación donde el nodo del cluster
puede resolver concurrentemente tantas consultas activas independientes como núcleos
tenga dicho nodo, es decir, en cualquier momento del tiempo pueden existir varios procesos
en el nodo, cada uno resolviendo completamente una consulta distinta. En el modo Sync
(asincrónico por lotes) se mantiene un solo proceso en cada nodo, aunque dicho proceso
podrá utilizar tantos threads como núcleos tenga el nodo. Cada proceso gestiona sus
consultas por lotes utilizando el esquema round-robin descrito en el Capı́tulo 2, de manera
de asignar a cada consulta la misma cuota de uso de recursos de procesador, disco y red a
lo largo de las iteraciones del modo Sync. Estas iteraciones se implementan utilizando los
supersteps del modelo BSP de computación paralela sobre memoria distribuida. Durante
el modo Async, también se continúa aplicando round-robin para facilitar el intercambio
dinámico entre modos.
La descripción detallada del modelo BSP es entregada en el Apéndice A. Una descripción breve de BSP es la siguiente. En BSP, las computaciones de los nodos del cluster se
organizan como un conjunto de supersteps. En cada superstep los nodos pueden realizar
28
cómputo sobre datos almacenados en memoria local e iniciar el envı́o de mensajes a otros
nodos. El final de cada superstep marca la transmisión efectiva de los mensajes y la sincronización de los nodos en forma de barrera. Esto implica que los mensajes están disponibles
en los nodos de destino al inicio del siguiente superstep. La barrera asegura que cada nodo
puede continuar con el siguiente superstep, sólo cuando todos han alcanzado el mismo
punto de sincronización.
La argumentación de la conveniencia de operar en ambos modos de operación ha sido
descrita en [63] donde se presenta un estudio detallado sobre la ganancia en eficiencia
entre uno u otro modo de operación dependiendo del tráfico de consultas. Se muestra que
el modo Sync/Async opera eficientemente cuando se aplica a distintos tipos de indexación
y métodos de procesamiento paralelo de consultas (Capı́tulo 2). También se propone un
algoritmo de control para seleccionar el modo de operación automáticamente. Sin embargo,
el estudio experimental presentado en [63] utiliza el enfoque antiguo de clusters formados
por nodos con un sólo núcleo.
La pregunta que surge para el modo Sync es cómo utilizar eficientemente los T núcleos
disponibles en cada uno de los P nodos que forman el cluster de procesadores multi-core.
Si se utiliza un solo thread por nodo, existirán T − 1 núcleos desocupados lo cual conduce
a un uso ineficiente de recursos. En este Capı́tulo se estudian dos alternativas:
La solución directa consiste en extender el concepto de Sync/Async a un total de
P ·T procesos conteniendo un thread cada uno y asignando cada proceso a un núcleo
distinto. Es decir, cada núcleo es tratado como un procesador individual. En el modo
Async, los P ·T procesos realizan paso de mensaje asincrónico entre ellos. En el modo
Sync, los P · T procesos se sincronizan periódicamente y realizan paso de mensajes
entre ellos aplicando computación BSP. La biblioteca de comunicación BSPonMPI ha
sido adaptada explı́citamente a sistemas multicore, de modo que la comunicación y la
sincronización entre procesos que residen en un mismo nodo sea muy eficiente ya que
utiliza para ello la memoria compartida. La ventaja de este esquema, es que se puede
utilizar directamente las implementaciones desarrolladas para sistemas de memoria
distribuida. Potencialmente podrı́a obtenerse algún beneficio en lo que respecta a la
localidad, ya que cada núcleo procesa datos locales y no existirán fenómenos como la
falsa compartición o costosas transferencias de datos entre núcleos. Desde el punto
de vista del software utilizado en la experimentación, este esquema implica desplegar
un total de P · T tareas de MPI-BSP en los P · T núcleos disponibles. El tráfico de
consultas se reparte equilibradamente entre los P · T procesos. El modo Sync se
29
activa tan pronto como existen n · P · T consultas en la cola de entrada del nodo con
n > 1.
La segunda alternativa consiste en trabajar explı́citamente con el hecho de que grupos de T núcleos comparten la misma memoria y por lo tanto se puede aplicar
paralelismo hı́brido. Es decir, se propone mezclar en un mismo sistema modelos de
programación para memoria compartida y memoria distribuida. En este caso existen
P procesos, uno por cada nodo multi-core del cluster, que realizan paso de mensajes
asincrónico entre ellos cuando se está operando en el modo Async. También los P
procesos realizan paso de mensajes sincrónico y sincronización por barrera cuando
están operando en el modo Sync. Además, en cada uno de los P procesos programados como sistemas de memoria distribuida, se tienen T threads programados como
sistemas de memoria compartida. Para esto se puede utilizar tanto OpenMP como
PThreads dentro de cada proceso MPI-BSP.
3.2.
Estrategia Hı́brida
La manera de procesar las consultas en el modo Sync es descrita a continuación. Cada
superstep es ejecutado en todos los nodos en paralelo. Se asume un escenario con un alto
tráfico de consultas, lo cual permite al broker enviar Q consultas con t términos a cada
nodo del cluster. Cada nodo es representado por un proceso MPI que contiene T threads.
Superstep i (Broadcast) Cada nodo obtiene Q−m consultas desde el broker y m consultas que se están resolviendo, necesitan una nueva iteración de tamaño K. Se
envı́a a todos los nodos Q peticiones de trozos de listas invertidas para cada término
involucrado en las consultas activas (nuevas y existentes).
Superstep i + 1 (Fetching) Cada nodo recepciona los mensajes y extrae desde memoria
comprimida o disco t·Q listas invertidas, cada una con K/P ı́temes, las cuales son
enviadas a los respectivos nodos que las solicitan.
Superstep i + 2 (Ranking)
Part 1 Cada nodo recibe t·Q·P listas invertidas de tamaño K/P , se reúnen las listas
por término y por consulta, y se almacenan en memoria contigua por consulta,
donde cada consulta ocupa espacio equivalente a t·K ı́temes de listas invertidas.
30
Part 2 (multi-threading) Cada nodo utiliza T ≤ Q threads para calcular el ranking de cada consulta que se está resolviendo. Cada consulta es procesada secuencialmente por un thread.
Part 3 Se actualiza el estado de cada consulta y si es necesario se solicita una nueva
iteración (superstep i + 1) o se envı́an los top-R resultados al broker.
Para lograr un balance de carga óptimo entre los threads, a cada thread se le permite
procesar el ranking de los documentos de un término en cada superstep.
En el modo Async los supersteps de BSP desaparecen, es decir, el proceso BSPonMPI
capaz de desplegar T threads durante ejecución se bloquea, y se desbloquea un total de
T procesos asincrónicos los cuales realizan paso de mensajes para ejecutar las mismas
iteraciones descritas para el modo Sync, pero ahora de forma completamente asincrónica.
Al igual que en el modo Sync, los procesos del modo Async trabajan sobre un solo ı́ndice
global para el nodo. En total existen P particiones del ı́ndice, una por cada nodo del
cluster. El enfoque de un proceso por núcleo supone la existencia de T · P particiones del
ı́ndice. En la experimentación presentada más abajo, los modos Sync y Async se comparan
contra un caso base dado por T · P procesos MPI, cada uno con su propia partición del
ı́ndice.
La secuencia de pasos descrita para el modo Sync supone que el ranking de documentos
no requiere que ellos contengan todos los términos de las consultas. En este caso, todas
las listas invertidas están ordenadas por frecuencia de manera decreciente. La parte más
costosa de este proceso, es decir, el ranking de documentos, justifica su paralelización con
OpenMP. Por lo tanto, sólo la parte 2 del superstep i + 2 utiliza todos los núcleos del
nodo. Los demás pasos ocupan una sola núcleo en cada nodo puesto que su costo es bajo
en comparación al costo del ranking.
En el caso que las consultas requieran documentos que contengan a todos los términos, es necesario realizar la intersección de las listas invertidas antes de enviar trozos de
tamaño K/P . Dado su alto costo, este proceso también justifica el uso de T threads. La
paralelización en este caso puede ser realizada de manera sencilla, puesto que los threads
pueden recorrer en paralelo las listas invertidas ordenadas por identificador de documento
(id doc) y considerar la intersección lineal en forma de merge sólo a los documentos que
satisfacen:
id doc
módulo T
31
= tid,
donde tid es el identificador del thread, lo cual equivale a realizar una partición virtual de
las listas invertidas en T threads.
Desde el punto de vista del modelo de programación, lo que ocurre es que los supersteps
de BSP son realizados a nivel de los P nodos, mientras que la computación realizada en
cada nodo durante un superstep dado es realizada en paralelo utilizando como máximo
tantos threads como núcleos disponibles. La computación de dichos threads es también
organizada en torno a los supersteps principales, el cual es un modo de computación
ampliamente promovido y optimizado por OpenMP en los ciclos parallel for por ejemplo.
En la implementación realizada para la experimentación, luego de establecer las listas
invertidas en espacio contiguo de memoria para cada consulta (Part 1, superstep i + 2), se
realiza un parallel for de OpenMP para procesar las consultas activas, lo cual eficientemente
despliega un equipo de T threads en los núcleos del nodo. La ventaja de este esquema es
que puede resultar relativamente sencillo transformar un servicio de ı́ndice construido
con el enfoque de nodos de un núcleo, a uno que utilice multi-threading en procesadores
multi-core.
3.3.
Experimentos sobre procesadores Intel
La primera plataforma experimental utilizada consta de dos nodos de 8 núcleos cada
uno. Cada nodo es un multiprocesador de memoria compartida que integra dos procesadores multi-core Intel’s Quad-Xeon cuyas caracterı́sticas principales se presentan en la
Tabla A.2 del Apéndice A.
Los experimentos fueron ejecutados asumiendo un tráfico alto y moderado de consultas.
Se asume que en todo momento existen Q consultas que fueron enviadas a cada nodo por
el broker. Se tiene un total de 16 núcleos considerando los dos nodos del cluster y los
experimentos consideran el valor constante T · P = 16 con el objeto de ir variando el
total P de procesos MPI y el total T de threads en cada proceso. También se realizan
experimentos con T · P = 8 para observar el rendimiento de un solo nodo bajo distintos
valores de T y P . Es importante tener presente que cada proceso MPI contiene su propia
partición del ı́ndice y que los threads de cada proceso MPI tienen acceso al ı́ndice local.
Los accesos siempre son de sólo lectura y por lo tanto no se requiere sincronización de
threads durante el procesamiento de las consultas.
Para ilustrar mejor el rendimiento comparativo entre las diferentes configuraciones
(P, T ) se muestran los resultados representados en relación al tiempo máximo de ejecución
32
alcanzado por cualquier (P, T ) del conjunto de experimentos. Todas las medidas fueron ejecutadas cinco veces utilizando una secuencia de muchas consultas y se observó variaciones
de menos del 5 % en los tiempos de ejecución finales de cada experimento.
Para el primer conjunto de experimentos, se tiene el caso para un alto tráfico de
consultas con Q · P = 512, mientras que el segundo representa un tráfico moderado de
consultas con Q · P = 128. Se ha llamado a estos dos experimentos runs-A y runs-B
respectivamente. Ambos experimentos fueron ejecutados en los dos nodos. También se
investigó la situación en un solo nodo por lo que se redujo a la mitad el tráfico de consultas,
runs-C y runs-D para un alto y moderado tráfico de consultas respectivamente.
Además, se estudiaron dos tipos algoritmos de ranking de documentos, uno llamado
light ranking donde los documentos rankeados utilizan una pequeña cantidad de tiempo
total de computación y heavy ranking método que demanda mayor computación. El tiempo
óptimo de ejecución se obtuvo para K = 128 en todos los experimentos y por lo tanto se
reportan resultados para ese valor de K. Para obtener los tiempos de ejecución de cada
configuración (P, T ) se utilizó un log de consultas de usuarios reales e ı́ndice invertido
tomado desde el buscador www.todocl.cl. En cada ejecución se ejecutan 127 mil consultas,
las cuales tienen en promedio 1.3 términos.
3.3.1.
Resultados
Las Tablas 3.1 muestran la comparación entre las distintas configuraciones (P, T ) con
tiempos de ejecución divididos por el máximo alcanzado por alguna de ellas, el cual siempre
resultó ser el caso (P = 8, T = 1). En este caso, se tienen P procesos MPI, cada uno
asignado a un núcleo distinto y cada uno con su ı́ndice local. En todos los experimentos,
el mejor rendimiento se alcanza cuando se tiene un proceso MPI por nodo y 8 threads
por cada proceso MPI. Es decir, estos resultados muestran la conveniencia de utilizar
el máximo de threads. Esto es más evidente para tráfico de consultas moderado como
resultado del desbalance de carga.
Las mejoras en rendimiento son más significativas en el caso en que se utilizan los dos
nodos. Esto se debe principalmente al incremento de la comunicación entre los P procesos
y la competencia por la interfaz de comunicación compartida. Por ejemplo, escogiendo la
configuración con mejor resultado (T=8) en runs-A y runs-B, se produce una diferencia
significativa en términos de rendimiento debido a los efectos de comunicación (alrededor de
10-35 %). Sin embargo, en tales situaciones, el esquema de paralelización hı́brido propuesto,
33
(a) Heavy Ranking. Un solo nodo
P T Runs-C Runs-D
1 8
1.04
1.16
2 4
1.04
1.16
4 2
1.04
1.12
8 1
1.00
1.00
(b) Light Ranking. Un solo nodo
P T Runs-C Runs-D
1 8
1.09
1.40
2 4
1.10
1.39
4 2
1.10
1.29
8 1
1.00
1.00
(c) Heavy Ranking. Dos nodos
P T Runs-A Runs-B
2 8
1.29
1,76
4 4
1.27
1,72
8 2
1.22
1,54
16 1
1.00
1,00
(d) Light Ranking. Dos nodos
P T Runs-A Runs-B
2 8
1.48
2.15
4 4
1.44
2.08
8 2
1.35
1.76
16 1
1.00
1.00
Tabla 3.1: Resultados para P procesos MPI con T threads cada uno para el caso en que
todos los núcleos se ocupan para resolver las consultas activas.
mejora el throughput en comparación con sus homólogos con cualquier tráfico de consultas
y ranking que reflejan mejoras de 1.35 a 2.15.
También es interesante estudiar el comportamiento de diferentes configuraciones cuando no todos los procesadores están disponibles. La Figura 3.1 muestra la aceleración para
dos nodos para tráfico alto y moderado de consultas, donde aceleración se ha definido de
la siguiente manera:
Speedup = executionTime(P = 2, T = 1)/executionTime( P, T )
Como era de esperar, el esquema propuesto basado en OpenMP presenta mejores resultados, aun cuando se limite el total de núcleos disponibles para procesar las consultas.
3.4.
Experimentos sobre procesadores Niagara
Como segunda plataforma experimental, con acceso a un mayor número de threads,
se utiliza el procesador Sun Microsystems UltraSPARC T1, cuyas caracterı́sticas se presentan en la Tabla A.1 del Apéndice A. Esencialmente, un núcleo de T1 es un procesador
multithreading de grano fino (FGM) [39] que alterna entre threads de ejecución por cada
ciclo para ocultar ineficiencias causadas por latencias tales como los accesos a la memoria
principal del procesador [92].
34
8
7
6
T=1
T=2
T=4
T=8
7
6
5
Speed-up
Speed-up
8
T=1
T=2
T=4
T=8
4
5
4
3
3
2
2
1
1
0
0
4
8
nThreads
16
4
8
nThreads
16
Figura 3.1: Speed-up obtenidos en dos nodos para tráfico alto y moderado de consultas.
El procesador T1 sólo proporciona una unidad de punto flotante para apoyar a los 8
núcleos, es decir, sólo un thread puede utilizar a la vez esta unidad. Por otra parte, hay
una penalización de 40 ciclos para acceder a la unidad de punto flotante. Las aplicaciones
de bases de datos tienen muy poco cómputo en punto flotante, por lo tanto, esta decisión
de diseño del procesador no es un obstáculo importante. Sin embargo, en el contexto de
este trabajo, una de las fases más costosas es la fase de ranking de documentos, operación
que utiliza aritmética de punto flotante para clasificar los documentos más relevantes
para una consulta. No es factible intentar una estrategia en que todos los núcleos estén
haciendo ranking de documentos en un momento dado del tiempo. Una solución es recurrir
a aritmética de punto fijo.
Por este motivo, se ha modificado la rutina del ranking de documentos para evitar
aritmética de punto flotante. Esta implementación usa datos de 32 bits de punto fijo
para representar la frecuencia de ocurrencia del término en cada documento de su lista
invertida. El ranking se realiza con la ayuda de una biblioteca de punto fijo que aprovecha
las ventajas de la ALU de cada núcleo. La sobrecarga que introduce la versión de punto
fijo es alrededor de 20 a 40 %, pero la penalización de introducir operaciones en punto
flotante en el ranking de documentos es mucho mayor que este costo.
La Figura 3.2 ilustra los beneficios de esta optimización. En la figura se muestra la
escalabilidad a través de un benchmark sintético que intenta imitar el tipo de cálculo
realizado en el proceso de ranking de documentos (se mezclan operaciones aritméticas
como divisiones, multiplicaciones, logaritmos y raı́ces cuadradas). El rendimiento de la
35
1
Floating-point
Fixed-point
0.9
Parallel Eficiency
0.8
0.7
0.6
0.5
0.4
0.3
0.2
0.1
0
2
4
8
16
32
nThreads
Figura 3.2: Benchmark sintético que intenta imitar el tipo de cómputo realizado en el
proceso de ranking utilizando aritmética de punto flotante y punto fijo.
versión en punto flotante es muy pobre mientras que su contraparte en punto fijo escala
razonablemente bien.
3.4.1.
Resultados
La similitud entre el código ejecutado por los threads durante el proceso de ranking
de documentos, donde todos ejecutan exactamente el mismo código sobre distintas listas
invertidas pertenecientes a consultas distintas, puede causar conflictos (penalizaciones) en
los recursos compartidos del procesador T1. Las consultas de usuarios tienden a contener
los mismos términos, especialmente en periodos en que ocurre un evento que atrae la
atención mundial de las personas.
Un primer conjunto de experimentos está destinado a estudiar una situación en que las
consultas activas con los mismos términos tienden a ser asignadas al mismo thread, pero
respetando el principio que todos los threads deben estar ocupados durante el ranking de
documentos.
La Figura 3.3 muestra el throughput (consultas por segundo) obtenido en el modo
Sync. Para estimar los beneficios potenciales de una heurı́stica de asignación de consultas
a los threads, se comparan dos escenarios extremos. La columna de color gris, corresponde
a la situación más desfavorable donde no hay términos repetidos en las consultas que
procesa cada thread, y entonces todos los threads compiten por acceder a la memoria
principal del T1. La columna de color negro, por el contrario, corresponde a la situación
36
45000
40000
35000
Throughput
30000
most adverse
most favorable
25000
20000
15000
10000
5000
0
1
2
4
8
16
32
nThreads
Figura 3.3: Rendimiento para el modo Sync.
más favorable: cada thread procesa consultas con términos idénticos y estos términos son
distintos a los que procesan los otros threads.
Los resultados muestran una ventaja de alrededor de 20 %. Una heurı́stica sencilla de
muy bajo costo es asignar por el término más frecuente en la consulta utilizando la regla
thread = id termino módulo T,
y luego corregir el desbalance de trabajo asignado a los threads. En cualquier caso, el
throughput es bastante satisfactorio en ambos escenarios. La aceleración aumenta proporcionalmente con el número de threads y en el escenario más favorable, el modo Sync
alcanza una aceleración de 22 utilizando 32 threads. Más allá de 32 threads el procesador
se satura.
Para el modo Async se supone el escenario en que su rendimiento es más eficiente que
el modo Sync según [63]. Es decir, tráfico bajo a muy bajo de consultas. En los siguientes
capı́tulos de esta tesis se estudiará en profundidad el caso en que el total de consultas
activas en un nodo es lo suficientemente grande como para mantener ocupados a los T
núcleos del nodo. Para ese caso se estudia el enfoque de usar T threads asincrónicos
o T threads sincrónicos para procesar tanto consultas (transacciones de lectura) como
actualizaciones del ı́ndice realizadas de manera on-line (transacciones de escritura). Para
el caso asincrónico se asume que cada thread procesa secuencialmente cada consulta o
actualización del ı́ndice, mientras que en el caso sincrónico se asume que todos los threads
participan de la solución de cada consulta o actualización. Adicionalmente, se estudia un
caso en que ambas técnicas de gestión de threads (e.g., un thread por consulta o todos los
37
threads en una consulta) se mezclan en una misma estrategia que puede operar tanto en
el modo Sync como Async.
En el siguiente experimento el supuesto es que el tráfico de consultas arribando al nodo
es tan bajo que los threads quedan desocupados. En este caso se puede recurrir al uso de
todos los threads asincrónicos para realizar el ranking de consultas individuales. Para
estos niveles de tráfico, la Figura 3.4 muestra que la ganancia proveniente del paralelismo
proporcionado por el multi-threading asincrónico no es realmente significativa. Con más de
4 threads el rendimiento no mejora. La razón es que el método de ranking de documentos
empleado en estos experimentos realiza terminación temprana del recorrido de las listas
invertidas de los términos de las consultas. Esto implica mantener una barrera que permite
filtrar los documentos de las listas invertidas que no son capaces de alcanzar un puntaje
lo suficientemente alto como para que existe una probabilidad razonable de que lleguen
a ser parte de los top-R resultados de la consulta. La actualización de dicha barrera
debe ser protegida mediante locks o secciones crı́ticas de OpenMP y esto conduce a una
serialización de los threads. No obstante, el tráfico de consultas es en definitiva muy bajo,
lo cual implica que no existe una carga suficiente en el nodo como para utilizar todos los
núcleos y un aspecto clave es que tampoco es relevante seguir optimizando el tiempo de
respuesta de una consulta individual dado que ya están en el rango de los mili-segundos. En
este tipo de aplicación lo relevante es optimizar el throughput y garantizar que el tiempo
de respuesta individual de cualquier consulta no suba más allá de una cota superior tal
como la fracción de segundo. Si la tasa de llegadas de consultas al nodo es muy baja,
entonces el throughput, es decir, la tasa de salida de consultas, no puede ser superior a la
tasa de llegada. A continuación se estudia el caso en que existe una carga de trabajo lo
suficientemente alta en el nodo como para mantener todos los núcleos ocupados.
Para tráfico alto es posible tener un número de consultas activas lo suficientemente
alto como para mantener a todos los threads ocupados de manera que subconjuntos de,
por ejemplo, 4 threads asincrónicos se dediquen a procesar secuencialmente las iteraciones
de una consulta a la vez. En la siguiente figura llamamos a este caso como OptimalAsync puesto que representa un caso en que cada vez que un grupo de 4 threads termina
de procesar completamente a una consulta, tienen disponible de inmediato una nueva
consulta en sus colas de entrada de mensajes. El objetivo de esta organización de threads
es explotar el nivel de intra-paralelismo presente en el método de ranking de documentos,
y el nivel de inter-paralelismo presente en la existencia de T /4 consultas disponibles para
procesamiento en todo momento en el nodo.
38
0.6
time per query
0.5
0.4
0.3
0.2
0.1
0
1
2
4
8
16
32
nThreads
Figura 3.4: Tiempo promedio (ms) por consulta utilizando el modo Async.
45000
40000
Async
Optimal-Async
Sync
35000
throughput
30000
25000
20000
15000
10000
5000
0
1
2
4
8
16
32
nThreads
Figura 3.5: Rendimiento para los modos Async y Sync con tráfico alto de consultas.
La Figura 3.5 muestra resultados para dos versiones del modo Async y el modo Sync
frente a tráfico alto de consultas. En esta figura las barras etiquetadas como Async representan el rendimiento en el modo Async para el caso en que todos los threads se utilizan
para resolver una consulta a la vez. Se observa que el rendimiento de esta estrategia es
muy pobre por las razones mencionadas anteriormente. El método de ranking posee un
paralelismo muy limitado y eso tiene un impacto en la poca escalabilidad de la estrategia.
La figura confirma lo observado en [63] para el caso de sistemas con T · P procesadores
con memoria distribuida, es decir, para tráfico alto se observa que Sync obtiene mejor
rendimiento que Async incluso también para el caso en que existen P nodos, cada uno con
T procesadores de memoria compartida, ejecutando T threads en cada núcleo.
39
45000
40000
Floating-point
Fixed-point
35000
throughput
30000
25000
20000
15000
10000
5000
0
1
2
4
8
16
32
nThreads
Figura 3.6: Throughput para diferentes representaciones numéricas: punto flotante (columna gris) y punto fijo (columna negra).
3.4.2.
Aritmética de punto fijo: Impacto en el rendimiento y validación
La Figura 3.6 analiza el impacto de la escalabilidad para una aritmética de punto fijo.
Se muestra el número de consultas por segundo que son capaces ser resueltas en la versión
sincrónica al realizar el ranking con aritmética de punto flotante o fijo. La versión de punto
flotante no escala bien con el número de threads.
Una última pregunta para concluir la discusión sobre el rendimiento en el procesador
T1 es si el uso de operaciones en punto fijo (en lugar de punto flotante) produce algún
efecto en (1) el conjunto final de documentos seleccionados como respuesta a una consulta
y (2) la posición relativa de los mejores R resultados. Estos dos puntos se evaluaron
experimentalmente mediante la ejecución tanto de punto fijo como flotante en las funciones
de ranking sobre el mismo ı́ndice invertido y conjunto de consultas.
Se realizaron dos pruebas con el conjunto de documentos generados en ambos casos
para cada consulta: conjunto A para resultados con punto fijo y conjunto B para resultados
obtenidos con punto flotante, con un par (A, B) para cada consulta considerada en el
experimento. La primera prueba calcula la relación de |A ∩ B|/|B| para los cuales se
obtuvieron valores cercanos a 1; se observó valores medios entre 0.99 y 1.0 a lo largo
de los pares (A, B) generados con las consultas. Esto indica que ambos conjuntos son
prácticamente iguales.
La segunda prueba calcula la correlación de Pearson de los conjuntos A y B para medir
la correlación entre posición relativa de un documento en A con respecto a su posición en
40
el conjunto B. Los valores obtenidos fueron también muy cercanos a 1, lo cual indica que
prácticamente no hay diferencia en la posición relativa de los documentos en A y en B.
Si bien el procesador T1 está descontinuado actualmente, estos resultados para punto
fijo no dejan de ser interesantes puesto que es sabido que los enteros pueden ser comprimidos de manera eficiente en ı́ndices invertidos y no ocurre lo mismo con los valores punto
flotante que se utilizan para almacenar las frecuencias de ocurrencia de los términos en
cada documento de las listas invertidas. Incluso se puede ir más lejos y diseñar una versión
del método vectorial de ranking de documentos que esté optimizada para punto fijo, con
aritmética de bits para aproximar logaritmos por ejemplo, y usar este método como una
primera función de ranking de bajo costo para descartar documentos que tengan pocas
probabilidades de quedar rankeados dentro de los primeros R presentados al usuario como
respuesta a su consulta.
3.5.
Conclusiones
La experiencia reportada en este capı́tulo constituye evidencia preliminar de que es
posible explotar de manera eficiente el paralelismo disponible en las arquitecturas multicore en aplicaciones de motores de búsqueda. La experimentación fue realizada suponiendo transacciones de sólo lectura y utilizando un método particular de procesamiento de
consultas (Sync/Async). En los capı́tulos restantes se profundizará en el caso general y
también en el escenario mucho más complejo del procesamiento concurrente de transacciones de lectura y escritura sobre nodos multi-core, y muy importantemente la existencia de
distintos tipos de caches de la aplicación en el nodo que también presentan la necesidad de
realizar control de concurrencia para evitar conflictos de lecturas y escrituras originadas
por los threads durante el procesamiento de las consultas (e.g, cache de listas invertidas y
cache de resultados parciales).
Una primera conclusión del estudio realizado es que es posible utilizar eficientemente
el multi-threading tanto en el caso en que los nodos realizan paso de mensajes asincrónico
entre ellos (modo Async) como en el caso en que los nodos realizan paso de mensajes
sincrónico (modo Sync). Los resultados para los dos nodos Intel muestran de que es más
eficiente programar los T · P núcleos disponibles como un sistema hı́brido con P nodos
de memoria distribuida (paso de mensajes) y cada nodo como un sistema de memoria
compartida con T threads. Una contribución del capı́tulo es una propuesta de multithreading perfectamente balanceado para el modo Sync el cual realiza round-robin de
41
las consultas a lo largo de varios supersteps. El método propuesto es particularmente
eficiente para tráfico alto de consultas. En términos de la arquitectura de software para un
motor de búsqueda construido sobre los modos Sync/Async, la solución propuesta para
el modo Sync permite abstraer el sistema en una arquitectura de T canales distribuidos
en P nodos, dentro de los cuales se aplica round-robin al procesamiento de las consultas
activas, inyectando una consulta por canal.
Luego el estudio se focalizó en las maneras de explotar el paralelismo disponible en un
sólo nodo multi-core, para lo cual se utilizó el procesador Niagara dado que posibilita el uso
eficiente de más threads que los posibles de utilizar en un solo nodo Intel (32 threads versus
8 threads respectivamente). Esto permitió estudiar escalabilidad dentro de un nodo visto
como un sistema que recibe consultas desde el exterior y calcula las respuestas utilizando
la partición del ı́ndice asignada al nodo. El paso de mensajes con otros nodos puede ser
asincrónico o sincrónico. En los experimentos se estudian ambos casos en función de la
intensidad de tráfico aplicada al nodo. Para bajo tráfico se supone el modo Async mientras
que el modo Sync se utiliza para tráfico alto.
Una contribución de los experimentos con el Niagara es la propuesta y muestra de la
factibilidad de eficiencia de lo que denominamos paralelismo a nivel vertical y paralelismo
a nivel horizontal y combinaciones entre ambos. Por paralelismo vertical o paralelismo
inter-consulta nos referimos al enfoque de utilizar un solo thread para procesar secuencialmente una consulta individual. En este caso el paralelismo disponible desde los núcleos del
procesador se explota asignando una consulta distinta a cada thread donde cada thread es
asignado a un núcleo. Para esto es necesario tener un tráfico de consultas lo suficientemente alto como para alimentar a los threads tan pronto como estos terminan de procesar sus
consultas. Paralelismo horizontal o paralelismo intra-consulta, consiste en asignar varios
threads para acelerar el procesamiento de consultas individuales. Como es de esperar, la
factibilidad del paralelismo vertical dependerá del nivel de tráfico arribando al nodo. Por
otra parte, el paralelismo horizontal depende fuertemente del tamaño del ı́ndice invertido
y del método de ranking aplicado en el motor de búsqueda. No obstante, los experimentos
con el modo Async muestran de que, aún en el caso de un ı́ndice relativamente pequeño y
método de ranking que presenta bajo nivel paralelismo como el usado en los experimentos,
es posible explotar eficientemente los núcleos del procesador mediante una combinación
entre paralelismo vertical y horizontal (en la Figura 3.5 denominamos a esta alternativa
como Optimal-Async).
Respecto de la ingenierı́a de software para motores de búsqueda, la experiencia adquirida en el estudio presentado en este capı́tulo muestra que OpenMP puede ser utilizado de
42
manera efectiva en la paralelización de módulos con gran costo en tiempo de ejecución. La
implementación de los códigos C++ utilizados en la experimentación, tuvo como punto de
partida una implementación sobre MPI desarrollada para la experimentación presentada
en [63]. Los cambios que fue necesario introducir para soportar multi-threading a nivel de
nodo multi-core fueron menores y se limitaron a unas pocas directivas OpenMP aplicadas
a ciclos parallel for utilizados para procesar el ranking de documentos. La implementación
del paso de mensajes entre nodos y la gestión de memoria para aspectos tales como el
estado de las consultas que se están resolviendo, no tuvo modificaciones.
43
Capı́tulo 4
Transacciones Concurrentes
En este capı́tulo se propone realizar procesamiento sincrónico por lotes a nivel de
threads como una alternativa más eficiente y escalable que el enfoque convencional basado en threads asincrónicos. Se muestra que este tipo de paralelismo es eficiente para
procesar transacciones concurrentes de lectura y escritura en nodos de búsqueda implementados sobre ı́ndices invertidos. Sobre esta forma de paralelismo se diseñan algoritmos
de procesamiento de consultas (transacciones de lectura) y actualización on-line del ı́ndice
(transacciones de escritura) que muestran ser particularmente eficientes frente a situaciones de tráfico alto de transacciones. Las estrategias propuestas son generales y pueden ser
aplicadas a cualquier tipo de motor de búsqueda.
Para un cluster con P ×D nodos, donde cada nodo contiene procesadores multi-core que
permiten ejecutar eficientemente T threads, y donde los nodos se utilizan para mantener
P particiones del ı́ndice invertido, cada partición replicada D veces, se le llama “nodo
de búsqueda” a cada nodo en que se despliega un servicio de ı́ndice que opera en modo
exclusivo en el nodo. Se asume que a cada nodo llegan mensajes conteniendo transacciones
de lectura y escritura que deben ser procesadas en tiempo real. Dichos mensajes son
depositados en una única cola de transacciones, las cuales son retiradas por los threads
para darles servicio. Para el conjunto de P ×D nodos de búsqueda, se asume que el valor de
P es tal que permite a un solo thread procesar completamente una transacción de manera
secuencial dentro de una cota superior para el tiempo de respuesta. Se asume que el nivel de
replicación D es tal que es posible alcanzar el throughput objetivo para la tasa de llegada
de transacciones en régimen permanente. Lo que propone este capı́tulo son soluciones
eficientes para enfrentar alzas bruscas en el tráfico de transacciones, especialmente alzas
44
Figura 4.1: Arquitectura de un Nodo de Búsqueda.
en las transacciones de sólo lectura [13, 49, 95] las cuales representan las consultas de los
usuarios del motor de búsqueda.
4.1.
Planteamiento del Problema
En la literatura para el diseño de motores de búsqueda es posible encontrar numerosas
estrategias de indexación y caching, las cuales pueden ser combinadas de distintas maneras
para alcanzar un rendimiento eficiente y escalable a sistemas de gran tamaño y tráfico
de consultas. La combinación especı́fica depende de los requerimientos que dependen de
aspectos tales como el tamaño del ı́ndice, el uso de memoria secundaria y/o compresión
del ı́ndice, y uso de distintos tipos de caches. En particular, el diseño de un servicio de
ı́ndice para un motor de búsqueda puede tener la forma mostrada en la Figura 4.1. El
cache de listas invertidas está formado por un gran número de bloques que se utilizan
para almacenar grupos de ı́temes de las listas invertidas del tipo (doc id, term freq). Cada
lista, por lo general ocupa varios de estos bloques. Un cache secundario almacena los
resultados top-K de las consultas más frecuentes resueltas por el nodo de búsqueda. La
lista invertida completa de cada término se puede mantener en el disco local al nodo o
en memoria principal en formato comprimido, es decir, se trata de una zona de memoria
relativamente más lenta que la memoria proporcionada por los caches.
Un camino factible para los threads se ilustra en la Figura 4.2. En este ejemplo, las
consultas llegan a la cola de entrada del nodo de búsqueda. Un número determinado de
45
threads se encarga de resolver las consultas. Cada vez que un thread obtiene una nueva
consulta desde la cola de entrada, se comprueba si la misma ya está almacenada en el
cache de top-K (1). Si hay un hit en este cache, el thread responde con los identificadores
de los K documentos almacenados en la entrada correspondiente (2). De lo contrario, el
thread verifica si todos los bloques de las listas invertidas de los términos de la consulta
están en el cache de listas (3). Si es ası́, el thread utiliza esos bloques para resolver la
consulta mediante la aplicación de un algoritmo de ranking de documentos (3.a). Una
vez que el thread obtiene los identificadores de los documentos top-K para la consulta,
se guarda esta información en el cache de top-K aplicando una polı́tica de reemplazo de
entradas del cache (4) y se envı́a un mensaje con la respuesta a la consulta (5). Si faltan
bloques de listas invertidas en el cache de listas (3.b) el thread deja la consulta en una
cola de requerimientos de memoria secundaria o ı́ndice comprimido (otro thread gestiona
esta transferencia de bloques) y comprueba si en esta segunda cola hay otra consulta
cuya transferencia de bloques al cache de listas ha sido completada para proceder con su
solución (3.c), (4) y (5).
El enfoque anterior de procesamiento de consultas utilizando varios threads, supone
la existencia de una estrategia capaz de abordar adecuadamente la ejecución concurrente
de las operaciones de lectura/escritura causadas por los threads en los caches y las colas.
Dado que las entradas de los caches son reemplazadas por nuevos datos constantemente,
es muy posible encontrar lecturas y escrituras concurrentes siendo ejecutadas en la misma entrada de un cache, y por lo tanto es necesario imponer un protocolo que permita
sincronizar los accesos de los threads a los recursos compartidos para prevenir conflictos
de lectura/escritura. Una solución intuitiva es aplicar locks a las entradas del cache. Sin
embargo, el problema es más complicado que eso como se describe a continuación.
Luego de calcular los identificadores de los documentos top-K para una consulta, es
necesario desalojar una entrada del cache de top-K para almacenar los nuevos identificadores top-K. Durante el mismo intervalo de tiempo, otros threads pueden estar leyendo
las entradas del cache para decidir si calcular o no los resultados de sus respectivas consultas. Si el control de concurrencia se realiza a nivel grueso utilizando un lock o unos
pocos locks para proteger todas las entradas del cache, los threads pueden ser serializados
severamente. Por otro lado, mantener un lock por cada entrada del cache incrementa la
concurrencia significativamente. Sin embargo, hacer que cada thread ejecute un lock por
cada entrada que lee mientras recorre el cache secuencialmente, puede también producir
un efecto de serialización de threads y dado que el costo de cada lock no es despreciable, el
tiempo de ejecución se puede degradar y aumentar con el número de threads. Como se ha
46
Figura 4.2: Camino seguido por las consultas.
señalado en [27], este problema puede aliviarse utilizando estrategias como SDC donde un
80 % de las entradas son de sólo lectura (cache estático) y un 20 % son de lectura/escritura
(cache dinámico).
En el cache de listas invertidas, es necesario seleccionar rápidamente un número suficientemente grande de bloques de cache para reemplazarlos por los bloques que contienen
los pares (doc id, term freq) de la nueva lista invertida siendo recuperada desde memoria
secundaria o desde un ı́ndice comprimido. Para este propósito se puede utilizar una cola
de prioridad la cual permite determinar de manera exacta y eficientemente los bloques
de mayor prioridad de desalojo en el cache. Alternativamente, es posible utilizar una lista enlazada administrada con la heurı́stica “mover al frente”, la cual permite obtener de
manera aproximada los bloques con mayor prioridad de ser desalojados del cache. Para la
polı́tica LRU, la estrategia de mover al frente deberı́a funcionar relativamente bien, mientras que para otras polı́ticas de reemplazo tales como Landlord [30], la cola de prioridad
es la alternativa de mejor rendimiento.
Si los bloques que contienen los ı́temes de las listas invertidas son modificados por
threads escritores, el problema de concurrencia se agrava, con la dificultad adicional que
debe haber consistencia entre los bloques almacenados en las entradas del cache y las
respectivas listas invertidas mantenidas en memoria secundaria o en memoria principal en
formato comprimido. Por ejemplo, si una estrategia de control de concurrencia se basa en
el bloqueo de las listas invertidas asociadas con los términos de la consulta, entonces, esto
también deberı́a provocar el bloqueo de todas las entradas del cache que mantienen los
47
bloques de las respectivas listas invertidas (o alternativamente el ocultamiento de estas
entradas del algoritmo de reemplazo de entradas).
Además, mientras un thread lee o actualiza una lista invertida ℓ, el algoritmo de
administración de cache no debe seleccionar para reemplazo los bloques asignados a ℓ.
Al igual que en el caso anterior, una solución sencilla es ocultar temporalmente todos los
bloques de ℓ antes que el thread los utilice. Este proceso también requiere de una correcta
sincronización de los threads y por lo tanto puede degradar el tiempo de ejecución del
thread que está operando sobre la lista ℓ.
El modificar una lista invertida también presenta problemas de coherencia entre la
nueva versión y las posibles entradas para el mismo término que ya están almacenadas
en el cache de top-K. Si el nuevo ı́tem que se inserta en la lista invertida tiene una
frecuencia lo suficientemente alta o mayor a la versión anterior, entonces es probable que
el documento asociado pueda también estar presente en la respectiva entrada del cache de
top-K. En este caso, es más práctico invalidar todas las entradas del cache relacionadas
con los términos que aparecen en las respectivas consultas, ya que la única manera de
estar seguros es volver a calcular el ranking de la consulta. Esto genera requerimientos
adicionales de control de concurrencia en el cache, los cuales imponen una carga adicional
en uso de locks o algún otro mecanismo de sincronización de threads.
4.2.
Solución Propuesta
La solución propuesta está basada en la observación de que si las consultas y actualizaciones del ı́ndice fueran procesadas de manera secuencial por un solo thread principal,
no serı́a necesario formular una solución para cada uno de los problemas anteriores. Por
lo tanto, lo que se propone es asignar un thread principal en el nodo de búsqueda a seguir
secuencialmente los pasos descritos en la Figura 4.2, el cual recurre al despliegue de tantos threads auxiliares como núcleos tenga el procesador para paralelizar las operaciones de
mayor costo tales como el ranking de documentos, actualización del ı́ndice y la administración de los caches. La granularidad de las demás operaciones en términos de costo es muy
pequeña y las puede ejecutar el mismo thread principal secuencialmente utilizando uno de
los núcleos del nodo sin pérdida de eficiencia tal como fue comprobado en las estrategias
presentadas en el Capı́tulo 3. Se utiliza paralelismo sincrónico para organizar de manera
eficiente las operaciones realizadas por los threads. El número Nt de threads disponibles
para ser utilizados por el thread principal puede variar dinámicamente en todo momento
48
Figura 4.3: Organización del ı́ndice invertido donde cada lista invertida es almacenada en
un número de bloques y cada bloque se encuentra lógicamente dividido en trozos que son
asignados a cada threads. Cada trozo está compuesto por un número de ı́temes de la lista
invertida, (doc id, freq). En este ejemplo, el primer bloque el término term 0 es procesado
en paralelo por los threads th0, th1, th2 and th3.
con el objetivo de permitir la ejecución de software de mantención y administración en el
nodo de búsqueda durante su operación en producción.
4.2.1.
Query Solver
Las consultas son resueltas en el componente llamado query solver, el cual es el encargado de recorrer las listas invertidas de todos los términos de la consulta y aplicar el
método de ranking de documentos. Los elementos de cada lista invertida se agrupan en
bloques consecuentes con el tamaño de las entradas del cache de listas invertidas. Se utilizan Nt threads para hacer el ranking de los documentos y cada thread trabaja sobre una
sección distinta de cada lista invertida, emulando un ı́ndice particionado por documentos.
Por ejemplo, la Figura 4.3 (organización de la estructura de datos utilizada) muestra esta
situación. La figura muestra que para el primer bloque del término term 0, existen Nt = 4
threads trabajando en paralelo en el mismo bloque.
Dado que el rendimiento de los procesadores multi-core es altamente dependiente de la
localidad de datos, durante el procesamiento de una consulta todos los threads deberı́an
maximizar el acceso a memoria local, es decir, accesos a datos almacenados en el cache
privado de los respectivos núcleos, donde los threads están siendo ejecutados. Con este fin,
cada thread mantiene una estructura de datos llamada fast track destinada a aumentar la
localidad de referencias y por lo tanto las consultas se procesan iterativamente siguiendo
dos pasos principales:
49
Fetching. El primer paso consiste en leer desde la memoria principal del nodo (cache
de listas) un trozo de lista invertida de tamaño K por cada término presente en la
consulta, y por cada uno de ellos almacenar un trozo distinto de tamaño K/Nt en la
memoria fast track de cada uno de los Nt threads que participan en la solución de la
consulta. En rigor, tan pronto como un segmento de tamaño K ha sido almacenado
en las Nt memorias locales, él o los bloques respectivos del cache pueden quedar
disponibles para operaciones de actualización dirigidas a esa sección del ı́ndice o
pueden ser desalojadas del cache.
Ranking. En el segundo paso, los Nt threads ejecutan en paralelo el ranking de
documentos y, si es necesario, se solicitan nuevos trozos de tamaño K de las listas
invertidas (fetching) para finalmente producir los K documentos mejor rankeados
para la consulta. El proceso de ranking puede implicar la determinación de los documentos que contienen todos los términos de la consulta, lo que implica realizar una
operación de intersección entre las listas invertidas involucradas.
De esta manera el proceso de ranking puede requerir varias iteraciones de la secuencia
(Fetch, Rank) en ser completado. Los documentos son asignados a los threads utilizando
la regla id doc módulo Nt , es decir, partición por documentos, lo cual aumenta la localidad
de operaciones costosas tales como la intersección de listas invertidas. Para facilitar el uso
de memoria contigua en las transferencias, los bloques que mantienen los pares (doc id,
term freq) de las listas invertidas se pueden mantener explı́citamente particionados en Nt
sub-bloques, donde Nt indica el total de threads a ser utilizados en régimen permanente, o
implı́citamente divididos en Nt particiones almacenando todos los pares (doc id, term freq)
asignados a una partición dada en una región contigua del bloque.
Por otro lado, en la cola de entrada del nodo de búsqueda también pueden haber
operaciones o transacciones de escritura que representen el caso en que nuevos documentos
son insertados en el ı́ndice (inserción), o que documentos existentes sean reemplazados por
una nueva versión (actualización). La Figura 4.4 ilustra el caso en que un nuevo documento
es insertado en el ı́ndice, donde se muestra el efecto de la inserción en un conjunto de
términos y secciones de las respectivas listas invertidas. Una operación de escritura puede
modificar cualquier bloque de una lista invertida y este puede encontrarse en el cache o
en memoria secundaria o memoria comprimida. Una transacción de escritura se procesa
distribuyendo uniformemente los términos en los Nt threads disponibles y cada thread
modifica las respectivas listas en paralelo.
50
Figura 4.4: Inserción/Actualización de documentos.
Para consultas de tipo OR (disjunctive queries), las listas invertidas son ordenadas
por frecuencia del término en los documentos, mientras que para consultas de tipo AND
(conjunctive queries) las listas son ordenadas por identificador de documento. Esto último
requiere de la intersección de todas las listas invertidas involucradas. En este caso, es útil
mantener las listas ordenadas por identificador de documento ya que las operaciones de
intersección pueden tomar tiempo lineal respecto de la longitud de las listas. Para apoyar
la inserción eficiente en listas ordenadas por frecuencia, se mantienen espacios vacı́os en
los bloques y se aplica una estrategia similar a la empleada en el B-Tree. Es decir, cuando
un bloque se llena, se crea uno nuevo bloque moviendo al nuevo bloque elementos tomados
desde el bloque afectado y el bloque adyacente a éste.
4.2.2.
Algoritmo Bulk Processing (BP Local)
La estrategia propuesta utiliza paralelismo sincrónico por lotes (bulk-synchronous parallelism [100]) para posibilitar el paralelismo a nivel de threads y donde las barreras de
sincronización de threads permiten evitar los posibles conflictos de lectura y escritura entre
las transacciones. Básicamente los threads son sincronizados al final de una secuencia de
iteraciones realizadas para resolver una consulta o al final de la inserción o actualización
de un documento en el ı́ndice invertido. Entre dos barreras de sincronización consecutivas
se procesa completamente una transacción de lectura o escritura. Los detalles se presentan
en la Figura 4.5, la cual presenta un caso donde el query solver tiene en su cola de entrada
un lote de transacciones de lectura y de escritura, y las procesa todas de una vez antes de
entregar los resultados.
En el algoritmo presentado en la Figura 4.5 se utiliza una forma relajada de sincronización de barrera que se ha diseñado para reducir el costo de la sincronización. Tı́picamente,
una barrera de sincronización estándar para memoria compartida se implementa utilizando una operación “conditional wait” para hacer que los threads se bloquen en el punto
51
F es un trozo de tamaño O(K) de memoria local al procesador.
D conjunto de documentos rankeados para una consulta.
R documentos mejor rankeados para una consulta.
Lt es la lista invertida de un término t.
tid es el identificador del thread.
pair( d, ft ) es un ı́tem de lista invertida, donde ft es la frecuencia
del término t en el documento d.
BulkProcessing( tid )
for each transaction Tn in input queue do
if Tn.type == QUERY then /* read transaction */
empty( D )
for each term t in Tn.query do
for each block b in Lt [tid] do
F = fetch( Lt [tid][b] )
rank( F , D )
endfor
endfor
Rtid = getLocalTopK( D )
else /* write transaction */
for each term t in Tn.termsForThread[tid] do
d = Tn.docId
if Tn.type == UPDATE then
update( Lt [tid], pair(d,ft ) )
else
insert( Lt [tid], pair(d,ft ) )
endif
endfor
endif
ObliviousBarrier( tid, Tn.id mod Nt )
if (Tn.type == QUERY) and (Tn.id mod Nt == tid) then
Rglobal = getGlobalTopK( { R1 , R2 , ..., RNt } )
ouputQueue[ tid ].store( Rglobal )
endif
endfor
EndBulkProcessing
Figura 4.5: Pasos ejecutados por cada thread en paralelo con los otros Nt − 1 threads.
52
de sincronización. El último threads despierta a todos los otros para lo cual también se
utiliza un contador protegido mediante un lock de acceso exclusivo. Cuando el valor del
contador es igual al total de threads, se indica el fin de la barrera y los Nt − 1 threads
bloqueados se activan. Para un sistema con gran número de threads, la competencia por
acceder al contador y a la instrucción de espera condicional puede degradar el rendimiento
al transformarse en una fuente de serialización de threads.
Basados en la idea de sincronización relajada (oblivious synchronization) para el modelo BSP sobre memoria distribuida [12], en la Figura 4.6 se propone una versión del mismo
concepto para memoria compartida, la cual usa locks y esperas condicionales. Básicamente
la idea consiste en ampliar a Nt el total de locks y esperas de manera de reducir la competencia de los threads por acceder a esos recursos compartidos. Las operaciones condWait(a,
b) y condSignal(a) tienen la misma semántica que sus homólogos en Posix-threads, es decir, se bloquea b y se duerme el thread con una variable de condición a y posteriormente
se despierta a los threads que esperan en la variable a.
Aparte de la mejora en rendimiento proveniente de la reducción de competencia por
recursos, el costo de los locks y esperas condicionales es amortizado por la vı́a del siguiente
concepto que el algoritmo BP explota para lotes de Nt o más consultas. La última parte
del procesamiento de las transacciones de lectura contiene una parte secuencial en que se
determinan los mejores K documentos para la consulta. Una ventaja de la sincronización
relajada es que permite que esa fase sea realizada para Nt consultas en paralelo. Lo mismo
es posible con la sincronización de barrera estándar, pero la diferencia aquı́ es que cada
threads puede ingresar a esa fase tan pronto como los otros threads han terminado de
calcular sus mejores documentos locales para la consulta. Este solapamiento tiene el efecto
de amortizar los costos de la sincronización.
4.2.3.
Alzas bruscas en el tráfico de consultas
Un nodo de búsqueda debe asegurar que el tiempo de respuesta de consultas individuales no esté por encima de un determinado lı́mite superior, incluso en una situación
de tráfico alto de consultas. En este escenario, es conveniente retrasar las operaciones de
escritura hasta un periodo de tráfico moderado o bajo.
La estrategia BP es lo suficientemente flexible como para enfrentarse a estos casos ya
que puede acomodar varias heurı́sticas orientadas a absorber periodos de saturación de
tráfico. Por ejemplo, el query solver puede imponer un lı́mite superior para el número de
53
ObliviousBarrier( my tid, tid )
set Lock( mutex[tid] )
counter[tid]++
if my tid == tid then
if counter[tid] < Nt then
condWait( cond[tid], mutex[tid] )
else
counter[tid]=0
endif
else if counter[tid] == Nt then
counter[tid]=0
condSignal( cond[tid] )
endif
unset Lock( mutex[tid] )
EndObliviousBarrier
Figura 4.6: Sincronización relajada ejecutada por cada thread con identificador my tid.
bloques que son recorridos durante el procesamiento de cada consulta y puede mantener
cola round-robin de consultas en ejecución. El lı́mite superior se puede ajustar en función
del número de consultas siendo procesadas en el query solver. Esto es importante ya que
la longitud de las listas invertidas siguen la ley de Zipf, donde un número reducido de
términos tienen listas muy grandes mientras que los restantes son tamaños relativamente
pequeños. Ası́, un nodo de búsqueda debe garantizar una cota superior para el tiempo de
respuesta de consultas individuales, llegando incluso a producir respuestas aproximadas
para las consultas con listas con un número muy grande de bloques, mientras que debido
a la aplicación de round-robin puede resolver de manera exacta las consultas con listas
invertidas más pequeñas.
4.3.
Un Caso Especial: BP global
La estrategia BP descrita en la sección anterior apoya el enfoque de que cada transacción es procesada con la ayuda de todos los Nt threads. La idea es procesar una o más
transacciones en el recorrido secuencial del thread principal por los distintos componentes
ejemplificados en la Figura 4.2. Esto supone la existencia de una cantidad suficiente de
paralelismo en las transacciones de manera que sea posible utilizar eficientemente los Nt
54
threads en cada transacción. No obstante, tal como se muestra en el Capı́tulo 3, Figura 3.5,
cuando el nodo de procesadores multi-core puede utilizar eficientemente una cantidad Nt
grande de threads, es posible también de manera eficiente dividir los threads en grupos y
aplicar paralelismo en cada grupo. En el caso de BP, es fácilmente posible aplicar paralelismo sincrónico utilizando para cada transacción una cantidad de Nt /n threads manteniendo
n instancias de BP que toman transacciones desde la cola de entrada de transacciones.
Siempre el objetivo es resolver la mayor cantidad de transacciones posibles en cada visita
que el thread principal hace al query solver de la Figura 4.2.
Por otra parte, existen servicios de ı́ndice, especialmente en motores de búsqueda
vertical (e.g., publicidad on-line), que están diseñados para trabajar con todo el ı́ndice
descomprimido almacenado en memoria principal. Es decir, no utilizan ningún tipo de
cache y resuelven completamente cada consulta que reciben sin importar si son repetidas.
Por lo general el ı́ndice es pequeño y no posee una cantidad de paralelismo lo suficientemente grande como en el caso supuesto para la estrategia BP. Notar que BP utiliza
indexación particionada por documentos. No obstante, para el caso descrito en esta sección, es perfectamente posible utilizar indexación particionada por términos para las listas
invertidas pequeñas y asumir indexación particionada por documentos para listas de gran
tamaño que permitan un buen nivel de paralelismo. En definitiva, ambos enfoques de indexación pueden ser utilizados intercambiablemente ya que una copia completa del ı́ndice
está almacenada en la memoria principal del nodo.
Cuando no existe el requerimiento de interactuar con otros elementos como caches,
entonces es posible relajar el requerimiento de procesar una transacción por cada sincronización. La idea es mantener, en todo momento, varias transacciones en distintos grados
de avance respecto de su solución completa. Sin embargo, es necesario contar con una
solución al problema de la concurrencia de lectores y escritores, respetando al menos el
concepto de transacción atómica en el sentido que si una transacción de lectura llega primero al nodo, entonces un escritor que llega posteriormente deberı́a modificar después
de la transacción de lectura una lista invertida que tenga un término presente en las dos
transacciones. Si hay más de un término en común, esta regla deberı́a respetarse en todas
las listas invertidas. Si se trata de un motor de búsqueda donde el cumplimiento de esta
restricción de serialidad no es relevante, al menos deberı́a respetarse la restricción a nivel
de lista invertida individual para evitar que la transacción de lectura omita documentos
durante el proceso de ranking.
Debido a que la estrategia BP puede avanzar el proceso de una transacción a lo largo de
varios supersteps o sincronizaciones de barrera, entonces es posible ajustar el momento en
55
que una transacción de escritura modifica una lista invertida. Cuando se trata de consultas
de tipo OR las listas invertidas se mantienen ordenadas por frecuencia de ocurrencia de
los términos en los documentos. En este caso, la transacción de escritura debe recorrer la
lista invertida secuencial examinando el primer y último ı́tem (doc id, term freq) de cada
bloque de la lista con el fin de determinar el bloque en que debe ser insertado el nuevo ı́tem
(doc id, term freq). Si cada thread mantiene una cola FIFO conteniendo las transacciones
que deben ser ejecutadas en el superstep actual, entonces el proceso de búsqueda del bloque
e inserción en el bloque seleccionado se puede realizar en el orden necesario para respetar
la restricción de serialidad y atomicidad de las transacciones.
Si se impone que a cada transacción se le permita procesar hasta una cierta cantidad
B de bloques en cada superstep, entonces es suficiente con procesar las transacciones en el
orden dado por la cola FIFO para respetar la restricción de serialidad tanto a nivel de lista
individual como a nivel de todas las listas de términos comunes a ambas transacciones.
Para mejorar el balance de carga, además de imponer que cada transacción visite B bloques
durante un superstep, también se puede imponer que en el superstep no se procesen más
de una cierta cantidad Q de términos. Con esto se puede acotar la cantidad máxima de
trabajo realizada por cualquiera de los threads durante el superstep.
En la Figura 4.7 se presenta el algoritmo propuesto para el caso de procesamiento
sincrónico particionado por términos (indexación global). Este es el algoritmo recomendado
para métodos de ranking que presentan un nivel bajo de paralelismo tales como servicios
de ı́ndice que contienen ı́ndices invertidos muy pequeños (e.g., publicidad) donde una gran
mayorı́a de las listas frecuentemente referenciadas por las consultas son listas con muy
pocos ı́temes (doc id, term freq) o para casos en que el método de ranking utiliza una
estrategia de terminación temprana muy agresiva que en la práctica conduce a examinar
pocos ı́temes.
Para el caso de consultas de tipo AND, dado que las listas se mantienen ordenadas por
el identificador de documentos, la inserción de un nuevo documento es sencilla puesta que
debe ir directamente al último bloque de la lista (o crear uno nuevo al final si dicho bloque si
está completo). La actualización de un documento existente requiere de un procedimiento
similar al empleado para las listas ordenadas por frecuencia, es decir, es necesario realizar
una búsqueda saltando de bloque en bloque.
Por lo general, las eliminaciones de documentos en las listas invertidas requieren reubicar ı́temes (doc id, term freq) y posiblemente reducir la cantidad de bloques y es por
lo tanto una operación muy cara en tiempo de ejecución. Especialmente si las listas están
56
BulkProcessing-TermPartitioning( tid )
while true do
for each operation Op in the thread FIFO queue do
for each block b in the next B blocks of list Lt with t = Op.term do
if Op.type == QUERY then
D = rank( Ft [b] )
Rt = updateLocalTopK( Rt , D )
else
if ft ≥ Ft [b][0] and ft ≤ Ft [b][K − 1] then
d = Op.doc id
if Tn.type == UPDATE then
update( Lt [b], pair(d,ft ) )
else
insert( Lt [b], pair(d,ft ) )
endif
endif
endfor
endfor
Barrier()
for each finished transaction Tn do
if (Tn.type == QUERY) and (Tn.id mod Nt == tid) then
for each term t in Tn do
Rglobal = updateGlobalTopK( Rglobal , Rt )
endfor
ouputQueue[ tid ].store( Rglobal )
endif
freeTransactionState( Tn )
endfor
if tid == 0 then
Assign newly arriving transactions to each thread FIFO queue
so that each read/write operation on a term t is assigned to
thread with tid = t mod Nt . At all time each queue maintains
upto Q terms, so when a queue gets full the remaining terms
are assigned in the next supersteps in FIFO order.
endif
Barrier()
endwhile
EndBulkProcessing
Figura 4.7: Pasos ejecutados por cada thread en paralelo con los otros Nt − 1 threads.
57
comprimidas. En términos prácticos es más eficiente marcar como “borrado” los documentos que son eliminados y realizar periódicamente, en momentos de tráfico bajo de
consultas, la recolección de basura en las listas invertidas que hayan alcanzado un cierto
porcentaje de relevante de elementos eliminados. Por otra parte, si un determinado bloque
contiene elementos eliminados, es posible que esos espacios sean ocupados por la inserción
de nuevos documentos de manera que realizar recolección de basura a intervalos largos de
tiempo puede ayudar a amortizar el costo total de las eliminaciones.
4.4.
Sobre la Administración de los Caches
Para evitar la fragmentación del espacio de memoria principal, los caches se implementan como un conjunto de bloques del mismo tamaño. En nuestro caso, estos bloques
coinciden con los bloques de las listas invertidas.
Independientemente de la polı́tica de admisión y desalojo aplicada en los caches del
nodo de búsqueda, el tipo de dato abstracto empleado para seleccionar los elementos de
mayor prioridad de ser reemplazados, debe implementar eficientemente la recuperación de
los nE elementos de mayor prioridad y la inserción de nI elementos con prioridad aleatoria.
En particular, para el caso del cache de listas normalmente será necesario extraer nE ≫ 1
elementos e inserta nI = nE elementos con la misma prioridad, donde siempre se aplica
una extracción inmediatamente seguida de una inserción. En este caso, los elementos son
bloques de listas invertidas y cuando se trata de bloques recuperados desde memoria
secundaria, es conveniente que dichos bloques sean almacenados en memoria contigua para
reducir la cantidad de accesos a disco y aumentar la velocidad de transferencia de bloques
entre el disco y la memoria principal. Para el cache top-K conviene extraer nE = Nt
elementos en un solo acceso cuando el query solver trabaja con Nt threads en paralelo
e insertar nI = Nt nuevos resultados top-K en esas nE entradas del cache. Por otra
parte, generalmente los caches para nodos de búsqueda se diseñan para un número fijo
de entradas. Se asume que el cache tiene un total de n = m · h entradas y que a cada
entrada se le asocia un valor numérico que representa la prioridad de ser reemplazada con
un nuevo contenido.
A continuación, se propone una cola de prioridad que permite realizar las operaciones
de extracción e inserción anteriormente descritas de manera eficiente. La estructura de
datos es ilustrada en la Figura 4.8. Los nodos ovalados verticales llamados buckets, son
todos del mismo tamaño y contienen m elementos donde cada elemento está asociado
58
Figura 4.8: Cola de prioridad para la administración de caches.
con un bloque del cache. Los m elementos referencian a m bloques ubicados en memoria
contigua. Los bloques de las listas invertidas son asociados a estos elementos de manera
dinámica de acuerdo con la polı́tica de administración del cache.
Sobre los h buckets existe un árbol binario completo destinado a seleccionar en tiempo
O(log h) el nodo que contiene el elemento de mayor prioridad. Cada hoja del árbol es
asociada con un bucket. Se define como elemento de mayor prioridad al elemento de
menor valor numérico. Los elementos almacenados en los nodos son pares (valor prioridad,
puntero bloque cache). Se mantiene un arreglo Prio[ 1 ... h ] que contiene el valor numérico
del elemento de mayor prioridad en cada uno de los h buckets. La función del árbol es
determinar el mı́nimo global entre los h mı́nimos locales, es decir, el elemento de mayor
prioridad entre los h elementos que son representantes de sus respectivos buckets.
El árbol es un arreglo de enteros CBT[ 1 ... 2 h − 1 ] tal que cualquier elemento
CBT[ i ] del arreglo puede contener un valor en el rango 1 ... h. En particular, el valor
Prio[ CBT[1] ] es el menor valor numérico entre las n = m · h prioridades almacenadas en
la cola de prioridad. El árbol está representado de manera implı́cita en el arreglo donde
cualquier nodo interno en la posición CBT[ p ] tiene a sus hijos en las posiciones CBT[ 2 p ]
y CBT[ 2 p + 1 ].
59
Para hacer que la raı́z CBT[1] del árbol haga referencia a la mejor prioridad en todo
el conjunto, recursivamente desde cada hoja hasta la raı́z, se aplica la siguiente asignación
a cada nodo interno CBT[ p ] del árbol,
if Prio[ CBT[ 2 p ] ] < Prio[ CBT[ 2 p + 1 ] ] then
CBT[ p ] = CBT[ 2 p ]
else
CBT[ p ] = CBT[ 2 p + 1 ]
endif
Es decir, el árbol se usa para mantener un torneo binario entre los valores almacenados en
el arreglo Prio[ 1 ... h ]. Cada vez que se modifica un valor Prio[ i ] se debe re-establecer el
invariante a partir del nodo interno ubicado en la posición ⌊i⌋ hasta la raı́z. Esta operación
toma costo O(log h). La ventaja sobre el heap clásico es que cada paso de actualización
en la estructura de datos requiere de una comparación entre dos valores de prioridad.
También, todas las actualizaciones no requieren llegar hasta la raı́z del árbol.
Las inserción de un nuevo par (valor prioridad, puntero bloque cache) se hace en el
bucket referenciado por i = CBT[ 1 ]. El nuevo par ocupa el lugar del par con la menor
prioridad en el bucket i y por lo tanto se modifica el valor de Prio[ i ], y entonces es necesario
actualizar el camino hacia la raı́z del árbol CBT. Dado que tı́picamente, por ser una
aplicación para caches, el nuevo par tendrá la mayor prioridad en el bucket, lo cual conduce
a la realización de un recorrido secuencial de los m − 1 pares almacenados en bucket i para
determinar el par con la menor prioridad. Sin embargo, si se trata de la inserción de una
secuencia de pares con la misma prioridad este costo O(m) será amortizado rápidamente.
Por ejemplo, cuando son bloques de una lista invertida éstos podrı́an incluso superar
la capacidad del bucket i, involucrando a nuevos buckets seleccionados por los sucesivos
valores de CBT[1] obtenidos luego de cada actualización del árbol. La Figura 4.8 muestra
tal situación, en la cual la inserción de los bloques de una lista invertida involucra ocupar
tres buckets completos (a), (b) y (c), y parte de un cuarto bucket (d) no identificado en
la figura. También se muestran las activaciones de la rutina de actualización del árbol. El
resultado de cada actualización le permite a la rutina de inserción saber cual es el siguiente
bucket.
La paralelización de esta cola de prioridad puede ser realizada en forma exacta o de
manera aproximada para producir un mejor rendimiento. A continuación se describen dos
propuestas de métodos de paralelización.
60
4.4.1.
Paralelización con Nt threads
Para sistemas de memoria distribuida, una estrategia de paralelización del problema de
colas de prioridad que alcanza un buen rendimiento en la práctica, consiste en simplemente
mantener P colas de prioridad secuenciales. Desde cada cola se extraen nE elementos para
formar un conjunto con nE · P elementos y extraer desde ellos los nE elementos de mejor
prioridad. Los restantes nE ·(P −1) elementos se re-insertan en las P colas de prioridad. La
inserción de nI elementos se hace simplemente insertando nI /P elementos en cada cola.
Para una cola de prioridad con N elementos y suponiendo n = nE = nI , el costo BSP
de esta estrategia es O( (n · P ) · log(n · P ) + log(N/P ) + (n · P ) · g + ℓ ) donde el g es el
costo de la red comunicación y ℓ la latencia de la sincronización entre procesadores. Los
factores (n·P ) pueden ser mejorados a O(n), con gran probabilidad, realizando unas pocas
iteraciones en que cada procesador extrae O(nE /P ) elementos, y al final de cada iteración
preguntar a los P procesadores si tienen elementos con mejor prioridad que los actuales
nE elementos seleccionados como los de mejor prioridad en forma global.
La misma estrategia puede ser implementada en la memoria compartida de un nodo
de búsqueda con la ventaja que (a) g ≫ g1 + g2 donde g1 representa el costo de transferir
datos desde el cache L2 al cache L1 en el procesador multi-core y g2 el costo de transferir
datos desde la memoria compartida al cache L2 del procesador, y que (b) las colas de
prioridad individuales aumentan la localidad de referencias de los threads.
Si en el cache se requiere insertar un total de n elementos provenientes de una lista
invertida, tal que n ≫ Nt , entonces una estrategia razonable es mantener P = Nt árboles
CBT y utilizarlos para determinar los Nt buckets de menor prioridad. Cada CBT se hace
cargo de h/Nt buckets distintos. Una vez que se han obtenido los Nt buckets, cada thread
reemplaza los elementos con prioridad igual al elemento de menor prioridad en el bucket.
Tan pronto como se encuentra un elemento con prioridad distinta en cada bucket o uno
o más de éstos se repletan de nuevos elementos, es necesario volver a repetir este proceso
determinando los siguientes Nt buckets de menor prioridad. Cada uno de estos ciclos
es delimitado por una barrera de sincronización de threads o superstep. Esta estrategia
produce resultados exactos sólo cuando en los buckets se almacenan elementos con la
misma prioridad.
La estrategia de los Nt CBTs falla cuando se trata de un sistema diseñado para utilizar un número variable de threads. Para este caso, una solución es utilizar la estrategia
propuesta en [58, 57]. En este caso, los nodos internos del CBT contienen c · NtS elemen-
61
tos, donde NtS es el total de threads utilizados en régimen permanente y c ≥ 1. En cada
nodo interno se mantiene el invariante de que un nodo padre tiene mayor prioridad que
un nodo hijo si todos los elementos referenciados por el padre tienen menor prioridad que
los que referencia el nodo hijo. Cuando se viola el invariante lo que se hace es intercambiar elementos entre los dos nodos hasta re-establecerlo. Una actualización toma un total
de log( h/(c · NtS ) ) pasos, donde en cada paso un número variable de threads Nt ≤ NtS
puede participar en el re-establecimiento del invariante. Una implementación para PosixPThreads de esta estrategia para el procesador Intel fue presentada en [67]. Los resultados
muestran que esta estrategia escala razonablemente bien con el número de threads.
Cuando los buckets son de un tamaño lo suficientemente grande como para hacer
probable la existencia de elementos con distintas prioridades, las estrategias anteriores que
utilizan tanto el enfoque de Nt colas de prioridad para seleccionar los buckets o una sola
cola de prioridad con nodos internos conteniendo referencias a c·NtS buckets, se obtiene una
solución que produce resultados aproximados. En teorı́a, es posible que existan resultados
aproximados, pero en la práctica, para ı́ndices con listas invertidas conteniendo una gran
cantidad de bloques con la misma prioridad, es muy poco probable que esto ocurra (en la
experimentación presentada en el Capı́tulo 5 no se produjo ningún caso).
Un solución pragmática para listas invertidas y para sistemas que utilizan Nt threads
en forma permanente, es aplicar los principios de indexación local manteniendo Nt colas
de prioridad independientes, cada una compuesta de los arreglos CBT[ 1 ... h/Nt − 1 ]
y Prio[ 1 ... h/Nt ] y un conjunto de h/Nt buckets de tamaño m/Nt . Los bloques de
las listas invertidas se distribuyen uniformemente al azar en las Nt colas de prioridad
utilizando una regla tal como tid = id bloque módulo Nt . La experimentación presentada
en el Capı́tulo 5 considera esta implementación puesto que puede trabajar de manera
natural con la estrategia BP de la Figura 4.5.
Por otra parte, una estrategia aproximada para un sistema con número variable de
threads puede utilizar el CBT con nodos internos de tamaño c · NtS y asignar un thread a
cada bucket de tamaño m, es decir, en este caso se aplica el principio de indexación global.
62
4.5.
Conclusiones
Las estrategias propuestas respetan el principio básico de toda transacción, es decir,
la atomicidad. En nuestro caso, esto equivale a que el resultado de una transacción sea
consistente con el estado de las listas invertidas al momento en que se inicia la ejecución de
la transacción. Es decir, durante el recorrido de las listas invertidas y respectivo ranking de
documentos, ningún otro thread escritor modifica el contenido de la lista. Esto es evidente
para la estrategia BP presentada en la Figura 4.5, puesto que todos los threads procesan
en paralelo cada transacción.
En la estrategia BP presentada en la Figura 4.7, el empleo de la cola FIFO en cada
thread y la manera en que son procesados los bloques de las listas invertidas, cualquier
escritor que haya arribado al query solver después de un lector, va a modificar la lista
invertida después de que el lector la haya leı́do.
La serialidad de transacciones tiene que ver con el concepto de que el resultado final de
un conjunto de transacciones concurrentes debe ser equivalente al resultado que se obtiene
cuando las transacciones son ejecutadas secuencialmente. Las estrategias BP son seriales
puesto que toman secuencialmente las transacciones desde la cola de entrada y respetan
ese orden en la ejecución de las operaciones de lectura y escritura sobre las listas invertidas.
Para la cola de prioridad propuesta en este capı́tulo, la recomendación es utilizar
el método exacto para caches top-K debido a su poco volumen de datos, y el método
aproximado para caches de listas debido a su gran volumen de datos. Tı́picamente en una
sistema en producción, el tamaño del cache de listas es de alrededor del 20 % del tamaño
del ı́ndice comprimido, mientras que el tamaño del cache de top-K es de un 5 %.
63
Capı́tulo 5
Comparación de Estrategias
En este capı́tulo se presenta un estudio que compara la estrategia BP con diversas
alternativas para el mismo problema tomadas desde la literatura de bases de datos y
programación concurrente. Se utilizaron implementaciones reales ejecutadas sobre el procesador Intel. Como cargas de trabajo se utilizaron benchmarks diseñados para simular
motores de búsqueda en texto (index service) y un servicio de user click-through el cual
debe responder en tiempo real con información de documentos visitados por usuarios anteriores para mejorar el ranking de documentos realizados por el motor de búsqueda.
5.1.
Estrategias alternativas
Para comparar el algoritmo BP de la Figura 4.5 se han escogido de la literatura actual
una serie de estrategias de control de concurrencia para transacciones de lectura y escritura,
las cuales fueron adaptadas al contexto de esta tesis. Sus principales caracterı́sticas se
resumen en la Tabla 5.1. Los pseudo-códigos con los detalles de cada estrategia se presentan
en el Apéndice B. En esta tabla, la primera columna indica si la estrategia de control
de concurrencia es serializable o no. La segunda columna indica el tipo de paralelismo
explotado en el procesador multi-core, es decir, un thread por transacción (prefijo “C”)
o todos los threads sobre una única transacción (prefijo “P”). La tercera columna indica
la primitiva de sincronización que se utiliza para sincronizar los threads y ası́ prevenir
conflictos entre lectores y escritores operando sobre las listas invertidas. Se enfatiza que
las estrategias de sincronización de transacciones deben evitar este tipo de conflictos,
64
Estrategia
BP
CR
TLP1
TLP2
RBLP
RTLP
Serializable
Yes
Yes
Yes
Yes
No
No
Tipo de Parallelismo
PRT and PWT
CRT and PWT
CRT (term sharing) and CWT
CRT (no sharing) and CWT
non-atomic: CRT and CWT
non-atomic: CRT and CWT
CRT= concurrent read transactions
CWT= concurrent write transactions
Sincronización
Thread Barrier
Thread Barrier
R/W List Locking
Exclusive List Locking
Block Locking
Term Locking
PRT= read transaction in parallel
PWT= write transaction in parallel
Tabla 5.1: Estrategias de sincronización de transacciones.
puesto que éstos pueden provocar un fallo en la aplicación y llegar incluso a abortar la
ejecución del programa.
Las estrategias más restrictivas garantizan la serialización de las transacciones, es decir,
se mantiene la ilusión de la ejecución en serie. Las otras estrategias pueden ser aplicadas
en escenarios donde no se exige serialidad, y potencialmente pueden ofrecer un mejor
rendimiento puesto que son más relajadas en exigencias de sincronización. Al igual que la
estrategia BP, todas las estrategias utilizan el mismo esquema de fetching y ranking sobre
la estructura de fast-track del procesador. Para hacer más justa la comparación, todas las
estrategias contienen una cola de entrada de transacciones donde, en varios casos, el paso
fundamental hacia la serialización es hacer un lock de acceso exclusivo a esta cola para
retirar una transacción y luego liberar el lock tan pronto como el respectivo protocolo de
sincronización lo permita.
El programa C++ utilizado en las implementaciones es, en su mayor parte, idéntico
para cada estrategia y los únicos cambios en el código tienen que ver con las primitivas
de sincronización y algunas estructuras de datos mı́nimas de apoyo. En otras palabras,
los tiempos de ejecución de cada estrategia se ven afectados solamente por los efectos
del protocolo de sincronización y no por detalles de programación. En cualquier caso, las
implementaciones son eficientes puesto que logran aceleraciones muy cercanas al óptimo
cuando se aumenta el número de threads.
La estrategia Concurrent-Reads (CR) es similar a la estrategia BP de la Figura 4.5 en
términos de la realización de las operaciones de escritura utilizando todos los threads en
paralelo. La diferencia es que permite el solapamiento entre transacciones de lectura a lo
largo del tiempo mediante la asignación de un thread a cada consulta. Cada thread resuelve
de principio a fin la consulta asignada. Antes de procesar una transacción de escritura,
65
CR espera a que todas las transacciones concurrentes de lectura en curso hayan finalizado
(más detalles en la Figura B.1 del Apéndice B). De esta manera, la estrategia CR explota
el paralelismo disponible desde todas consultas activas (transacciones de sólo lectura) en
un periodo de tiempo. Dado que algunos threads pueden quedar sin trabajo antes del
comienzo de la siguiente transacción de escritura, se probó una versión de CR donde los
threads sin trabajo cooperan con los demás para terminar las transacciones de lectura
en curso. Sin embargo, no se observó mejoras en el rendimiento debido a la computación
extra que es necesario hacer para la planificación de threads. En un sentido esta estrategia
es similar a la estrategia BP presentada en la Figura 4.7 y puede ser utilizada como una
alternativa a la estrategia BP de la Figura 4.5 cuando las listas invertidas son relativamente
pequeñas.
La estrategia Term-Level-Parallelism (TLP) permite el solapamiento entre transacciones de lectura y escritura. Cada transacción es ejecutada por un único thread y se utilizan
locks de exclusión mutua para proteger las listas invertidas involucradas. El lock sobre un
término en realidad implica un lock sobre la respectiva lista invertida en forma completa.
Para garantizar la serialidad de transacciones, un thread dado no libera el lock sobre la
cola de entrada de transacciones hasta que no ha adquirido los locks de todos los términos
que debe utilizar. Posteriormente, cada lock es liberado tan pronto como la respectiva lista
invertida ha sido procesada. Se implementaron dos versiones de TLP.
La primera versión, llamada TLP1, permite transacciones simultáneas de sólo lectura
sobre las mismas listas invertidas. Para esto fue necesario implementar locks de lectura,
es decir, locks que no bloquean a los threads conteniendo transacciones de lectura y que
tienen términos en común, mientras que los threads conteniendo transacciones de escritura
son bloqueados si comparten términos con las transacciones de lectura. Las transacciones
de escritura utilizan locks de acceso exclusivo a las listas invertidas. Los locks son servidos
en modo FIFO lo cual garantiza la serialidad (más detalles en la Figura B.2).
La segunda versión, llamada TLP2, es mucho más fácil de implementar puesto que
se impide el solapamiento de transacciones de lectura o escritura que tengan términos en
común. Esta estrategia es similar al protocolo de 2 fases de bases de datos. Una vez que
el thread adquiere el lock sobre la cola de entrada de transacciones, procede a solicitar
un lock de acceso exclusivo para cada uno de los términos involucrados en la transacción.
No existe posibilidad de deadlocks debido a que el lock de acceso exclusivo a la cola de
entrada de transacciones no se libera hasta que el thread ha adquirido todos los locks (más
detalles en la Figura B.3).
66
También se estudiaron dos estrategias adicionales que relajan los requisitos de serialidad y atomicidad de las transacciones. Al no adherir a estos dos requisitos, el nivel de
concurrencia de los threads aumenta significativamente. Para motores de búsqueda, donde
las consultas de usuarios son respondidas en una fracción de segundo, ambos requisitos
podrı́an ser prescindibles. Para otras aplicaciones tales como sistemas de bolsa electrónica
no es tan claro que estos requisitos sean prescindibles. Por otra parte, estas dos estrategias son buenos representantes del mejor rendimiento que podrı́a alcanzar una estrategia
optimista que cumpla al menos el requisito de atomicidad por la vı́a de abortar y reejecutar una transacción para la cual se detecta que las versiones de las listas invertidas
involucradas no son las que existı́an al momento de iniciar su ejecución.
La primera estrategia, llamada Relaxed-Block-Level-Parallelism (RBLP), es similar a
TLP pero no obliga a los threads a hacer un lock de la lista invertida completa por
cada término involucrado tanto para transacciones de lectura como para transacciones de
escritura. Las listas invertidas están compuestas de un conjunto de bloques. Por lo tanto,
los threads van haciendo locks exclusivos sólo de los bloques de las listas invertidas que se
están procesando en un momento dado del tiempo. Es decir, es minimal, se aplica el lock
sólo en la sección de la lista invertida donde es necesario prevenir que dos o más threads
lean y escriban los mismos ı́temes (doc id, term freq) de la lista. En la implementación de
esta estrategia, debido a que los bloques pueden eventualmente llenarse de ı́temes y por
lo tanto dividirse, lo que se hace es aplicar locks exclusivos sobre dos bloques consecutivos
en los casos en que el número de ı́temes dentro del bloque actual esté más allá de un valor
umbral B − Nt donde B es el tamaño del bloque (es decir, existe una probabilidad de
que una transacción de escritura divida el bloque que se ha llenado siguiendo el algoritmo
descrito al final de la Sección 4.2.1).
Dado que no existe el requerimiento de serialidad y atomicidad, existen tres optimizaciones adicionales que incrementan el nivel de concurrencia. Primero, los threads liberan el
lock de acceso exclusivo sobre la cola de entrada de transacciones tan pronto como copian
los datos de la transacción a memoria local. Segundo, los threads liberan los locks sobre los
bloques tan pronto como van siendo copiados al fast-track, y por lo tanto el ranking de los
documentos presentes en el bloque puede involucrar una gran cantidad de cómputo sin que
este retardo afecte el nivel de concurrencia. En los experimentos realizados se detectó que
el costo del ranking de documentos es la parte dominante en el tiempo de ejecución. Tercero, durante la solución de una consulta, el ranking de documentos avanza considerando un
sólo bloque por cada término en oposición al caso de primero procesar todos los bloques
67
de un término para pasar al siguiente. Esto reduce el nivel de contención entre threads
que comparten términos. Los detalles de RBLP se presentan en la Figura B.5.
El segundo enfoque, llamado Relaxed-Term-Level-Parallelism (RTLP), es similar a
RBLP pero antes de acceder a un bloque se hace un lock del término en lugar de lock de bloques como en RBLP. Esto evita la necesidad de tener que decidir si hacer un
lock de dos bloques consecutivos durante el recorrido de cada lista invertida. Otra ventaja
práctica es la significativa reducción en el tamaño del arreglo de locks que es necesario
definir en la implementación. En rigor, en RTLP se requiere definir una variable de tipo
lock por cada término del ı́ndice invertido, mientras que en RBLP es mucho más que esa
cantidad puesto que por cada término se tienen muchos bloques. En la implementación de
RBLP y RTLP en realidad se utiliza un arreglo de variables de tipo lock mucho menor y
se utiliza hashing sobre ese arreglo para requerir un lock de término y de bloque. Naturalmente, para un mismo tamaño de arreglo, la probabilidad de colisiones es mucho menor
en el caso de RTLP que en RBLP. Los detalles de la estrategia RTLP se presentan en la
Figura B.4.
5.2.
Evaluación utilizando un Servicio de Indice
En los experimentos que siguen se considera que siempre el conjunto de listas invertidas
requeridas para procesar una transacción se encuentra disponible en el cache de listas.
Las evaluaciones realizadas no consideran el costo de administración del cache. En una
experimentación separada se evalúa el rendimiento de la propuesta de cola de prioridad
para la administración del cache de listas. La motivación para separar ambos experimentos
es en primer lugar simplicidad y precisión de los resultados, y en segundo lugar es el hecho
de que si la estrategia BP se comporta mejor que las alternativas asincrónicas en el caso
de listas siempre residentes en memoria principal, entonces éste mismo enfoque debe ser
aplicado a la administración del cache.
Los experimentos se realizaron en el procesador Intel descrito en el Apéndice A. Se
asume que la tasa de llegada de transacciones es lo suficientemente alta como para mantener ocupado a todos los threads. La métrica principal de interés es la tasa de salida de
transacciones, es decir, el throughput. Esto porque todas las estrategias son capaces de
resolver una transacción en una fracción de segundo.
Las listas invertidas fueron construidas desde una muestra de la Web de UK proporcionada por Yahoo!. La Figura 5.1 muestra la distribución del largo de las listas invertidas y la
68
6000
Web UK
Web UK
5000
15000
Number of terms
Length of inverted lists per term
20000
10000
4000
3000
2000
5000
1000
0
0
0
100
200
300
400
Terms
500
600
700
(a)
0
100
200
300
400
Document IDs
500
600
(b)
Figura 5.1: (a) Largo de listas invertidas ordenadas de mayor a menor largo, y largos
mostrados cada 50 listas (Web UK). (b) Distribución de número de términos en los documentos.
distribución de términos en los documentos. Para las transacciones de lectura se utilizó un
log de consultas real que contiene búsquedas de usuarios sobre dicha Web. El texto de los
documentos que participan en las transacciones de escritura fue tomado desde la misma
Web. La generación del flujo de transacciones (traza) que llegan al nodo de búsqueda se
realiza mezclando de manera aleatoria las consultas de usuarios con documentos existentes
en la base de documentos.
La Figura 5.2 muestra la proporción de términos que figuran en el log de consultas y
que están contenidos en alguno de los documentos de la base de documentos. En el eje
y se refleja la proporción de términos del log que están en los documentos, y el eje x la
cantidad de documentos en unidades de diez mil. La figura indica que existe gran probabilidad de conflictos de escrituras/lecturas con al menos un término cuando se procesan
inserciones/actualizaciones de documentos en conjunto con las consultas de los usuarios.
También se muestra la distribución de la cantidad de términos que tienen las consultas de
los usuarios.
La proporción de nuevos documentos y consultas se controla por parte del generador
de trazas con el fin de poder disponer de un campo de exploración lo suficientemente
extenso. Además el generador de trazas también permite elegir el grado de conflicto entre
consultas consecutivas, es decir la posibilidad de encontrar términos idénticos en búsquedas
próximas. Esto se traduce en accesos simultáneos a las lista invertidas correspondientes.
69
1
14000
Normalized Frequency
12000
0.8
Frequency
10000
0.6
0.4
8000
6000
4000
0.2
2000
0
0
1
0
20
40
60
80
100
120
140
(a)
2
3
4
5
6
Number of Terms
7
8
(b)
Figura 5.2: (a) Frecuencia de ocurrencia de los términos del log de consultas en los documentos de la base de texto. (b) Distribución del número de términos en las consultas.
Un grado de conflicto alto se traduce en que la mayorı́a de las consultas poseen algún
termino común con las 40 búsquedas más próximas. De igual modo un grado de conflicto
bajo conlleva la existencia de, como máximo, un 10 % de las búsquedas con algún término
en común en las 40 consultas más próximas.
5.2.1.
Sobre el tamaño de bloque
Antes de presentar los resultados de cada estrategia, se presentan resultados que muestran el impacto del tamaño de bloque para las listas invertidas. El tamaño del bloque afecta
de maneras distintas a cada estrategia. Por ejemplo, para las estrategias RBLP y RTLP
existe un compromiso entre la cantidad de computación requerida para administrar cada
bloque, donde bloques más grandes demandan una menor cantidad de tiempo consumido
en administración, y el nivel de concurrencia que es posible alcanzar, donde bloques más
pequeños mejoran el nivel de concurrencia alcanzado por la estrategia, pero a la vez se
incrementa el número de locks ejecutados, los cuales consumen cierta cantidad de tiempo
de ejecución. Para todas las estrategias el tamaño de bloque también afecta la localidad
de referencias en los caches del procesador.
El rendimiento para consultas tipo OR se muestra en la Figura 5.3. Los resultados
indican que cada estrategia alcanza un óptimo para un mismo tamaño de bloque para
la lista procesada en cada thread. Este óptimo se alcanza para bloques de tamaño 64.
70
70
CR
BP
TLP1
TLP2
RTLP
RBLP
135
nQueries/sec
65
nQueries/sec
140
CR
BP
TLP1
TLP2
RTLP
RBLP
60
130
125
120
55
115
50
110
16
32
64
128
256
512
1024
2048
16
32
64
nThreds
128
256
512
1024
2048
1024
2048
nThreds
280
550
500
260
nQueries/sec
nQueries/sec
450
240
CR
BP
TLP1
TLP2
RTLP
RBLP
220
400
CR
BP
TLP1
TLP2
RTLP
RBLP
350
300
200
250
180
16
32
64
128
256
512
1024
2048
16
nThreds
32
64
128
256
512
nThreds
Figura 5.3: Throughput alcanzado por las diferentes estrategias con distinto tamaño de
bloque.
En la estrategia BP el óptimo para los valores del bloque son mayores a 64, pero éstos
deben ser divididos por el total de threads para obtener el tamaño de bloque de la lista
invertida siendo procesada en cada thread. Este valor de tamaño de bloque se mantiene
para consultas AND y los distintos tipos de cargas de trabajo aplicadas a los threads. Los
resultados mostrados en las secciones que siguen utilizan este tamaño óptimo de bloque
en cada estrategia.
5.2.2.
Resultados
La Figura 5.4 muestra el rendimiento alcanzado cuando la carga de trabajo contiene
transacciones de lectura y escritura. Estos son los resultados para consultas de tipo OR
(disjunctive queries). Cada grupo de barras muestra las ejecuciones para 1, 2, 4 y 8 threads.
En cada gráfico, la carga de trabajo tiene una proporción distinta de transacciones de
71
600
400
CR
BP
TLP1
TLP2
RTLP
RBLP
800
Transactions per Second
500
Transactions per Second
900
CR
BP
TLP1
TLP2
RTLP
RBLP
300
200
700
600
500
400
300
200
100
100
0
0
1
2
4
8
1
2
nThreads
0 % writers
1200
1400
CR
BP
TLP1
TLP2
RTLP
RBLP
1200
Transactions per Second
Transactions per Second
800
8
10 % writers
CR
BP
TLP1
TLP2
RTLP
RBLP
1000
4
nThreads
600
400
200
1000
800
600
400
200
0
0
1
2
4
8
1
nThreads
2
4
8
nThreads
20 % writers
30 % writers
Figura 5.4: Throughput (transacciones por segundo) alcanzado por las estrategias para
diferentes tasas de escritura y consultas OR, y un total de 40 mil transacciones.
lectura y escritura, manteniendo constante el número de total de transacciones procesadas.
Puesto que la inserción o actualización de un documento toma un tiempo mucho menor
a la solución de una consulta, a medida que aumenta el porcentaje de transacciones de
escrituras en la carga de trabajo, se reduce el tiempo total de ejecución. Es decir, la tasa
de transacciones procesadas por segundo (throughput) aumenta. En promedio, el tiempo
de solución de una transacción está por debajo de los 16 milisegundos.
Los resultados de la Figura 5.4 muestran dos aspectos relevantes para la estrategia
BP. Primero, la estrategia BP escala eficientemente con el número de threads independientemente de la proporción de transacciones de escritura. BP supera a todas las demás
estrategias a medida que aumenta el número de threads y para más de dos threads la
72
aceleración es super-lineal debido a la gran tasa de hits en el cache L2 que alcanza esta
estrategia. Sólo RTLP y RBLP logran un rendimiento competitivo, pero a expensas de
la pérdida de atomicidad y serialidad de las transacciones. Segundo, BP logra un rendimiento bastante similar a la estrategia CR para el caso de sólo lecturas (0 % writers).
Esto representa a los motores de búsqueda convencionales que no actualizan sus ı́ndices
de manera on-line y asignan un thread concurrente a cada consulta activa. Los resultados
indican que cualquiera de las estrategias pueden ser usadas en este caso, pero la ventaja
de utilizar BP es que el mismo enfoque de paralelización sincrónica puede ser aplicado a la
administración de los caches de top-K y listas, y por lo tanto se logra un mejor rendimiento
ya que en los caches ocurren operaciones de lectura y escritura en una proporción de 50 %.
Las estrategias CR y TLP1 solamente alcanzan resultados satisfactorios para tasas
bajas de transacciones de escritura. TLP2 no tiene un buen rendimiento debido a que las
transacciones de lectura incluyen términos que coinciden ocasionalmente. Estas coincidencias causan demoras sustanciales en la ejecución de los threads que comparten términos.
Esto debido a que cuando un thread obtiene el lock de la lista compartida, no lo libera
hasta ejecutar la operación de ranking de documentos sobre la lista completa. Luego los
threads que le suceden deben esperar por la liberación del lock. También, los términos que
se repiten en las transacciones tienden a ser términos populares que, por lo mismo, tienen
listas invertidas muy largas y el tiempo de ejecución del ranking es proporcional al largo
de las listas. Esto introduce retardos que degradan el rendimiento significativamente.
Una tendencia similar se observa cuando la carga de trabajo incluye consultas AND
(conjunctive queries). Los resultados se muestran en la Figura 5.5. Para este caso el rendimiento de BP respecto de las otras estrategias es relativamente mejor que para el caso de
consultas OR. En general, el throughput alcanzado para consultas AND es muy superior
que el alcanzado para las consultas OR puesto que el ranking se hace sobre listas invertidas
mucho más pequeñas, es decir, las listas que resultan de realizar la intersección entre las
listas de los términos de cada consulta.
Los gráficos de la Figura 5.6 muestran resultados para una carga de trabajo en la que
se aumenta significativamente el nivel de coincidencia entre términos. Ver figuras 5.6.a y
5.6.b para consultas OR, y figuras 5.6.c y 5.6.d para consultas AND. Para consultas OR
los resultados muestran la misma tendencia a lo observado en los gráficos de la Figura 5.4.
Para consultas AND, los resultados muestran que el rendimiento relativo de RBLP y RTLP
con respecto a BP, es relativamente mejor que el alcanzado en los gráficos de la Figura 5.5.
En este caso el ranking opera sobre listas mucho más pequeñas y por lo tanto su costo es
mucho menor que en consultas OR, lo cual reduce los tiempos de espera por locks.
73
5000
3500
CR
BP
TLP1
TLP2
RTLP
RBLP
6000
Transactions per Second
4000
Transactions per Second
7000
CR
BP
TLP1
TLP2
RTLP
RBLP
4500
3000
2500
2000
1500
5000
4000
3000
2000
1000
1000
500
0
0
1
2
4
8
1
2
nThreads
0 % writers
8000
10000
CR
BP
TLP1
TLP2
RTLP
RBLP
9000
8000
Transactions per Second
Transactions per Second
6000
8
10 % writers
CR
BP
TLP1
TLP2
RTLP
RBLP
7000
4
nThreads
5000
4000
3000
2000
7000
6000
5000
4000
3000
2000
1000
1000
0
1
2
4
0
8
1
nThreads
2
4
8
nThreads
20 % writers
30 % writers
Figura 5.5: Throughput (transacciones por segundo) alcanzado por las estrategias para
diferentes tasas de escritura y consultas AND, y un total de 40 mil transacciones.
Los resultados mostrados a continuación tienen que ver con tiempos promedio de
transacciones individuales para consultas OR. La Figura 5.7.a muestra el tiempo promedio de respuesta para las transacciones, mientras que la Figura 5.7.b muestra el tiempo
de respuesta máximo observado cada 1000 transacciones procesadas utilizando 8 threads.
La estrategia BP logra el menor tiempo esperado en cada caso, especialmente en el tiempo
máximo de respuesta para una transacción. Los resultados para BP son evidentes, puesto que por construcción de BP el tiempo está acotado a la máxima lista procesada en
el perı́odo. Debido al tipo de paralelismo que se utiliza en BP, esta estrategia garantiza
tiempos máximos no superiores a O( ℓmax /Nt ) donde ℓmax es el tamaño máximo de las
listas invertidas. Bajo situaciones de tráfico alto de transacciones, los resultados de las
74
600
400
CR
BP
TLP1
TLP2
RTLP
RBLP
1200
Transactions per Second
500
Transactions per Second
1400
CR
BP
TLP1
TLP2
RTLP
RBLP
300
200
1000
800
600
400
100
200
0
0
1
2
4
8
1
2
nThreads
(a) 0 % writers, OR
4500
7000
3000
CR
BP
TLP1
TLP2
RTLP
RBLP
6000
Transactions per Second
Transactions per Second
3500
8
(b) 30 % writers, OR
CR
BP
TLP1
TLP2
RTLP
RBLP
4000
4
nThreads
2500
2000
1500
1000
5000
4000
3000
2000
1000
500
0
0
1
2
4
8
1
nThreads
2
4
8
nThreads
(c) 0 % writers, AND
(d) 30 % writers, AND
Figura 5.6: Throughput alcanzado por las estrategias para diferentes tasas de escritura
y consultas OR y AND, y un total de 40 mil transacciones. Trazas con alto grado de
coincidencia entre términos de transacciones consecutivas.
figuras 5.4 y 5.5 muestran que BP es más estable y capaz de entregar un rendimiento
más eficiente que las otras estrategias. Por lo tanto, la cota superior para el tiempo de
respuesta individual de una transacción es menos suceptible de ser sobrepasado frente a
tráfico alto puesto que en BP, al tener un throughput mayor, los tiempos de espera en la
cola de entrada de transacciones tienden a ser menores que en las otras estrategias con
throughput inferior.
75
Average time per completed operation
0.018
CR
BP
TLP1
TLP2
RTLP
RBLP
0.016
0.014
0.012
0.01
0.008
0.006
0.004
0.002
0% writers
20% writers
40% writers
0
1
2
4
8
1 2 4 8
Number of threads
1
2
4
8
(a) Tiempo medio de respuesta por transacción.
Maximum time per completed operation
0.25
CR
BP
TLP1
TLP2
RTLP
RBLP
0.2
0.15
0.1
0% writers
0.05
40% writers
0
1
2
3
4
1
2
3
4
Batches of query/index operations
(b) Máximo tiempo de respuesta por transacción
para lotes de 1,000 transacciones y 8 threads.
Figura 5.7: Tiempo en segundos para transacciones de lectura/escritura.
5.3.
Evaluación utilizando un Servicio de Click-Through
Las máquinas de búsqueda para la Web generalmente hacen un seguimiento de los
clicks [10, 18, 22, 24, 55, 89, 103] realizados por los usuarios en las páginas web que
contienen los resultados de búsquedas. Esto permite considerar las preferencias de usuarios
anteriores en el proceso de ranking de documentos para una consulta dada. La máquina
de búsqueda registra todos los clicks de los usuarios sobre los URLs presentados en las
respuestas a sus consultas. Por cada consulta resuelta y respondida por la máquina de
76
búsqueda, en algún momento retornan a la máquina de búsqueda los URLs visitados por
el respectivo usuario.
Actualmente las optimizaciones al proceso de ranking de documentos que consideran
las preferencias de usuarios anteriores, provienen de cálculos realizados de manera off-line
sobre grandes conjuntos de consultas y sus respectivos user clicks. Esto significa que los
efectos de clicks anteriores a ser incluidos en el proceso de ranking de documentos, sólo
se ven reflejados a intervalos de horas o incluso dı́as. Una solución es permitir la inclusión
de user-clicks de manera on-line y mantener un servicio que permita, por cada consulta
que llega a la máquina de búsqueda principal, recepcionar una copia de cada consulta y
realizar en tiempo real el cálculo de un puntaje para los documentos más visitados por
usuarios que realizaron consultas similares en el pasado muy reciente.
Básicamente el problema consiste en tener un ranking eficiente de las URLs clickeadas
por usuarios anteriores que solicitaron información similar a la máquina de búsqueda. Para
ello, los clicks deben ser indexados de manera concurrente con el ranking, y los conflictos
de concurrencia surgen por las continuas actualizaciones del ı́ndice y las operaciones necesarias determinar las consultas similares y realizar el ranking de URLs. Las consultas
son “similares” si se encuentran correlacionadas de alguna manera, para este cálculo se
consideran los URL seleccionados y los respectivos términos de las consultas. El cálculo
de las probabilidades consulta-consulta, consulta-URL y URL-URL puede ser exigente en
tiempo ejecución y espacio de memoria.
El servicio de click-through fue implementado utilizando un ı́ndice invertido que contiene una tabla con los términos que aparecen en las consultas, y por cada término los
URLs seleccionados por los usuarios junto con información de la cantidad de clicks hechos
sobre el URL. Por otra parte, como se muestra en la Figura 5.8, se utiliza un ı́ndice adicional de modo que a partir de un conjunto de URLs se pueda llegar a nuevos términos. Una
operación básica es comenzar con los términos de la consulta recibida por el servicio para
llegar hasta el conjunto de URLs relacionados con esos términos, y a partir de esos URLs
llegar a más términos y URLs. Luego de reunir una cantidad lo suficientemente grande
URLs se aplica una rutina de ranking de URLs para responder con los top-K a la máquina
de búsqueda.
77
Figura 5.8: Estructuras de datos y secuencia de operaciones. La secuencia (1), (2) y (3)
indica que a partir de un término dado (1) es posible llegar a un nuevo término (2), que
a su vez conduce a un nuevo conjunto de URLs (3), los cuales se incluirán en el proceso
de ranking. Para cada ı́tem (URL, freq) del ı́ndice invertido, esta secuencia se repite para
cada elemento de la lista del segundo ı́ndice.
5.3.1.
Diseño de los experimentos
Los resultados mostrados a continuación están representados en términos de la aceleración, la cual se define como la razón A/B, donde A es el menor tiempo de ejecución
obtenido por cualquiera de las estrategias utilizando un thread, y B es el tiempo de ejecución obtenido por la estrategia utilizando dos o más threads. Los experimentos fueron
realizados evaluando dos escenarios para las transacciones que arriban a la cola de entrada
del nodo multi-core:
Workload A – Tráfico de transacciones alto pero concurrencia limitada –. Es el
escenario más adverso que se ha evaluado puesto que existe una alta probabilidad de
que las consultas posteriores (en la secuencia de llegada de transacciones al nodo) a
una consulta dada sean muy similares, conteniendo muchos términos en común. Por
ejemplo, esto emula una situación en que repentinamente un cierto evento o tema
78
capta la atención de muchos usuarios de la máquina de búsqueda. En este caso, las
operaciones de ranking de URLs y actualización de los ı́ndices tienden a coincidir
muy frecuentemente sobre el mismo subconjunto de términos.
Workload B – Alto tráfico, alta concurrencia –. Este caso es lo opuesto en el sentido
que la probabilidad de términos en común entre transacciones es menor, es la normal
observada en el log de consultas AOL que se utilizó en la experimentación, y por lo
tanto la competencia por las mismas secciones de los ı́ndices es mucho menor que
en Workload A. Dado que existe una menor correlación entre consultas posteriores,
existe un nivel de concurrencia mayor.
Las transacciones de actualización de los ı́ndices contienen documentos escogidos al azar
desde los resultados top-K de cada consulta del log de AOL. Las respuestas a las consultas
fueron calculados utilizando ranking vectorial sobre las listas invertidas construidas desde
la muestra de la Web de UK. Para estas transacciones de escritura, lo que recibe el nodo
multi-core desde el exterior son los términos de la consulta y los URLs que seleccionó el
usuario que originalmente envı́o la consulta a la máquina de búsqueda. Si los URLs son
nuevos, es decir, no existen en el ı́ndice entonces se trata de una operación de inserción
en las listas invertidas respectivas. Si son URLs que ya existen en el ı́ndice, entonces es
una operación de actualización que modifica la frecuencia de clicks sobre el URL para los
términos de la consulta. La cantidad de URLs, y los URLs especı́ficos, son valores definidos
aleatoriamente con distribución uniforme. Evidentemente esto no representa exactamente
las preferencias de los usuarios frente a la página de resultados, pero para el propósito de
la experimentación que concierne a este trabajo dicho supuesto es suficiente para evaluar
el rendimiento de las estrategias de control de concurrencia. Los experimentos consideran
casos en que el respectivo usuario selecciona uno o varios URLs.
Las transacciones de lectura son consultas del log de AOL y para simular el grado de
colisiones de términos en las transacciones, se asume que una consulta resuelta por el nodo
multi-core en el instante t1 , retorna al nodo como una transacción de escritura (compuesta
de la misma consulta y los URLs seleccionados por el usuario) en el instante t2 > t1 luego
de haber procesado una cierta cantidad de otras transacciones.
5.3.2.
Resultados
La Figura 5.9 muestra la escalabilidad de las diferentes estrategias utilizando el caso
workload B con selección de un URL para las transacciones de escritura y cinco URLs.
79
300
250
500
150
100
CR
BP
TLP1
TLP2
RTLP
RBLP
600
Throughput
200
Throughput
700
CR
BP
TLP1
TLP2
RTLP
RBLP
1 click
400
5 clicks
300
200
50
100
0
0
1
2
4
1
8
2
4
8
nThreads
nThreads
Figura 5.9: Escalabilidad de las diferentes estrategias para Workload B y suponiendo que
los usuarios hacen un click (gráfico izquierdo) en un enlace en cada búsqueda o hacen clicks
en 5 enlaces (gráfico derecho), los cuales se transforman en 5 transacciones de escritura.
Los resultados muestran que BP es la estrategia que escala mejor que las otras estrategias
para ambos casos.
Algo similar se observa en la Figura 5.10, la cual muestra resultados para las dos cargas
de trabajo ejecutadas utilizando 8 threads y variando la cantidad de URLs que contienen
las transacciones de escritura. Ambas cargas de trabajo tienen la caracterı́stica de que el
peso del proceso de ranking de URLs es mucho menor que en el caso de estudio anterior
sobre el servicio de ı́ndice. Esto porque las listas invertidas que se forman con los URLs
tienen tamaño mucho menores a las utilizadas por el servicio de ı́ndice.
Los resultados de la Figura 5.10 muestran que el rendimiento de las estrategias, con
excepción de TLP2, son relativamente independientes del nivel de la carga de trabajo. La
explicación es que si bien workload A posee mayor nivel de conflicto, en todo momento
existen tantas transacciones como threads en ejecución y por lo tanto la probabilidad de
coincidencia de términos está acotada.
Finalmente, en la Figura 5.11 se muestran los tiempos promedio de ejecución individual
por consultas e inserción/actualización. Distinto al caso de la Figura 5.7, para calcular los
tiempos de respuesta de transacciones individuales se ha medido directamente el intervalo
de tiempo que transcurre entre el inicio y el final del procesamiento de la transacción.
El gráfico de la Figura 5.11.a muestra que el tiempo promedio de las transacciones que
tienden a ser similares entre sı́. Para 5 clicks los tiempos son menores, lo cual indica que las
operaciones de inserción/actualización son más rápidas que las consultas sobre los ı́ndices.
80
1200
1000
CR
BP
TLP1
TLP2
RTLP
RBLP
1000
800
Throughput
800
Throughput
1200
CR
BP
TLP1
TLP2
RTLP
RBLP
600
600
400
400
200
200
0
0
1
2
3
Clicks
5
10
1
2
3
Clicks
5
10
Figura 5.10: Escalabilidad de las diferentes estrategias para Workload A (gráfico izquierdo)
y Workload B (gráfico derecho) para 8 threads y varios clicks indicados en el eje x.
A excepción de la estrategia BP, todas las estrategias asignan un threads para procesar de
forma secuencial la consulta y/o inserción/actualización. Sin embargo, BP tiene la carga de
la barrera de sincronización de threads con el fin de comenzar con la siguiente operación,
y los resultados muestran que este costo es significativo. Por otra parte, los puntos de
la Figura 5.11.b muestran claramente que todas las estrategias tienden a consumir una
cantidad significativa de tiempo para algunas operaciones, lo cual indica que de vez en
cuando, se produce un retraso debido a la contención de locks de los threads activos.
La estrategia BP no sufre este problema, sencillamente, procesa las operaciones de una
a la vez y, si bien usa locks para implementar la barrera de sincronización oblivious, los
threads no deben competir entre sı́ para adquirir locks al final de cada operación. Esto
explica mejor el rendimiento de BP con respecto a la métrica de resultados en este caso,
es decir, rendimiento de las operaciones consultas/inserción/actualización.
5.3.3.
Resultados para la Cola de Prioridad
A continuación se presentan resultados destinados a mostrar el rendimiento de la cola
de prioridad propuesta para administrar un cache de listas invertidas en el nodo de búsqueda. La carga de trabajo es la misma que la utilizada en el servicio de ı́ndice.
En la Tabla 5.2 se muestran los resultados divididos en dos secciones principales. La
primera sección contiene datos para un caso en que la tasa promedio de aciertos en el
cache (hits) es un 70 %, y en la segunda sección la tasa promedio de aciertos es de un
81
Average time per completed operation
0.03
CR
BP
TLP1
TLP2
RTLP
RBLP
0.025
0.02
0.015
0.01
1 click
5 clicks
0.005
0
1
2
4
8
1
2
4
8
Number of threads
(a) Tiempo medio de respuesta por transacción.
Maximum time per completed operation
0.0007
BP
CR
TLP2
TLP1
RTLP
RBLT
0.0006
0.0005
0.0004
0.0003
1 click
0.0002
0.0001
5 clicks
0
1
2
3
4
5
1
2
3
Batches of query/index-update operations
4
5
(b) Máximo tiempo de respuesta por transacción
para lotes de 1,000 transacciones y 8 threads.
Figura 5.11: Tiempo individual de transacciones. Parte superior se muestran los resultados
de ejecución del tiempo promedio para 1, 2, 4 y 8 threads. En la parte inferior se muestra
el tiempo máximo observado cada 1000 transacciones procesadas para 8 threads.
30 %. Los resultados fueron obtenidos utilizando transacciones de lectura que para su
solución requieren tener todos los bloques de las listas invertidas almacenadas en el cache.
Cada vez que un bloque no está en el cache, se selecciona una entrada del cache para que
sea asignada al bloque utilizando la polı́tica de reemplazo LRU.
Las columnas de las tablas contienen lo siguiente. La columna R presenta el total de
elementos almacenados en cada bucket de la estructura de datos. La columna T es el
82
R
8
16
32
64
R
8
16
32
64
T
110
91
119
250
T
94
96
80
131
S2
1.05
1.35
1.71
1.94
70 % cache hits
S4
S8
C
1.61 2.43 0.12
2.32 3.94 0.06
3.11 4.89 0.03
3.06 5.84 0.01
C4
0.63
0.63
0.64
0.64
C5
0.01
0.01
0.01
0.01
C6
0.34
0.34
0.34
0.34
S2
1.00
1.39
1.64
1.43
30 % cache hits
S4
S8
C
1.46 2.20 0.12
2.33 4.10 0.06
2.60 4.69 0.03
2.93 5.58 0.02
C4
0.29
0.30
0.32
0.33
C5
0.07
0.01
0.03
0.02
C6
0.63
0.68
0.63
0.63
Tabla 5.2: Resultados para la Cola de Prioridad.
tiempo de ejecución obtenido al procesar las transacciones secuencialmente utilizando un
thread.
Las columnas S2, S4, S8 son las aceleraciones obtenidas con 2, 4 y 8 threads respectivamente. La paralelización utiliza el enfoque de tener Nt colas de prioridad independientes
asignadas a cada thread (i.e., enfoque de indexación local). La aceleración se define como
la razón T (1)/T (Nt ) donde T (1) es el tiempo de ejecución obtenido con 1 thread y T (Nt )
es el tiempo de ejecución obtenido utilizando Nt threads.
La columna C contiene la fracción de llamadas a la rutina de actualización del árbol
CBT respecto del total de accesos realizados a los buckets. Las columnas C4, C5 y C6
representan la fracción de veces en que el algoritmo de solución de una consulta, mientras
recorre la lista invertida para un término dado, (i) encuentra todos los bloques de la lista
almacenados en el cache (C4), (ii) encuentra algunos de los bloques en el cache (C5), y
(iii) no encuentra ningún bloque en el cache (C6).
En general, los resultados muestran que existe un tamaño de bucket para el cual se
alcanza el mejor rendimiento respecto de tiempo de ejecución. Las aceleraciones alcanzadas para 2, 4 y 8 threads son razonablemente buenas. Los valores de C indican que las
actualizaciones en el árbol CBT son muy poco frecuentes. Las columnas C4, C5 y C6 que
esto se debe a que la mayorı́a de los bloques de las listas invertidas son asignadas a regiones
contiguas de memoria, es decir, los bloques tienden a ser puestos consecutivamente en cada
bucket abarcando completamente los buckets. Tal como lo indican los valores de C esto
permite amortizar significativamente el costo de las actualizaciones del árbol CBT. Todas
83
estas métricas muestran que la cola de prioridad propuesta es particularmente eficiente
para apoyar la gestión de caches de listas.
5.4.
Conclusiones
En este capı́tulo se ha presentado un estudio comparativo, basado en implementaciones reales, de las estrategias de procesamiento concurrente y paralelo de transacciones
de escritura y lectura. Como cargas de trabajo aplicadas a las estrategias se utilizó dos
aplicaciones exigentes pero factibles para máquinas de búsqueda para la Web. Ambas tienen caracterı́sticas muy distintas en cuanto a requerimientos de sincronización de threads
lectores y threads escritores.
La primera, el servicio de ı́ndice, contiene lo esperado para máquinas de búsqueda que
permiten indexar documentos de manera on-line. El conflicto entre escritores y lectores
proviene del hecho que los mismos términos populares tienden a estar presentes tanto en
los documentos como en las consultas de los usuarios. Dichos términos tienden a tener
listas invertidas de gran tamaño lo cual incrementa tanto su tiempo de procesamiento
como la duración de la competencia por el acceso concurrente a las listas, y por lo tanto se introducen tiempos de esperas por locks que pueden afectar significativamente el
rendimiento de las estrategias asincrónicas (TLP1, TLP2, RBLP, RTLP). Las estrategias
sincrónicas (CR, BP) no están afectas a este problemas puesto que aplican paralelismo a
nivel de threads para procesar las escrituras. Sólo las estrategias RBLP y RTLP alcanzan un rendimiento competitivo respecto de BP pero a costa de violar la atomicidad y la
serialidad de las transacciones. Aún en este caso de relajación extrema de sincronización
entre threads, la estrategia BP alcanza un mejor rendimiento que está entre un 10 % y
un 30 % dependiendo del total de threads y la proporción de transacciones de escritura.
En algunos casos la aceleración de la estrategia BP al incrementar el número de threads
es incluso super-lineal debido a los efectos de un mejor aprovechamiento de los caches del
procesador Intel.
La segunda carga de trabajo, el servicio user click-through, es un ejemplo de sistema
en que las listas invertidas son pequeñas y por lo tanto el ranking de documentos (URLs)
es muy rápido. Sin embargo, la tasa de escrituras es muy alta, al menos 50 % puesto que
por cada consulta de usuarios se espera el retorno algunos segundos más adelante de uno o
más clicks sobre los URLs presentados en la página de respuesta a la consulta. Cada click
es una transacción de escritura y por lo tanto las escrituras son mucho más frecuentes que
84
las consultas. En este escenario, la estrategia BP también alcanza un rendimiento superior
a las estrategias asincrónicas. Las diferencias observadas son también de un 30 %.
En general, los resultados obtenidos con los servicios muestran que la estrategia BP
alcanza un rendimiento eficiente y estable frente a varias condiciones de cargas de trabajo.
Esto muestra la conveniencia de aplicar el enfoque de una transacción a la vez y resulta por
todos los threads en paralelo. Para apoyar este enfoque de procesamiento de transacciones,
es necesario tener una estrategia similar para administrar los caches de listas y top-K en el
nodo de búsqueda. Los resultados obtenidos para la cola de prioridad propuesta para esta
tarea, muestran que su rendimiento alcanza buena eficiencia y que su diseño claramente
incentiva los accesos a regiones contiguas de memoria, lo cual incrementa el rendimiento
de accesos a disco o a segmentos del ı́ndices invertidos comprimidos.
85
Capı́tulo 6
Análisis de Escalabilidad
En este capı́tulo se define un modelo de simulación discreta del costo de las estrategias
de procesamiento de transacciones y un modelo de simulación discreta de una arquitectura genérica de procesador multi-core sobre la cual se ejecutan dichas estrategias. La
combinación de ambos modelos se utiliza para entender las causas por las cuales la estrategia sincrónica (BP) propuesta en este trabajo de tesis es más eficiente en rendimiento y
escalabilidad que sus contrapartes asincrónicas.
El modelo de costo y arquitectura están construidos sobre el modelo de computación
Multi-BSP, el cual fue propuesto recientemente para modelar el rendimiento de procesadores multi-core [101]. Multi-BSP permite abstraer el hardware real, rescatando y parametrizando las caracterı́sticas esenciales que originan los costos relevantes de un algoritmo
ejecutado sobre un procesador multi-core. El objetivo de este modelo no es representar
exactamente las computaciones realizadas por los procesadores reales, lo cual, incluso recurriendo a simuladores de arquitecturas especı́ficas, no serı́a factible en el tipo de aplicación
estudiada en esta tesis debido al gran volumen de datos y cantidad de computación realizada sobre ellos. Los modelos de computación tales como Multi-BSP permiten abstraer la
complejidad de este problema en una simplificación que no se aleja significativamente de
la realidad: “... models attempt to capture the key features of real machines while retaining
a reasonably high-level programming abstraction.” [84, 85].
Respecto del diseño y análisis de algoritmos, la estructura estrictamente sincrónica
del modelo Multi-BSP permite hacer matemáticamente tratable el análisis comparativo
de algoritmos diseñados para resolver un mismo problema. El modelo incluye aspectos
relevantes del rendimiento de algoritmos granularidad gruesa tales como el balance de
86
carga entre los núcleos y el efecto de la localidad de accesos a los caches más cercanos a
los núcleos. En este capı́tulo se utiliza Multi-BSP para mostrar que frente a transacciones de sólo lectura, tanto las estrategias asincrónicas como las estrategias sincrónicas de
procesamiento de consultas pueden alcanzar un rendimiento similar.
Sin embargo, el modelo Multi-BSP no considera aspectos crı́ticos del rendimiento de
algoritmos de granularidad fina tales como el solapamiento de las transferencias de datos
entre caches y el cómputo realizado por los núcleos, y los efectos de protocolos de coherencia
de caches y estrategias de prefetching de datos e instrucciones. El modelo sólo utiliza tasas
constantes de transferencia de datos en la jerarquı́a de caches. También el requerimiento
de sincronización perı́odica de componentes impuesto por Multi-BSP puede no modelar
bien el comportamiento completamente asincrónico que tienen tanto las distintas unidades
de un procesador multi-core como los threads ejecutados sobre los núcleos.
Con la ayuda de la técnica de simulación discreta orientada a procesos y recursos,
es posible construir un modelo más cercano a la realidad y lo suficientemente completo
como para evaluar en profundidad los efectos en el rendimiento de las interacciones entre
las transacciones de lectura y escritura. La metodologı́a empleada fue la siguiente. Sobre
un simulador asincrónico de computaciones tipo Multi-BSP, se simuló la ejecución de
trazas obtenidas desde la ejecución real de las estrategias sincrónicas y asincrónicas de
procesamiento de transacciones. Dichas trazas fueron obtenidas mediante la ejecución de
las implementaciones de las estrategias utilizadas en el capı́tulo anterior. Las trazas pueden
ser vistas como grafos acı́clicos dirigidos cuyos nodos representan las operaciones relevantes
que compiten por utilizar los recursos del procesador multi-core, es decir, los núcleos, los
caches del procesador y la memoria principal.
En esencia lo que hacen las estrategias reales es desplegar un conjunto de threads que
compiten por utilizar los recursos del procesador siguiendo los pasos indicados por la lógica del procesamiento de las transacciones. El simulador despliega exactamente los mismos
threads utilizando co-rutinas, las cuales ejecutan las mismas operaciones de las estrategias reales, pero simulan el costo de ellas causando en el tiempo de simulación retardos
equivalentes a los tiempos de ejecución que demandan las respectivas operaciones reales.
Puesto que todos los costos ocurren en el tiempo de simulación, es posible determinar las
razones principales por las cuales una estrategia es mejor que otra.
Dado que en la computación ejecutada por las estrategias reales existe un número
reducido de operaciones cuya suma de costos domina significativamente el tiempo total
de ejecución, las métricas de rendimiento obtenidas considerando en los nodos del grafo
87
dirigido sólo estas operaciones dominantes, son ajustadas a la realidad en términos de
comparación de estrategias. Esto porque todas las estrategias ejecutan exactamente las
mismas operaciones dominantes. Por lo tanto, las diferencias en rendimiento entre ellas
provienen de la manera como distribuyen la ejecución de esas operaciones entre los threads,
el uso de la jerarquı́a de memoria y el tipo de sincronización aplicado a los threads.
Para mejorar la limitaciones de Multi-BSP y en adición a la introducción de computaciones asincrónicas vı́a co-rutinas, el modelo de simulación del procesador multi-core fue
extendido de la siguiente manera. Se implementó la polı́tica LRU para administrar los caches del procesador. Cada cache está compuesto de un conjunto de bloques de memoria de
tamaño fijo (cache lines) los cuales son gestionados con LRU. También se implementó un
protocolo de coherencia de caches para posibilitar la invalidación de bloques compartidos
que son modificados por los threads. Finalmente se implementó una simulación de locks
basada en variables globales de acceso exclusivo. Los valores de los parámetros que representan el costo de la arquitectura fueron obtenidos mediante programas de benchmark
ejecutados sobre el procesador Intel.
6.1.
El modelo Multi-BSP
El modelo Multi-BSP es una extensión a procesadores multi-core con memoria compartida del modelo BSP de computación paralela para memoria distribuida. Para facilitar
la explicación de Multi-BSP conviene recordar la explicación resumida del modelo BSP
para memoria distribuida descrito en el Apéndice A. Un programa BSP está compuesto
de una secuencia de supersteps. Durante un superstep los procesadores pueden realizar
cómputo sobre datos almacenados en memoria local y realizar el envı́o de mensajes a otros
procesadores. El fin de cada superstep marca el envı́o efectivo de los mensajes acumulados
en el procesador y la transmisión de mensajes por la red de interconexión de procesadores
finaliza con la sincronización en forma de barrera de todos los procesadores. Esto implica
que los mensajes están disponibles en los procesadores de destino al inicio del siguiente
superstep. La barrera de sincronización asegura que ningún procesador puede continuar
hasta el siguiente superstep sino hasta cuando todos hayan alcanzado la barrera.
En el modelo Multi-BSP el concepto de supersteps y paso de mensajes en memoria
distribuida se ha extendido para considerar el costo de transferencia entre los distintos
niveles de la jerarquı́a de memoria presente en los procesadores multi-core. Al igual que en
BSP, el cómputo de un algoritmo Multi-BSP se divide en una secuencia de supersteps, pero
88
Figura 6.1: Ejemplo de arquitectura multi-core según Multi-BSP.
la ejecución de un algoritmo Multi-BSP incluye explı́citamente un modelo simplificado para
estimar el costo de las transferencias entre los distintos niveles de memoria. La descripción
formal y general del modelo Multi-BSP, la cual considera múltiples niveles de anidamiento
de caches y componentes, es presentada en el Apéndice A.
La Figura 6.1 muestra un ejemplo de un sistema de memoria compartida equipado
con 4 núcleos con sus correspondientes niveles privados de cache L1, y un nivel de cache
compartido L2. Los caches L2 están conectados con la memoria principal. En el modelo
Multi-BSP se asume que cada thread se ejecuta en un núcleo distinto y para este ejemplo
concreto se establecen tasas de transferencia entre L1 y L2 (parámetro g1 ), y entre L2
y memoria principal (parámetro g2 ). Los caches L1 y L2 tienen una capacidad m1 y m2
respectivamente y se supone que la memoria principal tiene capacidad suficiente para
almacenar todos los datos del algoritmo en ejecución.
Para simplificar el modelo y permitir cálculos aproximados de forma analı́tica, cada
superstep es delimitado por la sincronización en forma de barrera de los threads. Las
transferencias de información entre L1 y L2, que ocurren a una tasa g1 , son efectivas al
inicio del siguiente superstep de nivel 1 y el costo de la sincronización de este superstep
está dado por la latencia ℓ1 . Ası́ mismo, las transferencias entre L2 y la memoria principal
ocurren a una tasa de g2 , y son efectivas al inicio del siguiente superstep de nivel 2 a un
costo de sincronización dado por la latencia ℓ2 . Para realizar cómputos, cada núcleo C1
o C2 debe tener los datos necesarios en su respectivo cache L1. Puesto que éstos tienen
capacidad limitada, la ejecución de un algoritmo Multi-BSP habitualmente requerirá de
la transferencia de bloques de memoria entre los caches L1 y L2, y entre los caches L2
y la memoria principal (Ram). En general, las latencias ℓi acumulan tanto el costo de la
sincronización de barrera como todos los costos fijos que son necesarios para ejecutar los
pasos del algoritmo Multi-BSP en el superstep.
89
Como se observa, el modelo simplifica el comportamiento dinámico de la jerarquı́a de
memoria y asume la existencia de pasos o etapas en los cuales se realizan transferencias
entre los distintos niveles de la jerarquı́a de memoria. Se sabe que en un procesador real las
transferencias entre los distintos niveles se realizan de forma independiente y solapados en
el tiempo y existen técnicas para explotar el paralelismo a nivel de memoria. No es fácil por
tanto delimitar las etapas que se plantean en el modelo o ni siquiera es posible definirlas.
También se sabe que existen mecanismos adicionales como los prefetcher de hardware que
actúan de forma efectiva para ocultar las latencias de acceso y que el rendimiento viene
condicionado adicionalmente por el trafico provocado por los protocolos de coherencia de
cache, que tienen gran relevancia cuando existe compartición (falsa o verdadera) de datos.
No obstante, dada la naturaleza de la aplicación estudiada en este trabajo de tesis, en
el análisis de caso promedio se asume que la granularidad del paralelismo que se explota
es lo suficiente grande como para obviar el trafico de coherencia y se supone que todas
las estrategias se benefician por igual de los mecanismos de ocultación de las latencias de
acceso. Esto último tiene su justificación en el hecho de que todas las estrategias realizan el mismo tipo de acceso a los datos compartidos, los cuales están caracterizados por
secuencias largas de accesos a regiones contiguas de memoria.
Con estas simplificaciones, el costo del superstep de nivel 1 está dado por el núcleo que
ejecuta más operaciones en el superstep. El costo de transferencia entre L1 y L2 está dado
por el núcleo que transfirió más bloques al nivel L1. De la misma manera, el costo en
transferencia del superstep de nivel 2 está dado por la unidad L2 que transfirió más
bloques desde la memoria principal. Suponemos que las escrituras a niveles superiores de
la jerarquı́a de memoria no suelen ocasionar penalizaciones importantes en el rendimiento
en nuestro contexto, ya que suponemos que no implican invalidaciones debido al protocolo
de coherencia y suponemos que no limitan el ancho de banda con memoria ya que pueden
ocultarse con los correspondientes mecanismos de buffering de escrituras. No obstante, con
la ayuda de simulación discreta, varios de estos supuestos se relajan más adelante en este
capı́tulo para obtener un modelo de procesador multi-core más cercano a la realidad.
Ambos tipos de supersteps pueden solaparse o pueden actuar en forma consecutiva en
pares, es decir, hypersteps compuestos de un superstep de nivel 1 inmediatamente seguido
de uno de nivel 2. La manera especı́fica dependerá del modelo de costo que más se ajuste
a la naturaleza de los algoritmos siendo analizados o simplemente del modelo que haga el
análisis matemáticamente tratable.
90
El modelo descrito en la forma de los dos supersteps globales de nivel 1 y 2 simplifica el
análisis caso promedio presentado en la siguiente sección. Esto contrasta con los procesadores reales los cuales, si bien tienen relojes que sincronizan globalmente sus instrucciones,
presentan una alta asincronı́a y solapamiento de actividades entre sus distintas unidades.
Sin embargo, desde la descripción general de Multi-BSP presentada en el Apéndice A,
se puede concluir que este problema se puede reducir permitiendo que cada componente
pueda operar de manera asincrónica respecto de los otros componentes mediante la ejecución de sus propios supersteps locales. Por ejemplo, en la Figura 6.1 cada par (núcleo-L1,
L2) y (L2, Ram) puede ser considerado como un componente asincrónico. Por otra parte,
si se acota la duración de los supersteps a un valor lo suficientemente pequeño, donde
cada actividad que no alcanzó a ser realizada en el superstep actual es planificada para
el siguiente superstep, entonces el modelo se transforma en una discretización del sistema
contı́nuo donde los componentes del procesador y los threads ejecutados sobre los núcleos
aproximan un comportamiento asincrónico.
6.2.
Análisis Caso Promedio
Si suponemos un sistema que admite transacciones de sólo lectura, entonces no es
necesario utilizar locks o barreras para sincronizar los threads. No obstante, es relevante
mencionar que las barreras de sincronización utilizadas en la estrategia BP son implementadas utilizando locks e instrucciones de espera condicional. El costo de estas últimas
es similar al costo de los locks. En general, se deberı́a esperar que la cantidad de locks
ejecutados por las estrategias asincrónicas sea mayor que las estrategias sincrónicas. Por
ejemplo, las estrategias RTLP y RBLP descritas en el capı́tulo anterior ejecutan una cantidad de locks que es proporcional al largo de las listas invertidas por cada transacción,
mientras que la estrategia BP solamente incurre en un costo similar a 2 p locks por cada
transacción, donde p es el número de threads. Más aún, frente a una situación de tráfico
alto de transacciones, el costo de los 2 p locks por transacción se puede amortizar procesando lotes de p transacciones en cada superstep. Esto tiene la ventaja de que los cálculos
de los resultados top-k globales para las transacciones de lectura, pueden ser realizados en
paralelo utilizando un thread distinto después de la sincronización de barrera.
Para analizar el costo de las transacciones de sólo lectura y sin locks, se puede pensar en
una estrategia sincrónica y otra asincrónica que se comparan bajo el contexto de resolver
un flujo grande de consultas que contienen un término t cada una. El largo de la lista
invertida del término t es nt y cada estrategia utiliza p threads para determinar los k
91
documentos mejor rankeados para la consulta. Se asume que el costo de calcular el valor
de relevancia de los documentos presentes en una lista invertida es proporcional al largo
de la lista invertida.
Los parámetros Multi-BSP son g1 , g2 para la tasa de transferencia de bloques de cache/memoria, y ℓ0 , ℓ1 y ℓ2 son las latencias de sincronización. Se supone que las estrategias
procesan transacciones utilizando tres supersteps que operan de manera asincrónica entre
ellos. Los supersteps de tipo 0 realizan cómputo sobre datos locales almacenados en el
cache L1 del thread. El costo de este superstep es el costo incurrido por el thread que
realizó la máxima cantidad de trabajo en el superstep.
Los supersteps de tipo 1 se utilizan para las transferencias de bloques de datos entre los
caches L1 y L2, mientras que los supersteps de tipo 2 transfieren bloques de datos entre el
cache L2 y la memoria principal del procesador multi-core. El costo de ambos supersteps
está dado por el componente que envió y/o recibió más datos.
Los parámetros αt y βt son estimaciones de la tasa promedio de aciertos (hits) que el
término i tiene en los caches L1 y L2 respectivamente. Se supone que cada estrategia debe
incurrir en una latencia de software constante γ para establecer lo que sea necesario para
administrar el estado de las consultas siendo resueltas. Suponemos que las p núcleos están
organizadas de manera que se tienen c núcleos por cada cache L2, y cada núcleo tiene su
propio cache L1 local.
Si se utiliza la estrategia BP entonces cada uno de los p threads trabaja en paralelo
resolviendo una sola consulta por vez. Cada thread calcula la relevancia de cada documento
en la lista invertida del término t y los ordena para determinar los mejores k documentos
locales a un costo O( γ + nt /p + (nt /p) · log(nt /p) + ℓ0 ). No obstante, puesto que interesa
determinar los mejores k resultados locales, no es necesario ordenar puesto que el thread
puede mantener un heap de tamaño O(k) donde va manteniendo los documentos mejor
rankeados. Por lo tanto, el costo en computación puede ser mejorado a O( γ +nt /p+(nt /p)·
log k +ℓ0 ). Para ejecutar esa cantidad de trabajo fue necesario pagar en promedio un costo
O( (1−αt )·(nt /p)·c·g1 +ℓ1 ) en transferencias de datos entre los caches L1 y L2. También fue
necesario pagar O( (1−αt )·(1−βt )·(c·(nt /p))·(p/c)·g2 +ℓ2 ) = O( (1−αt )·(1−βt )·nt ·g2 +ℓ2 )
para la transferencias entre el cache L2 y la memoria principal.
Luego de que cada thread ha determinado sus mejores k documentos locales, uno
de ellos se hace cargo de ordenar los k · p resultados para determinar los mejores k que
constituyen la respuesta a la consulta. Dado que cada uno de los p conjuntos de tamaño
k ya están ordenados, entonces se puede hacer un merge de los p conjuntos a un costo
92
O( k · p · log p ). Como las CPUs están organizadas de manera que se tienen c núcleo por
cada cache L2, entonces el costo total en transferencias de datos hasta el cache L1 del
núcleo encargado de almacenar y ordenar los k · p resultados es O( (c + p) · k · g1 + ℓ1 +
(c · k) · ((p/c) − 1) · g2 + ℓ2 ), lo cual para c constante es O( p · k · (g1 + g2 ) + ℓ1 + ℓ2 ). Por
otra parte, no es necesario que todos los núcleos envı́en k resultados, estos pueden hacer
algunas iteraciones enviando cada vez sus siguientes k/p mejores resultados. El peor caso
es hacer p iteraciones. Con gran probabilidad luego de unas pocas iteraciones se tendrán
los k mejores a nivel global. Es decir, el costo de la ordenación (merge) se puede reducir
a O( k · log p ), lo cual tiene un costo en caches de O( k · (g1 + g2 ) + ℓ1 + ℓ2 ).
Para poder comparar con la estrategia asincrónica es necesario considerar un grupo de
p o más consultas, puesto que en el caso asincrónico se procesan p consultas en paralelo,
donde cada consulta se procesa secuencialmente. Para tráfico alto de consultas, se supone
que en un superstep de la estrategia BP se pueden procesar p consultas al mismo tiempo.
Se define para cualquier término el caso promedio como α = ᾱt , β = β̄t , n = n̄t . Entonces
considerando que la determinación de los k mejores documentos finales para cada consulta
se hace en paralelo debido a la sincronización oblivia, el costo total de procesar p consultas
en BP está dado por
Costo computación Sync → p · γ + n + n · log k + k · log p + ℓ0 +
Costo caches L1-L2
→ (1 − α) · n · g1 + k · p · g1 + ℓ1 +
Costo cache L2-Ram
→ (1 − α) · (1 − β) · n · p · g2 + k · p · g2 + ℓ2 .
Para el caso de la estrategia asincrónica se puede seguir un razonamiento similar con
dos importantes diferencias. Primero, la transferencia de una lista de tamaño nt se hace
completamente desde el camino que va desde la Ram hasta el núcleo asignado al thread
que va a procesador el término t. No es necesario copiar p pedazos de tamaño nt /p en
los p núcleos como en el caso de BP, pero ahora el tamaño del dato a transferir es p
veces más grande, es decir, ambos costos se compensan. Por ejemplo, para competir con
BP la estrategia asincrónica debe copiar p listas de tamaño promedio n en los caches L2,
colocando c listas en cada cache y por lo tanto desde el punto de vista del componente
Ram la comunicación es equivalente a realizar una transferencia de costo O( n · p · g2 + ℓ2 ).
Segundo, los resultados finales para las p consultas resueltas en forma concurrente deben
quedar en Ram, lo cual implica que este componente recibe p conjuntos de tamaño k a un
93
costo O(k · p · g2 + ℓ2 ). Luego el costo total de la estrategia asincrónica está dado por
Costo computación Async → γ + n + n · log k + ℓ0 +
Costo caches L1-L2
→ (1 − α) · n · g1 + k · g1 + ℓ1 +
Costo cache L2-Ram
→ (1 − α) · (1 − β) · n · p · g2 + k · p · g2 + ℓ2 .
Los clusters actuales para máquinas de búsqueda incluyen memorias principales de
tamaños muy grandes en sus nodos multi-core. Esto implica que las listas invertidas pueden
ser muy grandes haciendo que el costo O(n) de asignar puntajes a los documentos sea
dominante. El valor de k es constante y muy pequeño comparado con el n promedio, y lo
mismo ocurre con p. Es decir, en la práctica ambas estrategias deberı́an tener un costo
O( n +
(1 − α) · n · g1
+
(1 − α) · (1 − β) · n · p · g2 ).
(6.1)
Sólo casos muy especiales con listas invertidas pequeñas y métodos de ranking de documentos que sean muy agresivos respecto de evitar recorrer las listas completas, escapan a
la tendencia del costo de la expresión 6.1. Por otra parte, de acuerdo a la tecnologı́a actual
para procesadores multi-core, uno deberı́a esperar g2 ≫ g1 , y entonces resulta relevante
estudiar el comportamiento de α y β en ambas estrategias.
Para diferenciarlos, ambos parámetros se denominan (αS , β S ) y (αA , β A ) para las
estrategias sincrónica y asincrónica respectivamente. Para el caso asincrónico un thread
puede procesar una consulta conteniendo cualquier término t. Si el término es frecuente,
entonces podrı́a en momentos distintos ser procesado por threads distintos. Estas copias
múltiples de la lista invertida en los caches de dos threads distintos puede hacer que se
reemplacen entradas del cache que contienen listas de términos que pueden ser utilizadas
por los threads en el futuro cercano. Esto significa que la estrategia asincrónica tiende a
hacer un uso menos eficiente del espacio total disponible para cache considerando la suma
del espacio sobre los p núcleos. Una solución es utilizar una regla tal como id termino
módulo total threads. Pero si hay términos muy frecuentes existirán problemas de balance
de carga.
Por el contrario, la estrategia sincrónica siempre almacena en un y sólo un cache los
segmentos de listas invertidas que necesita para resolver las consultas. Esto conduce a un
uso óptimo del espacio total de caches sin incurrir en problemas de balance de carga. Sin
embargo, en procesadores tales como el Intel utilizado en este trabajo de tesis, el tamaño
del cache L1 es bastante más pequeño que el cache L2. En varios casos las listas invertidas
de un término son más grandes que el tamaño completo del cache L1. No ocurre lo mismo
94
respecto del tamaño del cache L2. Entonces con gran probabilidad lo que ocurre es lo
siguiente
αS
< αA
βS
> βA
Lo cual a la vista de lo presentado en la expresión 6.1 resulta favorable para la estrategia
sincrónica. En la siguiente sección se valida esta afirmación mediante simulaciones. El caso
αS < αA ocurre en situaciones bien fortuitas y la ventaja tiende a desaparecer cuando p
aumenta.
Por ejemplo, dada una secuencia de dos términos muy frecuentes t1 y t2 donde t1 ocurre
en las consultas qa y qc , y el término t2 ocurre en la consulta qb . Si estas consultas llegan
al nodo multi-core en el orden qa , qb y qc , y los términos t1 y t2 , por ser muy frecuentes
tienen, por tanto listas invertidas de tamaños n1 y n2 muy grandes respectivamente, tal
que n1 /p y n2 /p son valores mayores a la capacidad de cualquier cache L1.
Entonces, la estrategia sincrónica procesa las tres consultas en el orden qa , qb y qc , lo
cual produce que en el momento de resolver qc ya no exista en ningún cache L1 segmentos
de la lista invertida del término t1 . En cambio, la estrategia asincrónica puede acumular
de una sola vez una cantidad de hits equivalentes al tamaño completo de un cache L1 si
uno de los threads procesa qa y qc , mientras que un segundo thread procesa qb .
Cuando los caches son lo suficientemente grandes, como en el caso de los caches L2
del procesador Intel, entonces es evidente que la estrategia sincrónica no elimina todas las
entradas para el término t1 y por lo tanto puede acumular hits en los p/c caches L2.
6.3.
Simulador
El modelo de simulación utiliza el enfoque de procesos y recursos. Los procesos representan los threads de las estrategias de procesamiento de transacciones. Los recursos
son las listas invertidas de los términos involucrados en las transacciones, los caches del
procesador multi-core y las variables sobre las cuales se solicitan/ejecutan locks. El modelo
de simulación se implementa sobre la biblioteca LibCppSim [70], donde cada proceso se
representa con una co-rutina que puede ser bloqueada y desbloqueada a voluntad durante
la simulación. Estas co-rutinas ejecutan las operaciones relevantes de los threads utilizados
por las estrategias de procesamiento de transacciones tales como recorridos por las listas
invertidas y ranking de documentos, e inserción y actualización de ı́temes en las listas
invertidas.
95
Figura 6.2: Una de las co-rutinas (concurrent routines) simulando los pasos seguidos por
un thread para procesar una consulta y generando costo en el tiempo de simulación.
Para simular el tiempo de duración de cada operación de costo relevante del sistema
real, las co-rutinas utilizan la operación hold(t) la cual bloquea a la co-rutina durante t unidades de tiempo de simulación. Con esta primitiva básica, es posible simular los distintos
costos de las estrategias de procesamiento de transacciones. Los costos fueron determinados mediante ejecuciones reales de las estrategias. En particular, los costos dominantes
provienen desde la operación de ranking de documentos, intersección de listas invertidas
y actualización del ı́ndice invertido. Para todas las estrategias se ejecutan las mismas operaciones de costo relevante. La Figura 6.2 muestra un ejemplo, el cual no considera la
simulación de costos de la transferencia de datos en la jerarquı́a de caches.
Además, se dispone de primitivas tales como passivate() y activate() las cuales permiten
bloquear y despertar respectivamente a una co-rutina durante la simulación. Estas dos
primitivas permiten implementar las respectivas simulaciones de las primitivas de locks y
barreras de sincronización utilizadas por los threads del sistema real.
La carga de trabajo que se ejecuta sobre el modelo de simulación es la misma que la
utilizada en las implementaciones reales. En la práctica, lo que hace el simulador es ejecutar
exactamente los mismos pasos que ejecuta la implementación real de las estrategias. Por
cada uno de ellos la co-rutina ejecuta según corresponda operaciones tales como hold(t),
passivate() y activate() sobre el sistema de simulación LibCppSim.
96
Los caches del procesador son administrados con la polı́tica LRU de reemplazo de
entradas, donde cada entrada es un bloque de memoria cuyo tamaño es una fracción de
un bloque de lista invertida. El tamaño de cada bloque es 64 bytes, lo cual es consecuente
con el tamaño de los bloques de cache del procesador Intel.
La Figura 6.3 muestra los pasos ejecutados durante la simulación de una operación
relevante del sistema sobre el procesador ejemplo de la Figura 6.1. Dicha operación toma
un tiempo total de t_cpu unidades de tiempo de simulación en completarse. Para realizar
sus cómputos la operación trabaja sobre un espacio de memoria contigua la cual se inicia
en la dirección dir_base y se extiende a lo largo de una cierta cantidad de bytes. La
función run( t_cpu, dir_base, bytes ) es ejecutada por la co-rutina para simular el
costo en el tiempo de simulación que toma una operación ejecutada por el thread.
Las funciones thread->causeCost*(...) simulan los costos en cada componente del
computador Multi-BSP asincrónico. Existen tres tipos de costos. Un costo destinado a
simular el uso de los núcleos de cada procesador. Otro destinado a simular el costo de
las transferencias de bloques de datos entre los caches L2 y L1. Finalmente, un costo
destinado a simular la transferencia entre la memoria Ram y el cache L2. Las entradas de
los caches que son reemplazadas y que han sido modificadas, son copiadas a la memoria
inmediatamente inferior en la jerarquı́a. La ejecución de las distintas funciones de costo
entre los threads se solapa en el tiempo de simulación.
La coherencia de los caches se mantiene con un protocolo sencillo de tipo directorio.
Por cada bloque de memoria principal, se mantiene una lista de los caches que contienen
una copia de los datos. Cada vez que un bloque almacenado en uno de los caches es
modificado por un thread, se provoca la invalidación de todas las copias almacenadas en
los otros caches. Las invalidaciones son instantaneas respecto del tiempo de simulación
puesto que en el instante del tiempo real en que se hacen dichas invalidaciones, sólo una
de las co-rutinas del simulador está activa. Entonces no es necesario implementar acciones
de comunicación entre los caches a bajo nivel, las cuales son necesarias en un sistema real
para transferir los cambios de estado entre ellos.
En la Figura 6.4 se muestra la implementación de las rutinas de solicitud y liberación
de locks utilizadas en el simulador. Se asume que la solicitud de un lock causa un cierto
retardo en el thread que la ejecuta. Este valor es determinado experimentalmente y se
supone constante para todos los threads. El principio utilizado en este caso es que la
variable de tipo lock siempre debe ser leida y escrita en la memoria principal. Es decir,
cada thread que ejecuta un lock sobre una de estas variables, debe revisar su valor trayendo
97
void Core::run( double t_cpu, string dir_base, int bytes )
{
nbloques = (int)ceil( double(bytes) / double(BLOQUE_CACHE) );
t_cpu = t_cpu / nbloques;
for(int i= 0; i < nbloques; i++)
{
sprintf(str,"%s %d", dir_base.c_str(), i);
if ( cacheL1->hit( str ) == true )
{
cacheL1->update( str );
thread->causeCostCPU( t_cpu );
}
else
{
if ( cacheL2->hit( str ) == true )
{
cacheL2->update( str );
thread->causeCostCommL1L2( Latencia_L1_L2 );
cacheL1->insert( str );
thread->causeCostCPU( t_cpu );
}
else
{
thread->causeCostCommL2Ram( Latencia_L2_Ram );
cacheL2->insert( str );
thread->causeCostCommL1L2( Latencia_L1_L2 );
cacheL1->insert( str );
thread->causeCostCPU( t_cpu );
}
}
}
}
Figura 6.3: Pasos principales ejecutados durante la simulación de una operación relevante de las transacciones. Las co-rutinas del simulador, las cuales simulan los threads del
procesador, ejecutan esta función run(...) a medida que ejecutan los pasos asociados al
procesamiento de cada transacción según la lógica de las estrategias BP, RTLP o TLP2.
el contenido desde la memoria principal hasta su cache L1, y en ese punto toma la decisión
de bloquearse o continuar su ejecución. Para una misma variable de tipo lock, las lecturas
y escrituras del valor de la variable que hacen dos o más threads de manera concurrente se
serializan mediante accesos de tipo exclusivo. Existe paralelismo de lecturas y escrituras
98
void Locks::set_lock( string nombreLock, Thread *thread
{
thread->holdLock( nombreLock, Lantencia_Lock );
)
colaPeticiones[ nombreLock ].push( thread );
if ( colaPeticiones[ nombreLock ].size() > 1 )
thread->passivate();
}
void Locks::set_unlock( string nombreLock )
{
colaPeticiones[ nombreLock ].pop();
if ( colaPeticiones[ nombreLock ].size() > 0 )
{
thread= colaPeticiones[ nombreLock ].front();
thread->activate();
}
colaPeticiones.erase( nombreLock );
}
Figura 6.4: Simulación de locks de acceso exclusivo.
entre distintas variables de tipo lock. La Figura 6.5 muestra la simulación de la barrera
de sincronización la cual es implementada utilizando las mismas variables de tipo lock.
6.4.
Simulaciones
El simulador construye un procesador multi-core formado por grupos de 4 núcleos
conectados a un cache L2. Cada núcleo tiene su propio cache L1 y todos los caches L2
están conectados a la memoria principal. El tamaño de los caches se mantiene constante
independientemente del total de núcleos, por lo tanto se podrı́an observar aceleraciones
super-lineales cuando se aumenta el total de threads.
Una verificación importante para los simuladores de las distintas estrategias de procesamiento de transacciones, fue que al final de cada ejecución, todas las estrategias hayan
acumulado exactamente la misma cantidad de unidades de costo en operaciones relevantes
en el tiempo de simulación. Se excluye de esto el costo en tiempo de simulación generado por las transferencias de datos entre los caches y el costo de los locks, todo lo cual
depende de la estrategia especı́fica de procesamiento de transacciones. Estos costos junto
99
void Thread::ovBarrier( int my_tid, int tid )
{
set_lock( "barrera", this_thread );
ov_barrier[tid]++;
if ( my_tid == tid )
{
if ( ov_barrier[tid] < NumThreads )
{
unset_lock( "barrera" );
this_thread->passivate();
return;
}
else
ov_barrier[tid] = 0;
}
else
if ( ov_barrier[tid] == NumThreads )
{
ov_barrier[tid] = 0;
pointerThread[tid]->activate();
}
unset_lock( "barrera" );
}
Figura 6.5: Simulación de barrera de sincronización.
con el nivel de balance de carga alcanzado por los threads desplegados en los núcleos del
procesador, definen las diferencias en rendimiento de las estrategias.
6.4.1.
Configuración de simuladores
De acuerdo a la metodologı́a de modelación y simulación propuesta, para ajustar los
parámetros que describen el costo de las operaciones relevantes (ranking y actualización
del ı́ndice) y los parámetros que definen el costo de la arquitectura (costo de locks y costos
de transferencias de bloques de memoria entre Ram y L2, y entre L2 y L1), fue necesario
ejecutar programas de benchmark sobre los dos procesadores Intel Xeon Quad-Core para
realizar mediciones desde 1 a 8 núcleos.
Para medir el costo de las operaciones relevantes tales como ranking de documentos y
actualización del ı́ndice se realizaron ejecuciones de las implementaciones reales, activando
sólamente la operación siendo medida en cada ejecución. Cada una de estas operaciones
100
1500
3000
CR
BP
TLP1
TLP2
RTLP
RBLP
2500
Transactions per Second
Transactions per Second
2000
1000
500
2000
CR
BP
TLP1
TLP2
RTLP
RBLP
1500
1000
500
0
0
4
6
8
12
nThreads
16
24
4
10 % writers
6
8
12
nThreads
16
24
40 % writers
Figura 6.6: Throughput alcanzado por las estrategias para diferentes tasas de escritura y
consultas OR, y un total de 40 mil transacciones en un procesador Intel i7.
principales está compuesta de sub-operaciones las cuales fueron medidas separadamente.
Las mediciones en este caso tuvieron variaciones de menos del 1 %. Se encontró que del
tiempo total de ejecución de cualquiera de las estrategias reales, aproximadamente el 6 %
del costo corresponde al costo de administrar las estructuras de datos y los costos de la
jerarquı́a de memoria. Por ejemplo, una de las ejecuciones reportó 571 segundos para la
ejecución de transacciones de sólo lectura con ranking de documentos habilitado, mientras
que la misma ejecución sin ranking habilitado alcanzó 32 segundos, donde 32/(571-32) =
0.059. El tiempo total se reduce a 218 segundos cuando se introduce un 40 % de transacciones de escritura, lo cual incrementa a un 17 % el peso de la administración de datos y
el efecto de la jerarquı́a de memoria en el costo total de la estrategia.
Las medidas anteriores muestran que el servicio de ı́ndice es una aplicación de granularidad gruesa y como tal el rendimiento relativo entre las estrategias no deberı́a ser
afectado mayormente por las caracterı́sticas de la arquitectura del procesador. Como validación de esta afirmación y para complementar los resultados mostrados en la Figura 5.4
para el Intel Xeon Quad-Core, en la Figura 6.6 se muestran resultados para un procesador
de tecnologı́a reciente, Intel i7, el cual contiene tres niveles de cache y cuyo rendimiento
es superior. Este procesador permite el uso de hasta 24 threads de manera eficiente. Los
resultados muestran que las diferencias relativas en rendimiento de las distintas estrategias
son similares a lo presentado en la Figura 5.4. Para transacciones de sólo lectura todas las
estrategias alcanzan un rendimiento muy similar.
101
Puesto que se conocen los largos de las listas invertidas, es posible determinar el costo
promedio de procesar un bloque de lista invertida durante el ranking, excluyendo el costo
de administración de datos y jerarquı́a de memoria. Lo mismo para la actualización de un
bloque de lista invertida y para la obtención de los k resultados para una consulta. Si el
costo de hacer el ranking sobre un bloque de lista invertida toma x unidades de tiempo
de simulación, se obtuvo que el costo promedio aproximado de las otras dos operaciones
relevantes es x/10 y x/5 respectivamente.
En el capı́tulo anterior se determinó que el tamaño óptimo para los bloques de listas
invertidas es de 64 pares (id doc, freq term). Por lo tanto, en los simuladores se utiliza el
mismo tamaño de bloque de lista invertida, lo cual da un costo promedio por par de x/64
unidades de tiempo de simulación para el ranking. Se define como R esta unidad de básica
de costo x/64 y las simulaciones fueron ejecutadas utilizando el valor R= 0.13. Este valor
de R fue determinado de manera que las simulaciones entregaran la misma proporción del
6 % para el peso de la ejecución de transacciones de sólo lectura utilizando la estrategia
BP sin ejecutar barreras de sincronización.
Respecto de locks, se ejecutaron programas de benchmark que ejecutan millones de
locks para distinto número de núcleos. Los tiempos, si bien resultan ser del orden de los
micro-segundos y muy inferiores a los tiempos obtenidos para las operaciones de ranking
y actualización de listas, aumentaron linealmente con el total de núcleos, lo cual es consecuente con la modalidad escogida para simular los locks, es decir, obligar a que cada
variable de tipo lock siempre sea leı́da y actualizada en modo exclusivo en la memoria
principal.
En las implementaciones reales de las estrategias sincrónicas se utilizan barreras de
sincronización implementadas con locks. En las simulaciones se utiliza la misma implementación. A partir del costo determinado para los locks, fue posible estimar el costo de
las esperas condicionales que deben ejecutar los threads que esperan por otros threads
en la barrera. Esta estimación se obtuvo restando el costo de los locks al costo total de
la ejecución de la barrera de sincronización. Dicho costo fue levemente inferior al costo
de los locks en promedio. Para incluir este efecto en la simulación de la barrera, el costo
de las latencias de los locks utilizados para implementar la barrera en el simulador (ver
Figura 6.5) es 1.5 veces el costo de un lock normal. La Figura 6.7.a muestra resultados
para la ejecución de locks y barreras en el procesador Intel. Los tiempos en el eje y están
dados en unidades de 10−5 segundos.
102
8
6
barrier
lock
7
4k
8k
16k
32k
5.5
6
5
5
4
4.5
3
4
2
3.5
1
0
1
2
3
4
5
6
7
8
3
64k
(a)
128k
256k
512k
(b)
Figura 6.7: (a) Valores observados en las operaciones de locks y barreras. (b) Razón entre
el tiempo de acceso a los caches L2 y L1 en función del tamaño de los datos y la disperción
de los accesos.
La tasa de transferencia de bloques de memoria a través de la jerarquı́a de caches
(parámetros g1 y g2 ) se obtuvo ejecutando programas de benchmark destinados a realizar
accesos consecutivos dentro de posiciones de arreglos de enteros (los recorridos que hacen
las transacciones de lectura y escritura sobre los bloques de listas invertidas son también
recorridos consecutivos en regiones de memoria contigua). Se definió un arreglo Ai por
cada thread i y antes de iniciar la medición, cada thread anota en un segundo arreglo Bi
las posiciones consecutivas que debe recorrer cada thread i dentro del arreglo principal
Ai . La idea es referenciar a ambos arreglos al mismo tiempo durante las mediciones. Los
tamaños de los arreglos se ajustan para hacerlos calzar con el tamaño de cada cache. Se
realizaron ejecuciones variando los tamaños de los arreglos Ai y Bi para determinar la
razón g2 /g1 . Una vez obtenida esta razón, se ajustaron los valores de g1 y g2 para hacer
que el costo total de las operaciones de acceso a las estructuras de datos compartidas
representen un 6 % del tiempo total de simulación utilizando transacciones de sólo lectura
y sin barreras (locks). Esto se logró para g1 = 0.010 y g2 = 0.044 puesto que se encontró que
g2 /g1 = 4.4 es un buen compromiso para aplicaciones que acceden a estructuras de datos
de gran tamaño en forma consecutiva en memoria contigua.
El valor g2 /g1 = 4.4 se obtuvo de la siguiente manera. Se realizaron experimentos
variando el tamaño n de los arreglos Ai y Bi para 8 núcleos. Se obtuvo la razón Tg2 +g1 /Tg1
para los distintos valores de n. El tiempo Tg2 +g1 representa el caso en que todos los threads
inician sus accesos a los arreglos Ai y Bi asegurándose que las mediciones se inician cuando
103
esos arreglos están almacenados en la memoria principal y no en alguno de los caches L1 y
L2. Para esto previamente cada thread ejecuta accesos a arreglos auxiliares de un tamaño
mucho más grande que la capacidad del cache L2. Durante la medición del tiempo de
ejecución, se realiza un total de m accesos a los arreglos Ai y Bi y se realizan algunas
multiplicaciones sobre los elementos de Ai . El tamaño efectivo de cada cache L1 para
almacenar datos es de 16KB y cada cache L2 tiene una capacidad de 4MB.
Para obtener el tiempo Tg1 primero se hace que una copia del arreglo B0 se encuentre
almacenada en todos los caches L2, y se hace que cada thread i tenga su arreglo Ai
almacenado en el respectivo cache L1. Luego se mide Tg1 haciendo que todos los threads i
en paralelo hagan m accesos a posiciones contiguas del arreglo B0 y asignen el valor en
cada posición a su arreglo local Ai , y ejecuten multiplicaciones sobre los datos obtenidos
en cada asignación. Esto genera transferencias de datos entre los caches L2 y L1.
La razón Tg2 +g1 /Tg1 puede ser representada por la siguiente ecuación:
Tg2 +g1
m · c + 2 · n · g1 + 2 · n · g2
=
Tg1
m · c + n · g1
donde c es el tiempo promedio consumido en cada una de las m operaciones realizadas
sobre los arreglos Ai y Bi de tamaño n. A partir de los resultados para Tg1 con 8 núcleos
se midió el valor de c y g1 . Los valores de c resultaron muy estables con un promedio de
aproximadamente 3.6×10−9 segundos. Dado que experimentalmente se determina Tg2 +g1 ,
Tg1 , c y g1 , entonces es posible determinar g2 . La Figura 6.7.b muestra los valores obtenidos
para g2 /g1 para los distintos valores de n donde cada curva representa g2 /g1 para un valor
distinto de m. El promedio es 4.4 y las curvas muestran una tendencia general hacia ese
promedio cuando n crece. Las listas invertidas ocupan espacios de memoria contigua que
son similares en extensión a la memoria ocupada por los arreglos Ai y Bi para n grande.
Las figuras 6.8.a y 6.8.b muestran los valores respectivos para g1 y g2 . Se observa
que ambas curvas tienden a decaer cuando n crece. Presumiblemente esto ocurre debido
a los efectos del pre-fetching de datos del procesador. Para el caso de las simulaciones
presentadas en este capı́tulo, lo relevante es la razón g1 /g2 puesto que el valor de g1
se ajusta para satisfacer el requerimiento del 6 % en el tiempo total de simulación para
transacciones de sólo lectura utilizando BP sin sincronización de barrera.
104
3
10
4k
8k
16k
32k
2.5
4k
8k
16k
32k
9
8
7
2
6
1.5
5
4
1
3
2
0.5
1
0
64k
128k
256k
512k
0
64k
(a) g1
128k
256k
512k
(b) g2
Figura 6.8: Valores promedio para g1 y g2 en unidades de 10−9 segundos.
6.4.2.
Protocolo optimista de timestamps
El estudio con las implementaciones reales presentado en el Capı́tulo 5, muestra que
las estrategias BP y RTLP alcanzan el mejor rendimiento general para todos los casos
considerados. RTLP no procesa las transacciones de manera atómica. Sin embargo, pese a
que la comparación con BP no es justa por ser RTLP una estrategia mucho más relajada
respecto de requerimientos de sincronización que BP, una justificación para estudiar el
rendimiento de RTLP fue que es una estrategia que representa lo mejor que puede lograr un
protocolo optimista de sincronización de transacciones. También RTLP es el representante
de las estrategias asincrónicas que alcanza el mejor rendimiento entre ellas.
Para complementar el análisis de las estrategias de sincronización, a continuación se
define (e incluye en los experimentos) una versión optimista de RTLP la cual asegura que
las transacciones son atómicas. A cada transacción Ti se le asigna un timestamp con valor
único Ts (i) que permita establecer un orden entre las transacciones. A cada lista invertida
Lt de un término t se le asocian dos valores:
WT S (Lt ) : Máximo de los timestamps de las transacciones que han escrito en Lt ,
escritura realizada por la operación write(Lt ).
RT S (Lt ) : Máximo de los timestamps de las transacciones que han leı́do Lt , lectura
realizada por la operación read(Lt ).
105
El protocolo es:
Si Ti intenta ejecutar read(Lt )
◦ Si Ts (i) < WT S (Lt ) no se ejecuta la operación y Ti se aborta.
◦ Si Ts (i) > WT S (Lt ) se ejecuta la operación y RT S (Lt ) = máx{RT S (Lt ), Ts (i)}.
Si Ti intenta un write(Lt )
◦ Si Ts (i) < RT S (Lt ) no se ejecuta la operación y Ti se aborta.
◦ Si Ts (i) < WT S (Lt ) no se ejecuta la operación y Ti continúa.
◦ Si no, sı́ se ejecuta la operación y WT S (Lt ) = Ts (i).
Al abortar transacción se debe hacer el rollback de ésta y todas las transacciones que dependen de ella, es decir, transacciones que hayan leı́do valores escritos por la primera. Esto
es recursivo y se llama efecto cascada. El efecto cascada se puede evitar obligando a que
las transacciones sólo puedan leer valores escritos por transacciones que hayan terminado,
lo cual introduce estados de espera que pueden afectar el rendimiento. El requerimiento de
rollbacks recursivos, necesarios para garantizar la serialidad de las transacciones, se puede
relajar asegurando sólo la atomicidad de las transacciones. Para esto sólo se re-ejecuta
la transacción abortada sin afectar a las que dependan de ella. Esta versión de RTLP es
denominada RTLP-RB (relaxed term level paralelism with roll-backs).
6.4.3.
Resultados para Intel y Niagara
En la Figura 6.9 se muestran resultados para el throughput alcanzado por el simulador considerando hasta 128 threads con cada thread siendo asignado a un núcleo distinto.
Considerando el rango entre 1 y 8 threads, los resultados muestran una tendencia en rendimiento bastante similar a lo observado para las implementaciones reales de las estrategias.
Para más de 8 threads se observa que las estrategias RTLP y RTLP-RB no escalan eficientemente con el total de threads. El simulador revela que se producen esperas muy
largas por los locks entre transacciones que comparten términos frecuentes, y la tasa de
roll-backs crece significativamente cuando existen transacciones de escritura.
La Tabla 6.1.a revela la razón de la pérdida de rendimiento de la estrategia RBLP-RB.
La cantidad de roll-backs (columnas RB) aumenta hasta el punto de que poco más de la
mitad de las transacciones deben ser re-ejecutadas. La tabla también muestra el efecto que
106
600
10000
BP
RTLP
RTLP-RB
500
8000
7000
Throughput
400
Throughput
BP
RTLP
RTLP-RB
9000
300
200
6000
5000
4000
3000
2000
100
1000
0
0
1
2
4
8
16
32
nThreads
0 % writers, 1 to 8 threads
2000
128
0 % writers, 16 to 128 threads
35000
BP
RTLP
RTLP-RB
1800
64
nThreads
BP
RTLP
RTLP-RB
30000
1600
25000
Throughput
Throughput
1400
1200
1000
800
600
20000
15000
10000
400
5000
200
0
0
1
2
4
8
16
nThreads
32
64
128
nThreads
40 % writers, 1 to 8 threads
40 % writers, 16 to 128 threads
Figura 6.9: Throughputs que el modelo de simulación predice para las estrategias BP,
RTLP y RTLP con Rollbacks (RTLP-RB), en el procesador Intel.
dichos roll-backs tienen en el balance de carga (columnas LB) de las computaciones ejecutadas por los núcleos. El balance de carga se mide considerando la cantidad computación
realizada por los threads en los núcleos. Para esto se calcula el promedio de computación
observado en los threads dividido por el máximo observado en cualquiera de los threads.
Los valores de la Tabla 6.1.a muestran el balance de carga promedio para intervalos grandes de tiempo de simulación. Los resultados muestran que el balance de carga se degrada
significativamente a causa de los roll-backs, donde el balance óptimo se alcanza en 1 y el
desbalance extremo para valores cercanos a 0.
La Tabla 6.1.b muestra resultados que explican la pérdida de rendimiento de la estrategia RTLP cuando aumenta considerablemente el total de threads que participan en
107
NT
2
4
8
16
32
64
128
20 %
RB
LB
0.04
1
0.10 0.95
0.18 0.95
0.26 0.92
0.33 0.82
0.41 0.70
0.46 0.56
40 %
RB
LB
0.06 0.95
0.13 0.98
0.21 0.89
0.31 0.87
0.42 0.78
0.51 0.69
0.57 0.41
NT
2
4
8
16
32
64
128
(a) 20 % and 40 % writers (RTLP-RB)
0%
Pw
LB
0
1
0.01 0.99
0.02 0.99
0.04 0.97
0.06 0.94
0.10 0.88
0.21 0.84
40 %
Pw
LB
0
1
0.01
1
0.03
1
0.09
1
0.14 0.99
0.17 0.97
0.37 0.90
(b) 0 % and 40 % writers (RTLP)
Tabla 6.1: Razones de la pérdida de rendimiento de las estrategias RTLP-RB y RTLP.
0.5
0.5
BP
RTLP
0.4
Time per transaction
Time per transaction
0.4
0.3
0.2
0.1
BP
RTLP
0.3
0.2
0.1
0
0
1
2
3
4
5
1
2
3
4
5
Batches of query/index-update operations
1
2
3
4
5
1
2
3
4
5
Batches of query/index-update operations
0 % writers
40 % writers
Figura 6.10: Tiempos de respuesta de transacciones recolectados a intervalos regulares del
tiempo de simulación para 8 threads.
la solución de las transacciones. Existe una probabilidad no despreciable de que para
cualquier transacción un thread dado tenga que esperar por un lock. Esta probabilidad
aumenta con el número de threads (columnas Pw ). Esto hace que se pierda gradualmente
el balance de carga (columnas LB).
La Figura 6.10 muestra las variaciones en el tiempo de respuesta de transacciones individuales al final de periodos dados por el procesamiento de un lote de transacciones. Los
resultados muestran que BP mantiene tiempos bajos de respuesta y que RTLP ocasionalmente supera el tiempo de procesamiento secuencial de la transacción debido a esperas
por la asignación de locks.
108
0% W
NT= 1
2
4
8
16
32
Hits cache L1
BP
RTLP BP/RTLP
0.00
14.07
0.00
0.00
18.81
0.00
0.00
21.16
0.00
0.00
23.82
0.00
0.02
30.94
0.00
0.05
40.83
0.00
40 % W
NT= 1
2
4
8
16
32
Hits cache L1
BP
RTLP BP/RTLP
0.00
6.70
0.00
0.00
8.02
0.00
0.00
8.30
0.00
0.00
11.90
0.00
0.10
18.05
0.01
0.37
22.77
0.02
0% W
NT= 1
2
4
8
16
32
Hits cache L2
BP
RTLP BP/RTLP
0.05
0.99
0.05
7.64
6.59
1.16
54.89
16.09
3.41
118.84 21.00
5.66
157.06 22.77
6.90
177.02 22.29
7.94
40 % W
NT= 1
2
4
8
16
32
Hits cache L2
BP
RTLP BP/RTLP
1.08
4.15
0.26
8.53
6.75
1.26
20.30
8.51
2.39
29.27
8.48
3.45
36.03
7.87
4.58
40.98
7.46
5.49
Tabla 6.2: Total de aciertos (cache hits) en los caches L1 y L2. Valores en millones de
aciertos cada 10 mil transacciones.
La Tabla 6.2 verifica las conclusiones del análisis del caso promedio (Sección 6.2). La
tasa de hits L1 de la estrategia RTLP es mucho mejor que los hits L1 alcanzados por
la estrategia BP. No obstante, la tasa de hits en los caches L2 que alcanza BP es muy
superior a la tasa de hits que alcanza la estrategia RTLP, y al menos en el procesador
Intel las transferencias de datos desde memoria principal al cache L2 toman más tiempo
en concretarse que las transferencias entre L1 y L2. También debido a que los segmentos
de listas invertidas en la estrategia RTLP tienden a estar replicados en varios caches L2, el
total de invalidaciones de entradas de cache tiende a ser mayor en RTLP que en BP para
un número grande de threads. En las simulaciones con 40 % de escrituras se observó la
secuencia (NT, VRT LP /VBP )= (16, 1.14), (32, 1.26), (64, 1.34) y (128, 1.40), donde VRT LP
y VBP indican el total de entradas de invalidadas en los cache L1 y L2 para las estrategias
RTLP y BP respectivamente.
El efecto del mejor uso de la localidad en la arquitectura del procesador simulado que
realiza la estrategia BP se puede observar en la Figura 6.11. En este caso se simula una
situación de granularidad muy fina reduciendo el costo del proceso de ranking de documentos a un valor 100 veces menor. Los resultados muestran que en este caso la estrategia
RTLP no escala eficientemente más allá de 8 threads. En todos los casos la estrategia BP
alcanza tiempos de ejecución más eficientes para esta situación de granularidad muy fina.
109
1
BP 40% W
RTLP 40% W
BP 20% W
RTLP 20% W
BP 100% R
RTLP 100% R
Normalized running time
0.8
0.6
0.4
0.2
0
2
4
8
16
32
Number of threads
64
128
Figura 6.11: Tiempos totales de ejecución normalizados a 1 para un caso en que se simula
granularidad fina haciendo que el costo del ranking de documentos sea 100 veces menor.
Se hace énfasis en que el modelo de simulación de la arquitectura del procesador es el
mismo en ambas estrategias.
También dado que la estrategia RTLP alcanza un rendimiento escalable cuando la
granularidad del ranking es cercana a lo real (Figura 6.9), los resultados de la Figura 6.11
sirven para comprobar que el efecto de la arquitectura del procesador es menos relevante en el rendimiento general de las estrategias de sincronización de transacciones. Los
factores relevantes son aspectos tales como el balance de carga y los tiempos de espera
por la asignación de solicitudes de locks. La cantidad total de locks ejecutados por BP is
directamente proporcional al total de threads mientras que para RTLP el total de locks
ejecutados es constante. Para 128 threads, la estrategia BP ejecuta un total de locks que
es 8 veces menor al total de locks ejecutados por RTLP.
Los resultados anteriores son para simulaciones sobre el procesador Intel descrito en
la Sección A.1.2 y ampliado hasta 128 núcleos siguiendo la misma lógica de grupos de 8
pares (núcleo, cache L1) conectados a un mismo cache L2, y desplegando varios de estos
grupos conectados a la memoria principal hasta alcanzar el total de núcleos.
El procesador Niagara T1 (Sección A.1.1) permite de manera eficiente la ejecución de
4 threads en cada par (núcleo, cache L1) y conecta 8 de estos núcleos a un mismo cache
L2. Aplicando el mismo procedimiento que el utilizado en el procesador Intel, se puede
110
250
BP
RTLP
RTLP-RB
BPG
3500
3000
Throughput
200
Throughput
4000
BP
RTLP
RTLP-RB
BPG
150
100
2500
2000
1500
1000
50
500
0
0
1
2
4
8
16
nThreads
700
14000
BP
RTLP
RTLP-RB
BPG
12000
10000
Throughput
600
Throughput
128
0 % writers, 16 to 128 threads
BP
RTLP
RTLP-RB
BPG
800
64
nThreads
0 % writers, 1 to 8 threads
900
32
500
400
300
8000
6000
4000
200
2000
100
0
0
1
2
4
8
16
nThreads
32
64
128
nThreads
40 % writers, 1 to 8 threads
40 % writers, 16 to 128 threads
Figura 6.12: Throughputs que el modelo de simulación predice para las estrategias BP,
RTLP, RTLP-RB y BP-Global (BPG), en el procesador Niagara T1.
incrementar la cantidad total de unidades (8·(4 threads, 1 núcleo + 1 cache L1), cache
L2) hasta alcanzar 128 threads.
Los resultados de simulación obtenidos con el procesador Niagara son similares a los
obtenidos con el procesador Intel respecto de las diferencias relativas en rendimiento entre
las estrategias BP, RTLP y RTLP-RB. Estos resultados se muestran en la Figura 6.12. En
esta figura también se muestran resultados para la estrategia BP-Global (BPG) propuesta
en la Sección 4.3. Para ejecuciones con gran número de threads y operaciones con gran
número de términos como en el caso de las transacciones de escritura, BPG pierde eficiencia
debido al desbalance de carga que se produce entre los threads.
111
6.5.
Conclusiones
En este capı́tulo se ha presentado un análisis del rendimiento de las distintas estrategias
de sincronización de transacciones estudiadas en este trabajo de tesis. El análisis valida
las conclusiones obtenidas con las implementaciones reales ejecutadas sobre el procesador
Intel de 8 núcleos. Es decir, la estrategia BP-Local propuesta en esta tesis alcanza mejor
rendimiento y escalabilidad que sus contrapartes asincrónicas, las cuales pierden eficiencia
principalmente debido al desbalance de carga en los núcleos del procesador y a las esperas
por la asignación de locks.
Los resultados de las simulaciones también muestran que las estrategias asincrónicas
optimistas drásticamente pierden eficiencia debido al incremento de la tasa de roll-backs
cuando aumenta el porcentaje de transacciones de escritura. Por otra parte, la estrategia
BP-Global se comporta de manera eficiente para un número pequeño de threads y para
operaciones con pocos términos. Esto sugiere que BP-Global se adapta mejor a cargas
de trabajo donde los documentos contienen pocos términos. No obstante, en esta tesis
se plantea como trabajo a futuro el estudio de estrategias de asignación de términos a
los threads y balance dinámico de carga. La estrategia aplicada en la implementación de
BP-Global simplemente utiliza la regla id término módulo total threads para asignar las
listas invertidas a los threads. Los resultados muestran que esa regla produce desbalance
cuando existen términos más frecuentes que otros en las transacciones.
El análisis realizado estuvo basado en el uso del modelo Multi-BSP de computación en
procesadores multi-core, el cual permite abstraer los detalles de hardware y software de
sistema, encapsulando sus costos en parámetros que pueden ser obtenidos vı́a programas
de benchmark. Sobre este modelo de computación se construyó un simulador que permite
predecir el rendimiento de las estrategias cuando se aumenta el total de threads (núcleos)
significativamente. Esto permitió estudiar la escalabilidad de las estrategias incluyendo el
efecto de factores que son intratables analı́ticamente tales como la tasa de roll-backs y
tiempos de espera por locks.
En resumen, la metodologı́a de análisis que permitió la identificación de los factores
relevantes en el rendimiento de las estrategias de procesamiento de transacciones, estuvo
compuesta de la combinación de (a) el modelo Multi-BSP, lo cual proporcionó la base
para modelar y parametrizar el hardware y software de sistema, (b) el uso de simulación
discreta orientada a procesos, lo cual permitió representar en forma precisa las acciones
relevantes de las estrategias y proyectar sus costos en el tiempo de simulación para medir
rendimiento en forma exacta, y (c) la inyección de cargas de trabajo realistas.
112
Capı́tulo 7
Conclusiones Finales
En este trabajo de tesis se ha estudiado en profundidad el problema de la gestión
eficiente de threads en procesadores multi-core utilizados en máquinas de búsqueda para
la Web. El énfasis estuvo centrado en el procesamiento de transacciones de lectura y
escritura puesto que recientemente las máquinas de búsqueda han comenzado a incluir
dentro de las respuestas a las consultas de sus usuarios, documentos generados hace pocos
minutos o segundos respecto del instante en que el usuario envı́a su consulta.
La contribución principal de esta tesis es la propuesta de una estrategia llamada “BP”,
la cual resuelve el problema de sincronización de transacciones utilizando paralelismo
sincrónico por lotes (bulk-synchronous parallelism) a nivel de threads. La experimentación realizada con implementaciones reales y el análisis basado en simulación discreta,
muestran que: (1) BP es más eficiente tanto en throughput como en tiempo de respuesta
de transacciones individuales que las estrategias basadas en procesamiento asincrónico de
transacciones, y (2) BP escala más eficientemente al aumentar el total de núcleos que las
alternativas asincrónicas.
Dada la magnitud del tráfico de consultas que reciben las máquinas de búsqueda, con
tasas del orden de los cientos de miles de consultas por segundo, y lo inmensamente activos
que son algunos sistemas de uso masivo tales como Twitter o Yahoo! Answers, los cuales
llegan a generar miles o millones de nuevos textos por segundo, el estudio presentado en
este trabajo se concentró en una representación tecnológicamente factible del problema.
Esta consiste en modelar un nodo ejecutando un servicio de ı́ndice como un sistema que
posee una cola de entrada de transacciones de lectura y escritura, las cuales son atendidas
113
por c servidores, donde cada servidor es representado por un par (1 thread, 1 núcleo) en
el procesador Intel o un par (4 threads, 1 núcleo) en el procesador Niagara T1.
Dadas las latencias de los sistemas de archivos distribuidos de los distintos clusters
que componen un centro de datos, la alternativa de acumular documentos para indexarlos periódicamente, y luego reemplazar las distintas particiones del ı́ndice en producción
almacenadas en los P × D nodos, no es factible puesto que ésta alternativa impide incluir
nuevos documentos en las respuestas a las consultas en tiempos de menos de un minuto. La
solución factible es aplicar el esquema utilizado en este trabajo de tesis, es decir, los nuevos
documentos se envı́an directamente a los nodos del cluster en la forma de transacciones de
escritura, las cuales son servidas en cada nodo en forma concurrente con las transacciones
de lectura o consultas de los usuarios.
Desde la teorı́a de colas, la representación escogida para el nodo del cluster puede
ser vista como un sistema G/G/c el cual, al menos para los datos de la Web de UK,
incluso puede ser similar a M/G/c o tal vez M/M/c. Sin embargo, el foco de la evaluación
del rendimiento de las estrategias sincrónicas y asincrónicas estuvo centrado en el caso
en que la tasa de llegada de transacciones es tan alta que siempre es posible asignar
una transacción a cada par (thread, núcleo). Desde el punto de vista de throughput este
es el caso interesante, puesto que cualquiera de las estrategias estudiadas en esta tesis
es capaz de entregar un tiempo de respuesta individual por transacción que está dentro
de la fracción de segundo. Aparte del throughput, la segunda métrica de interés fue la
escalabilidad de las estrategias, es decir, que tan capaces son las estrategias de mantener
su eficiencia a medida que se aumenta el total de pares (thread, núcleo), a la vez que se
mantiene constante el tamaño de los datos sobre los cuales las estrategias deben operar.
Esta es una medida de la eficiencia con que las estrategias estudiadas pueden utilizar los
recursos disponibles en el procesador, en particular el número de núcleos y el espacio en
los caches de los núcleos.
Como trabajo a futuro, se pueden estudiar los siguientes problemas relevantes para el
estado del arte en el diseño de máquinas de búsqueda para la Web.
Las nuevas arquitecturas de procesadores están incluyendo la posibilidad de poner
en estado de bajo consumo de energı́a a grupos de cores, caches y bancos de memoria principal que reciben poco trabajo durante un perı́odo de tiempo determinado.
Apoyándose en la metodologı́a de evaluación del rendimiento propuesta en este trabajo de tesis, es decir, la metodologı́a basada en simulación discreta orientada a
procesos, y la representación de la arquitectura del procesador utilizando una repre114
sentación asincrónica del modelo Multi-BSP de computación, es posible abordar el
estudio de estrategias de planificación de trabajos para este problema. Estas estrategias deberı́an detectar perı́odos de tráfico bajo de consultas y determinar el grupo de
cores que es capaz de servir el menor throughput requerido en estos perı́odos. Tanto
las estrategias asincrónicas estudiadas en este trabajo de tesis, como las estrategias
sincrónicas BP propuestas, tienen la capacidad de trabajar con un número variable
de núcleos. Por lo tanto, resulta interesante estudiar el rendimiento de éstas bajo
este escenario pensando en cuán resistentes son al perı́odo que pasa desde que se
detecta la necesidad de utilizar más núcleos hasta que estos están disponibles para
aceptar carga.
En buscadores verticales, los cuales son de un propósito especı́fico tal como publicidad, muchos de los cuales mantienen en memoria principal ı́ndices invertidos de
tamaño bien pequeño y sin comprimir, y que además reciben documentos con muy
pocos términos, resulta interesante estudiar más en detalle el rendimiento de la versión de BP propuesta para estos casos. Si bien, buena parte del rendimiento de esta
estrategia fue estudiado para el caso de paralelismo hı́brido presentado en el Capı́tulo 3 y en las simulaciones del Capı́tulo 6, la atención de este trabajo de tesis estuvo
centrada en ı́ndices de gran tamaño. Es decir, el caso real para grandes máquinas de
búsqueda, con ı́ndices invertidos conteniendo una cantidad de paralelismo suficiente
como para utilizar todos los threads para procesar cada transacción de lectura y
escritura. En este caso, interesa aprovechar al máximo la memoria principal y los
núcleos, para ası́ contribuir a reducir el total de nodos desplegados en el centro de
datos.
En el caso de ı́ndices pequeños para buscadores verticales, algunas de estas listas,
las de los términos más populares, deberı́an tener una cantidad de paralelismo suficiente. Sin embargo, pueden existir muchas otras listas que es mejor procesarlas
con menos threads y lo mismo para documentos que contengan unas pocas decenas
de términos. Luego aquı́ surge un problema combinatorial puesto que la cantidad
de combinaciones posibles es muy grande. Algunas listas pueden ser procesadas utilizando todos los threads, y otras más pequeñas pueden ser procesadas utilizando
un thread distinto por cada transacción. Incluso es posible aplicar algoritmos de
planificación dinámica de tareas, o planificación semi-estática, con las transacciones
agrupadas en la cola de entrada del nodo en algún periodo del tiempo. La solución
a este problema podrı́a ser abordada con estrategias de optimización combinatorial.
Todo esto constituye un tema amplio de investigación en sı́ mismo.
115
Apéndice A
Arquitecturas y Modelos
A.1.
Arquitecturas Multi-Core
Como su nombre lo indica, este tipo de arquitectura combina dos o más núcleos de procesamiento en un solo circuito integrado (Figura A.1). Multi-core de memoria compartida,
son sistemas con múltiples procesadores que comparten un único espacio de direcciones de
memoria. Cualquier procesador puede acceder a los mismos datos. Las últimas versiones
de tipo de arquitectura como Niagara e Intel usan redes punto a punto, como sistema de
interconexión.
A.1.1.
Procesador UltraSPARC T1
El procesador UltraSPARC T1 [51], conocido como Niagara y desarrollado por Sun
Microsystems, se caracteriza por integrar múltiples núcleos con capacidad para ejecutar
múltiples threads simultáneamente. Sus caracterı́sticas principales se presentan en la tabla A.1. Su diseño está orientado a aplicaciones tales como servidores de bases de datos y
servidores web.
La cache L1 está dividida en cache de instrucciones y cache de datos, y puede ser
compartida por 4 threads. La principal diferencia entre la cache de instrucciones y de datos
es que la primera tiene el doble de tamaño que la segunda. La cache L2 está dividida entre
los núcleos para facilitar la concurrencia en las operaciones de lectura y escritura.
116
Figura A.1: Ejemplo de arquitectura dual-core.
Processor
Memory
Operating System
Sun C/C++ Compiler
v5.8 Switches
SUN UltraSPARC-T1 8 core processor (1.2GHz)
(4-way fine-grain multithreading core)
L1 Cache
16+8 KB (instruction+data)
(per core)
4-way associative, LRU
L2 Unified
3MB (4Banksx768KB)
12-way associative, pseudo-LRU
Cache
16 GBytes
(4x4GBytes) DIMMS
533 MHz DDR2 SDRAM
SunOS 5.10 (Solaris 10) for UltraSparcT1
-fast -xarch=v9 -xipo=2
Parallelization with OpenMP: -xopenmp=parallel
Tabla A.1: Caracterı́sticas del procesador SUN UltraSPARC-T1.
117
Processor
Memory
Operating
System
Intel C/C++ Compiler
v10.1 Switches (icc)
MPI Library
BSPonMPI Library
Intel Quad-Xeon (2.66 GHz)
L1 Cache 4x32KB + 4x32KB (inst.+data)
(per core)
8-way associative, 64 byte/line
L2 Unified
2x4MB (4MB shared/2 procs)
16-way associative, 64-byte/line
Cache
16 GBytes
(4x4GB) 667 MHz FB-DIMM memory
1333 MHz system bus
GNU Debian System Linux
kernel 2.6.22-SMP for 64 bits
-O3 -march=pentium4 -xW -ip -ipo
Parallelization with OpenMP: -openmp
mpich2 v1.0.7 compiled with icc v10.1
http://bsponmpi.sourceforge.net
Tabla A.2: Caracterı́sticas principales del procesador Intel Quad-Xeon.
A.1.2.
Procesador Intel Xeon Quad-Core
El procesador Intel Xeon Quad-Core [23] incorpora 4 núcleos en un solo procesador.
Este procesador resulta idóneo para cómputo y servidores de alto rendimiento a un bajo
costo económico. Aumenta la eficiencia de las transferencias de datos desde la cache L2 al
procesador, lo que maximiza el ancho de banda entre la memoria principal y el procesador,
y reduce la latencia. En la tabla A.2 se ven reflejadas las principales caracterı́sticas de este
procesador.
A.2.
Modelos de computación paralela
A.2.1.
Bulk-Synchronous Parallel Model (BSP)
El modelo BSP (“The Bulk-Synchronous Parallel Model”) [36, 41, 93, 100] es un modelo de memoria distribuida que organiza el cómputo paralelo en una secuencia de pasos
llamados supersteps. BSP puede ser visto como un modelo de programación, donde se
describe el punto de vista del programador del sistema distribuido o paralelo, o como un
modelo de computación utilizado en el diseño de algoritmos, el cual tiene asociado un
modelo de costos que permite predecir el desempeño de los algoritmos.
118
BSP puede ser expresado en una gran variedad de sistemas y lenguajes de programación. Los programas BSP pueden ser escritos utilizando librerı́as de comunicación existentes como PVM [45, 47] ó MPI [46]. Lo único que requiere, es que provean un mecanismo de
comunicación no bloqueante y una forma de implementar la sincronización por barreras.
Un programa BSP es iniciado por el usuario en una máquina, y luego este programa se
duplica automáticamente en las máquinas restantes que conforman el cluster. Cada uno
ejecuta el mismo código pero con sus datos locales. Entre las librerı́as de comunicación
especialmente escritas para el modelo BSP se encuentran: “BSP lib” [44], “BSP pub” [48]
y “BSPonMPI” [43].
La idea fundamental de BSP es la división entre computación y comunicación. Se define un step como una operación básica que se realiza sobre datos locales de un computador.
Todo programa BSP consiste en un conjunto de steps que dan forman a los supersteps.
Durante cada superstep los computadores trabajan sobre datos almacenados en su memoria y envı́an mensajes hacia otros computadores. El fin de cada superstep está dado por
la sincronización de todos los computadores, punto en el cual se produce el envı́o efectivo
de los mensajes. Los computadores continúan con el siguiente superstep una vez que todos han alcanzado el punto de sincronización y los mensajes han sido entregados en sus
destinos.
La Figura A.2 muestra una máquina BSP genérica, la cual se define como:
Un conjunto de pares procesador-memoria.
Una red de comunicaciones que permite la entrega de mensajes punto a punto.
Un mecanismo de sincronización de los procesadores en los supersteps.
Una máquina BSP queda caracterizada por el ancho de banda de la red de interconexión, el número de procesadores, sus velocidades y por el tiempo de sincronización de los
procesadores. Todas estas caracterı́sticas forman parte de los parámetros de una máquina
BSP.
Modelo de Costos. El costo de un programa en BSP está dado por la suma acumulativa
del costo de cada superstep [82] y el costo de cada superstep está dado por la expresión
w + hG + L
119
PROCESADORES
COMPUTACIONES
LOCALES
COMUNICACIÓN
SINCRONIZACIÓN
(BARRIER)
Figura A.2: Modelo BSP.
donde
w es el número máximo de operaciones de computación realizado por alguno de los
procesadores,
h es el número máximo de mensajes enviados/recibidos en los procesadores,
G costo de enviar un mensaje por la red en una situación de tráfico continuo en
unidades normalizadas, y
L es el costo de una sincronización de barrera de los procesadores.
El efecto de la arquitectura del computador está incluida en los valores especı́ficos de
los parámetros G y L, los cuales son una función creciente en el número de procesadores
P . Estos valores pueden ser determinados empı́ricamente para cada arquitectura mediante
la ejecución de programas de benchmark.
Un ejemplo que muestra la manera en que el modelo de costo BSP se aplica es el
siguiente. Supongamos que existen P procesadores (computadores) de un cluster que necesitan tener en su memoria una copia idéntica de un arreglo de n elementos para poder
trabajar, pero dicho arreglo se encuentra almacenado en un sólo procesador, por ejemplo
el procesador 0. Lo que puede hacer el procesador 0 es dividir el arreglo en P partes cada
una de n/P elementos y enviar una parte distinta a cada uno de los P − 1 procesadores
restantes del cluster. Esto tiene un costo O(n + (n/P ) P G + L) incluyendo al procesador
0. Luego de este superstep, cada procesador queda con una parte distinta del arreglo.
Luego, en un segundo superstep, cada procesador envı́a a todos los otros la parte del
120
Figura A.3: Modelo Multi-BSP para 8 núcleos.
arreglo de tamaño n/P que tiene almacenada en su memoria. El costo de esta parte es
O(n/P + (n/P ) P G+ L). Al final de este superstep todos los procesadores quedan con una
copia completa del arreglo y el costo de esta operación de broadcast es O(n + n G + L), es
decir, este algoritmo tiene un costo independiente de P y por lo tanto altamente escalable.
En cambio una estrategia alternativa es simplemente hacer que el procesador 0 envı́e
un mensaje a cada uno de los P − 1 restantes procesadores con una copia del arreglo. Esto
es menos eficiente que el primer método, porque no existe el paralelismo que se produce
en el segundo superstep cuando todos los procesadores, al mismo tiempo, están enviando
una copia de su parte de tamaño n/P a todos los otros. El costo de esta operación es
O(n + n P G + L). Este algoritmo de broadcast es simple pero no es eficiente para un
número grande de procesadores puesto que tiene escalabilidad O(P ).
A.2.2.
Multi-BSP
Multi-BSP es una extensión del modelo BSP. Presenta múltiples niveles con parámetros
explı́citos dependiendo del número de procesadores, tamaño de memoria/cache, costos de
comunicación y sincronización. El nivel más bajo corresponde a la memoria compartida o
PRAM. Ver Figura A.3.
Un modelo Multi-BSP para profundidad d queda especificado por 4d parámetros
numéricos (p1 , g1 , ℓ1 , m1 ), (p2 , g2 , ℓ2 , m2 ), (p3 , g3 , ℓ3 , m3 ), ..., (pd , gd , ℓd , md ). El
modelo es un árbol de profundidad d con memorias/caches en los nodos internos and procesadores en las hojas. En cada nivel los cuatro parámetros cuantifican, respectivamente,
121
el número de sub-componentes, el ancho de banda, el costo de sincronización y el tamaño
de la memoria o cache. En el nivel i existe un conjunto de componentes especificados por
los parámetros (pi , gi , ℓi , mi ), cada uno conteniendo un número de componentes de nivel
i − 1, donde:
(a) pi es el número de componentes de nivel i − 1 dentro de un componente de nivel i.
Si i = 1, entonces p1 es el número de procesadores o CPUs en el componente del
nivel más bajo. Un paso computacional en el procesador sobre datos en el nivel 1 de
memoria es considerado como la unidad básica de tiempo de ejecución.
(b) gi el parámetro para el ancho de banda de la comunicación, es el número de operaciones que el procesador puede ejecutar en un segundo, divido por el número de
bytes que pueden ser transmitidos en un segundo entre el componente de nivel i y la
memoria del componente de nivel i + 1 del cual es parte. Se asume que las memorias
de nivel 1 tienen una velocidad suficiente como para no introducir esperas en los
procesadores, es decir, g0 = 1.
(c) Un superstep de nivel i dentro de un componente de nivel i, permite a cada uno de
sus pi componentes de nivel i − 1 ejecutar operaciones independientemente hasta
que cada uno alcanza una barrera de sincronización. Cuando los pi componentes
han alcanzado la barrera, cada uno de sus pi componentes de nivel i − 1 puede
intercambiar información con la memoria de tamaño mi del componente de nivel i.
El siguiente superstep de nivel i puede comenzar luego de finalizar el intercambio
de datos. Un costo ℓi es asignado a la barrera de sincronización a cada superstep de
nivel i. Se asume L1 = 0, puesto que los sub-componentes de un nivel 1 no tienen
memoria y pueden leer y escribir directamente en la memoria de nivel 1.
(d) mi es el número de bytes de memoria y caches dentro de un componente de nivel i,
que no está dentro de ningún componente de nivel i − 1.
Los parámetros del modelo pueden ser determinados empı́ricamente mediante la ejecución
de programas de benchmark en el procesador, o pueden ser estimados desde la especificación técnica del procesador. Por ejemplo, para un procesador Niagara T1 que contiene p
núcleos, los parámetros pueden ser los siguientes:
Nivel 1: 1 core tiene 1 procesador con 4 threads más un cache L1:
(p1 = 4, g1 = 1, ℓ1 = 3, m1 = 8KB).
122
Nivel 2: 1 chip tiene 8 núcleos más un cache L2:
(p2 = 8, g2 = 3, ℓ2 = 23, m2 = 3MB).
Nivel 3: p multi-core chips con memoria externa m3 accesada vı́a una red con tasa g2 :
(p3 = p, g3 = ∞, ℓ3 = 108, m3 ≤ 128GB).
A.2.3.
MPI y OpenMP
MPI,Message Passing Interface, es un estándar que define la sintaxis y la semántica
de las funciones contenidas en una biblioteca de paso de mensajes diseñada para ser usada
en programas que exploten la existencia de múltiples procesadores. El paso de mensajes
es una técnica empleada en programación concurrente para aportar sincronización entre
procesos y permitir la exclusión mutua, de manera similar a como se hace con semáforos,
monitores, etc. Su principal caracterı́stica es que no precisa de memoria compartida, por
lo que es muy importante en la programación de sistemas distribuidos. Los elementos
principales que intervienen en el paso de mensajes son el proceso que envı́a, el que recibe
y el mensaje.
MPI es un protocolo de comunicación entre computadoras. Es el estándar para la
comunicación entre procesadores que ejecutan un programa en un sistema de memoria
distribuida. Las implementaciones en MPI consisten en un conjunto de bibliotecas de
rutinas que pueden ser utilizadas en programas escritos en los lenguajes de programación
C, C++, Fortran, etc. La ventaja de MPI sobre otras bibliotecas de paso de mensajes,
es que los programas que utilizan la biblioteca son portables (dado que MPI ha sido
implementado para casi toda arquitectura de memoria distribuida), y rápidos, (porque
cada implementación de la biblioteca ha sido optimizada para el hardware en la cual se
ejecuta).
Por otra parte, OpenMP es un estándar para programación de memoria compartida.
Permite añadir concurrencia a los programas escritos en C, C++ y Fortran sobre la base
del modelo de ejecución “fork-join”. Está disponible en muchas arquitecturas. Se compone
de un conjunto de directivas de compilador, rutinas de biblioteca, y variables de entorno
que influencian el comportamiento en tiempo de ejecución.
OpenMP es un modelo de programación portable y escalable que proporciona a los
programadores una interfaz simple y flexible para el desarrollo de aplicaciones paralelas
para plataformas variadas como clusters de computadores.
123
Una aplicación construida con un modelo de programación paralela hı́brido se puede
ejecutar en un cluster de computadoras utilizando OpenMP y MPI, o más transparentemente a través de las extensiones de OpenMP para sistemas de memoria distribuida: la
comunicación entre nodos es a través de MPI y dentro de cada nodo. Los threads se comunican de forma implı́cita accediendo a memoria compartida. Con las directiva OpenMP
solo se distribuye el trabajo a los threads. Este modelo permite expresar dos niveles de
paralelismo dentro de un programa.
124
Apéndice B
Estrategias Alternativas
En cada caso, se tiene que
F es un trozo de tamaño O(K) de memoria local al procesador.
H es el tamaño de la tabla de hashing utilizada para los locks.
Lt lista invertida de un término.
tid identificador del thread.
pair(d, ft ) es un ı́tem de lista invertida, donde ft es la frecuencia del término t en el
documento d.
lockRead(t) permite a todos los lectores entrar en una zona protegida y lockWrite(t)
espera a que todos los que están en la zona protegida terminen y luego el thread
entra en modo exclusivo.
lock(t) es un lock de acceso exclusivo.
125
global: TnW, q write = false;
local: Tn;
while( true )
lock( input queue )
if q write == false then
Tn = extractNextTransaction( input queue )
endif
if q write == false and Tn.type != QUERY then
TnW = Tn;
q write = true
endif
if q write == true then Tn = TnW;
unlock( input queue )
if Tn.type == QUERY then /*read transaction*/
for each term t in Tn.query do
rank( Lt )
endfor
else /*write transaction*/
Barrier()
for each term t in Tn.termsForThread[tid] do
d = Tn.docId
if Tn.type == UPDATE then
update( Lt , pair( d, ft ) )
else
insert( Lt , pair( d, ft ) )
endif
endfor
if tid == 0 then q write = false
Barrier()
endif
endwhile
Figura B.1: Pseudo-Código CR.
126
while ( true )
lock( input queue )
Tn = extractNextTransaction( input queue )
if Tn.type == QUERY then /*read transaction*/
for each term t in Tn.query do
lockRead( t %H )
endfor
unlock( input queue )
for each term t in Tn.query do
rank( Lt )
unlockRead( t %H )
endfor
else /*write transaction*/
for each term t in Tn.terms do
lockWrite( t %H )
endfor
unlock( input queue )
for each term t in Tn.terms do
d = Tn.docId
if Tn.type == UPDATE then
update( Lt , pair( d, ft ) )
else
insert( Lt , pair( d, ft ) )
endif
unlockWrite( t %H )
endfor
endif
endwhile
Figura B.2: Pseudo-Código TLP1.
127
while ( true )
lock( input queue )
Tn = extractNextTransaction( input queue )
if Tn.type == QUERY then /*read transaction*/
for each term t in Tn.query do
lock( t %H )
endfor
unlock( input queue )
for each term t in Tn.query do
rank( Lt )
unlock( t %H )
endfor
else /*write transaction*/
for each term t in Tn.terms do
lock( t %H )
endfor
unlock( input queue )
for each term t in Tn.terms do
d = Tn.docId
if Tn.type == UPDATE then
update( Lt , pair( d, ft ) )
else
insert( Lt , pair( d, ft ) )
endif
unlock( t %H )
endfor
endif
endwhile
Figura B.3: Pseudo-Código TLP2.
128
while( true )
lock( input queue )
Tn = extractNextTransaction( input queue )
unlock( input queue )
if Tn.type == QUERY then /*read transaction*/
for each term t in Tn.query do
for each block b in Lt do
lock( t %H )
F = fetch(Lt [b])
unlock( t %H )
rank( F )
endfor
endfor
else /*write transaction*/
for each term t in Tn.terms do
d = Tn.docId
lock ( t %H )
if Tn.type == UPDATE then
update( Lt , pair( d, ft ) )
else
insert( Lt , pair( d, ft ) )
endif
unlock ( t %H )
endfor
endif
endwhile
Figura B.4: Pseudo-Código RTLP.
129
while( true )
lock( input queue )
Tn = extractNextTransaction( input queue )
unlock( input queue )
if Tn.type == QUERY then /*read transaction*/
for each term t in Tn.query do
for each block b in Lt do
lock( b %H ); lock( (b + 1) %H )
F = fetch(Lt [b])
unlock( (b + 1) %H ); unlock( b %H )
rank( F )
endfor
endfor
else /*write transaction*/
for each term t in Tn.terms do
d = Tn.docId
for each block b in Lt do
lock ( b %H ); lock ( (b + 1) %H )
if isRightBlock( b, ft ) == true then
if Tn.type == UPDATE then
update( b, pair( d, ft ) )
else
insert( b, pair( d, ft ) )
endif
unlock ( (b + 1) %H ); unlock ( b %H )
next term
endif
unlock ( (b + 1) %H ); unlock ( b %H )
endfor
endfor
endif
endwhile
Figura B.5: Pseudo-Código RBLP.
130
Bibliografı́a
[1] R. Acker, C. Roth, and R. Bayer. Parallel query processing in databases on multicore
architectures. In 8th international conference on Algorithms and Architectures for
Parallel Processing (ICA3PP), pages 2–13, 2008.
[2] K. Amiri, S. Park, and R. Tewari. DbProxy: A dynamic data cache for web applications. In 19th International Conference on Data Engineering (ICDE), pages
821–831, 2003.
[3] V. Anh and A. Moffat. Inverted index compression using Word-Aligned Binary
Codes. Inf. Retr., 8(1):151–166, 2005.
[4] C. Badue, R. Baeza-Yates, B. Ribeiro, and N. Ziviani. Distributed query processing
using partitioned inverted files. In 9th String Processing and Information Retrieval
Symposium (SPIRE), pages 10–20, 2001.
[5] R. Baeza-Yate, F. Junqueira, V. Plachouras, and H.F. Witschel. Admission policies
for caches of search engine results. String Processing and Information Retrieval,
4726:74–85, 2007.
[6] R. Baeza-Yates, W. Cunto, U. Manber, and S. Wu. Proximity matching using fixedqueries trees. In 5th Combinatorial Pattern Matching (CPM), pages 198–212, 1994.
[7] R. Baeza-Yates, A. Gionis, F. Junqueira, V. Murdock, V. Plachouras, and F. Silvestri. The impact of caching on search engines. In 30th annual international ACM
SIGIR conference on Research and development in information retrieval, pages 183–
190, 2007.
[8] R. Baeza-Yates, A. Gionis, F. Junqueira, V. Murdock, V. Plachouras, and F. Silvestri. Design trade-offs for search engine caching. ACM TWEB, 2(4):1–28, 2008.
131
[9] R. Baeza-Yates and B. Ribeiro-Neto. Modern Information Retrieval. ISBN:0-20139829-X, 1999.
[10] B. Billerbeck, G. Demartini, C. Firan, T. Iofciu, and R. Krestel. Exploiting clickthrough data for entity retrieval. In 33rd international ACM SIGIR conference on
Research and development in information retrieval (SIGIR), pages 803–804, 2010.
[11] R. Blanco, E. Bortnikov, F. Junqueira, R. Lempel, L. Telloli, and H. Zaragoza. Caching search engine results over incremental indices. In 19th international conference
on World Wide Web (WWW), pages 1065–1066, 2010.
[12] O. Bonorden, B. Juurlink, I. von Otte, and I. Rieping. The paderborn university
BSP (PUB) library. In Parallel Computing, 29(2):187–207, 2003.
[13] L. Brunie and H. Kosch. Control strategies for complex relational query processing
in shared nothing systems. SIGMOD Rec., 25(3):34–39, 1996.
[14] S. Buttcher, C. Clarke, and G. Cormack. Information retrieval: Implementing and
evaluating search engines. MIT Press, 2010.
[15] F. Cacheda, V. Plachouras, and I. Ounis. Performance analysis of distributed architectures to index one terabyte of text. In European Conference on Information
Retrieval Research (ECIR), pages 395–408, 2004.
[16] B.B. Cambazoglu, F.P. Junqueira, V. Plachouras, S. Banachowski, B. Cui, S. Lim,
and B. Bridge. A refreshing perspective of search engine caching. In 19th international conference on World Wide Web (WWW), pages 181–190, 2010.
[17] C. Celik. Effective use of space for pivot-based metric indexing structures. In First
Workshop on Similarity Search and Applications (SISAP), pages 113–120, 2008.
[18] R. Cen, Y. Liu, M. Zhang, B. Zhou, L. Ru, and S. Ma. Exploring relevance for clicks.
In 18th ACM conference on Information and knowledge management (CIKM), pages
1847–1850, 2009.
[19] E. Chavez, J. Marroquin, and G. Navarro. Fixed queries array: A fast and economical data structure for proximity searching. In Multimedia Tools and Applications.,
volume 14, pages 113–135, 2001.
[20] B. Chidlovskii and U.M. Borghoff. Semantic caching of web queries. VLDB J.,
9(1):2–17, 2000.
132
[21] B. Chidlovskii, C. Roncancio, and ML. Schneider. Optimizing web queries through
semantic caching. In 15èmes Journées Bases de Données Avancées, BDA, pages
23–40, 1999.
[22] C.L.A. Clarke, E. Agichtein, S. Dumais, and R.W. White. The influence of caption
features on clickthrough patterns in web search. In 30th annual international ACM
SIGIR conference on Research and development in information retrieval (SIGIR),
pages 135–142, 2007.
[23] Intel Corpation. Intel C/C++ and Intel Fortran Compilers for Linux.
[24] K.S. Dave and V. Varma. Learning the click-through rate for rare/new ads from
similar ads. In 33rd international ACM SIGIR conference on Research and development in information retrieval (SIGIR), pages 897–898, 2010.
[25] P. Deshpande, P. Deepak, and K. Kummamuru. Efficient online top-K retrieval
with arbitrary similarity measures. In 11th international conference on Extending
database technology (EDBT), pages 356–367, 2008.
[26] S. Ding, J. Attenberg, and T. Suel. Scalable techniques for document identifier
assignment in inverted indexes. In 19th international conference on World wide web
(WWW), pages 311–320, 2010.
[27] T. Fagni, R. Perego, F. Silvestri, and S. Orlando. Boosting the performance of web
search engines: caching and prefetching query results by exploiting historical usage
data. ACM Trans. Inf. Syst., 24(1):51–78, 2006.
[28] F. Ferrarotti, M. Marin, and M. Mendoza. A last-resort semantic cache for web
queries. In 16th String Processing and Information Retrieval Symposium, pages
310–321, 2009.
[29] R.M. Fujimoto. Parallel discrete event simulation. Communications of the ACM,
33(10):30–53, 1990.
[30] Q. Gan and T. Suel. Improved techniques for result caching in web search engines. In
18th international conference on World Wide Web (WWW), pages 431–440, 2009.
[31] S. Garcia, H. Williams, and A. Cannane. Access-ordered indexes. In 27th Conference
on Australasian Computer Science, 26:7–14, 2004.
133
[32] V. Gil-Costa and M. Marin. Distributed sparse spatial selection indexes. In 16th Euromicro International Conference on Parallel, Distributed and Network-Based Processing (PDP), pages 440–444, 2008.
[33] V. Gil-Costa, M. Marin, and N. Reyes. An empirical evaluation of a distributed
clustering-based index for metric-space databases. In International Workshop on
Similarity Search and Applications (SISAP), pages 386–393, 2008.
[34] P. Godfrey and J. Gryz. Answering queries by semantic caches. In 10th International
Conference on Database and Expert Systems Applications (DEXA), pages 485–498,
1999.
[35] J. Goldstein, R. Ramakrishnan, and U. Shaft. Compressing relations and indexes. In
Fourteenth International Conference on Data Engineering (ICDE), pages 370–379,
1998.
[36] M.W. Goudreau, K. Lang, S. Rao, T. Suel, and T. Tsantilas. Towards efficiency and
portability: Programming with the BSP model. 8nd Annual ACM Symposium on
Parallel Algorithms and Architectures (SPAA), pages 1–12, 1996.
[37] S. Gurajada and S. Kumar. On-line index maintenance using horizontal partitioning.
In 18th ACM conference on Information and knowledge management (CIKM), pages
435–444, 2009.
[38] D. Harman. Relevance feedback and others query modification techniques. Information retrieval: data structures and algorithms, pages 241–263, 1992.
[39] J.L. Hennessy and D.A. Patterson. A quantitative approach. Computer Architecture,
2006.
[40] M. Herlihy and V. Luchangco. Distributed computing and the multicore revolution.
In SIGACT News, volume 39, pages 62–72, 2008.
[41] J.M.D. Hill, B. McColl, D.C. Stefanescu, M.W. Goudreau, K. Lang, S. Rao, T. Suel,
T. Tsantilas, and R.H. Bisseling. BSPlib: The BSP programming library. Parallel
Computing, 24(14):1947–1980, 1998.
[42] W. Hon, R. Shah, and S. Wu. Efficient index for retrieving top-k most frequent
documents. 6th International Symposium, (SPIRE), pages 182–193, 2009.
[43] http://bsponmpi.sourceforge.net/. The BSP on MPI communication library.
134
[44] http://www.bsp-worldwide.org. BSP and Worldwide Standard.
[45] http://www.csm.ornl.gov/pvm. Parallel Virtual Machine.
[46] http://www.mcs.anl.gov/mpi/index.html. Message Passing Interface.
[47] http://www.netlib.org/pvm3/book/pvm-book.html. Parallel Virtual Machine.
[48] http://www.uni paderborn.de/bsp. BSP PUB library at paderborn university.
[49] S.J. Hyun and S.Y.W. Su. Parallel query processing strategies for object-oriented
temporal databases. In Fourth international conference on on Parallel and distributed information systems(DIS), pages 232–249, 1996.
[50] B.S. Jeong and E. Omiecinski. Inverted file partitioning schemes in multiple disk
systems. In IEEE Transactions on Parallel and Distributed Systems, 6:142–153,
1995.
[51] P. Kongetira, K. Aingaran, and Kunle Olukotun. Niagara: A 32-way multithreaded
sparc processor. IEEE Micro, 25(2):21–29, 2005.
[52] R. Kumar, K. Punera, T. Suel, and S. Vassilvitskii. Top-k aggregation using intersections of ranked inputs. In Second ACM International Conference on Web Search
and Data Mining (WSDM), pages 222–231, 2009.
[53] R. Lempel and S. Moran. Predictive caching and prefetching of query results in
search engines. In 12th international conference on World Wide Web (WWW),
pages 19–28, 2003.
[54] X. Long and T. Suel. Three-level caching for efficient query processing in large
web search engines. In 14th international conference on World Wide Web (WWW),
pages 257–266, 2005.
[55] Y. Lu, P. Tsaparas, A. Ntoulas, and L. Polanyi. Exploiting social context for review
quality prediction. In 19th international conference on World Wide Web (WWW),
pages 691–700, 2010.
[56] A. MacFarlane, S.E. Robertson, and J.A. McGann. On concurrency control for
inverted files. In 18th Annual BCS Colloquium on Information Retrieval, 1995.
[57] M. Marin. Binary tournaments and priority queues: PRAM and BSP. Technical
report PRG-TR-7-97, Oxford University, 1997.
135
[58] M. Marin. Priority queue operations on EREW-PRAM. In European International
Conference on Parallel Processing (Euro-Par), 1300:417–420, 1997.
[59] M. Marin, C.Bonacic, V Gil-Costa, and C. Gomez. A search engine accepting online updates. In 13th European Conference on Parallel and Distributed Computing
(EuroPar), pages 28–31, 2007.
[60] M. Marin, F. Ferrarotti, M. Mendoza, C. Gomez-Pantoja, and V. Gil-Costa. Location
cache for web queries. In 18th ACM Conference on Information and Knowledge
Management (CIKM), 2009.
[61] M. Marin and V. Gil-Costa. High-performance distributed inverted files. 16th Conference on Information and Knowledge Management (CIKM), pages 935–938, 2007.
[62] M. Marin and V. Gil-Costa. (sync—async)+ mpi search engines. In 14th Euro
PVM/MPI Recent Advances in Parallel Virtual Machine and Message Passing Interface, pages 117–124, 2007.
[63] M. Marin, V. Gil-Costa, C. Bonacic, R. Baeza-Yates, and I.D. Scherson. Sync/async
parallel search for the efficient design and construction of web search engines. In
Parallel Computing (Elsevier), volume 36, pages 153–168, 2010.
[64] M. Marin, V. Gil-Costa, and C. Gomez-Pantoja. New caching techniques for web
search engines. In 19th ACM International Symposium on High Performance Distributed Computing (HPDC), 2010.
[65] M. Marin, V. Gil-Costa, and R. Uribe. Hybrid index for metric space databases. In
8th International Conference on Computational Science (ICCS), page 2008, 23–25.
[66] M. Marin, C. Gomez, S. Gonzalez, and G.V. Costa. Scheduling intersection queries
in term partitioned inverted files. In 14th European Conference on Parallel and
Distributed Computing (EuroPar), pages 26–29, 2008.
[67] M. Marin, R. Paredes, and C. Bonacic. High-performance priority queues for parallel
crawlers. In 10th ACM International Workshop on Web Information and Data
Management (WIDM), 2008.
[68] E. Markatos. Main memory caching of web documents. In Fifth international World
Wide Web conference on Computer networks and ISDN systems, pages 893–905,
1996.
136
[69] E. Markatos. On caching search engine query results. Computer Communications,
24:137–143, 2000.
[70] M. Marzolla. LibCppSim: A SIMULA-like, portable process-oriented simulation
library in C++. In European Simulation Symposium (ESM), 2004.
[71] M. Mendoza, M. Marin, F. Ferraroti, and B. Poblete. Learning to distribute queries
onto web search nodes. In 32nd European Conference on Information Retrieval
(ECIR), 2010.
[72] A. Moffat and T.A.H. Bell. In situ generation of compressed inverted files. J. Am.
Soc. Inf. Sci., 46(7):537–550, 1995.
[73] A. Moffat, W. Webber, and J. Zobel. Load balancing for term-distributed parallel
retrieval. In 29th annual international ACM SIGIR conference on Research and
development in information retrieval, pages 348–355, 2006.
[74] A. Moffat, W. Webber, J. Zobel, and R. Baeza-Yates. A pipelined architecture for
distributed text query evaluation. Inf. Retr., 10(3):205–231, 2007.
[75] A. Moffat, J. Zobel, and N. Sharman. Text compression for dynamic document
databases. IEEE Trans. on Knowl. and Data Eng., 9(2):302–313, 1997.
[76] G. Navarro, E.S. De Moura, M. Neubert, N. Ziviani, and R. Baeza-Yates. Adding
compression to block addressing inverted indexes. Inf. Retr., 3(1):49–77, 2000.
[77] M. S. Oyarzun, S. Gonzalez, M. Mendoza, M. Chacon, and M. Marin. A vector model
for routing queries in web search engines. International Conference on Computational Science (ICCS), 2010.
[78] V.S. Pai, P. Druschel, and W. Zwaenepoel. IO-Lite: a unified I/O buffering and
caching system. ACM Trans. Comput. Syst., 18(1):37–66, 2000.
[79] A. Papadopoulos and Y. Manolopoulos. Distributed processing of similarity queries.
In Distributed and Parallel Databases, number 1, pages 67–92, 2001.
[80] M. Persin. Document filtering for fast ranking. pages 339–348, 1994.
[81] M. Persin, J. Zobel, and R. Sacks-Davis. Filtered document retrieval with frequencysorted indexes. Journal of the American Society for Information Science, 47(10):749–
764, 1996.
137
[82] A.M. Printista. Modelos de prediccion en computacion paralela. Master’s thesis,
Universidad Nacional del Sur, Argentina, 2001.
[83] D. Puppin, F. Silvestri, R. Perego, and R. Baeza-Yates. Load-balancing and caching
for collection selection architectures. In 2nd international conference on Scalable
information systems (Infoscale), pages 1–10, 2007.
[84] V. Ramachandran, B. Grayson, and M. Dahlin. Emulations between QSM, BSP,
and LogP: a framework for general-purpose parallel algorithm design. In 10th annual
ACM-SIAM symposium on Discrete algorithms (SODA), pages 957–958, 1999.
[85] V. Ramachandran, B. Grayson, and M. Dahlin. Emulations between QSM, BSP,
and LogP: a framework for general-purpose parallel algorithm design. J. Parallel
Distrib. Comput., 63:1175–1192, 2003.
[86] B.A. Ribeiro-Neto and R.A. Barbosa. Query performance for tightly coupled distributed digital libraries. In 3th ACM conference on Digital libraries, pages 182–190,
1998.
[87] M. Robertson, S. E. Robertson, and J. A. Mccann. On concurrency control for
inverted files. In Manchester Metropolitan University, pages 67–79, 1996.
[88] S. Robertson and K. Jones. Simple proven approaches to text retrieval. Tech. Rep.
TR356, Cambridge University Computer Laboratory, 1997.
[89] T. Sakaki, M. Okazaki, and Y. Matsuo. Earthquake shakes twitter users: real-time
event detection by social sensors. In 19th international conference on World Wide
Web (WWW), pages 851–860, 2010.
[90] J. Sang, X. Long, and T. Suel. Performance of compressed inverted list caching in
search engines. 17th international conference on World Wide Web (WWW), pages
387–396, 2008.
[91] F. Scholer, H.E. Williams, J. Yiannis, and J. Zobel. Compression of inverted indexes
for fast query evaluation. In 25th annual international ACM SIGIR conference on
Research and development in information retrieval (SIGIR), pages 222–229, 2002.
[92] D. Sheahan. Developing and tuning applications on Ultrasparc T1 chip multithreading systems. http://www.sun.com/blueprints/1205/819-5144.html, page 51, 2007.
[93] D.B. Skillicorn, Jonathan M.D. Hill, and W.F. Mccoll. Questions and answers about
BSP. Scientific Programming, 6(3), 1996.
138
[94] G. Skobeltsyn, F. Junqueira, V. Plachouras, and R. Baeza-Yates. Resin: a combination of results caching and index pruning for high-performance web search engines.
In 31st annual international ACM SIGIR conference on Research and development
in information retrieval, pages 131–138, 2008.
[95] C. Soleimany and S.P. Dandamudi. Performance of a distributed architecture for
query processing on workstation clusters. Future Gener. Comput. Syst., 19(4):463–
478, 2003.
[96] C. Stanfill. Partitioned posting files: A parallel inverted file structure for information
retrieval. In 13th Annual International ACM SIGIR Conference on Research and
Development in Information Retrieval, pages 413–428, 1990.
[97] S. Tatikonda, F. Junqueira, B.B. Cambazoglu, and V. Plachouras. On efficient
posting list intersection with multicore processors. 32nd international ACM SIGIR
conference on Research and development in information retrieval (SIGIR), pages
738–739, 2009.
[98] A. Tomasic and H. Garcia-Molina. Performance issues in distributed shared-nothing
information-retrieval systems. In Inf. Process. Manage., number 6, pages 647–665,
1996.
[99] F. Transier and P. Sanders. Out of the box phrase indexing. String Processing and
Information Retrieval, 5280:200–211, 2009.
[100] L.G. Valiant. A bridging model for parallel computation. Comm. ACM, 33:103–111,
Aug. 1990.
[101] L.G. Valiant. A bridging model for multi-core computing. In 16th annual European
symposium on Algorithms (ESA), pages 13–28, 2008.
[102] A.N. Vo and A. Moffat. Compressed inverted files with reduced decoding overheads.
In 21st annual international ACM SIGIR conference on Research and development
in information retrieval, pages 290–297, 1998.
[103] M. Wu, A. Turpin, and J. Zobel. Aggregated click-through data in a homogeneous
user community. In 31st annual international ACM SIGIR conference on Research
and development in information retrieval, pages 731–732, 2008.
[104] W. Xi, O. Sornil, M. Luo, and E.A. Fox. Hybrid partition inverted files: Experimental
validation. Research and Advanced Technology for Digital Libraries, 2458:101–116,
2002.
139
[105] H. Yan, S. Ding, and T. Suel. Compressing term positions in web indexes. In 32nd
international ACM SIGIR conference on Research and development in information
retrieval (SIGIR), pages 147–154, 2009.
[106] H. Yan, S. Ding, and T. Suel. Inverted index compression and query processing
with optimized query ordering. 18th international conference on World Wide Web
(WWW), pages 401–410, 2009.
[107] Q. Yang and H.H.Zhang. Integrating web prefetching and caching using prediction
models. 10th international conference on World Wide Web (WWW), 4(4):299–321,
2001.
[108] N.E. Young. On-line file caching. In ninth annual ACM-SIAM symposium on Discrete algorithms (SODA), pages 82–86, 1998.
[109] J. Zhou, J. Cieslewicz, K.A. Ross, and M. Shah. Improving database performance
on simultaneous multithreading processors. In 31st international conference on Very
large data bases (VLDB), pages 49–60, 2005.
[110] J. Zobel and A. Moffat. Inverted files for text search engines. ACM Comput. Surv.,
38(2):6, 2006.
[111] M. Zukowski, S. Heman, N. Nes, and P. Boncz. Super-scalar RAM-CPU cache
compression. 22nd International Conference on Data Engineering (ICDE), page 59,
2006.
140
Descargar