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