Generación del cubo de datos empleando paralelismo de
Transcripción
Generación del cubo de datos empleando paralelismo de
Instituto Politécnico Nacional Centro de Investigación en Computación Generación del cubo de datos empleando paralelismo de GPUs y CPUs multinúcleo T E S I S Que para obtener el grado de: Maestro en Ciencias de la Computación P R E S E N T A: Mario Alfonso Torres Rivera Directores: Dr. Gilberto Lorenzo Martı́nez Luna Dr. Adolfo Guzmán Arenas 2013 SIP-14 bis INSTITUTO POLITÉCNICO SECRETARíA DE INVESTIGACiÓN NACIONAL Y POSGRADO ACTA DE REVISI6N DE TESIS En la Ciudad de noviembre de México, D.F. siendo las 10:00 horas del día 21 del mes de 2Q13 se reunieron los miembros de la Comisión Revisora de la Tesis, designada por el Colegio de Profesores de Estudios de Posgrado e Investigación del: Centro de Investigación en Computación para examinar la tesis titulada: "Generación del cubo de datos empleando paralelismo de GPUs y CPUs multinúcleo" Presentada por el alumno: TORRES RIVERA Apellido paterno MARIO ALFONSO Apellido materno Con registro: aspirante de: MAESTRíA EN CIENCIAS '----""---L-..."--l.....-"--"-=--L-=--L-=--L-...=----.J DE LA COMPUTACiÓN Después de intercambiar opiniones los miembros de la Comisión manifestaron APROBAR LA TESIS, en virtud de que satisface los requisitos señalados por las disposiciones reglamentarias vigentes. LA COMISiÓN REVISORA Directores de Tesis Dr. Ricardo Barrón Fernández M. en Dr. M~rco Antonio Ramírez Salinas INSTITUTO POLITÉCNICO NACIONAL SECRETARIA DE INVESTIGACIÓN Y POSGRADO CARTA CESIÓN DE DERECHOS En la Ciudad de México el dı́a 26 del mes Noviembre del año 2013 , el (la) que suscribe Torres Rivera Mario Alfonso alumno (a) del Programa de Maestrı́a en Ciencias de la Computación con número de registro B110843 , adscrito al Centro de Investigación en Computación , manifiesta que es autor (a) intelectual del presente trabajo de Tesis bajo la dirección de Dr. Gilberto Lorenzo Martı́nez Luna y Dr. Adolfo Guzmán Arenas y cede los derechos del trabajo intitulado Generación del cubo de datos usando paralelismo de GPUs y CPUs multinúcleo , al Instituto Politécnico Nacional para su difusión, con fines académicos y de investigación. Los usuarios de la información no deben reproducir el contenido textual, gráficas o datos del trabajo sin el permiso expreso del autor y/o director del trabajo. Este puede ser obtenido escribiendo a la siguiente dirección Av. Juan de Dios Bátiz, Esq. Miguel Othón de Mendizábal, Col. Nueva Industrial Vallejo, Delegación Gustavo A. Madero, C.P 07738, México D.F. Si el permiso se otorga, el usuario deberá dar el agradecimiento correspondiente y citar la fuente del mismo. Mario Alfonso Torres Rivera Resumen La generación de cubos de datos de manera eficiente es un problema central en los almacenes de datos y el procesamiento analı́tico en lı́nea. Es un proceso que puede implicar la ejecución de gran número de operaciones aritméticas, además de consumir bastante tiempo cuando se realiza a partir de datos de gran volumen. Para una relación R con n atributos o dimensiones más un atributo de medida M , R(A1 , A2 , ..., An , M ), el problema básico del cálculo del cubo de datos implica la agregación de R para construir 2n grupos de tuplas respecto a toda posible combinación de las n dimensiones (i.e., el conjunto potencia de las n dimensiones de R), a cada uno de estos grupos de tuplas se le llama cuboide. Dicho problema ha sido investigado y se han propuesto estrategias para resolverlo, sin embargo, hasta ahora la mayorı́a de los algoritmos no consideran las ventajas del paralelismo y las recientes arquitecturas de CPUs y GPUs. En este trabajo se presenta el diseño de un conjunto de operaciones paralelas llamadas primitivas que aprovechan el paralelismo proporcionado por los modelos recientes de GPUs y CPUs multinúcleo. Las primitivas facilitan la generación de cubos de datos llevando a cabo rutinas de ordenamiento, partición y agregación. La implementación del software para GPU de este trabajo se realizó mediante la plataforma de cómputo en paralelo conocida como CUDA del fabricante de procesadores gráficos NVIDIA y para implementar el paralelismo en procesadores multinúcleo se utilizaron hilos POSIX. Posteriormente, se introducen tres métodos paralelos para generación de cubos de datos completos y de tipo iceberg. Además de las primitivas previamente diseñadas, estos métodos utilizan hilos POSIX con el fin de explotar el paralelismo de CPUs multinúcleo en la construcción simultánea de varios cuboides, i.e., todos los cuboides distribuyen en grupos y posteriormente los cuboides de cada grupo se generan en paralelo. Se utiliza almacenamiento en memoria lineal a través de arreglos de una dimensión para almacenar tuplas en memoria principal, evitando costos relacionados con la construcción de estructuras de datos más complejas. Ası́ mismo, se utilizan algunas estrategias conocidas en la literatura a fin de agilizar la generación del cubo de datos, sin embargo, a diferencia de los trabajos previos, los métodos presentados en esta tesis constan de un paralelismo de grano fino que se obtiene a través del uso de las primitivas paralelas. 4 Abstract Efficient data cube computing is a core problem in data warehousing and online analitical processing fields. This is a process that may involve very large amount of tuple group summarization over big data. For a given relation R with n dimensions and a measure attribute M , R(A1 , A2 , ..., An , M ), the basic data cube generation problem involves the aggregation of R for the construction of 2n tuple groups on every possible combination of the n dimensions (i.e., the power set of the n dimensions of R), each of this groups is called a cuboid. This problem has been researched extensively, however, most of algorithms have been proposed without considering the advantages of the modern CPU and GPU architectures. This work presents the design and implementation of a set of parallel operations called primitives which take advantage of the modern GPU and multicore CPU parallelism. Primitives help to generate data cubes conducting routines such as sort, partition, and aggregate. The GPU software was implemented using CUDA, a parallel computing platform introduced by the graphics processor manufacturer NVIDIA, on the other hand, multicore CPU parallelism was implemented using POSIX threads. Subsequently, we introduce tree parallel methods for efficient generation of full and iceberg data cubes. Besides the previously mentioned parallel primitives, this methods use POSIX threads to exploit the multicore CPU parallelism in the simultaneous construction of several cuboids, i.e., all cuboids are distributed into groups and then cuboids in each group are constructed in parallel. Linear memory storage is used to keep tuples in main memory, avoiding additional costs related to building more complex data structures. Likewise, we use some well known strategies to accelerate the data cube computing process, but unlike previous work, our methods feature fine grained parallelism provided by parallel primitives. 5 Agradecimientos Este trabajo fue realizado bajo la dirección de los profesores Dr. Gilberto Lorenzo Martı́nez Luna y Dr. Adolfo Guzmán Arenas. Quiero agradecer ampliamente su apoyo, confianza y guı́a a lo largo del desarrollo de la tesis. Agradezco también a los profesores, M. en C. Alejandro Botello Castillo, Dr. Marco Antonio Ramı́rez Salinas, Dr. José Giovanni Guzmán Lugo y Dr. Ricardo Barrón Fernández, por sus valiosos comentarios para el enriquecimiento de este trabajo. A mis compañeros y amigos del CIC, Rodolfo Vilchis, Eliezer Alcázar, Edgar Garcı́a. Siempre recordaré los momentos que compartimos. Finalmente, agradezco a mi familia: a mis padres, Esteban y Lucı́a, a quienes jamás terminaré de corresponder por el gran amor, comprensión y apoyo que me han brindado en cada momento de mi vida; a mi hermana Lucı́a Isabel, con mucho cariño; a la familia Torres - Rodriguez, por todo su afecto y apoyo incondicional. 6 Índice general Agradecimientos 6 1. Introducción 22 1.1. Motivación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.2. Objetivo general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 1.2.1. Objetivos particulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 1.3. Justificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 1.4. Alcances y limitaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 1.5. Aportaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 1.6. Organización de la tesis 26 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Fundamentos y trabajos previos 27 2.1. El procesamiento analı́tico en lı́nea y los cubos de datos . . . . . . . . . . . . . . 28 2.1.1. Funciones de agregación . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.1.2. Operadores GROUP BY y CUBE BY . . . . . . . . . . . . . . . . . . . . 33 2.1.3. Generación del cubo de datos . . . . . . . . . . . . . . . . . . . . . . . . 34 2.2. Paralelismo en bases de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.2.1. Paralelismo a gran escala . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2.2.2. Arquitecturas paralelas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 2.3. Tecnologı́a multinúcleo y de muchos núcleos . . . . . . . . . . . . . . . . . . . . 44 2.3.1. Procesamiento paralelo en CPUs multinúcleo . . . . . . . . . . . . . . . . 45 2.3.2. Cómputo de propósito general en unidades procesamiento gráfico . . . . 47 2.4. Estado del arte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 2.4.1. Generación secuencial de cubos de datos . . . . . . . . . . . . . . . . . . 51 2.4.2. Generación de cubos de datos empleando paralelismo de clusters de PCs 53 2.4.3. Estrategias de uso de memoria caché en estructuras de datos y generación de cubos de datos empleando memoria caché . . . . . . . . . . . . . . . . 55 7 2.4.4. Generación en paralelo de cubos de datos usando tecnologı́a multinúcleo y de GPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.5. Similaridades entre trabajos previos y los métodos propuestos . . . . . . 3. Arquitectura de la solución 3.1. Configuración del sistema de cómputo . . . . . . . . . . . 3.2. Primitivas paralelas . . . . . . . . . . . . . . . . . . . . . 3.2.1. Formato de almacenamiento en memoria lineal . . 3.2.2. Recolección . . . . . . . . . . . . . . . . . . . . . 3.2.3. Ordenamiento . . . . . . . . . . . . . . . . . . . . 3.2.4. Proceso de ordenamiento de tuplas . . . . . . . . 3.2.5. ParticiónLocal y particiónCuboide . . . . . . . . 3.2.6. Reducción y reducciónSegmentada . . . . . . . . 3.2.7. Construcción de cuboides . . . . . . . . . . . . . 3.3. Métodos paralelos de generación de cubos de datos . . . 3.3.1. Método MCBUC . . . . . . . . . . . . . . . . . . 3.3.2. Método SPCube . . . . . . . . . . . . . . . . . . 3.3.3. Método GPUgenCube . . . . . . . . . . . . . . . 3.3.4. Comparativa de los métodos para cubos de datos 4. Pruebas y resultados 4.1. Equipo de pruebas . . . . . . . . . . . . 4.2. Conjunto de datos de prueba . . . . . . . 4.3. Desempeño del ordenamiento . . . . . . 4.4. Desempeño de los algoritmos de cubos de 4.4.1. Cubo completo . . . . . . . . . . 4.4.2. Cubo iceberg . . . . . . . . . . . 4.4.3. Sesgo . . . . . . . . . . . . . . . . 4.4.4. Funciones de agregación . . . . . 4.4.5. Observaciones . . . . . . . . . . . . . . . . . . . . . . . datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 57 . . . . . . . . . . . . . . 59 60 62 63 65 67 75 80 84 88 90 91 97 103 108 . . . . . . . . . 109 110 110 111 112 113 116 119 121 130 5. Conclusiones 132 5.1. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Referencias 135 8 Índice de figuras 2.1. Tabla cruzada de la relación ventas sobre los atributos Artı́culo y Fabricante. En este caso, el atributo capacidad tiene asignado el valor especial ALL . . . . . . . 29 2.2. Conceptualización de un cubo de datos de tres dimensiones. El cubo de datos muestra los totales de ventas para una tienda de dispositivos electrónicos dispuestos de acuerdo a toda combinación de Artı́culo, Fabricante y Capacidad. Por ejemplo, se puede ver que se vendieron 20 unidades de estado solido del fabricante F1, 7 de 128GB, 12 de 32GB y 1 de 16GB. . . . . . . . . . . . . . . . . . . . . . 31 2.3. Operador relacional GROUP BY: Particiona una tabla en grupos. Cada grupo es agregado por una función. La función de agregación sumariza alguna columna de grupos regresando un valor por cada grupo. . . . . . . . . . . . . . . . . . . . 33 2.4. Cubo de datos sobre una relación de ventas de automóviles. . . . . . . . . . . . 34 2.5. Conceptualización de un cubo iceberg de tres dimensiones con umbral de soporte mı́nimo SUM(ventas)≥10. El cubo iceberg muestra los totales de ventas para una tienda de dispositivos electrónicos dispuestos de acuerdo a toda combinación de Artı́culo, Fabricante y Capacidad. Se puede ver que las vistas solo incluyen valores agregados que satisfacen el umbral de soporte mı́nimo. . . . . . . . . . . 35 2.6. Estructura de lattice para un cubo de datos de cuatro dimensiones. . . . . . . . 36 2.7. Ejemplo de árbol de procesamiento presente en algoritmos descendentes. . . . . 37 2.8. Ejemplo de árbol de procesamiento presente en algoritmos ascendentes. . . . . . 37 2.9. Representación conceptual de la arquitectura memoria compartida. Cualquier procesador tiene acceso a cualquier módulo de memoria o unidad de disco a través de una conexión rápida. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 2.10. Representación de los diseños de procesadores multinúcleo (CPU) y de muchos núcleos (GPU) respectivamente. Los procesadores multinúcleo normalmente tienen entre dos y ocho núcleos, orientados a la aceleración de procesos secuenciales. En contraste, las GPUs están enfocadas a problemas paralelos, contando con cientos de núcleos menos potentes que los de una CPU multinúcleo. . . . . . . . 44 9 2.11. Representación conceptual de un proceso de UNIX. Un proceso de UNIX cuenta con recursos que permiten la ejecución de un programa como una pila, texto de programa y datos usados por el programa. . . . . . . . . . . . . . . . . . . . . . 45 2.12. Representación conceptual de proceso de UNIX con dos hilos. Los hilos duplican los recursos que les permiten existir como código ejecutable dentro de un proceso de UNIX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 2.13. Organización tı́pica de una GPU conformada por 30 multiprocesadores, cada uno con 8 ALUs SIMD y una espacio de memoria local compartida. . . . . . . . . . . 48 2.14. Jerarquı́a de hilos, bloques y mallas en CUDA con sus respectivos espacios de memoria. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.1. Representación de la jerarquı́a de memoria en una GPU Fermi [32] del fabricante NVIDIA. La memoria compartida y caché L1 se encuentran en el mismo nivel ya que la latencia de acceso a ellas es equivalente. . . . . . . . . . . . . . . . . . . . 60 3.2. Conversión de cuatro tuplas en formato tabular a un formato vectorial. Cada columna es escrita a una sección del vector resultante, la primera sección contiene a los elementos de la primera columna, la segunda sección a los de la segunda y ası́ sucesivamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.3. Representación la primitiva recolección sobre un vector de ocho elementos. El valor de la posición 0 del mapa (es el 1, de izquierda a derecha) indica que se accederá la posición 1 del vector de valores que este caso contiene el valor 21 el cual irá en la posición 0 de la salida; el valor de la posición 1 del mapa (es el 2) indica el acceso al valor 9 que irá en la posición 1 de la salida; el valor de la posición 2 del mapa (es el 0) indica el acceso al valor 10 que irá en la posición 2 de la salida; el proceso continua hasta acceder los 8 elementos de la entrada. . . 66 3.4. Ejemplo de ordenamiento por clave de un vector de seis elementos. El vector de claves sirve como referencia para ordenar un vector de valores. . . . . . . . . . . 68 3.5. Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 1 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al primer dı́gito decimal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 3.6. Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 2 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al segundo dı́gito decimal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.7. Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 3 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al tercer dı́gito decimal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 10 3.8. Conceptualización de los histogramas del radix sort paralelo. Cada bloque de hilos de GPU consta de un histograma para ordenar una sección de la secuencia de entrada. En cada pasada del radix sort se realiza una operación de suma de prefijos global a través de todos los histogramas para determinar la posición global de los elementos ordenados por cada bloque de hilos en el arreglo de salida. 74 3.9. Ordenamiento de cuatro tuplas respecto a la combinación de atributos ABC. Las tuplas a ordenar son (2, 2, 1), (1, 1, 1), (3, 1, 2) y (5, 1, 1). Esto se muestra en el vector superior. El resultado de ordenar las tuplas a la combinación ABC es: (1, 1, 1), (2, 2, 1), (3, 1, 2) y (5, 1, 1). Esto se muestra en el vector inferior. . . . 76 3.10. Generación de un mapa para ordenar las tuplas de la Figura 3.9 respecto a la combinación ABC. El mapa es inicialmente una secuencia ascendente de enteros cuyos valores van permutando debido a una serie de ordenamientos por clave. Las claves son los valores de un cierto atributo de los datos en bruto y se inicia por ordenar respecto al atributo menos significativo, en este caso C. La versión final del mapa permite usar a la recolección para re-acomodar los valores de cada sección del vector de tuplas (datos en bruto) correspondiente a un atributo, dejando las tuplas ordenadas respecto a la combinación que se consideró, en este caso ABC. Véase la Figura 3.11. . . . . . . . . . . . . . . . . . . . . . . . . . . 77 3.11. Fase final del ordenamiento de las tuplas en la Figura 3.9. Se usa el mapa de la Figura 3.10 para aplicar la recolección a cada sección del vector de tuplas que corresponde a un atributo de los datos. Esta última fase deja a las tuplas de la Figura 3.9 ordenadas respecto a la combinación ABC. . . . . . . . . . . . . . . . 78 3.12. Particionamiento de una tabla con cinco tuplas respecto a la combinación de atributos ABC. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 3.13. Representación del proceso de partición de un grupo de tuplas en formato de vector respecto a la combinación de atributos ABC. . . . . . . . . . . . . . . . . 82 3.14. Ejemplo del proceso de partición de un vector con cinco tuplas respecto a la combinación de atributos ABC. ParticiónLocal se encarga de lanzar un hilo de CPU para particionar cada atributo que se requiera (en este caso A, B y C). ParticiónCuboide realiza la unión con los marcadores producidos por particiónLocal (en este caso, marcadores de A, B y C). El resultado de particiónCuboide es un conjunto de marcadores que permite la generación de un cuboide o vista del cubo (en este ejemplo son para el cuboide ABC). . . . . . . . . . . . . . . . . . . . . 83 3.15. Reducción de un vector de ocho elementos. . . . . . . . . . . . . . . . . . . . . . 84 11 3.16. Reducción de un vector en dos fases. En la primera fase, un conjunto de bloques de hilos reduce varias secciones de un arreglo o vector de valores, produciendo resultados parciales. La segunda fase obtiene el resultado final de la reducción usando un solo bloque de hilos sobre los resultados parciales de la primera fase. . 86 3.17. ReducciónSegmentada de un vector. Los elementos del vector de la ilustración están divididos en tres segmentos y la operación realizada es una sumatoria. La reducción de cada segmento del vector produce a un escalar. . . . . . . . . . . . 87 3.18. Representación del proceso de construcción de un cuboide de tres dimensiones utilizando la función SUM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 3.19. Árbol de procesamiento del algoritmo BUC [3] para un cubo de datos de cuatro dimensiones. Los números indican el orden de cálculo para los cuboides. . . . . . 91 3.20. Particionamiento usado en los métodos BUC [3] y MCBUC de un conjunto de datos de cuatro dimensiones. Los ai son valores del atributo A, los valores bi corresponden al atributo B y ası́ sucesivamente. . . . . . . . . . . . . . . . . . . 92 3.21. Ejemplo de poda Apriori. Los grupos que no cumplen con la cláusula iceberg son ignorados al construir un cuboide. Para este ejemplo, se ignoran los grupos con menos de dos tuplas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 3.22. Esquema conceptual de la ejecución del método MCBUC para la generación de un cubo de datos de dos dimensiones. Las flechas muestran el flujo de procesamiento recursivo de este método. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 3.23. Esquema del método SPCube. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 3.24. Lattice para un cubo de datos de cuatro dimensiones. Las flechas indican rutas potenciales de cálculo para el método SPCube. . . . . . . . . . . . . . . . . . . . 98 3.25. Árbol de procesamiento generado por el método SPCube. Los números a la derecha de cada combinación de atributos indican la cantidad de tuplas del cuboide, M y K indican millones y miles respectivamente. Estas cantidades permiten realizar una selección que producirá el menor costo al generar vistas del cubo a partir de otras más detalladas (ancestros en la jerarquı́a de lattice). . . . . . . . . . . . 99 3.26. Esquema de ejecución del método SPCube para un cubo de tres dimensiones. Los cuboides son generados a partir de otros más detallados en lugar de los datos en bruto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 3.27. Esquema del método GPUgenCube. . . . . . . . . . . . . . . . . . . . . . . . . . 103 3.28. Lattice para un cubo de datos de cuatro dimensiones. . . . . . . . . . . . . . . . 105 12 3.29. Esquema de ejecución del método GPUgenCube para un cubo de tres dimensiones. Las tareas son ejecutadas una a una por GPUgenCube. Los cuboides de una cierta tarea son generados y escritos simultáneamente a memoria secundaria por un hilo de CPU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 4.1. Desempeño de algoritmos de ordenamiento sobre conjuntos de datos distribuidos aleatoriamente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 4.2. Desempeño de algoritmos de ordenamiento sobre conjuntos de enteros en orden decreciente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 4.3. Generación del cubo completo, dimensiones = 7, cardinalidad = 10, sesgo = 0. 113 4.4. Generación del cubo completo, tuplas = 10m, dimensiones = 7, sesgo = 0. . . . 114 4.5. Generación del cubo completo, tuplas = 10m, cardinalidad = 20, sesgo = 0. . . 114 4.6. Generación del cubo completo, dimensiones = 8, cardinalidad = 10, sesgo = 0. 115 4.7. Generación del cubo completo, tuplas = 10m, dimensiones = 7, sesgo = 0, sin considerar el tiempo de escritura a disco. . . . . . . . . . . . . . . . . . . . . . . 115 4.8. Generación del cubo completo, tuplas = 10m, cardinalidad = 20, sesgo = 0, sin considerar el tiempo de escritura a disco. . . . . . . . . . . . . . . . . . . . . . . 116 4.9. Generación del cubo iceberg, tuplas = 10m, dimensiones = 7, sesgo = 0, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 4.10. Generación del cubo iceberg, tuplas = 10m, dimensiones = 8, sesgo = 0, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 4.11. Generación del cubo iceberg, tuplas = 10m, cardinalidad = 20, dimensiones = 7, sesgo = 0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 4.12. Generación del cubo iceberg, tuplas = 10m, cardinalidad = 20, dimensiones = 8, sesgo = 0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 4.13. Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . 119 4.14. Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 40, minsup = 50. . . . . . . . . . . . . . 120 4.15. Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 100, minsup = 50. . . . . . . . . . . . . 120 4.16. Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 8, cardinalidad = 100, minsup = 10. . . . . . . . . . . . . 121 4.17. SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 4.18. SPCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. . . . 122 13 4.19. SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 4.20. SPCube: Generación del cubo iceberg variando en el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. . . . . 123 4.21. SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 4.22. SPCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. . . . . 124 4.23. GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 4.24. GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. . 125 4.25. GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 4.26. GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. . . 126 4.27. GPUgenCube: Generación del cubo iceberg, AVG, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. . . . . . . . . . . . . . . . . . . . . . . . . . . 127 4.28. GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. . . 127 4.29. GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 4.30. GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. . 128 4.31. GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 4.32. GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. . . 129 14 Índice de tablas 2.1. Representación tabular de los datos en la Figura 2.1. . . . . . . . . . . . . . . . 30 2.2. Métodos previos de generación de cubos de datos, estrategias empleadas. Las similaridades con los métodos MCBUC, SPCube y GPUgenCube propuestos en esta tesis se resaltan en la última columna de la derecha. . . . . . . . . . . . . . 58 3.1. Variables comúnmente usadas como configuración para una función kernel de CUDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.2. Tuplas en formato tabular. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 3.3. Tuplas de la Tabla 3.2 en formato de vector. . . . . . . . . . . . . . . . . . . . . 63 3.4. Prototipo de la primitiva recolección. La primitiva recibe dos arreglos de enteros, Re y mapa, de tamaño n como entrada, su función es acceder las posiciones de Re de acuerdo con los valores en mapa, i.e., Re [mapa[i]] donde i va de 1 a n. . . 65 3.5. Prototipo de la primitiva ordenamiento. Re y claves son arreglos de una dimensión. Los valores en claves son utilizados como referencia para ordenar los valores de Re . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 3.6. Prototipo de la primitiva particiónLocal. Su función es recorrer en Re las secciones correspondientes a los valores de cada atributo de la combinación respecto a la cual se desea particiónar, obteniendo un conjunto de marcadores por cada atributo. Los marcadores son las posiciones de Re donde se registró un cambio de valor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 3.7. Prototipo de la primitiva particiónCuboide. La función de esta primitiva es realizar una operación de unión con los conjuntos de marcadores de cada atributo de la combinación respecto a la que se va a particionar. Los marcadores se encuentran almacenados en un arreglo Re que para esta fase puede contener marcadores para otros atributo no incluidos en la combinación. El resultado es un arreglo de marcadores Rs tomando en cuenta solo a los atributos de la combinación. . . . . 81 15 3.8. Prototipo de la primitiva reducción. La función de esta primitiva es evaluar los elementos de un arreglo de entrada Re mediante un operador binario asociativo ⊕, produciendo un solo elemento Rs como salida. . . . . . . . . . . . . . . . . . 85 3.9. Prototipo de la primitiva reducciónSegmentada. La función de esta primitiva es evaluar varios segmentos de un arreglo de entrada Re mediante un operador binario asociativo ⊕, produciendo un elemento como salida por cada segmento, es decir, un arreglo de salida Rs . Los segmentos se encuentran delimitados por valores continuos en un segundo arreglo (claves) del mismo tamaño del arreglo a evaluar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 3.10. Asignación de tareas en el método MCBUC para un cubo de datos de cuatro dimensiones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 3.11. Asignación de tareas en el método SPCube para un cubo de datos de cuatro dimensiones. Los cuboides son agrupados de acuerdo al número de atributos. . . 98 3.12. Asignación de tareas en el método GPUgenCube para un cubo de datos de cuatro dimensiones. Las tareas son procesadas una a una, empleando paralelismo de grano fino a través de las primitivas paralelas y generando tuplas en paralelo para los cuboides de una cierta tarea en paralelo. . . . . . . . . . . . . . . . . . 105 3.13. Resumen de caracterı́sticas para los métodos SPCube, MCBUC y GPUgenCube. 108 3.14. Funciones de agregación en los métodos MCBUC, SPCube y GPUgenCube. Las marcas de verificación indican las funciones implementadas para cada método. . 108 4.1. Especificación técnica del sistema de pruebas. . . . . . . . . . . . . . . . . . . . 110 16 Índice de algoritmos 1. 2. 3. 4. 5. 6. 7. 8. conteoPalabras . . . . . . ordenamiento por cuentas ordenarTuplas . . . . . . . BottomUpCube . . . . . . MCBUC . . . . . . . . . . padreMenorCosto . . . . . SPCube . . . . . . . . . . GPUgenCube . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 . 70 . 79 . 94 . 95 . 100 . 101 . 106 17 Definiciones A continuación se definen varios términos comúnmente utilizados esta tesis. Cardinalidad: En el contexto de bases de datos, cardinalidad se refiere a la unicidad de los valores contenidos en una columna o atributo de una tabla. Alta cardinalidad significa que una columna contiene alto porcentaje de valores totalmente únicos. Baja cardinalidad implica que la columna contiene alto porcentaje de valores repetidos. La cardinalidad de un atributo A se simboliza mediante |A|. Claves: Es una agrupación de valores enteros organizada mediante un arreglo unidimensional, se utiliza ya sea como referencia para ordenar un segundo grupo de valores o bien para particionar otro arreglo de igual longitud. Cluster : Grupo (decenas o cientos) de computadoras conectadas a través de una red para trabajar en conjunto, de manera que pueden verse como un solo sistema. Cubo completo: Se refiere a la generación de todas las vistas del cubo de datos. Cubo iceberg: Se refiere a la generación de las vistas o la parte de las vistas del cubo de datos que cumplen con un umbral de soporte mı́nimo previamente especificado. Cuboide: Se utiliza para denotar una vista de un cubo de datos. Formato tabular: Se refiere a la representación de una cierta información en forma de tabla con filas y columnas. Datos en bruto: Se refiere a la totalidad de las tuplas a partir de las cuales se está generando un cubo de datos. Función de agregación: Es una función que toma como parámetro un conjunto de valores y devuelve uno solo como resultado. Una función de agregación pueden ser clasificada como distributiva, algebraica u holı́sticas. SUM, MAX, MIN y COUNT son ejemplos de funciones distributivas, AVG es algebraica y funciones como la mediana y el rango son holı́sticas. 18 Núcleo: Es una unidad central de procesamiento independiente integrada dentro de un componente de cómputo o multiprocesador. Mapa: En este trabajo se utiliza para denotar un conjunto de ı́ndices utilizados para acceder posiciones de un arreglo. Fı́sicamente el mapa es un arreglo de valores enteros. Marcador: Es un valor que define una posición o ı́ndice de un arreglo. Mosaico: Se refiere a un trozo o sección de un arreglo de una dimensión. Relación: Es un conjunto de tuplas que tienen los mismos atributos. En el contexto de bases de datos, una relación usualmente se describe como una tabla, la cual está organizada en renglones y columnas. Relación base: En este trabajo se refiere a la relación a partir de la cual se está generando un cubo de datos. Suma de prefijos: Es una suma acumulativa, i.e., una secuencia de sumas parciales de una secuencia dada. Por ejemplo, las sumas acumulativas de la secuencia {a, b, c, ...} son a, a + b, a + b + c, .... Tabla cruzada: Se le conoce también como tabla dinámica, es una tabla donde los valores de uno de los atributos forman las cabeceras de las filas, los valores de otro atributo forman las cabeceras de las columnas y los valores de cada celda se obtienen mediante la agregación de los valores de un conjunto de atributos. Tarea: Denota un conjunto de cuboides que pueden agruparse en base a alguna caracterı́stica y calcularse simultáneamente. Tupla: Es una lista ordenada de elementos. Por ejemplo, (a, b, c, d) denota una tupla con 4 elementos (4-tupla). Vector: Se refiere a un simplemente a arreglo unidimensional. Vista: En la generación de un cubo de datos de n dimensiones a partir de una relación R, una vista es el resultado de la agregación de R respecto a una cierta combinación de las dimensiones del cubo. Por ejemplo, considere un cubo de datos con dimensiones A y B, las combinaciones posibles son {{}, A, AB}, entonces la vista {} corresponde a la agregación total de las tuplas de R; la vista A corresponde a la agregación de R respecto a los grupos formados por cada distinto valor ai de A; la vista AB corresponde a la agregación de R respecto a los grupos formados por cada distinto par de valores (ai , bi ) donde ai ∈ A y bi ∈ B. 19 Siglas y acrónimos Las siguientes siglas y acrónimos son utilizadas con frecuencia en los capı́tulos de esta tesis. ALU: Unidad aritmético - lógica (en inglés,Aritmetic Logic Unit). Es un circuito digital que realiza operaciones aritméticas y lógicas. La ALU es un bloque fundamental en una unidad central de procesamiento. CPU: Unidad central de procesamiento (en inglés, Central Processing Unit). Es el componente principal de una computadora o cualquier otro dispositivo programable, se encarga de interpretar las instrucciones contenidas en los programas, realizando operaciones aritméticas, lógicas y de entrada/salida. CSV: Valores separados por coma (en inglés, Comma-Separated Values). Es un formato comúnmente utilizado para representar información en formato tabular. CUDA: Arquitectura de dispositivo de cómputo unificado (en inglés, Compute Unified Device Architecture). Es una plataforma de cómputo en paralelo y modelo de programación que utiliza GPUs como motores de cálculo. GPU: Unidad de procesamiento gráfico (en inglés, Graphics Processing Unit). Es un circuito electrónico dedicado al procesamiento de gráficos u operaciones de coma flotante, su función es aligerar la carga de trabajo de la unidad central de procesamiento en aplicaciones como los videojuegos. Las GPUs también son utilizados como motores de cómputo de propósito general. MPI: Interfaz de paso de mensajes (en inglés, Message Passing Interface). Es un sistema portable y estandarizado de paso de mensajes diseñado para funcionar sobre una amplia variedad de computadoras paralelas. 20 OLAP: Procesamiento analı́tico en lı́nea (en inglés, On-Line Analytical Processing). Es una solución utilizada en el campo de la Inteligencia de Negocios, la cual consiste en consultas a estructuras multidimensionales (o cubos de datos OLAP) que contienen datos resumidos de grandes bases de datos o sistemas transaccionales. PCI Express: Interconexión de componentes periféricos Express (en inglés, Peripheral Component Interconnect Express). Es un bus serial de alta velocidad que permite conectar dispositivos electrónicos con un sistema de cómputo. POSIX: Interfaz portable de sistema operativo (en inglés, Portable Operating System Interface). Es una familia de estándares especificada por la organización IEEE para mantener la compatibilidad entre sistemas operativos. SIMD: Simple instrucción múltiples datos (en inglés, Single Instruction Multiple Data). Es una clase de computadora paralela que realiza simultáneamente una misma operación sobre diferentes conjuntos de datos. SQL: Lenguaje estructurado de consulta (en inglés, Structurated Query Language). Es un lenguaje de programación de propósito especial diseñado para la gestión de datos en sistemas manejadores de base de datos relacionales. 21 Capı́tulo 1 Introducción El cálculo de cubos de datos de manera eficiente es un problema central en los almacenes de datos y el procesamiento analı́tico en lı́nea. Es un proceso que puede implicar la ejecución de gran número de operaciones aritméticas, además de consumir bastante tiempo cuando se realiza a partir de datos de gran volumen. Para una relación R con n atributos o dimensiones más un atributo de medida M , R(A1 , A2 , ..., An , M ), el problema básico del cálculo del cubo de datos implica la agregación de R para construir 2n grupos de tuplas respecto a toda posible combinación de las n dimensiones (i.e., el conjunto potencia de las n dimensiones de R), a cada uno de estos grupos de tuplas se le llama cuboide o vista del cubo. Cada cuboide corresponde a un conjunto de tuplas donde uno o más de los elementos de cada tupla es un valor agregado que se calculó a partir de una partición de la relación en base a la cual se generó el cubo de datos [16]. Como una especialización al problema del cálculo del cubo de datos, el cubo iceberg fue introducido en [3] y consiste en calcular la parte de los cuboides que cumple con un umbral de soporte mı́nimo previamente especificado por el usuario. Si existe una condición iceberg, la cual toda partición de la relación base debe cumplir, por ejemplo SUM(M) > 100, la tarea es calcular un cubo iceberg, de lo contrario se calcula el cubo completo. En la práctica el tamaño de los cubos de datos puede aumentar de manera exponencial respecto al de la relación en base a la cual se generaron, ocupando gigabytes de espacio en disco. El tamaño máximo de un cubo de datos de n dimensiones generado a partir de K tuplas distintas es igual a (2n − 1)K + 1. En [8] por ejemplo, se construyó un cubo de datos de 8 dimensiones sobre un conjunto de 256 millones de tuplas, el resultado fue de aproximadamente 7 billones de tuplas (200 gigabytes). Es entonces poco probable que las plataformas con un solo procesador puedan manejar el enorme tamaño de los mas recientes y futuros sistemas de soporte a la toma de decisiones, para estos casos, el procesamiento en paralelo ofrece dos ventajas básicas: mayor capacidad de cómputo a través de múltiples procesadores y mayor ancho de banda mediante almacenamiento paralelo. 22 La generación de cubos de datos ha sido ampliamente investigada, sin embargo, hasta ahora la mayorı́a de los algoritmos no consideran las ventajas del paralelismo y las recientes arquitecturas de cómputo. En la actualidad la mayorı́a de equipos de cómputo recientes constan de procesadores que pueden ser utilizados para implementar el paralelismo, tal es el caso de los CPUs multinúcleo y las unidades de procesamiento gráfico o GPUs. Los procesadores multinúcleo pueden ser programados para ejecutar simultáneamente un moderado número de tareas utilizando varios procesadores integrados dentro de una sola unidad de cómputo. Es común encontrar chips de entre dos y ocho núcleos, donde cada núcleo es un procesador que implementa el conjunto completo de instrucciones x86. Por otro lado, las GPUs son dispositivos que constan de cientos de procesadores y que pueden ser usados para efectuar tareas masivamente paralelas. En este trabajo se proponen tres métodos paralelos para la generación de cubos de datos completos y de tipo iceberg utilizando tecnologı́a de GPUs y CPUs multinúcleo. 1.1. Motivación A causa del abaratamiento de la tecnologı́a de almacenamiento y el incremento del ancho de banda de las conexiones a Internet, es frecuente encontrar almacenes de datos cuyo tamaño oscila en las centenas de terabytes, a los que regularmente se les añaden grandes volúmenes de datos, tal crecimiento conduce a extremos la habilidad de sistemas con un solo procesador para manejar dichas cargas. La tecnologı́a actual permite colocar hasta cierto lı́mite de transistores en un solo chip, por lo que el desarrollo de procesadores cada vez más rápidos no representa una opción factible para aumentar el desempeño de los sistemas de cómputo. Esta limitación hizo necesaria la búsqueda de alternativas, dando como resultado al paralelismo. Las GPUs y CPUs multinúcleo son dispositivos comunes en sistemas de cómputo recientes, con gran capacidad de cálculo, mediano costo y moderado consumo energético que pueden ser utilizadas para mejorar el tiempo de respuesta del procesamiento analı́tico en lı́nea en sistemas de apoyo a la toma de decisiones ası́ como en la aceleración de tareas de minerı́a en almacenes de datos extensos. 23 1.2. Objetivo general Diseño e implementación de métodos paralelos para generación de cubos de datos completos y de tipo iceberg, aprovechando las ventajas del procesamiento en GPUs y CPUs multinúcleo. 1.2.1. Objetivos particulares Diseñar e implementar un conjunto de primitivas paralelas que permitan el manejo eficiente de datos aprovechando el paralelismo de GPUs y CPUs multinúcleo, ası́ como los niveles de memoria de la GPU. Diseñar e implementar métodos paralelos de generación de cubos de datos completos y de tipo iceberg empleando tecnologı́a multihilo y primitivas paralelas. Diseñar e Implementar funciones de agregación distributivas y algebraicas para los métodos de cubos de datos. 1.3. Justificación El cubo de datos es una estructura multidimensional con un alto porcentaje de valores agregados, fundamental en algunas áreas que soportan la toma de decisiones, ya que permite representar y analizar datos de acuerdo a una medida de interés. El cálculo eficiente de cubos de datos ha sido uno de los puntos hacia donde la investigación en el área de bases de datos se ha enfocado desde la introducción de los almacenes de datos y el OLAP. Sin embargo, hasta el momento la mayorı́a de los algoritmos de cubos de datos han sido propuestos sin considerar las ventajas de los procesadores multinúcleo y los recientes modelos de GPUs. En la actualidad las GPUs y CPUs multinúcleo están presentes en la mayor parte de equipos de cómputo, dotando a máquinas ordinarias de potentes procesadores paralelos con gran capacidad de cálculo que pueden emplearse para ejecutar de manera concurrente rutinas comunes en el proceso de generación del cubo de datos como ordenamientos, operaciones aritméticas y accesos a datos de gran volumen. 24 1.4. Alcances y limitaciones En este trabajo se presenta el diseño e implementación de tres métodos paralelos para la generación de cubos de datos completos y de tipo iceberg. Los métodos están basados en un conjunto de primitivas paralelas que aprovechan las caracterı́sticas de una GPU de modelo reciente y el paralelismo de los CPUs multinúcleo. En el estudio experimental se probaron medidas distributivas (SUM, MAX, MIN, COUNT) y algebráicas (AVG) utilizando conjuntos de datos numéricos, evaluando el desempeño del software con respecto a otros métodos bien conocidos en la literatura de bases de datos como BUC [3] y MM-Cubing [43]. Las principales limitaciones tecnológicas con las que se lidió durante el desarrollo de este trabajo son: La tecnologı́a de almacenamiento: A diferencia de los procesadores que incrementan su desempeño anualmente en un 50 − 60 % aproximadamente, los discos duros y otros dispositivos de almacenamiento secundario logran un incremento solo de 8 − 10 % debido a limitaciones mecánicas y otros factores, además, las capacidades de almacenamiento se han incrementado a un ritmo mayor al de la velocidad de transferencia [21]. Esta diferencia hace cada vez más difı́cil utilizar la capacidad de los discos de manera efectiva, ya que los tiempos de acceso a memoria secundaria demoran el procesamiento de grandes volúmenes de datos. La capacidad del bus PCI Express: Este bus que conecta a la memoria principal de la computadora con la GPU forma un cuello de botella al realizar un alto número de transferencias. Esta situación se da a causa del tamaño limitado de la RAM de video, ya que los modelos recientes cuentan entre 1-6 GB. 25 1.5. Aportaciones Las aportaciones de esta tesis son: Un método paralelo de generación de cubos de datos llamado MCBUC que es una versión replicada del algoritmo BUC [3] que funciona mediante hilos de CPU. Un nuevo método paralelo de generación de cubos de datos llamado SPCube basado en primitivas paralelas de GPUs y CPUs multinúcleo que genera vistas del cubo a partir de otras más detalladas a fin de reducir cálculos. Un nuevo método paralelo de generación de cubos de datos llamado GPUgenCube que utiliza primitivas paralelas de GPUs y CPUs multinúcleo. Este método genera grupos de vistas en paralelo, realizando la agrupación de las vistas en base a sus atributos, ahorrando también operaciones de ordenamiento. 1.6. Organización de la tesis El resto de la tesis está organizado de la siguiente manera: En el capı́tulo 2 se presentan los fundamentos teóricos sobre el procesamiento analı́tico en lı́nea y los cubos de datos. Se proporcionan también varios conceptos tecnológicos referentes al hardware de procesamiento que se utilizó durante el desarrollo de este trabajo y se presenta una revisión del trabajo previo a esta investigación. El capı́tulo 3 plantea el modelo de solución, iniciando por describir la configuración del sistema de cómputo que se utilizó para implementar el software diseñado en esta tesis. Posteriormente se describe el diseño e implementación de varias primitivas paralelas. Para finalizar el capı́tulo, se proponen tres métodos paralelos para generación de cubos de datos completos y de tipo iceberg. En el capı́tulo 4 se presenta un estudio experimental. Se describe el escenario de pruebas ası́ como los resultados obtenidos. Finalmente, en el capı́tulo 5 se dan las conclusiones y directivas para posibles trabajos futuros. 26 Capı́tulo 2 Fundamentos y trabajos previos En este capı́tulo se describen los fundamentos que sustentan a este trabajo y los conceptos necesarios para su lectura auto contenida. La primera parte del capı́tulo presenta algunos conceptos básicos referentes al OLAP y los cubos de de datos. Ası́ también se dan las definiciones de las funciones de agregación más comunes (SUM, MAX, MIN, COUNT, AVG) y se recopilan algunas estrategias conocidas para el cálculo de cubos completos y de tipo iceberg. La segunda parte da un breve repaso sobre el paralelismo en bases de datos y sus aspectos funcionales. La tercera parte de este capı́tulo presenta algunos detalles de la tecnologı́a de procesamiento multinúcleo y el cómputo de propósito general en unidades de procesamiento gráfico. Para concluir el capı́tulo dos, se presenta el estado del arte de esta investigación. 27 2.1. El procesamiento analı́tico en lı́nea y los cubos de datos El procesamiento analı́tico en lı́nea o simplemente OLAP (acrónimo en inglés de On-Line Analytical Processing) es una solución utilizada en el campo de la Inteligencia de Negocios (en inglés, Business Intelligence), la cual consiste en consultas a estructuras multidimensionales (o cubos de datos OLAP) que contienen datos resumidos de grandes bases de datos o sistemas transaccionales. Se usa en informes de negocios de ventas, marketing, informes de dirección, minerı́a de datos y áreas similares. Un sistema OLAP permite a un analista visualizar resúmenes de datos multidimensionales. La palabra en lı́nea (en inglés, online) indica que el analista debe ser capaz de solicitar nuevos resúmenes y obtener respuestas en lı́nea, es decir, dentro de unos cuantos segundos, y no debe estar forzado a esperar un largo tiempo para ver el resultado de una consulta. Las versiones iniciales de muchas herramientas OLAP asumı́an que los datos estaban residentes en memoria. El análisis de moderadas cantidades de datos puede realizarse incluso utilizando aplicaciones de hoja de cálculo, como Excel. Sin embargo, el OLAP sobre grandes cantidades de datos requiere de la utilización de bases de datos con soporte para pre - procesamiento eficiente de datos ası́ como de procesamiento en lı́nea de consultas. Considere una aplicación en que una tienda de dispositivos de electrónicos digitales desea averiguar cuales son los artı́culos más vendidos. Suponga que los artı́culos están caracterizadas por su nombre, fabricante y su capacidad, además, que se tiene la relación ventas con el esquema ventas(Artı́culo, Fabricante, Capacidad, Cantidad). Suponga que el atributo Artı́culo puede adoptar los valores {Unidad Mini-SATA, Memoria flash, Memoria RAM, Unidad de estado solido}, Fabricante puede adoptar los valores {F1, F2, F3} y capacidad tomar entre {16GB, 32GB, 128GB}. Dada una relación utilizada para el análisis de datos se pueden identificar algunos de sus atributos como atributos de medida, es decir atributos que miden algún valor y pueden agregarse. Por ejemplo, el atributo Cantidad de la relación ventas es un atributo de medida, ya que en este caso mide la cantidad de unidades vendidas. Algunos otros atributos de la relación (pueden ser todos) se identifican como atributos de dimensión, ya que definen las dimensiones en las que se ven los atributos de medida y los resúmenes de los atributos de medida. En la relación ventas, Artı́culo, Fabricante y Capacidad son atributos de dimensión. Los datos que pueden modelarse como atributos de dimensión y como atributos de medida se denominan atributos dimensionales. 28 Para analizar los datos multidimensionales puede que el analista desee ver los datos dispuestos como se encuentran en la tabla de la Figura 2.1. La cual muestra las cifras totales de diferentes combinaciones de Artı́culo y Fabricante. El valor para atributo capacidad en este caso es todas, esto es, los valores mostrados son resumen para todos los valores de la tabla. Para representar la situación de este atributo se utiliza el valor especial ALL. Figura 2.1: Tabla cruzada de la relación ventas sobre los atributos Artı́culo y Fabricante. En este caso, el atributo capacidad tiene asignado el valor especial ALL La tabla mostrada en la Figura 2.1 es un ejemplo de tabla cruzada o tabla dinámica. En general las tablas cruzadas son tablas en donde los valores de uno de los atributos forman las cabeceras de las filas, los valores del otro atributo forman las cabeceras de las columnas y los valores de cada celda se obtienen de la siguiente manera: considere 2 atributos A y B, cada celda puede identificarse como (ai , bj ) donde ai es un valor de A y bi es un valor de B. El valor de la celda (ai , bj ), se obtiene mediante la agregación de las tuplas correspondientes (si es que existen en la relación). En este ejemplo la agregación utilizada es la suma sobre los valores del atributo número para todos los valores de capacidad, como se indica por capacidad ALL en la tabla de la Figura 2.1. En este caso la tabla cruzada también tiene una columna y una fila adicionales que guardan los totales de las celdas de cada fila o columna. La mayor parte de la tablas cruzadas tienen esas filas y columnas de resumen. Las tablas cruzadas son diferentes de las tablas comunes que normalmente se guardan en las bases de datos relacionales, ya que el número de columnas de la tabla cruzada depende de los datos. Una modificación en los valores de los datos puede dar lugar a que se añadan más columnas, lo que no resulta deseable para el almacenamiento de los datos. No obstante, la vista de tabla cruzada es deseable para mostrarla al usuario. 29 La representación de las tablas cruzadas sin valores de resumen en formato tabular con un número fijo de columnas es sencilla. La tabla cruzada con columnas o filas resumen puede representarse introduciendo el valor especial ALL para representar los subtotales, como en la Tabla 2.1. La norma SQL:1999 utiliza realmente el valor null, para evitar confusiones, se utilizará el valor ALL. Artı́culo Fabricante Capacidad Cantidad Unidad Mini-SATA Unidad Mini-SATA Unidad Mini-SATA Unidad Mini-SATA Memoria flash Memoria flash Memoria flash Memoria flash Memoria RAM Memoria RAM Memoria RAM Memoria RAM Unidad de estado solido Unidad de estado solido Unidad de estado solido Unidad de estado solido ALL ALL ALL ALL F1 F2 F3 ALL F1 F2 F3 ALL F1 F2 F3 ALL F1 F2 F3 ALL F1 F2 F3 ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL ALL 8 35 10 53 20 10 5 35 14 7 28 49 20 2 5 27 62 54 48 164 Tabla 2.1: Representación tabular de los datos en la Figura 2.1. Ahora bien, considere las tuplas (Unidad Mini-SATA, ALL, ALL) y (Memoria flash, ALL, ALL). Las cuales se han obtenido eliminando las tuplas individuales con diferentes valores de fabricante y capacidad, ası́ como sustituyendo el valor de número por un agregado, en este caso una suma. El valor ALL puede considerarse como una representación del conjunto de los valores de un atributo. Las tuplas con el valor ALL para las dimensiones fabricante y capacidad pueden obtenerse mediante una agregación de la relación ventas agrupando con respecto al atributo Artı́culo. De manera similar se puede utilizar una agrupación con respecto a fabricante y capacidad para obtener las tuplas con el valor ALL en Artı́culo y una agrupación sin atributo 30 alguno para obtener la tupla con el valor ALL en los atributos Artı́culo, Fabricante y Capacidad. La generalización de las tablas cruzadas de dos dimensiones a n dimensiones puede visualizarse como una estructura de n dimensiones, denominada cubo de datos, en este caso, a cada tabla cruzada se le llama vista o cuboide del cubo de datos. La Figura 2.2 muestra un cubo de datos para la relación ventas. El cubo de datos tiene tres dimensiones, Artı́culo, Fabricante y Capacidad, el atributo de medida es número. Cada celda se identifica por los valores de estas tres dimensiones. Cada celda del cubo de datos contiene un valor, igual que en la tabla cruzada. En la Figura 2.2, el valor contenido en la celda se muestra en una de las caras de la celda; las otras caras de la celda se muestran en blanco si son visibles. Todas las celdas contienen valores aunque no sean visibles. Si el valor de una dimensión es ALL, entonces la celda afectada contendrá un resumen de todos los valores de esa dimensión, como en las tablas cruzadas. El número de maneras diferentes en que las tuplas pueden agruparse para su agregación es 2n , esto es, para una tabla con n dimensiones, se puede realizar la agregación de sus tuplas con respecto a la agrupación de cada uno de los 2n subconjuntos de las n dimensiones. Figura 2.2: Conceptualización de un cubo de datos de tres dimensiones. El cubo de datos muestra los totales de ventas para una tienda de dispositivos electrónicos dispuestos de acuerdo a toda combinación de Artı́culo, Fabricante y Capacidad. Por ejemplo, se puede ver que se vendieron 20 unidades de estado solido del fabricante F1, 7 de 128GB, 12 de 32GB y 1 de 16GB. 31 2.1.1. Funciones de agregación Las funciones de agregación toman una colección (un conjunto o multiconjunto) de valores como entrada y devuelven un solo valor. Recordando la explicación anterior sobre las tablas cruzadas y su generalización como un cubo de datos, los valores para construir estas tablas o vistas de cubo se obtienen mediante funciones de agregación, es decir, estas funciones se aplican a conjuntos de tuplas agrupados de cierta manera para dar origen a los valores agregados de las vistas del cubo. El SQL ofrece cinco funciones básicas: MIN: Regresa el valor más pequeño dentro de un conjunto de datos. MAX: Regresa el valor más grande dentro de un conjunto de datos. SUM: Regresa el total de un conjunto de valores numéricos. COUNT: Regresa el número de elementos en un conjunto. AVG: Regresa el centro de un conjunto de datos (media aritmética). Sea x1 , x2 , x3 , ..., xN un conjunto de N valores, tales como los de algún atributo numérico como salario, la PN x i media del conjunto está dada: x̄ = i=1 = x1 ,x2 ,xN3 ,...,xN N Las funciones de agregación pueden clasificarse en tres categorı́as [16]. Considere la agregación de un conjunto de tupas T . Sea {Si |i = 1, ..., n} cualquier conjunto completo de subconjuntos disjuntos T tal que ∪i Si = T y ∩i Si = ∅. Una función de agregación F es distributiva si existe una función G tal que F (T ) = G({F (Si |i = 1, ..., n}). SUM, MIN y MAX son distributivas con G = F . COUNT es distributiva con G = SUM. Una función de agregación F es algebraica si existe una función G cuyo valor de retorno es una M -tupla y una función H tal que F (T ) = H({G(Si )|i = 1, ..., n}) y M es constante independiente de |T | y n. Todas las funciones distributivas son algebraicas, ası́ como lo son la media aritmética (AVG) y la desviación estándar (σ). Para AVG, la función G produce SUM y COUNT, y H obtiene SUM/COUNT. Una función de agregación F es holı́stica si no es algebraica. Por ejemplo, la mediana y el rango (RANK) son holı́sticas. En esta investigación las funciones evaluadas son distributivas y algebraicas. 32 2.1.2. Operadores GROUP BY y CUBE BY Dado que las funciones de agregación regresan un solo valor, utilizando el operador GROUP BY el SQL es posible crear una tabla de varios valores agregados indexados por un grupo de atributos. Por ejemplo, la siguiente consulta reporta el total de ventas para cada cada producto en cada tienda: SELECT id producto, id tienda, SUM(ventas unitarias) FROM tienda GROUP BY id producto, id tienda El operador GROUP BY particiona la relación en conjuntos disjuntos de tuplas y entonces agrega sobre cada conjunto de tuplas. Véase la Figura 2.3. Figura 2.3: Operador relacional GROUP BY: Particiona una tabla en grupos. Cada grupo es agregado por una función. La función de agregación sumariza alguna columna de grupos regresando un valor por cada grupo. Como una extensión al SQL, el operador CUBE BY fue introducido por Jim Gray y otros en [16]. Su función es generalizar el operador GROUP BY para calcular agregados para toda combinación del conjunto de atributos que se ha especificado. Por ejemplo, consideremos una relación ventas(Modelo, Año, Color, Unidades), aplicando al operador CUBE BY sobre esta relación, indicando los atributos Modelo, Año, Color y el agregando SUM sobre el atributo Unidades, el resultado contendrá la sumatoria de ventas para toda la relación, para cada atributo (Modelo), (Año), (Color), para cada par (Modelo, Año), (Modelo, Color), (Año, Color) y finalmente para (Modelo, Año, Color). La Figura 2.4 muestra el resultado en formato tabular de la ejecución del operador CUBE BY para la construcción de un cubo de datos a partir de la relación ventas antes mencionada. Como puede observarse, hay tuplas para toda combinación de Modelo, Año y Color. El operador CUBE BY no es estándar, por lo que no todos los manejadores de bases de datos modernos lo incluyen. 33 Figura 2.4: Cubo de datos sobre una relación de ventas de automóviles. 2.1.3. Generación del cubo de datos La generalización o agregación de los datos es un proceso que abstrae un conjunto grande de datos de datos relevantes a una tarea en una base de datos partiendo de un nivel conceptual relativamente bajo pasando a niveles conceptualmente más altos. Los usuarios de sistemas OLAP requieren obtener con facilidad y sencillez grandes conjuntos de datos resumidos en forma clara y concisa, a diferentes niveles de detalle y desde diferentes ángulos. Tales descripciones ayudan a proporcionar una visión general de un conjunto de datos. Para una relación R con n atributos o dimensiones más un atributo de medida M , R(A1 , A2 , ..., An , M ), el problema básico del cálculo del cubo de datos implica la agregación de R para construir 2n grupos de tuplas respecto a toda posible combinación de las n dimensiones (i.e., el conjunto potencia de las n dimensiones de R), a cada uno de estos grupos de tuplas se le llama cuboide o vista del cubo. Cada cuboide corresponde a un conjunto de tuplas donde uno o más de los elementos de cada tupla es un valor agregado que se calculó a partir de una partición de la relación en base a la cual se generó el cubo de datos [16]. 34 Normalmente el tamaño del cubo de datos supera por mucho al de la relación a partir de la cual fue calculado. El tamaño de cada agrupamiento o cuboide está en función de las cardinalidades de sus dimensiones, posiblemente, esta cantidad es equivalente al producto de Pn tales cardinalidades, i.e., Tamaño del cubo ≈ i=1 |A1i | × |A2i | × ... × |Ami | donde m es el número de atributos de la vista. Cuando el producto de las cardinalidades para un agrupamiento es grande con respecto al número de celdas que realmente aparecen en un cuboide, se dice que el cuboide es disperso (en inglés, sparse). Cuando el número de cuboides dispersos es grande con respecto al número total de cuboides, se dice que el cubo es disperso. El cubo iceberg Como una especialización al problema del cálculo del cubo de datos, el cubo iceberg fue introducido en [3] y consiste en calcular la parte de los cuboides que cumple con un umbral de soporte mı́nimo previamente especificado por el usuario. Si existe una condición iceberg, la cual toda partición de la relación base debe cumplir, por ejemplo SUM(ventas)≥10, la tarea es calcular un cubo iceberg (véase la Figura 2.5), de lo contrario se calcula el cubo completo. Figura 2.5: Conceptualización de un cubo iceberg de tres dimensiones con umbral de soporte mı́nimo SUM(ventas)≥10. El cubo iceberg muestra los totales de ventas para una tienda de dispositivos electrónicos dispuestos de acuerdo a toda combinación de Artı́culo, Fabricante y Capacidad. Se puede ver que las vistas solo incluyen valores agregados que satisfacen el umbral de soporte mı́nimo. La relación base y sus valores únicos de atributos pueden dar origen a un cubo de datos enorme, metafóricamente esto puede visualizarse como un iceberg completo, mientras tanto, la respuesta es pequeña, es decir, el número de tuplas que satisfacen el umbral es menor, esto 35 representarı́a la punta del iceberg, la parte de los cuboides que es de más interés. En otras palabras, la generación del cubo iceberg permite calcular selectivamente las tuplas que satisfacen una condición de agregación. De manera similar a lo que sucede en una consulta SQL que utiliza la cláusula HAVING, cuando se genera una vista o cuboide del cubo iceberg se remueven ciertas tuplas cuyos valores agregados caen bajo el umbral de soporte mı́nimo. Por ejemplo, para una relación R(A1 , A2 , ..., An , M1 , M2 , ..., Mm ) y un umbral N donde los Ai son atributos de agrupamiento y las Mi agregaciones sobre algún Ai , la consulta serı́a como se muestra a continuación: SELECT A1 , A2 , ..., An , M1 , M2 , ..., Mm FROM R GROUP BY A1 , A2 , ..., An HAVING Mi ≥ N Donde Mi ≥ N es la condición de soporte mı́nimo que toda tupla resultante debe cumplir. Algoritmos secuenciales para generación de cubos de datos La mayorı́a de los algoritmos para cálculo de cubos de datos utilizan a la estructura de lattice para conceptualizar la jerarquı́a entre las vistas del cubo. La Figura 2.6 muestra una lattice para un cubo de cuatro dimensiones (A, B, C y D). Los nodos en la lattice representan vistas o cuboides del cubo de datos, mismos que se encuentran etiquetados de acuerdo a sus atributos de agrupamiento. Los arcos de la estructura de lattice muestran rutas potenciales de calculo. En su mayorı́a, los algoritmos para calculo de cubos de datos convierten esta estructura en un árbol de procesamiento dirigido. Por tanto, cada nodo del árbol de procesamiento tiene solo un padre, ya que se calcula a partir de su padre o de los datos en bruto. Figura 2.6: Estructura de lattice para un cubo de datos de cuatro dimensiones. 36 Los algoritmos de cubos de datos normalmente realizan el cálculo de los cuboides de alguna manera en particular. Los algoritmos que siguen los arcos de la lattice desde los datos en bruto hacia el valor del agregado total (ALL) son conocidos como algoritmos descendentes (en inglés, top-down). Los algoritmos que calculan los cuboides en reversa se les llama ascendentes (en inglés, bottom-up). Actualmente se conocen algoritmos como [49] y [43] que no siguen ninguno de estos dos modelos. La Figura 2.7 muestra un ejemplo del enfoque descendente, de ahı́ puede observarse que los cuboides se calculan partiendo de ABCD hasta A. Figura 2.7: Ejemplo de árbol de procesamiento presente en algoritmos descendentes. Por otro lado, el enfoque ascendente va en la dirección opuesta. La Figura 2.8 ilustra un árbol de procesamiento de un algoritmo de tipo ascendente, los números indican el orden en que se calculan los cuboides. Figura 2.8: Ejemplo de árbol de procesamiento presente en algoritmos ascendentes. 37 Estrategias en algoritmos descendentes Generalmente los algoritmos de cubos de datos intentan descubrir y aprovechar los elementos comunes entre un nodo y su padre en la estructura de lattice. Varios elementos han sido explotados por los algoritmos descendentes, algunas de estas técnicas fueron listadas en [1]: Padre de menor costo (en inglés, Smallest - parent): Tiene el objetivo de calcular un couboide a partir del más pequeño previamente calculado. Por ejemplo, considerando la Figura 2.7, es posible calcular AB a partir de ABC y ABD, sin embargo, de entre los dos potenciales padres, se selecciona el de menor tamaño, ya que calcular a partir del padre de menor tamaño dará lugar a un menor costo. Cacheo de resultados (en inglés, Cache - results): Esta técnica trata de calcular un cuboide cuando su padre está todavı́a en memoria, reduciendo costos en operaciones de entrada/salida a disco. Amortizar escaneos (en inglés, Amortize - scans): Esta técnica también tiene por objetivo reducir las operaciones de entrada/salida a disco, esto se realiza amortizando las lecturas a través del calculo de tantos couboides como sea posible juntos en memoria. Por ejemplo, considerando la Figura 2.7, durante el escaneo del cuboide ABCD, es posible calcular ABC, ACD, ABD, BCD al mismo tiempo. Compartir ordenamiento (en inglés, Share - sorts): Algunos usan esta técnica para compartir costos de ordenamiento entre múltiples cuboides. Compartir particionamiento (en inglés, Share - partitions): Esta técnica es propia de los algoritmos basados en tablas hash. Cuando una tabla hash no cabe en memoria, los datos son particionados en chunks que si caben en memoria. Habiendo leı́do un chunk, se calculan múltiples cuboides con el fin de compartir costos de particionamiento. Estrategias en algoritmos ascendentes El primer método para generar el cubo de datos de tipo ascendente fue introducido en [3] por K. Beyer y R. Ramakrishnan, el cual se especializa en el cálculo de cubos iceberg reduciendo el cálculo a través de una estrategia de poda Apriori [2]. La asignación de umbrales de soporte mı́nimo en las consultas iceberg permite remover gran cantidad de tuplas en los cuboides. 38 2.2. Paralelismo en bases de datos La arquitectura de un sistema de bases de datos esta influida en gran medida por el sistema informático subyacente en el que se ejecuta, en concreto, por aspectos de la arquitectura de la computadora como la conexión a la red, el paralelismo y la distribución. El procesamiento paralelo dentro de una computadora permite acelerar las actividades del sistema de base de datos, proporcionando respuestas más rápidas a las transacciones ası́ como la capacidad de ejecutarlas en mayor número. Las consultas pueden procesarse de manera que se explote el paralelismo ofrecido por el sistema subyacente. Los sistemas paralelos mejoran el desempeño en el procesamiento y la velocidad en operaciones de entrada y salida de datos ya que CPUs, memorias y discos funcionan en paralelo. Este tipo de sistemas va siendo cada vez más común, lo que hace muy importante su estudio y aplicación en las bases de datos. Una de las principales razones que ha impulsado el desarrollo de sistemas de bases de datos paralelos es el manejo de datos extremadamente grandes (del orden de terabytes) o que deben procesar gran número de transacciones por segundo (del orden de miles de transacciones por segundo), es aquı́ donde las bases de datos centralizadas y cliente-servidor son incapaces de soportar tales aplicaciones. En el procesamiento paralelo se realizan una gran cantidad de operaciones simultáneamente, mientras que en el procesamiento secuencial se realizan en serie. El procesamiento en paralelo puede ser clasificado como de grano grueso (en inglés, coarse grained system) o de grano fino (en inglés, fine grained system), donde el grano grueso se refiere a los sistemas con un pequeño número de procesadores potentes, comúnmente de dos a 16 procesadores, mientras que el grano fino define a una máquina masivamente paralela, que cuenta con cientos de procesadores más pequeños y es capaz de soportar un grado de paralelismo mucho mayor. Hoy en dı́a, las recientes GPUs son claro ejemplo de máquinas masivamente paralelas, por tanto, su aplicación a las bases de datos es un área que ha comenzado a ser objeto de estudio, como es el caso de este trabajo. 39 2.2.1. Paralelismo a gran escala En la actualidad existen sistemas de cómputo en paralelo a gran escala como los clusters de computadoras que a diferencia de las GPUs operan mediante procesadores débilmente acoplados. Estos sistemas consisten en un grupo (decenas o cientos) de computadoras conectadas a través de una red para trabajar en conjunto, de manera que pueden verse como un solo sistema. Para este tipo de procesadores existen modelos de programación como MPI [12] y MapReduce [10]. MPI es un sistema estandarizado y portable de paso de mensajes diseñado por un grupo de investigadores de la academia y la industria para funcionar sobre una variedad de computadoras paraleles. El estándar define la sintaxis y semántica de una biblioteca de rutinas aplicable a un amplio rango de usuarios que desean escribir programas de paso de mensajes con portabilidad empleando lenguajes de programación como C o Fortran. Actualmente existen diversas implementaciones de MPI, algunas disponibles en la web para uso académico y comercial como OpenMpi [29]. Por otra parte, MapReduce es un modelo de programación y una implementación asociada para procesar y generar grandes conjuntos de datos. Los usuarios especifican una función MAP que procesa un par clave/valor para generar un conjunto intermedio de pares clave/valor y una función REDUCE que une todos los valores intermedios asociados con la misma clave intermedia. Empleando MapReduce, los programas escritos en este estilo funcional son automáticamente paralelizados y ejecutados en un cluster de computadoras comunes. El sistema de tiempo de ejecución de MapReduce se encarga de detalles como particionar los datos de entrada, planificar la ejecución del programa a través de un conjunto de máquinas, manejar fallos de máquinas y administrar la comunicación entre máquinas que sea necesaria. Esto permite a los programadores sin experiencia en sistemas paralelos y distribuidos aprovechar fácilmente los recursos de un gran sistema distribuido. 40 Como ejemplo de este modelo de programación, considere el problema de contar el número de ocurrencias de cada palabra en una gran colección de documentos. El usuario escribirı́a para ello un código similar al siguiente pseudocódigo: Algoritmo 1 conteoPalabras 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: Función map(clave, valor) . clave: nombre de un documento, valor: contenidos Para cada palabra w en valor hacer EmitirIntermedio(w,“1”) Fin Para Fin Función Función reduce(clave, valores) . clave: una palabra, valores: una lista de conteos resultado ← 0 Para cada v en valores hacer resultado ← resultado + v Emitir(resultado) Fin Para Fin Función La función MAP emite cada palabra más un conteo asociado de ocurrencias (solo “1” en este sencillo ejemplo). La función REDUCE suma todos los conteos emitidos para una palabra en particular. Adicionalmente, el usuario escribe código para completar un objeto de especificación MapReduce con los nombres de los archivos de entrada y salida, ası́ como algunos parámetros opcionales de afinación. El usuario invoca la función MapReduce, pasando el objeto de especificación. Una de las implementaciones más populares de MapReduce es Apache Hadoop [13]. Este software es una biblioteca de funciones que permite distribuir el procesamiento de grandes conjuntos de datos a través de clusters de computadoras usando sencillos modelos de programación. Además, este proyecto es de código abierto, lo que permite su utilización sin cargos por licencias. 41 2.2.2. Arquitecturas paralelas En los sistemas de bases de datos paralelos se pueden encontrar aspectos funcionales que los hacen diferentes. Más especı́ficamente, existen diversas arquitecturas para máquinas paralelas, de manera breve los modelos básicos son: Memoria compartida: Todos los procesadores comparten una memoria común. Disco compartido: Todos los procesadores comparten un conjunto de discos. Sin compartimiento: Los procesadores no comparten memoria ni discos. Hı́brida: Trata de combinar los beneficios de las anteriores En este trabajo se empleo la combinación CPU - GPU, que opera esencialmente como una máquina de arquitectura de memoria compartida, por lo que se describirá en lo que resta de la sección. Memoria compartida Debido que los procesadores están interconectados entorno a una memoria principal y uno o varios discos compartidos, los GPUs y CPUs multinúcleo funcionan de manera similar a la arquitectura de memoria compartida. En el enfoque de memoria compartida, cualquier procesador tiene acceso a cualquier módulo de memoria o unidad de disco a través de una conexión rápida (por ejemplo, un bus de alta velocidad), véase la Figura 2.9. Todos los procesadores están bajo el control de un solo sistema operativo. El beneficio de esta arquitectura es la eficiencia en la comunicación entre procesadores, esto es, cualquier procesador puede acceder a los datos en la memoria compartida sin necesidad de la intervención de software. Los procesadores pueden enviarse mensajes a través de la escritura en memoria, por tanto la comunicación es muy rápida. Por ende, en este enfoque es posible que los procesadores compartan información de control (bloqueos a tablas) y meta-información (directorio) de una base de datos, ası́, el desarrollo de software de base de datos para esta arquitectura no difiere demasiado del realizado en sistemas con un solo procesador. 42 En este tipo de arquitectura, como es el caso de las GPUs y CPUs multinúcleo, los procesadores suelen contar con una memoria caché de tamaño considerable para reducir los accesos a memoria compartida, sin embargo, esta reducción no puede lograrse siempre, por que debido a su menor tamaño en comparación con la memoria compartida, no todos los datos podrı́an caber en ella. Además, se necesita mantener la coherencia en las cachés, por ejemplo, si un procesador realiza una escritura en cierta posición de la memoria compartida, los datos de dicha posición deben actualizarse en todos los procesadores que los mantienen en caché, este mantenimiento de coherencia sobrecarga el sistema conforme aumenta el número de procesadores. Figura 2.9: Representación conceptual de la arquitectura memoria compartida. Cualquier procesador tiene acceso a cualquier módulo de memoria o unidad de disco a través de una conexión rápida. 43 2.3. Tecnologı́a multinúcleo y de muchos núcleos Desde el 2003, la industria de semiconductores se ha basado en dos principales enfoques para el diseño de procesadores [48], los enfoques multinúcleo y de muchos núcleos. El enfoque multinúcleo persigue el sustento de la velocidad en la ejecución de programas secuenciales utilizando varios procesadores integrados dentro de una sola unidad de cómputo. Es común encontrar chips de entre dos y ocho núcleos, donde cada núcleo es un procesador que implementa el conjunto completo de instrucciones x86. En contraste, el enfoque de muchos núcleos busca el mejoramiento en el desempeño de las aplicaciones paralelas. Los ejemplos más notables de este enfoque son las GPUs, cuyo número de núcleos se duplica en cada nueva generación. Las GPUs han mostrado un mejor desempeño que los CPUs en tareas concurrentes, esto se debe a las diferencias de diseño en estos dos tipos de procesadores, véase la Figura 2.10. A diferencia de las GPU, los diseños de CPU están optimizados para mejorar el desempeño de código secuencial, mediante una sofisticada lógica de control. Figura 2.10: Representación de los diseños de procesadores multinúcleo (CPU) y de muchos núcleos (GPU) respectivamente. Los procesadores multinúcleo normalmente tienen entre dos y ocho núcleos, orientados a la aceleración de procesos secuenciales. En contraste, las GPUs están enfocadas a problemas paralelos, contando con cientos de núcleos menos potentes que los de una CPU multinúcleo. Ası́, las GPUs están diseñadas como motores de calculo numérico y por lo tanto no se desempeñarı́an adecuadamente en algunas tareas para las que los CPUs están diseñados. En esta tesis se diseñaron métodos que utilizan estas dos tecnologı́as dentro de distintas fases del proceso de construcción del cubo de datos. 44 2.3.1. Procesamiento paralelo en CPUs multinúcleo En arquitecturas de multiprocesadores de memoria compartida, como los multiprocesadores simétricos (en inglés, Symmetric multiprocesors), los hilos pueden usarse para implementar paralelismo. Para los sistemas UNIX y derivados, se ha especificado una interfaz estándar para manejo de hilos usando el lenguaje C a través del estándar IEEE POSIX 1003.1c [23]. Todo el software derivado de este trabajo se desarrolló en un sistema Linux y se utilizaron este tipo de hilos, comúnmente conocidos como hilos POSIX. Un hilo está definido como un flujo independiente de instrucciones que puede ser planificado por el sistema operativo para ejecutarse. Es decir, para el programador, un hilo podrı́a ser descrito por el concepto de un procedimiento que se ejecuta de manera independiente respecto a su programa principal o proceso padre. En UNIX, los procesos son creados por el sistema operativo y requieren de una buena cantidad de información acerca de recursos de programa y estado de ejecución de programa, entre los que podemos mencionar: ID de proceso, ID de grupo de proceso, ID de usuario e ID de grupo Entorno, directorio de trabajo Instrucciones de programa, registros, pila (en inglés, stack ) Descriptores de archivos, bibliotecas compartidas Herramientas para comunicación entre procesos (e.g., colas de mensajes, tuberı́as, semáforos o memoria compartida). La Figura 2.12 muestra una representación de un proceso de UNIX. Figura 2.11: Representación conceptual de un proceso de UNIX. Un proceso de UNIX cuenta con recursos que permiten la ejecución de un programa como una pila, texto de programa y datos usados por el programa. 45 Los hilos usan y existen dentro de esos recursos de los procesos, sin embargo son capaces de ser planificados por el sistema operativo y ejecutarse como entidades independientes en gran parte porque duplican solo los recursos esenciales que los habilitan para existir como código ejecutable. La Figura 2.12 muestra una representación de dos hilos coexistiendo dentro de un proceso de UNIX. Figura 2.12: Representación conceptual de proceso de UNIX con dos hilos. Los hilos duplican los recursos que les permiten existir como código ejecutable dentro de un proceso de UNIX. De manera que el flujo de control independiente de los hilos se logra a gracias a que cada una de estas entidades cuenta con elementos individuales como: Apuntador a pila Registros Propiedades de planificación Conjunto de señales de pendiente y bloqueado Datos especı́ficos del hilo 46 2.3.2. Cómputo de propósito general en unidades procesamiento gráfico Conforme los diseños de hardware de las GPUs fueron evolucionando, incluyendo cada vez más procesadores, su arquitectura se fue asemejando a las de las computadoras paralelas de alto desempeño. Con la llegada de las GPUs con soporte DirectX 9, algunos investigadores notaron la tendencia de incremento en el desempeño de las GPUs y comenzaron a explorar esta tecnologı́a a fin de poder aplicarla en la resolución de problemas de ciencia e ingenierı́a, sin embargo, en ese momento las GPUs estaban diseñadas únicamente para satisfacer las necesidades de las interfaces gráficas, por lo tanto, para acceder estos recursos de cómputo, los programadores tenı́an que traducir su trabajo en operaciones gráficas nativas y ası́ el calculo podı́a ser resuelto mediante llamadas a interfaces gráficas como OpenGL [36] o DirectX [28]. Esta técnica es llamada cómputo de propósito general en unidades de procesamiento gráfico, y fue ampliamente utilizada hasta el 2006, resultando complicada la utilización de las GPUs en aplicaciones de propósito general hasta ese momento, ya que los programadores debı́an tener conocimiento sobre herramientas como OpenGL y DirectX. NVIDIA CUDA CUDA [33] es el acrónimo de Compute Unified Device Architecture y es una arquitectura de cómputo en paralelo de propósito general, introducida por la corporación NVIDIA [35] en noviembre de 2006. CUDA tiene el propósito de aprovechar a las GPUs NVIDIA como motores de cómputo en paralelo para resolver problemas complejos de cálculo de una manera más eficiente que en un CPU. Esta arquitectura de hardware y software permite que las GPUs NVIDIA ejecuten programas escritos en lenguajes de programación como C, C++, Fortran, OpenCL, etc. Además del paralelismo proporcionado por los hilos POSIX, en este trabajo se utilizó esta plataforma para aprovechar la capacidad de cómputo de los números procesadores de una GPU. Una GPU es una máquina masivamente paralela conectada a un equipo de cómputo convencional a través de un bus PCI Express, la Figura 2.13 muestra la organización tı́pica de una GPU NVIDIA de modelo reciente. 47 Figura 2.13: Organización tı́pica de una GPU conformada por 30 multiprocesadores, cada uno con 8 ALUs SIMD y una espacio de memoria local compartida. En el modelo CUDA un programa es ejecutado a través de un sistema anfitrión o host y un dispositivo o Device, con anfitrión se hace referencia un equipo de cómputo, mientras que el dispositivo es la GPU. Generalmente el sistema anfitrión se encarga de iniciar el programa y de ejecutar las partes secuenciales mientras que la GPU realiza las paralelas. Un programa en CUDA invoca de manera serial o paralela a funciones llamadas kernel. Un kernel es a groso modo una función que se ejecuta mediante la GPU a través de un bloque de hilos paralelos. El programador o compilador responsable de organizar esos hilos en bloques y mallas de bloques de hilos que tienen acceso a diferentes niveles de memoria en la GPU. Es decir, un bloque de hilos es esencialmente un conjunto de hilos ejecutándose en paralelo, los cuales pueden cooperar entre ellos mismos con la barrera que representa la sincronización y la memoria que comparten. De manera que en la GPU se crean instancias de un kernel en una malla de bloques de hilos, donde cada hilo dentro del bloque se encarga de ejecutar una instancia del kernel y consta de un atributo identificador, un contador de programa, registros, memoria privada por hilo, entradas y salidas de resultados. Una malla es un arreglo de bloques de hilos que ejecutan el mismo kernel, leen la entrada de la memoria global, escriben la salida a la memoria global y se sincronizan a través de llamadas dependientes de kernel. En el modelo de programación CUDA, cada hilo tiene una espacio de memoria individual usado para volcar el contenido de los registros, llamadas a funciones y variables arreglo automáticas del lenguaje C. Cada bloque de hilos tiene un espacio de memoria compartido individual usado para la comunicación entre hilos, intercambio de datos y de resultados en algoritmos paralelos. Las mallas de bloques de hilos comparten resultados en espacio memoria global después de que se realiza la sincronización global de los kernels. La Figura 2.14 conceptualiza esta jerarquı́a. 48 Figura 2.14: Jerarquı́a de hilos, bloques y mallas en CUDA con sus respectivos espacios de memoria. La jerarquı́a de hilos de CUDA está relacionada con la jerarquı́a de procesadores de la GPU. Más especı́ficamente, una GPU ejecuta una o más mallas de funciones kernel, un multiprocesador ejecuta uno o más bloques de hilos, mientras que los núcleos CUDA y otras unidades de ejecución dentro del multiprocesador ejecutan hilos. Fı́sicamente, los multiprocesadores ejecutan hilos en grupos de 32, a los cuales se les llama warps. Aunque en la mayorı́a de los casos por sencillez se puede ignorar la ejecución en warps, el desempeño de los programas mejora de manera importante si los hilos dentro de un warps ejecutan el mismo código y acceden direcciones cercanas de memoria. 49 2.4. Estado del arte Varios trabajos han aportado métodos secuenciales para el calculo eficiente de cubos de datos completos y de tipo iceberg, dentro de esta categorı́a se destacan los métodos BUC [3], Star-Cubing [49] y MM-cubing [43] otros más como [31] han aportado métodos paralelos en clusters de computadoras. Más recientemente, conforme el uso de las GPUs para la resolución de problemas de cómputo fue ganando aceptación, en el área de los almacenes de datos y el OLAP se desarrollaron trabajos como [39], [26], [51] y [47] que de manera similar al presente, están enfocados al aprovechamiento de la tecnologı́a de GPUs y CPUs multinúcleo. En el Centro de Investigación en Computación se han realizado también trabajos sobre cubos de datos como [27] que presenta una técnica para realizar minerı́a de datos o descubrir conocimiento en datos generalizados y sumarizados mediante cubos. En el artı́culo citado se presenta una herramienta que permite definir y utilizar cubos de datos, eligiendo las regiones de interés y definiendo los patrones de comportamiento o situaciones anómalas a localizar en las regiones de interés. Ası́ mismo, se han realizado tesis sobre paralelización de consultas SQL como [38] donde se construyó una versión del operador de junta natural (en inglés, natural join) que funciona a través de una GPU utilizando como base al motor de base de datos conocido como SQLite [45]. El operador de junta natural permite realizar la combinación de los registros de dos o más tablas de una base de datos, la junta es un medio para combinar los campos de dos o más tablas mediante el uso de valores en común. En [7] se propuso un sistema de integración de bases de datos utilizando un enfoque semiautomático para combinar resultados parciales de consultas a distintas bases de datos y obtener los resultados relevantes, simplificando al mismo tiempo la formulación de consultas complejas. En otras ramas del área de bases de datos, el uso de GPUs y CPUs se ha hecho presente en trabajos como [25], donde se implementaron consultas por contenido a través de búsquedas de similitud usando una GPU, es decir consultas donde no se buscan objetos exactamente iguales al de la consulta, si no objetos similares, lo que implica medir la disimilitud entre el objeto de consulta y cada objeto de la base de datos. En el artı́culo citado se utiliza el paradigma conocido como espacio métrico (en inglés metric space) que permite modelar problemas de búsqueda por similitud; se utiliza una estructura de datos llamada permutation index [9] adaptada para funcionar mediante la arquitectura de una GPU. En [14] se presenta un análisis de desempeño de la estructura de datos conocida como arreglo de sufijos (en inglés, suffix array) en un multiprocesador con 32 núcleos. Un arreglo de sufijos es una estructura de datos que contiene todos los sufijos de una cadena dada; normalmente se utilizan para resolver consultas complejas en aplicaciones relacionadas con bases de datos de texto. En lo que resta de la sección se describen brevemente varios trabajos relacionados con esta 50 investigación, mismos que se encuentran clasificados de acuerdo a su enfoque de generación del cubo de datos. 2.4.1. Generación secuencial de cubos de datos Calculo ascendente de cubos dispersos y de tipo iceberg (tı́tulo en inglés, BottomUp Computation of Sparse and Iceberg Data Cubes) El BUC [3] fue el primer algoritmo en abordar el problema del cubo iceberg. Es un algoritmo de tipo ascendente ya que inicia calculando las vistas menos detalladas del cubo de datos. El BUC es un método recursivo, inicia siempre por generar la vista menos detallada del cubo de datos y posteriormente se ejecuta recursivamente sobre cada atributo del cubo, produciendo tuplas para un conjunto de cuboides cada que se procede con alguno de los n atributos. Más especı́ficamente, la recursividad del BUC forma arboles de procesamiento con raı́z en cada una de las n dimensiones o atributos del cubo, por ejemplo, para un cubo de tres dimensiones, A, B y C, el orden de cálculo de los cuboides serı́a: ALL, A, AB, ABC, AC, B, BC, C. La principal estrategia de este método es la incorporación de la poda Apriori en cubos iceberg, que tiene la finalidad de reducir cálculos y consiste en descartar la agregación de particiones que no cumplen con el umbral de soporte mı́nimo del cubo iceberg. La poda es posible gracias que los cuboides son calculados de menor a mayor detalle y a la propiedad antimonotonica de algunas funciones de agregación. Considere de nuevo un cubo de datos de tres dimensiones, A, B, y C, al iniciarse la recursividad en un atributo como A por ejemplo, los datos en bruto son ordenados y particionados respecto a A, entonces el algoritmo BUC toma la primera partición y procede particionando y agregando cada vez con más detalle (respecto a AB y luego respecto a ABC), la recursividad termina al no satisfacerse el umbral de soporte mı́nimo, es aquı́ donde la propiedad antimonotonica asegura que si el umbral no fue satisfecho, toda aquella partición más detallada tampoco lo hará. Sin embargo, este método es susceptible al sesgo y alta dimensionalidad en los datos. Como se verá más adelante, el BUC ha servido como base para el diseño de varios métodos paralelos como es el caso de MCBUC presentado en esta tesis. Star-Cubing: Calculando cubos iceberg mediante integración descendente y ascendente (tı́tulo en inglés, Star-Cubing: Computing Iceberg Cubes by Top-Down and Bottom-Up Integration) Star-Cubing [49] es un método desarrollado con la idea de integrar estrategias presentes en algoritmos ascendentes y descendentes. En lo que respecta a los ascendentes, Star-Cubing utiliza el principio de poda Apriori introducido por el método BUC [3] y de los algoritmos descendentes integra la agregación simultanea sobre varias dimensiones (utiliza resultados de agregaciones 51 previas para volver a agregar y ası́ reducir cálculos). Estas estrategias son posibles gracias a que Star-Cubing utiliza una estructura de datos llamada Star tree, que además proporciona compresión sin perdida de información. Cada cuboide del cubo de datos es representado por Star-Cubing mediante un árbol star tree, donde cada nivel del star tree es una dimensión y los nodos en un nivel del árbol constan de cuatro atributos: el valor de atributo, un valor agregado, apuntadores a posibles ancestros (cuboides más detallados) y apuntadores a posibles descendientes (cuboides de menos detalle). El star tree facilita la agregación simultanea ya que la agregación se realiza a través de los niveles del árbol utilizando los valores de los nodos y permite también podar la parte de los cuboides que no satisface el umbral de soporte mı́nimo usando el principio Apriori por que conceptualmente las tuplas están almacenadas en el star tree de menor a mayor detalle. El desempeño de este método decrece en datos con alta cardinalidad (alto número de valores por dimensión) ya que la construcción del árbol star tree se vuelve cada vez más costosa. Star-Cubing no es muy similar a los métodos de esta tesis, sin embargo, es de importancia mencionarlo ya que es un método bien conocido en la literatura para generación de cubos completos y de tipo iceberg. MM-Cubing: Calculando cubos iceberg mediante la factorización del espacio de lattice (tı́tulo en inglés, MM-Cubing: Computing Iceberg Cubes by Factorizing the Lattice Space) Como se ha mencionado, un cubo de datos de D dimensiones se conceptualiza a través de una estructura de lattice con 2D nodos. Esta lattice a su vez puede visualizarse como una malla donde todo valor diferente de ALL en una de las D dimensiones converge en un mismo punto, por tanto, esta malla solo tiene 2D nodos, a esto se le conoce como “espacio de lattice”. En [43] se introduce el concepto de “Factorización del espacio de lattice” que consiste en dividir los nodos del espacio de lattice de acuerdo con la frecuencia de los distintos valores unificados ellos, dando como resultado, la creación de varios sub espacios. No hay intersección entre estos sub espacios y la suma de todos ellos da por resultado al espacio de lattice original. Con base en esta factorización, el artı́culo citado propone un método recursivo para cubos completos y de tipo iceberg llamado MM-Cubing, la idea de este método es tomar en cuenta la densidad de valores en los datos antes de proceder con la generación de las vistas del cubo, esto se realiza tomando diferentes heurı́sticas para calcular sub espacios densos (alta densidad de valores) y dispersos (baja densidad de valores). El conteo para obtener la frecuencia de valores utilizada para la factorización se realiza en el artı́culo citado a través de un algoritmo llamado Count and Sort. El estudio experimental presentado en este trabajo sugiere que MM-Cubing obtiene un alto desempeño en varios tipos de distribuciones de datos superando a BUC [3], Star cubing [49] entre otros métodos. Sin embargo, este método tiene un alto consumo de memoria ya que las 52 llamadas recursivas a MM-Cubing requieren almacenamiento extra para almacenar estructuras de datos. MM-Cubing no realiza la generación del cubo de datos de manera similar a los métodos de esta tesis, sin embargo, se encuentra entre los métodos secuenciales más rápidos conocidos en la literatura. 2.4.2. Generación de cubos de datos empleando paralelismo de clusters de PCs Cálculo de cubos iceberg mediante clusters de PCs (tı́tulo en inglés, Iceberg-cube Computation with PC Clusters) En [31], se diseñaron métodos de generación de cubos de datos completos y de tipo iceberg utilizando paralelismo de grano grueso en clusters de computadoras. La principal aportación de este trabajo es la evaluación de varios algoritmos paralelos. Más especı́ficamente, este artı́culo presenta cuatro métodos para generación de cubos de datos, RP, BPP, ASL y PT. Los métodos RP y BPP son versiones paralelas del algoritmo BUC que explotan la propiedad que tiene el algoritmo BUC de formar arboles de procesamiento independientes con raı́z en cada atributo del cubo. La diferencia básica entre los algoritmos RP y BPP está en la forma en que distribuyen los datos en bruto entre los nodos del cluster de computadoras para calcular el cubo de datos. RP requiere la duplicación de los datos para cada árbol de procesamiento con raı́z en un cierto atributo, por lo tanto requiere bastante espacio en disco, además de no ser escalable a clusters con alto número de nodos ya que solo permite dividir la generación del cubo en n tareas para un cubo de n dimensiones. En contraste, en BPP los árboles del BUC se procesan uno a uno, siendo la primera fase para calcular un árbol del BUC particionar los datos en bruto y distribuir las particiones resultantes entre los nodos del cluster. Lo anterior permite procesar en paralelo varias particiones pertenecientes a un solo árbol del BUC. Posteriormente, en el artı́culo citado se introduce al método ASL, que calcula y mantiene los cuboides del cubo de datos en una estructura de datos conocida como skip list. ASL es un algoritmo descendente ya que comienza por calcular los cuboides de mayor detalle, lo que le permite compartir operaciones de ordenamiento, por ejemplo, si un procesador ha creado una estructura skip list para generar el cuboide ABCD, este mismo procesador será encargado de calcular el cuboide ABC sin ordenamientos adicionales. La distribución de carga en de trabajo en ASL es más uniforme que en RP y BPP ya que los cuboides son agrupados y distribuidos entre los nodos del cluster simplemente de acuerdo a sus atributos, por ejemplo, los cuboides ABCD, ABC, AB y A pueden agruparse porque comparten atributos. Por ultimo, se presenta PT, otro algoritmo paralelo basado en el BUC pero que mejora la división de carga de trabajo de los algoritmos RP y BPP. PT utiliza los arboles de procesamiento generados por el BUC en una forma similar al algoritmo RP pero en 53 lugar de asignar un procesador a cada árbol, los árboles son divididos en grupos con el mismo número de cuboides para su procesamiento en los nodos del cluster. Este trabajo fue unos de los primeros en presentar la paralelización del proceso de generación del cubo de datos, los métodos que propone son variaciones del BUC adaptadas a clusters de computadoras, poco similares a los métodos SPCube y GPUgenCube propuestos en esta tesis, sin embargo, RP y PT son en cierta medida similares al MCBUC de esta tesis. Construyendo grandes cubos de datos en paralelo (tı́tulo en inglés, Building Large ROLAP Data Cubes in Parallel ) En [8] se presenta un método paralelo llamado Parallel-Shared-Nothing-Data-Cube para la generación total (2n vistas, con n igual al número de dimensiones del cubo) o parcial (subconjunto de las 2n vistas) de cubos de datos en un multiprocesador de arquitectura sin compartimiento, es decir, un cluster de computadoras donde los nodos de procesamiento constan de discos locales y no comparten acceso a memoria. El método utiliza una técnica de particionamiento que permite la escalabilidad en clusters de PCs con discos locales conectados a través de un conmutador (en inglés, switch). Considere un conjunto de datos, R, con N registros y d atributos (D1 , ..., Dd ). Sin perdida de generalidad sea |D1 | ≥ |D2 | ≥ ... ≥ |Dd |, donde |Di | es la cardinalidad para la dimensión Di , 1 ≥ i ≥ d (i.e., el número de distintos valores para la dimensión Di ). Como entrada, se asume que los datos en bruto, R, con N registros y d dimensiones, se encuentran igualmente distribuidos a través de p discos. El algoritmo construye una plan de generación para el cubo de datos que consta de d grupos de cuboides llamados “subcubos”, los cuales serán ejecutados uno a uno a través del cluster. Esta agrupación en subcubos de realiza de acuerdo a los atributos de los cuboides, por ejemplo para un cubo de cuatro dimensiones (A, B, C y D), los d = 4 subcubos y el orden de generación de los cuboides seria, subcubo 1: ABCD, ABC, AB, A, AC, ABD, ACD, AD, subcubo 2: BCD, BC, B, BD, subcubo 3: CD, C y subcubo 4: D, ALL. Empleando también la estrategia de reducción de cálculo conocida como padre mı́nimo, donde se calculan cuboides a partir de otros más detallados en lugar de utilizar los datos en bruto. Las operaciones de ordenamiento para los subcubos se realizan a través de un algoritmo de ordenamiento global que funciona a través de los nodos del cluster. ParallelShared-Nothing-Data-Cube es un método reciente de paralelización del proceso de generación del cubo de datos en clusters de computadoras, la división de la carga de tareas se asemeja ligeramente al método GPUgenCube presentado en esta tesis, ya que se realiza en base a los atributos de los cuboides, sin embargo, el flujo general del método es distinto y el grado de paralelismo del GPUgenCube es superior. 54 2.4.3. Estrategias de uso de memoria caché en estructuras de datos y generación de cubos de datos empleando memoria caché Indexado usando memoria caché para soporte a decisiones en memoria principal (tı́tulo en inglés, Cache conscious indexing for decision-support in main memory ) En [39] se propusieron mejoras a procesos de indexado y búsqueda en memoria principal para varias en estructuras de datos como indices hash, árboles de búsqueda binaria, árboles T, árboles B+, búsqueda por interpolación y búsqueda binaria en arreglos, considerando caracterı́sticas de los CPUs modernos y el comportamiento de la memoria caché. El objetivo del artı́culo citado es lograr un tiempo de búsqueda inferior al de la búsqueda binaria a través de la localidad de referencias y el uso de la memoria caché. El artı́culo citado propone una técnica de indexado llamada “Caché Sensitive Search Trees”. Está técnica almacena un estructura de directorios en un arreglo ordenado. Los nodos en este directorio tienen un tamaño que ajusta al tamaño de la caché de la máquina. El directorio es almacenado en un arreglo y sin necesidad de guardar apuntadores a nodos internos; los nodos hijos se pueden encontrar realizando operaciones aritméticas. Aunque el artı́culo citado no es sobre generación de cubos de datos es de importancia mencionarlo ya que aborda varias estrategias para utilizar la memoria caché de procesadores modernos en la aceleración de procesos de indexado en varias estructuras de datos como las que son utilizadas para almacenar tuplas en el proceso de generación de cubos de datos. Cálculo del cubo de datos usando memoria caché en un procesador moderno (tı́tulo en inglés, Cache-conscious data cube computation on a modern processor ) En [26] se propone un método de generación de cubos llamado CC-Cubing que utiliza la memoria caché para agilizar el cálculo de cubos de datos en un CPU moderno. Este método es en esencia una versión optimizada del BUC que mejora su desempeño a través del uso de la memoria caché. Al igual que el BUC, la ejecución de CC-cubing para un cubo de n dimensiones está constituida por n árboles de procesamiento independientes. El particionamiento de los datos en bruto en cada árbol de procesamiento se realiza de manera recursiva, recorriendo conceptualmente la estructura lattice del cubo en profundidad y amplitud. Posteriormente, con el fin de aprovechar la tecnologı́a de procesamiento multihilo, se introduce CC-Cubing-SMT, una versión de CC-cubing que utiliza múltiples hilos para calcular simultáneamente todos los n árboles generados por el algoritmo BUC original. El artı́culo citado presenta varios métodos poco similares a los que se proponen en esta tesis, sin embargo, se utilizan hilos y la memoria caché de un procesador moderno para acelerar la generación del cubo de datos. 55 2.4.4. Generación en paralelo de cubos de datos usando tecnologı́a multinúcleo y de GPUs Cálculo en paralelo del cubo en CPUs y GPUs modernos (tı́tulo en inglés, Parallel cube computation on modern CPUs and GPUs) De manera similar a [26], en [51] se diseñan métodos paralelos basados en el BUC [3] utilizando almacenamiento por columnas. En primera instancia se presenta un algoritmo llamado CC-BUC que mejora la utilización de las lineas de caché de la CPU y la localidad de accesos a ella. Posteriormente se introduce MC-Cubing una versión multinúcleo de CC-BUC. Este método es recursivo al igual que el BUC y procede particionando los datos en bruto de menos a más detalle, el paralelismo en este método se obtiene procesando cada partición usando un núcleo de la CPU. Por ejemplo, particionando una dimensión A, el número de particiones será igual cardinalidad de A, de manera que a cada uno de los n núcleos de procesamiento le corresponderán |A|/n particiones. De la misma manera, al procesar la siguiente dimensión, B, habrá aproximadamente |A| × |B| particiones que pueden procesarse separadamente y ası́ sucesivamente. Para concluir, en el artı́culo citado se comenta el diseña una versión de MC-Cubing para GPUs. Los métodos que propone el artı́culo citado son variaciones paralelas del BUC poco similares a los métodos propuestos en esta tesis, sin embargo, también aprovechan el paralelismo y la memoria caché de GPUs y CPUs multinúcleo. Generación en paralelo del cubo de datos usando la estructura H-tree en procesadores gráficos (tı́tulo en inglés, Parallel H-Tree Based Data Cubing on Graphics Processors) [47] presenta un método llamado ONLINE CUBING para generación de cubos de datos basado en la estructura de datos conocida como H-tree [19]. Esta estructura de datos permite calcular cubos de datos parcialmente materializados ası́ como actualización y consulta en lı́nea de cubos de datos. Se presentan los algoritmos paralelos basados en GPU para operaciones paralelas de construcción y actualización incremental de la estructura H-tree. En esté trabajo se utilizan varias primitivas paralelas diseñadas para una GPU (compresión (en inglés, compact), ordenamiento (en inglés, sort), ordenamiento segmentado (en inglés, segmented sort), recolección (en inglés, gather ), dispersión (en inglés, scatter ), etc.), mismas que son utilizadas por las operaciones de la estructura H-tree. El estudio experimental de este trabajo muestra que el desempeño de los algoritmos de GPU mejoró en comparación con sus contrapartes de CPU. De manera similar a los métodos GPUgenCube y SPCube propuestos en esta tésis ONLINE CUBING utiliza varias operaciones paralelas de GPU como subrutinas en la generación del cubo de datos, sin embargo, el método general es poco similar a GPUgenCube y SPCube. 56 2.4.5. Similaridades entre trabajos previos y los métodos propuestos En esta tesis proponen tres métodos paralelos llamados MCBUC, SPCube y GPUgenCube para generación de cubos de datos completos y de tipo iceberg. Como se verá en el siguiente capı́tulo, estos tres métodos utilizan algunas estrategias conocidas en la literatura y presentes en trabajos previos. MCBUC utiliza la poda Apriori por ser una sencilla versión paralela del BUC [3], SPCube emplea el cálculo de cuboides a partir de otros más detallados y GPUgenCube comparte operaciones de ordenamiento al calcular varios cuboides simultáneamente. Sin embargo, a diferencia de los métodos previos, exceptuando a [47], los métodos de cubos de datos presentados en esta tesis constan de un paralelismo de grano fino que se obtiene a través del uso de un conjunto de operaciones básicas paralelas. Estas operaciones paralelas permiten utilizar completamente los procesadores de la GPU o CPU multinúcleo al ejecutar subrutinas dentro del proceso de generación del cubo. Además, los núcleos de la CPU proporcionan a los métodos presentados en esta tesis un paralelismo de grano grueso que permite construir simultáneamente tuplas para un grupo de cuboides. En [47] también se utilizaron varias subrutinas paralelas durante la generación del cubo de datos utilizando la estructura de datos conocida como Htree. En contraste, los métodos de esta tesis utilizan arreglos de una dimensión, evitando costos relacionados con la construcción de estructuras de datos más complejas. La principal desventaja de los métodos presentados en esta tesis respecto a trabajos como Star-Cubing [49] y MM-cubing [43] es que no se integra simultáneamente en ellos estrategias de métodos ascendentes (poda Apriori) y descendentes (compartir operaciones de cálculo y ordenamiento al generar un grupo de cuboides). La Tabla 2.2 muestra un resumen de las estrategias usadas por los métodos descritos anteriormente. El indicador “Travesı́a por la lattice” se refiere a la manera en que el método recorre conceptualmente la lattice del cubo de datos, “Comparte cálculo” indica si el método implementa alguna estrategia para reducir la cantidad de información a agregar en base a agregaciones previas, “Poda” es un indicador de si el método puede descartar particiones en base al umbral de soporte mı́nimo de cubos iceberg, “Paralelo” indica si el método utiliza paralelismo de algún tipo, “Hardware de procesamiento” describe el tipo de hardware de procesamiento utilizado por el método, por último, la columna “Similaridades con MCBUC, SPCube y GPUgenCube” menciona el parecido de los métodos previos en comparación con los de está tesis. 57 Método Travesı́a por la lattice Comparte cálculo Poda Paralelo Hardware de procesamiento Similaridades con MCBUC, SPCube y GPUgenCube BUC Ascendente No Si No CPU MCBUC es una versión paralela del BUC Star-Cubing Descendente/ ascendente Si Si No CPU No aplica MM-Cubing No aplica Si Si No CPU No aplica RP Ascendente No Si Si Cluster MCBUC es una versión paralela del BUC similar a RP BPP Ascendente No Si Si Cluster No aplica ASL Descendente Si No Si Cluster No aplica PT Ascendente No Si Si Cluster MCBUC es una versión paralela del BUC similar a PT ParallelSharedNothingData-Cube Descendente Si No Si Cluster GPUgenCube comparte ordenamientos de manera similar a este método CC-Cubing Ascendente No Si No CPU/Caché No aplica CC-CubingSMT Ascendente No Si Si CPU multi núcleo/Caché No aplica CC-BUC Ascendente No Si No CPU/Caché No aplica MC-Cubing Ascendente No Si Si CPU multinúcleo/Caché No aplica ONLINE CUBING Descendente Si Si Si GPU/Caché SPCube y GPUgenCube usan paralelismo a nivel de operación Tabla 2.2: Métodos previos de generación de cubos de datos, estrategias empleadas. Las similaridades con los métodos MCBUC, SPCube y GPUgenCube propuestos en esta tesis se resaltan en la última columna de la derecha. 58 Capı́tulo 3 Arquitectura de la solución Este capı́tulo presenta tres métodos paralelos para construcción de cubos de datos utilizando almacenamiento en memoria lineal y un conjunto de operaciones básicas denominadas primitivas: recolección, ordenamiento, particiónLocal, particiónCuboide, reducción y reducciónSegmentada. El uso de las primitivas como subrutinas del proceso de generación del cubo de datos permite aprovechar las ventajas del el paralelismo de CPUs multinúcleo ası́ como el masivo paralelismo de la GPU. Para comenzar este capı́tulo y con el fin de dar una idea más clara acerca de las caracterı́sticas fı́sicas del equipo de cómputo y la GPU utilizados en este trabajo, se describe brevemente el entorno de hardware para el cual se diseñaron los métodos y primitivas paralelas. En la segunda parte se presenta un formato vectorial utilizado para almacenar tuplas en memoria principal. El empleo de este formato es adecuado para reservar espacio en memoria de la GPU, facilitando la transferencia de información entre memoria RAM y memoria de global de la GPU ası́ como la aplicación de las primitivas paralelas. Posteriormente se muestran detalles de diseño de las primitivas junto con algunos ejemplos de aplicación. En esta segunda parte también se describe el proceso que se lleva a cabo para ordenar tuplas que están en el formato vectorial, donde intervienen las primitivas recolección y ordenamiento. Para concluir la segunda parte, se ejemplifica la construcción de un cuboide o vista del cubo de datos, empleando hilo de CPU y la información proporcionada por las primitivas de partición. La tercera parte de este capı́tulo se presentan los métodos para generación de cubos de datos completos y de tipo iceberg: MCBUC, SPCube y GPUgenCube. Se describe la manera en que agrupan los cuboides o vistas del cubo de datos para generar tareas, flujo de ejecución y la manera en que se emplean las primitivas de la primera parte para incorporar el paralelismo dentro de las fases de construcción del cubo de datos. 59 3.1. Configuración del sistema de cómputo Para definir un panorama del entorno para el que se diseñaron las primitivas y métodos para generación de cubos de datos, es de importancia mencionar que se cuenta con una CPU de 3 núcleos, 4GB de memoria RAM y una GPU modelo Fermi [32] del fabricante NVIDIA de 336 núcleos (agrupados en 7 multiprocesadores) que cuenta con 1GB de RAM de video. Recordando la sección del capı́tulo 2 que describió al modelo de programación CUDA, la GPU utilizada en este trabajo cuenta con una jerarquı́a de memoria de tres niveles. Esta jerarquı́a se observa conceptualizada en la Figura 3.1. Se considera que la memoria compartida y caché L1 se encuentran en el mismo nivel ya que la latencia de acceso a ellas es equivalente. La memoria compartida de la GPU es usada en este trabajo como almacén temporal, esta memoria tiene un tamaño de 48KB en el modelo de GPU que se está utilizando, la memoria caché L1 tiene un tamaño de 16KB y la memoria caché L2 por su parte almacenar hasta 512KB. Figura 3.1: Representación de la jerarquı́a de memoria en una GPU Fermi [32] del fabricante NVIDIA. La memoria compartida y caché L1 se encuentran en el mismo nivel ya que la latencia de acceso a ellas es equivalente. 60 Cada hilo lanzado dentro de la GPU tiene acceso a la memoria global del dispositivo. Sin embargo, como se mencionó, en CUDA, los hilos son agrupados en bloques y al invocar una función kernel, cada bloque de hilos tiene acceso a una sección de la memoria de niveles superiores. La ejecución de una función kernel para la GPU se configura normalmente a través de la especificación de una tupla de parámetros, indicando el número de bloques (malla de bloques), número de hilos por bloque, configuración de memoria caché, entre otros parámetros, vease la Tabla 3.1. threadIdx.x Variable de identificación de un hilo dentro de un bloque de hilos blockIdx.x Variable de identificación de un bloque de hilos dentro de una malla blockDim.x,y,z Es una estructura que permite configurar el número de hilos en un bloque de hasta 3 dimensiones gridDim.x,y,z Es una estructura que permite configurar el número bloques de hilos en una malla de hasta 3 dimensiones Tabla 3.1: Variables comúnmente usadas como configuración para una función kernel de CUDA Para el código de GPU realizado en este trabajo se utilizaron bloques de 256 hilos organizados en una sola dimensión (blockDim.x), este número es potencia de 2 para mejorar el uso del ancho de banda a memoria [34], además de haberse determinado empı́ricamente que proporciona un buen desempeño. El número de bloques de hilos es igual al número de multiprocesadores con que cuenta el modelo de GPU, en este caso 7, ya que aunque los hilos se ejecutan a través de diferentes núcleos de procesamiento dentro de un mismo multiprocesador, es a nivel de bloque (multiprocesador) en donde la comunicación es más rápida. Los datos que darán origen a los cubos se encuentran almacenados en el disco duro en formato tabular y se leen del disco para depositarlos en la memoria principal (RAM) de la CPU. A partir de este momento los datos pueden ser manipulados mediante las subrutinas de los métodos de generación de cubos de datos. 61 3.2. Primitivas paralelas En esta sección comienza por describir el formato de almacenamiento en memoria lineal utilizado para manejar tuplas en memoria RAM y en memoria de GPU. Posteriormente se describe un conjunto de primitivas paralelas utilizadas como subrutinas dentro de los métodos de generación de cubos de datos que se describirán posteriormente. Ası́ mismo, esta sección muestra la manera en que se ordenan tuplas que se encuentra en el formato aquı́ descrito y la manera en que se construye un cuboide cuando se ejecuta una función de agregación en la GPU. De manera breve, las primitivas son las siguientes: Recolección: Re-acomoda los elementos de un arreglo de acuerdo a una colección de ı́ndices. Ordenamiento: Realiza un ordenamiento ascendente de pares (clave, valor). El ordenamiento se realiza sobre el valor del componente llamado clave. ParticiónLocal y particiónCuboide: Permiten delimitar grupos de tuplas con valores en común para facilitar su agregación. Reducción y reducciónSegmentada: Permiten resolver funciones de agregación como SUM, MAX y MIN de manera eficiente. Las primitivas funcionan a través de una GPU, proporcionando un paralelismo de grano fino mediante sus numerosos procesadores o en el caso de las primitivas de partición, mediante paralelismo de CPU usando hilos POSIX. 62 3.2.1. Formato de almacenamiento en memoria lineal La memoria de la GPU normalmente se reserva de manera lineal, es decir, en arreglos de una dimensión. Por esta razón y con el fin de maximizar el uso del ancho de banda al acceder a regiones de memoria RAM y transferirlas a la memoria global de la GPU, las tuplas que darán origen a las vistas o cuboides del cubo de datos son recuperadas de disco en formato tabular (véase la Tabla 3.2) y transformadas a un formato secuencial para ser almacenadas en memoria RAM mediante un vector (véase la Tabla 3.3). Este método de almacenamiento en memoria lineal es conocido en la literatura como column-major order. En otras palabras, los valores correspondientes a una columna de una tabla son situados en una sección de un vector, como se observa en la Tabla 3.3. El almacenamiento en memoria lineal facilita también la aplicación de las primitivas paralelas durante el proceso de construcción del cubo de datos. A1 A2 ... a11 a12 : a1m a21 a22 : a2m ... an1 ... an2 ... : ... anm An Tabla 3.2: Tuplas en formato tabular. A1 A2 a11 , a12 , ..., a1m a21 , a22 , ..., a2m ... An ... an1 , an2 , ..., anm Tabla 3.3: Tuplas de la Tabla 3.2 en formato de vector. Este formato permite también transferir de la memoria RAM a la memoria global de la GPU solo aquellas secciones de los datos que se requieren durante cierta etapa del proceso de generación del cubo de datos. Por ejemplo, al aplicar una función de agregación como SUM que se realiza mediante la GPU, solo es necesario transferir de memoria RAM a la memoria global de la GPU la sección correspondiente al atributo de medida de los datos. En las siguientes secciones se describe este proceso con más detalle. 63 La Figura 3.2 muestra una representación del proceso de conversión de una tabla con cuatro tuplas al formato de vector. Los valores de cada columna de la relación se sitúan de manera contigua. Figura 3.2: Conversión de cuatro tuplas en formato tabular a un formato vectorial. Cada columna es escrita a una sección del vector resultante, la primera sección contiene a los elementos de la primera columna, la segunda sección a los de la segunda y ası́ sucesivamente. 64 3.2.2. Recolección Esta primitiva realiza una lectura indexada a los elementos de un arreglo, es decir, su función es acceder a los elementos de un arreglo almacenado en memoria global de la GPU de acuerdo a una colección de ı́ndices que llamaremos mapa y acomodarlos en un rango de destino. Un mapa es un arreglo de enteros que indica las posiciones de otro arreglo a ser accedidas. En otras palabras, la recolección realiza una lectura aleatoria a un vector y posteriormente realiza una escritura secuencial. La recolección es conocida en la literatura como gather [20]. Como ejemplo de la recolección, consideremos dos arreglos de cuatro elementos: (3, 1, 2, 0) y (40, 20, 30, 10), y un arreglo de salida también de tamaño cuatro. Al arreglo (3, 1, 2, 0) le llamaremos mapa y a (40, 20, 30, 10) vector de valores. La función de la recolección es acceder el vector de valores respecto a los valores de mapa, esto es, comenzando de izquierda a derecha, el valor de la posición 0 del mapa es 3, por lo tanto se accederá a la posición 3 del vector de valores que contiene al valor 10 y se escribirá en la posición 0 del arreglo de salida; el valor de la posición 1 del mapa es 1, por lo tanto de accederá a la posición 1 del vector de valores que contiene al valor 20 y se escribirá en la posición 1 del arreglo de salida; el valor de la posición 2 del mapa es 2, por lo tanto se accederá a la posición 2 del vector de valores que contiene al valor 30 y se escribirá en la posición 2 del arreglo de salida; el valor de la posición 3 del mapa es 0, por lo tanto se accederá a la posición 0 del vector de valores que contiene al valor 40 y se escribirá en la posición 3 del arreglo de salida. El arreglo de salida queda (10, 20, 30, 40) y serı́a entonces una versión re acomodada del vector de valores. En este trabajo las primitivas recolección y ordenamiento se utilizan como subrutinas para ordenar tuplas que están almacenadas en el formato lineal que se describió anteriormente, este proceso de ordenamiento se explicará con detalle en una sección posterior. Primitiva: recolección(Re , mapa) Entrada: mapa[1, ..., n]: Indices para lectura, Re [1, ..., n]: Vector de valores a accederse mediante mapa, Rs [1, ..., n]: Vector de valores de Re accedidos respecto al orden de los ı́ndices en mapa Rs [i] = Re [mapa[i]], i = 1, ..., n Salida: Función: Tabla 3.4: Prototipo de la primitiva recolección. La primitiva recibe dos arreglos de enteros, Re y mapa, de tamaño n como entrada, su función es acceder las posiciones de Re de acuerdo con los valores en mapa, i.e., Re [mapa[i]] donde i va de 1 a n. 65 La implementación paralela de esta primitiva se realizó utilizando bloques de hilos de GPU. Cada bloque de hilos accede una región del vector a recolectar cuyo tamaño es equivalente a su número de hilos y utiliza la memoria compartida de la GPU para almacenar temporalmente los elementos accedidos. Cada hilo de un bloque accede a una posición del vector de acuerdo al indice del mapa que le corresponde. La Figura 3.3 representa la ejecución de la primitiva recolección sobre un vector de ocho elementos a través de dos bloques de hilos. Como puede observarse, el vector de entrada es indexado de acuerdo a los valores del mapa, el resultado es un nuevo vector re acomodado de acuerdo a estos indices. El mapa y los valores de entrada están almacenados fı́sicamente en la memoria global de la GPU mediante dos arreglos, los bloques de hilos de la GPU leen los valores de ambos arreglos para escribir la salida a un tercer arreglo que también reside en la memoria global de la GPU. Figura 3.3: Representación la primitiva recolección sobre un vector de ocho elementos. El valor de la posición 0 del mapa (es el 1, de izquierda a derecha) indica que se accederá la posición 1 del vector de valores que este caso contiene el valor 21 el cual irá en la posición 0 de la salida; el valor de la posición 1 del mapa (es el 2) indica el acceso al valor 9 que irá en la posición 1 de la salida; el valor de la posición 2 del mapa (es el 0) indica el acceso al valor 10 que irá en la posición 2 de la salida; el proceso continua hasta acceder los 8 elementos de la entrada. 66 3.2.3. Ordenamiento El ordenamiento de datos es una operación que ha sido bien estudiada en la teorı́a de algoritmos [24] y es de suma importancia en el ámbito de construcción de cubos de datos ya que se requiere agrupar tuplas relacionadas. En el cálculo de cubos la agregación se realiza sobre tuplas (o celdas) que comparten el mismo conjunto de valores de dimensión. Por tanto es importante ordenar y agrupar las tuplas para facilitar el cálculo de los agregados y debe realizarse de forma eficiente ya que algunos algoritmos emplean gran parte del tiempo en este tipo de operaciones. La función de la primitiva de ordenamiento es simplemente ordenar de manera ascendente un conjunto de pares de valores. Es decir, esta primitiva toma como entrada a dos arreglos de una dimensión que residen en memoria global de la GPU, el primero contiene un conjunto de valores ordenar y el segundo contiene un conjunto de valores que serán utilizados como referencia para ordenar a los valores del primero. Llamaremos claves a los valores del arreglo que será usado como referencia. Por ejemplo, considere como entrada al arreglo de valores: (3, 2, 6, 10, 15, 12) y a un arreglo de referencia (claves): (1, 0, 3, 2, 5, 4), el resultado del ordenamiento por clave de estos dos arreglos es: (2, 3, 10, 6, 12, 15) y (0, 1, 2, 3, 4, 5). En este trabajo, las primitivas de ordenamiento y recolección tienen el propósito de funcionar como subrutinas en el proceso de ordenar tuplas almacenadas en el formato vectorial descrito en la Figura 3.3. Este proceso se describirá en una sección posterior. El algoritmo de ordenamiento paralelo empleado en esta tesis funciona mediante la GPU y fue implementado de manera similar a que se presenta en [41]. Este algoritmo de ordenamiento está basado en el bien conocido radix sort. El radix sort es uno de los algoritmos de ordenamiento más antiguos y mejor conocidos, en máquinas secuenciales es con frecuencia también uno de los más eficientes cuando se ordena claves pequeñas. El algoritmo asume que las claves son números de d dı́gitos y ordena sobre un dı́gito de las claves a la vez, de menos a más significativo. Para un tamaño fijo de clave d la complejidad de ordenar n registros de entrada será O(n) [24]. Primitiva: ordenamiento(Re , claves) Entrada: Re [1, ..., n]: Vector de valores a ordenar, claves[1, ..., n]: claves de ordenamiento Re [1, ..., n]: Relación ordenada respecto a claves Radix sort paralelo basado en histogramas Salida: Función: Tabla 3.5: Prototipo de la primitiva ordenamiento. Re y claves son arreglos de una dimensión. Los valores en claves son utilizados como referencia para ordenar los valores de Re . 67 La Figura 3.4 muestra una representación del ordenamiento por clave del arreglo de enteros antes mencionado. Figura 3.4: Ejemplo de ordenamiento por clave de un vector de seis elementos. El vector de claves sirve como referencia para ordenar un vector de valores. Antes de pasar a la paralelización del radix sort conviene explicar como funciona en máquinas secuenciales. Como ejemplo consideremos la siguiente lista de enteros: (170, 45, 75, 90, 802, 2, 24, 66). Dividiendo cada elemento de esta lista de números en dı́gitos decimales se tiene que los números más grandes de la lista a ordenar constan de tres dı́gitos (170, 802), entonces, el radix sort requiere aplicar tres pasadas para ordenar la lista completa. Como la base de los dı́gitos es 10 se requieren a lo más 10 almacenes temporales o buckets para ordenar los dı́gitos. 1. Comenzando por ordenar el dı́gito menos significativo se tiene: 170, 090, 802, 002, 024, 045, 075, 066 bucket 0: 170, 090; bucket 2: 802, 002; bucket 4: 024; bucket 5: 045, 075; bucket 6: 066 2. Ordenando el dı́gito siguiente se obtiene: 802, 002, 024, 045, 066, 170, 075, 090 bucket 0: 802, 002; bucket 2: 024; bucket 4: 045; bucket 6: 066; bucket 7: 170, 075; bucket 9: 090 3. Ordenando por el dı́gito más significativo: 002, 024, 045, 066, 075, 090, 170, 802 bucket 0: 002, 024, 045, 066, 075, 090; bucket 1: 170; bucket 8: 802 Nótese que cada uno de los pasos anteriores requiere solo una pasada sobre los datos, ya que cada elemento se coloca en la posición correcta de un bucket sin tener que comparar con otros elementos. 68 El algoritmo de ordenamiento utilizado dentro de las d pasadas del radix sort es normalmente un ordenamiento por cuentas (en inglés, counting sort) [42], que como se mostró en las pasadas del ejemplo anterior, trabaja sobre un dı́gito de las claves a ordenar. Cada dı́gito base 2b es una cadena de b bits dentro de la clave. Para ordenar un dı́gito dado de cada clave en una cierta pasada del radix sort, se calcula el número de claves cuyos dı́gitos son más pequeños más el número de claves que ocurrieron previamente y que tuvieron el mismo dı́gito en la secuencia. Este será el indice de salida en el que el elemento será escrito, al cual se le referirá como el rango (en inglés, rank ) del elemento. Por ejemplo, consideremos cuatro dı́gitos decimales de clave: (0, 2, 3, 3), el primer dı́gito (0) de izquierda a derecha irá en la posición 0 de la salida por que no hubo ocurrencias de dı́gitos de clave iguales o más pequeños en la secuencia a ordenar, el segundo dı́gito (2) va en la posición 1 de la salida por que antes de el hubo un dı́gito más pequeño (0), el tercer dı́gito (3) va en la posición 2 de la salida ya que antes de el hubo dos ocurrencias de claves más pequeñas (0, 2), el cuarto dı́gito a ordenar (2) va en la posición 3 de la salida ya que antes de el hubo dos ocurrencias de dı́gitos más pequeños (0, 2) y una de un dı́gito igual (3). Habiendo calculado el rango de cada elemento, se completa la pasada dispersando los elementos arreglo de salida. El ordenamiento de cada dı́gito de menos a más significativo garantiza que el radix sort dejará la secuencia de claves correctamente ordenada después de completar las d pasadas. Más en concreto, el Algoritmo 2 muestra el pseudocódigo con los detalles acerca de la ejecución del ordenamiento por cuentas dentro de cada pasada del radix sort, tomando un dı́gito (base 10) de cada clave. Las lı́neas 1-4 muestran el cálculo de un histograma que registra las ocurrencias de elemento iguales en la secuencia a ordenar. Las lı́neas 6-10 muestran el ciclo donde se calcula el ı́ndice de salida correcto para cada elemento a ordenar usando el histograma de las lı́neas 1-4. A la operación efectuada en las lı́neas 6-10 de este pseudocódigo se le conoce comúnmente con el nombre de suma de prefijos o suma acumulativa. 69 Algoritmo 2 ordenamiento por cuentas Entrada: Dı́gitos a ordenar Salida: Dı́gitos en orden ascendente 1: Reservar un arreglo conteo[k] y asignar cero a cada posición . k es la base de los dı́gitos 2: Para cada dı́gito x hacer . Calcular histograma 3: Incrementar conteo[x] 4: Fin Para 5: total ← 0 6: Para i ← 0; i < k; i + + hacer . Suma de prefijos: calcula el indice inicial para cada dı́gito 7: cuentaAnterior ← conteo[i] 8: conteo[i] ← total 9: total ← total + cuentaAnterior 10: Fin Para 11: Reservar un arreglo salida[n] . n es el número de dı́gitos a ordenar 12: Para cada dı́gito x hacer . Copia los dı́gitos en orden a un arreglo de salida 13: salida[conteo[x]] ← x 14: Incrementar conteo[x] 15: Fin Para Pasando a la implementación paralela del ordenamiento, varios estudios sugieren que el radix sort está entre los algoritmos de ordenamiento más sencillos para implementar en paralelo y es tan eficiente como algunos algoritmos más sofisticados como el sample sort [5, 11]. En esencia, la paralelización del radix sort mediante la GPU consiste en dividir la secuencia de entrada para cada pasada del radix sort en secciones que son repartidas entre un grupo de bloques hilos de GPU. A cada una de estas secciones de la entrada le llamaremos mosaico. En cada pasada del radix sort paralelo, los elementos de cada mosaico son ordenados localmente mediante un bloque de hilos de la GPU, cada bloque de hilos consta de un histograma local que permite ordenar localmente a los elementos del mosaico. Posteriormente una operación de sumas de prefijos a través de los histogramas de los todos los bloques de hilos permite determinar las posiciones globales que los dı́gitos deben ocupar en el arreglo de salida. Un ejemplo se presenta a continuación. 70 Como ejemplo del radix sort paralelo, considere de nuevo la lista de enteros (170, 45, 75, 90, 802, 2, 24, 66) mencionada anteriormente y dos bloques de hilos. A cada uno de estos bloques le será asignada una sección de la lista a ordenar. Utilizando por claridad del ejemplo dı́gitos decimales, la primera pasada del radix sort paralelo quedarı́a como se observa en la Figura 3.5. Cada bloque de hilos calcula un histograma de las ocurrencias del dı́gito considerado actualmente en su respectiva sección de la entrada. En esta primera pasada se evalúa el primer dı́gito de las claves. El histograma del bloque 0 registra la ocurrencia de dos dı́gitos 0 (por 179 y 90) y dos dı́gitos 5 (por 45 y 75). El histograma del bloque 1 registra la ocurrencia de dos dı́gitos 2 (por 2 y 24), de un dı́gito 4 (por 24) y de un dı́gito 6 (por 66). Los histogramas anteriores permiten ordenar localmente a los elementos de la sección de la entrada que le corresponde al bloque de hilos. Para determinar las posiciones globales que los elementos de los mosaicos deben ocupar en el arreglo de salida se realiza una operación de suma de prefijos a través de todos los histogramas. Esta suma de prefijos es una suma acumulativa de los conteos de cada dı́gito de los histogramas. Por ejemplo, en la Figura 3.5 el resultado de la suma de prefijos es 0, 2, 4, 5 y 7 ya que se registraron dos dı́gitos 0 (2), dos dı́gito 2 (4), un dı́gito 4 (5) y dos dı́gitos 5 (7). Los resultados de la suma de prefijos se observan en las Figuras 3.5, 3.6 y 3.7 apuntando con flechas a la posición correspondiente del arreglo de salida. Figura 3.5: Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 1 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al primer dı́gito decimal. La Figura 3.6 muestra dos bloques de hilos ordenando la salida producida en la Figura 3.5. La segunda pasada del radix sort procede con el siguiente dı́gito de los números a ordenar, en este ejemplo la segunda pasada procede con las decenas. 71 Cada bloque de hilos re-calcula su propio histograma con el nuevo orden de los elementos que produjo la primera pasada (Figura 3.5). Figura 3.6: Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 2 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al segundo dı́gito decimal. Para concluir con el ordenamiento de la lista de números, la Figura 3.7 muestra dos bloques de hilos ordenando la salida producida en la Figura 3.6. La tercera pasada re-acomoda los elementos de acuerdo al dı́gito más significativo de las claves, en este caso las centenas y deja correctamente ordenada la secuencia de entrada. Figura 3.7: Ejemplo radix sort paralelo para ordenar una lista de ocho números: pasada 3 de 3. La ilustración muestra el ordenamiento de la lista de números respecto al tercer dı́gito decimal. 72 En el ejemplo anterior del radix sort, los números de la lista a ordenar fueron divididos en dı́gitos decimales, pero en la implementación los números son manipulados en su representación binaria, evaluando grupos de b bits a la vez. Una manera sencilla de paralelizar las pasadas del radix sort es ordenando las claves usando 1 bit a la vez, esto se realizó en [4] bajo el nombre de “operación de división” (en inglés, split operation), sin embargo, esto no es particularmente eficiente ya que para claves de 32 bits habrı́a 32 pasadas del radix sort para re-ordenar la secuencia completa. Una manera lógica de solucionar esto es considerar más de b = 1 bits por pasada. La idea de esta implementación es hacer uso eficiente del ancho de banda de la memoria minimizando el número de escrituras dispersas a memoria global de la GPU y maximizando la coherencia de las dispersiones. La división de los datos en bloques y un tamaño de dı́gito b > 1 logra aumentar el uso del ancho de banda de acceso a memoria global de la GPU. La minimización de escrituras dispersas a memoria global de la GPU se logra utilizando la memoria compartida de la GPU para ordenar los bloques de datos respecto al dı́gito base 2b actual. Esta estrategia convierte las escrituras dispersas a memoria externa en escrituras dispersas en memoria compartida. Cada pasada del radix sort se implementó en cuatro fases. A falta de un mecanismo de sincronización entre bloques de hilos en la plataforma CUDA [33], cada una de las siguientes fases corresponde a la invocación de una función kernel distinta: 1. Cada bloque carga y ordena su mosaico en memoria caché usando b iteraciones, es decir, una iteración por cada división de 1 bit. 2. Cada bloque escribe su histograma de 2b entradas de dı́gitos y el mosaico ordenado a memoria global. 3. Se realiza una suma de prefijos sobre la tabla de p×2b histogramas, almacenada fı́sicamente en formato por columnas (column-major order ), a fin de calcular los desplazamientos globales de dı́gitos [11, 50]. Véase la Figura 3.8. 4. Usando los resultados de la suma de prefijos, cada bloque de hilos copia sus elementos a la posición de salida correcta (en preparación para la siguiente pasada si es que la hay). Entonces, con p bloques de hilos y b bits, se tendrán p buckets con 2b entradas, donde cada hilo de GPU obtendrá el rango de un conjunto de elementos en cada pasada del radix sort. 73 Figura 3.8: Conceptualización de los histogramas del radix sort paralelo. Cada bloque de hilos de GPU consta de un histograma para ordenar una sección de la secuencia de entrada. En cada pasada del radix sort se realiza una operación de suma de prefijos global a través de todos los histogramas para determinar la posición global de los elementos ordenados por cada bloque de hilos en el arreglo de salida. Se determinó empı́ricamente la utilización de un tamaño b = 4 para la implementación de las pasadas, ya que utilizar un número más pequeño implica más pasadas y un número mayor reduce la coherencia de los accesos a memoria. 74 3.2.4. Proceso de ordenamiento de tuplas En este trabajo, el proceso de ordenamiento de una relación en formato de vector se realiza mediante la ejecución de las primitivas paralelas de recolección y ordenamiento. Con este método, los valores de los atributos o dimensiones de una relación no se ordenan directamente, en su lugar, para ordenar la relación se construye un vector de enteros cuyos valores permiten determinar las posiciones que los valores de cada atributo las tuplas tomarı́an al estar ordenados respecto a una cierta combinación de atributos. La primitiva de recolección utiliza este vector como mapa para dar el orden deseado a las tuplas que se encuentran almacenadas en formato de vector. Por ejemplo, para ordenar las tuplas de una relación con tres atributos, A, B y C, respecto a la combinación ABC se necesita primeramente construir un vector de enteros que será utilizado como mapa por la primitiva de recolección para determinar las nuevas posiciones de los valores de A, B y C. La recolección de los valores de A, B y C usando este vector como mapa, dejará las tuplas ordenadas respecto a la combinación ABC. Ahora bien, el tamaño de este vector o mapa en el contexto de la recolección, es el número de tuplas a ordenar e inicialmente es una secuencia de enteros que va desde cero hasta el número de tuplas menos uno. Para obtener la versión final de este mapa con el que los valores de cada atributo serán re-acomodados, habrá que realizar un proceso donde se aplica la recolección a los valores de cada atributo de los datos usando este vector como mapa y luego se ordena al mismo mapa usando como clave a los valores que han sido previamente recolectados. Este proceso de recolección-ordenamiento se realiza comenzando por el atributo menos significativo respecto al que se desea ordenar las tuplas. Por ejemplo, retomando el ejemplo anterior de ordenar un conjunto de tuplas respecto a la combinación de atributos ABC, para construir el mapa que dará el ordenamiento final a las tuplas primero habrá que recolectar el atributo C usando al mapa y luego ordenar al mapa usando los valores de la nueva versión de C como claves, después se procede a realizar la recolección B usando al mapa y se ordena de nuevo al mapa usando como clave a la nueva versión del atributo B, por ultimo se realiza la recolección del atributo A usando al mapa y se ordena al mapa usando como clave a la nueva versión del atributo A. Este proceso de recolección-ordenamiento no cambia de lugar a los valores de los atributos del vector de tuplas, ya que se utiliza un vector temporal para almacenar los resultados de la recolección de cada atributo. Cabe remarcar que siempre es necesario realizar este proceso de recolección-ordenamiento para todo atributo de los datos aunque solo se requiera ordenar respecto a algunos, por ejemplo, al ordenar tuplas respecto a un solo atributo de una relación con 3 atributos habrá que registrar en el mapa también los valores de los otros dos no importando el orden en que se ejecuten. 75 Para concluir con el ordenamiento de tuplas, este vector o mapa es aplicado a cada uno de los segmentos del vector de tuplas que contiene los valores de cada atributo, obteniendo el nuevo orden de las tuplas. Este método para ordenamiento de tuplas se basa en el orden lexicográfico, el cual es una generalización de la forma en que el orden alfabético de las palabras se basa en el orden alfabético de las letras que lo componen. Dados dos conjuntos parcialmente ordenados A y B, el orden lexicográfico sobre el producto cartesiano de A × B se define como (a, b) ≤ (a0 , b0 ) si y solo si a < a0 o (a = a0 ) y (b ≤ b0 ). El resultado es un orden parcial. Si A y B están totalmente ordenados entonces el resultado también es un orden total. De manera más general, se puede definir el orden lexicográfico sobre el producto cartesiano de n conjuntos ordenados, sobre el producto cartesiano de una familia infinita contable de conjuntos ordenados y sobre la unión de dichos conjuntos. Un ejemplo detallado aparece a continuación. Antes de comenzar con el ejemplo, observe que la la parte superior de la Figura 3.9 muestra un vector con 4 tuplas: (2, 2, 1), (1, 1, 1), (3, 1, 2) y (5, 1, 1) de tres dimensiones: A, B y C. La parte inferior de la Figura 3.9 muestra el mismo vector ordenado respecto a la combinación de atributos ABC, es decir, los valores del atributo A varı́an lentamente, los valores de B varı́an un poco más rápido y los valores de C varı́an más rapidamente. Figura 3.9: Ordenamiento de cuatro tuplas respecto a la combinación de atributos ABC. Las tuplas a ordenar son (2, 2, 1), (1, 1, 1), (3, 1, 2) y (5, 1, 1). Esto se muestra en el vector superior. El resultado de ordenar las tuplas a la combinación ABC es: (1, 1, 1), (2, 2, 1), (3, 1, 2) y (5, 1, 1). Esto se muestra en el vector inferior. Como ejemplo del ordenamientos de tuplas, considere el vector de la Figura 3.9; el proceso de construcción del mapa para ordenar las tuplas de esta ilustración se muestra en la Figura 3.10. Como se ha mencionado, inicialmente el mapa es una secuencia incremental de enteros cuyo tamaño es el número de tuplas a ordenar, en este caso es (0, 1, 2, 3). 76 Los valores del mapa se ordenan usando a los valores en cada una de los atributos o dimensiones como claves, este proceso registra mediante el mapa las permutaciones en los valores de cada atributo de los datos. Se comienza con la dimensión menos significativa en la combinación a tomarse en cuenta, en este caso es C. Al terminar con una dimensión, el mapa es utilizado para recolectar los elementos de la siguiente dimensión, en este ejemplo es B y se procede nuevamente a ordenar usando el siguiente conjunto de claves (valores de B). Este proceso se repite hasta terminar con todas las dimensiones de los datos, en este caso solo queda A. Figura 3.10: Generación de un mapa para ordenar las tuplas de la Figura 3.9 respecto a la combinación ABC. El mapa es inicialmente una secuencia ascendente de enteros cuyos valores van permutando debido a una serie de ordenamientos por clave. Las claves son los valores de un cierto atributo de los datos en bruto y se inicia por ordenar respecto al atributo menos significativo, en este caso C. La versión final del mapa permite usar a la recolección para re-acomodar los valores de cada sección del vector de tuplas (datos en bruto) correspondiente a un atributo, dejando las tuplas ordenadas respecto a la combinación que se consideró, en este caso ABC. Véase la Figura 3.11. 77 Para terminar con el ordenamiento de las tuplas, la fase final es aplicar la primitiva de recolección a cada una de las dimensiones de los datos usando el mapa que se construyó registrando las permutaciones en los valores de cada atributo o dimensión de los datos. La Figura 3.11 muestra la ejecución de esta ultima fase sobre las tuplas de la Figura 3.9 usando la versión final del mapa mostrado en la Figura 3.10. Figura 3.11: Fase final del ordenamiento de las tuplas en la Figura 3.9. Se usa el mapa de la Figura 3.10 para aplicar la recolección a cada sección del vector de tuplas que corresponde a un atributo de los datos. Esta última fase deja a las tuplas de la Figura 3.9 ordenadas respecto a la combinación ABC. Los detalles generales del proceso de ordenamiento de tuplas se dan en el Algoritmo 3. La función actualizarPermutación() permuta los valores del vector que será usado como mapa en la fase final del ordenamiento de tuplas, esta función no modifica los valores del vector que contiene las tuplas, ya que se usa un vector auxiliar para almacenar temporalmente los valores de un cierto atributo. Los marcadores inicio y f in son utilizados para delimitar una sección correspondiente a cierto atributo en el vector de tuplas. La fase final es realizada por la función aplicarPermutación(), aplicando la primitiva de recolección a las secciones del vector de tuplas usando como mapa al vector construido por actualizarPermutación(). 78 Algoritmo 3 ordenarTuplas Entrada: global ntuplas: Número de tuplas a ordenar global ndimensiones: Número de dimensiones de los datos datos[ntuplas ∗ ndimensiones]: Vector con las tuplas a ordenar orden[ndimensiones]: Numeración de los atributos de la relación a ordenar, de más a menos significativo (la posición en el arreglo lo indica) permutación[ntuplas]: Arreglo para registrar permutaciones y re-acomodar valores mediante la primitiva de recolección. Inicialmente es una secuencia de enteros (0, 1, ..., ntuplas − 1) Salida: Vector de tuplas ordenadas respecto a una combinación de atributos. 1: Función actualizarPermutación(datos, inicio, f in, permutación) 2: temporal[ntuplas] . Almacén temporal 3: temporal ← recolección(datos[inicio, ..., f in], permutación) 4: ordenamiento(permutación, temp) 5: Fin Función 6: Función aplicarPermutación(datos, inicio, f in, permutación) 7: temporal[ntuplas] . Almacén temporal 8: temporal ← datos[inicio, ..., f in] 9: datos[inicio, ..., f in] ← recolección(temporal, permutación) 10: Fin Función 11: Para i ← ndimensiones − 1; i ≥ 0; i − − hacer . Recolección y ordenamiento a partir del atributo menos significativo 12: inicio ← orden[i] ∗ ntuplas . Inicio de la sección de valores de un atributo 13: f in ← ntuplas + (orden[i] ∗ ntuplas) . Final de la sección de valores de un atributo 14: actualizarPermutación(datos, inicio, f in, permutación) 15: Fin Para 16: Para i ← ndimensiones − 1; i ≥ 0; i − − hacer . Obtiene el orden final de las tuplas 17: inicio ← orden[i] ∗ ntuplas 18: f in ← ntuplas + (orden[i] ∗ ntuplas) 19: aplicarPermutación(datos, inicio, f in, permutación) 20: Fin Para 79 3.2.5. ParticiónLocal y particiónCuboide En los métodos de cubos de datos que se presentarán más adelante, la partición es una etapa posterior al ordenamiento de tuplas y que va seguida de la agregación cuando se construye un cuboide o vista del cubo. Es un proceso que determina los valores que formarán las tuplas de un cierto cuboide y delimita los grupos de datos que producirán los respectivos valores agregados. En la Figura 3.12 se observa una tabla con cinco tuplas siendo particionada respecto a la combinación de atributos ABC, si adicionalmente se aplicara la función de agregación COUNT(*) sobre los grupos de esta relación, los resultados serı́an 1, 1, 1 y 2 respectivamente (iniciando de arriba hacia abajo). Figura 3.12: Particionamiento de una tabla con cinco tuplas respecto a la combinación de atributos ABC. La partición como normalmente se lleva a cabo, es un proceso secuencial, donde se toma un conjunto de tuplas ordenadas respecto a una combinación de atributos y se procede a revisar los valores de los atributos para delimitar segmentos a agregarse. El modelo de partición aquı́ utilizado se diseño mediante dos fases, esto con el fin de reutilizar los marcadores producidos por la primera fase de la partición (particiónLocal) para particionar varios cuboides que comparten los mismos atributos y ası́ reducir el número de pasadas a los datos en bruto. Por ejemplo, si particionamos respecto a la combinación de atributos ABC, los mismos marcadores servirán para particionar respecto a las combinaciones AB y A. 80 Recordando del formato de datos aquı́ utilizado que los datos de la relación base se encuentran almacenados en memoria RAM mediante un vector, es claro que los valores correspondientes a un atributo de la relación base corresponden a una sección de dicho vector. La primera fase de la partición se encarga de asignar un hilo de CPU (no de GPU, abajo se explicará porqué) a cada sección para ser particionada. Más especı́ficamente, cada hilo recorre una sección del vector de tuplas y obtiene un conjunto de marcadores (marcadores relativos a la sección del vector de tuplas) que registran cambios en los valores. De manera que las dimensiones de la relación base son particionadas en paralelo, tal es la función que realiza la primitiva particiónLocal. Primitiva: particiónLocal(Re , combinación, Rs ) Entrada: Salida: Función: Re : Vector de tuplas a particionar, combinación: Atributos a considerarse Rs : Vector de marcadores de segmentos por cada atributo en combinación Particionamiento paralelo de los atributos en combinación Tabla 3.6: Prototipo de la primitiva particiónLocal. Su función es recorrer en Re las secciones correspondientes a los valores de cada atributo de la combinación respecto a la cual se desea particiónar, obteniendo un conjunto de marcadores por cada atributo. Los marcadores son las posiciones de Re donde se registró un cambio de valor. Una vez que la primitiva particiónLocal obtiene los marcadores de segmentos por cada dimensión considerada para la construcción de un cuboide cuboide o vista del cubo, la segunda fase realiza una unión con dichos marcadores (eliminando repetidos), i.e., {SD1 ∪SD2 ∪...∪SDm } donde SDi es un conjunto de marcadores para una dimensión y m es el número de dimensiones del cuboide, este es el trabajo de la primitiva particiónCuboide. El resultado de las dos fases obtiene un conjunto de marcadores que servirá para determinar los grupos de datos a ser agregados a partir de la relación base y construir las tuplas en el cuboide correspondiente. Primitiva: particiónCuboide(Re , combinación, Rs ) Entrada: combinación: Atributos a considerarse, Re : Vector de marcadores de segmentos por cada atributo en combinación Rs : Vector de marcadores de segmentos tomando en cuenta todos los atributos en combinación Unión de marcadores de segmentos de los atributos en combinación Salida: Función: Tabla 3.7: Prototipo de la primitiva particiónCuboide. La función de esta primitiva es realizar una operación de unión con los conjuntos de marcadores de cada atributo de la combinación respecto a la que se va a particionar. Los marcadores se encuentran almacenados en un arreglo Re que para esta fase puede contener marcadores para otros atributo no incluidos en la combinación. El resultado es un arreglo de marcadores Rs tomando en cuenta solo a los atributos de la combinación. 81 La Figura 3.13 muestra de un esquema del proceso de partición, comenzando por la asignación de los datos en memoria. Como puede observarse, cada dimensión de los datos es particionada localmente respecto a cada uno de los atributos en la combinación a tomarse en cuenta. Por ejemplo, si se requiere particionar los datos respecto a la combinación de atributos ABC, es necesario primero particionar con respecto a A posteriormente con B y luego con C. En una segunda fase de partición, se realiza una operación de unión entre los segmentos obtenidos en la partición local para obtener los grupos respecto a la combinación deseada. Figura 3.13: Representación del proceso de partición de un grupo de tuplas en formato de vector respecto a la combinación de atributos ABC. La razón de emplear hilos de CPU (POSIX) en lugar de hilos de GPU en las primitivas de partición corresponde a que normalmente el número de dimensiones del cubo de datos no supera las decenas, además, dado que la partición de una sección del vector de tuplas es un proceso secuencial, los núcleos de la CPU son más adecuados en este tipo de tareas. 82 Como ejemplo consideremos la ilustración de la Figura 3.14, se tiene una tabla con cinco tuplas y con tres dimensiones (A, B y C) convertida a formato de vector, para construir el cuboide ABC, la primera fase de la partición se encargarı́a de obtener los marcadores correspondiente a los atributos A, B y C, es decir, tres hilos se encargarı́an de registrar cambios en los valores de las secciones correspondientes a A, B y C. Posteriormente la segunda fase realizarı́a una unión que determinarı́a los marcadores para construir el cuboide ABC. En este caso, comenzando por la posición cero, los marcadores son: para el atributo A {0, 1, 3}, para el atributo B {0, 1, 3} y para el atributo C {0, 2}. Entonces, realizando una unión entre estos conjuntos el resultado es {0, 1, 2, 3}, es decir, hay cuatro particiones en los datos respecto a la combinación de atributos ABC (0-1, 1-2, 2-3 y 3-4). Figura 3.14: Ejemplo del proceso de partición de un vector con cinco tuplas respecto a la combinación de atributos ABC. ParticiónLocal se encarga de lanzar un hilo de CPU para particionar cada atributo que se requiera (en este caso A, B y C). ParticiónCuboide realiza la unión con los marcadores producidos por particiónLocal (en este caso, marcadores de A, B y C). El resultado de particiónCuboide es un conjunto de marcadores que permite la generación de un cuboide o vista del cubo (en este ejemplo son para el cuboide ABC). La información proporcionada por el particionamiento es también suficiente para efectuar la función de agregación COUNT empleando operaciones aritméticas con los marcadores. Por ejemplo, utilizando los marcadores de la Figura 3.14, es sencillo determinar que las particiones del cuboide ABC contienen 1 − 0 = 1, 2 − 1 = 1, 3 − 2 = 1 y 5 − 3 = 2 tuplas respectivamente. 83 3.2.6. Reducción y reducciónSegmentada Durante la construcción de un cuboide o vista del cubo de datos a partir de datos de gran volumen, funciones de agregación como MAX, MIN, SUM y AVG producen altas cantidades de operaciones aritméticas y lógicas. En este trabajo se utilizaron los algoritmos conocidos como reducción (en inglés, reduction) [37] y reducciónSegmentada (en inglés, segmented reduction) [52] para producir los valores agregados correspondientes a un cuboide o vista del cubo. Como las tuplas para generar el cubo de datos se encuentran almacenadas en un arreglo de una dimensión, las primitivas reducción y reducciónSegmentada permiten agregar las secciones que corresponden a atributos de medida, esta sección explica su funcionamiento. La reducción es una clase de algoritmo paralelo que toma una entrada de datos O(N ) y genera un resultado O(1) calculado mediante un operador binario asociativo ⊕. La reducción en GPUs fue presentada por primera vez en [37]. Algunos ejemplos de operaciones que pueden resolverse mediante esta operación son: MAX, MIN, SUM, suma de cuadrados, AND, OR y el producto cartesiano de dos vectores. Como el operador binario es asociativo, las O(N) operaciones para P calcular la reducción pueden ejecutarse en cualquier orden. e.g., ai = a0 ⊕ a1 ⊕ a2 ⊕ a3 ⊕ a4 ⊕ a5 ⊕ a6 ⊕ a7 . La Figura 3.15 muestra dos opciones de procesar un arreglo de ocho elementos. Figura 3.15: Reducción de un vector de ocho elementos. La implementación serial se muestra solo para contrastar, en ese caso solo se necesita una unidad de ejecución para aplicar el operador ⊕, pero el desempeño es pobre ya que se necesitan 7 pasos para completar el cálculo. El otro modelo de la reducción requiere de O(log2 N ) pasos (tres pasos en este caso) para calcular el resultado. Con P hilos ejecutándose fı́sicamente en paralelo (P procesadores), la complejidad en tiempo es O(N/P + log2 N ). 84 La reducción de paso logarı́tmico mostrada en la Figura 3.15 muestra una estrategia de intercalado entre elementos. Esta estrategia mejora el uso del ancho de banda cuando se lee de memoria global de la GPU. Una versión de la reducción que utiliza pares es intuitiva, pero el tener un solo hilo accediendo a regiones adyacentes de memoria causa transacciones de memoria separadas. El factor de intercalado en la Figura 3.15 es cuatro. En memoria global de la GPU, el utilizar un factor de intercalado múltiplo del producto del tamaño de bloque y el tamaño de malla (blockDim.x*gridDim.x) produce un buen desempeño por que todas las transacciones de memoria se producen de manera adyacente. Primitiva: reducción(Re , ⊕, Rs ) Entrada: Re [1, ..., n]: Vector a agregar (atributo de medida), ⊕: Operador binario Rs : Valor agregado Rs = ⊕Re [i], i, ..., n Salida: Función: Tabla 3.8: Prototipo de la primitiva reducción. La función de esta primitiva es evaluar los elementos de un arreglo de entrada Re mediante un operador binario asociativo ⊕, produciendo un solo elemento Rs como salida. La implementación de la reducción en GPU fue realizada a través de un método de dos fases, empleando el nivel de memoria compartida para almacenar resultados parciales por bloque de hilos. La Figura 3.16 muestra una representación conceptual de como se realiza el proceso de reducción en la GPU. Como hemos mencionado, en la GPU los hilos se agrupan en bloques cuando se lanza una función kernel, la ilustración de la Figura 3.16 muestra un vector de 16 elementos siendo reducido por cuatro bloques de hilos en una primera fase. Los resultados intermedios de las reducciones efectuadas por cada bloque de hilos son almacenados en la memoria compartida de la GPU, en el pequeño ejemplo de la figura los dos bloques estarı́an integrados por dos hilos. Al termino de la segunda fase, los resultados de las reducciones efectuadas por los bloques de hilos son situados en un vector almacenado en memoria global. La segunda fase de la reducción toma el vector producido por la primera y usando un solo bloque de hilos obtiene el resultado final de la reducción. 85 Figura 3.16: Reducción de un vector en dos fases. En la primera fase, un conjunto de bloques de hilos reduce varias secciones de un arreglo o vector de valores, produciendo resultados parciales. La segunda fase obtiene el resultado final de la reducción usando un solo bloque de hilos sobre los resultados parciales de la primera fase. Este enfoque de dos fases de utilizó para lidiar con la imposibilidad de sincronizar bloques de hilos en CUDA. Es decir, se necesita lanzar una segunda función kernel a falta de un mecanismo de comunicación entre bloques que permita determinar cuando procesar el resultado final. Cabe mencionar que existen otras formas de implementar la reducción en GPUs. Para este trabajo, este enfoque fue adecuado. La reducciónSegmentada opera de manera similar a la reducción, con la diferencia de que en esta primitiva se tiene un arreglo particionado a través de un vector de claves, siendo su función reducir cada partición a un escalar. En [17] se realizó la reducción sobre segmentos arbitrarios del vector de entrada utilizando un vector de claves y [52] presentó por primera vez la reducciónSegmentada en un bloque de hilos de GPU. Cada hilo de GPU reduce los elementos de un arreglo con el mismo valor de clave, en una sumatoria por ejemplo, cada hilo está encargado de acumular los valores de algunos de los elementos de un arreglo, tales elementos deben contar con el mismo valor de clave para ser acumulados. Si un hilo alcanza una posición del vector a reducir con un valor de clave diferente entonces tal hilo ya ha terminado de reducir la sección del segmento que le corresponde. 86 Primitiva: reducciónSegmentada(Re , claves, ⊕, Rs ) Entrada: Re [1, ..., n]: Vector a agregar (atributo de medida), claves[1, ..., m]: Particiones en Re , ⊕: Operador binario Rs [1, .., m]: Vector de valores agregados Rs [i] = ⊕Re [j], donde claves[j] = i, i = 1, ..., m Salida: Función: Tabla 3.9: Prototipo de la primitiva reducciónSegmentada. La función de esta primitiva es evaluar varios segmentos de un arreglo de entrada Re mediante un operador binario asociativo ⊕, produciendo un elemento como salida por cada segmento, es decir, un arreglo de salida Rs . Los segmentos se encuentran delimitados por valores continuos en un segundo arreglo (claves) del mismo tamaño del arreglo a evaluar. La Figura 3.17 muestra la una conceptualización de la reducciónSegmentada sobre un vector de 16 elementos dividido en tres segmentos. Como puede observarse, las claves que delimitan los segmentos a reducir comparten valores y cada segmento se reduce a un escalar. Este modelo se implementó en este trabajo ya que facilita la producción de agregados para un cierto cuboide. Figura 3.17: ReducciónSegmentada de un vector. Los elementos del vector de la ilustración están divididos en tres segmentos y la operación realizada es una sumatoria. La reducción de cada segmento del vector produce a un escalar. En la ilustración de la Figura 3.17 los valores de clave para los tres segmentos son diferentes (0, 1 y 2), pero basta con un cambio en el valor de clave para delimitar un nuevo segmento. 87 3.2.7. Construcción de cuboides En los métodos de generación de cubos de datos SPCube y GPUgenCube que se presentarán en la siguiente sección, los cuboides son organizados en tareas, es decir, grupos de cuboides que comparten una cierta caracterı́stica. De manera que los 2n cuboides o vistas del cubo de datos quedan repartidas entre las tareas. Este agrupamiento se realiza con el fin de calcular grupos de cuboides en paralelo. Al calcular una de estas tareas, se designa un hilo de CPU para cada cuboide en ella, que producirá y escribirá en memoria secundaria las tuplas correspondientes. Como se mencionó, en el caso de que la instrucción de cubo de datos a ejecutar incluya alguna función como SUM, MAX, MIN o AVG, esta será resuelta mediante la GPU produciendo un vectores de valores agregados que le serán proporcionado a los hilos a fin de construir las tuplas de los cuboides correspondientes. Recordando el proceso de partición y los marcadores producidos en esta fase, el hilo encargado de producir tuplas para un cierto cuboide usa los marcadores para indexar el vector que contiene los datos de la relación base y ası́ recuperar los valores que le corresponden a cada atributo del cuboide. Como ejemplo supongamos que se tiene una relación base de tres dimensiones (A, B y C) con 1000 tuplas y las primitivas de partición determinaron los marcadores 0, 100, 200 y 500 para la construcción del cuboide (ABC,COUNT(*)), es decir, se tienen cuatro particiones (0-99, 100199, 200-499, 500 - 999). El hilo encargado del cuboide ABC puede entonces indexar el vector que contiene los datos de la relación base en las posiciones 0, 100, 200 y 500 relativas a la sección que contiene los valores correspondientes a cada atributo. Como la función de agregación en este caso es COUNT(*), los marcadores proporcionan la información necesaria para producir los valores agregados mediante operaciones aritméticas. Es decir, dado que la fase de partición ha determinado los marcadores 0, 100, 200 y 500 entonces, el conteo de valores para la primera partición es 100 (100-0), para la segunda 100 valores (200-100), para la tercera 300 (500-200) y la para la cuarta 500 (1000-500), 1000 en total. En lo que respecta a las funciones de agregación SUM, MAX, MIN que son efectuadas por la GPU y la función algebraica AVG que se calcula en función de SUM y COUNT, los marcadores producidos en la fase de partición son utilizados para producir las claves que serán empleadas por la reducciónSegmentada. Considerando una vez más el ejemplo de los marcadores 0, 100, 200 y 500, las claves para la reducciónSegmentada se generan considerando el número de elementos en cada partición, en este ejemplo para delimitar el primer segmento se habrá que generar una secuencia de con 100 repeticiones del número cero, para el segundo 100 repeticiones del número uno, para el tercero 300 repeticiones del número dos y para el cuarto segmento 500 repeticiones del número tres. En conjunto las claves se almacenan en un arreglo para ser utilizadas por la reducciónSegmentada de la siguiente manera: (0, ..., 0, 1, ..., 1, 2, ..., 2, 3, ..., 3), como se observa, las claves de un mismo segmento comparten el mismo valor. 88 Dado que los datos se encuentran almacenados en un arreglo unidimensional, para efectuar la reducción y reducciónSegmentada, solo se transfiere a la memoria de video la sección del vector correspondiente al atributo a ser agregado y la información correspondiente a las particiones (claves). Esto permite reducir los requerimientos de espacio en memoria de video, ya que al construir un grupo de tuplas la parte que no requiere de cálculos se realiza mediante la CPU. La Figura 3.18 muestra una conceptualización del proceso de construcción del cuboide (ABC, SUM(D)) para una relación con cinco tuplas, como puede observarse, la columna “D” de la relación base que va a ser agreda por la función SUM se transfiere a la GPU junto con un arreglo de claves. Figura 3.18: Representación del proceso de construcción de un cuboide de tres dimensiones utilizando la función SUM. 89 3.3. Métodos paralelos de generación de cubos de datos Esta sección presenta tres métodos para generación de cubos de datos. El proceso de construcción del cubo de datos se agiliza mediante el aprovechamiento de la tecnologı́a de procesadores multinúcleo y de muchos núcleos. Es decir, la GPU está a cargo de ejecutar las operaciones que requieren aplicar gran cantidad de operaciones aritméticas y de comparación, como la aplicación de funciones de agregación y los ordenamientos, empleando un paralelismo de grano fino a través de sus numerosos procesadores. En una fase posterior y a través de hilos de CPU, se construyen tuplas completas para un grupo de cuboides usando los valores agregados generados por la GPU. Tipos de cubos de datos a resolver En este trabajo se evaluó la construcción de cubos de datos completos y del tipo iceberg sobre conjuntos de datos numéricos. Se evaluaron condiciones para cubos iceberg efectuando funciones como: SUM, MAX, MIN, COUNT(*) y AVG. Para la aplicación de la poda Apriori en el método MCBUC se requiere que las condiciones de cubos iceberg sean del tipo antimonotónicas [30] (i.e., condiciones tales como COUNT(*) ≥ 50). Tales condiciones tienen la propiedad de que si una condición iceberg es violada para alguna celda c, entonces todo ancestro de c también violara tal condición. Por ejemplo, si la cantidad de un articulo I vendido en un región R1 es menor que 50, entonces el mismo artı́culo I vendido en una subregión de R1 nunca podrá satisfacer la condición COUNT(*) ≥ 50. 90 3.3.1. Método MCBUC MCBUC es un método paralelo y recursivo basado en [3], la idea del método es mejorar el desempeño de algoritmo BUC a través de la división de la totalidad de cuboides entre los procesadores de un CPU multinúcleo. El algoritmo BUC calcula los cuboides o vistas del cubo iniciando con los menos detallados, recorriendo conceptualmente la estructura lattice en profundidad y amplitud. El BUC consta de un paralelismo inherente, ya que su recursividad forma árboles de procesamiento independientes, cada uno con raı́z en un cierto atributo o dimensión del cubo de datos. La Figura 3.19 muestra un árbol de procesamiento construido por el algoritmo BUC para un cubo de cuatro dimensiones, como puede observarse, el método calcula el cuboide menos detallado en la jerarquı́a de lattice y posteriormente la recursividad comienza en los cuboides de una dimensión. En el método MCBUC, cada uno de los árboles con raı́z en un atributo o dimensión se vuelve una tarea como lo muestra la Tabla 3.10. En otra palabras para un cubo con n atributos habrá n tareas. Figura 3.19: Árbol de procesamiento del algoritmo BUC [3] para un cubo de datos de cuatro dimensiones. Los números indican el orden de cálculo para los cuboides. Tareas Combinaciones T1 ALL T2 A, AB, ABC, ABCD, ABD, AC, ACD, AD T3 B, BC, BCD, BD T4 C, CD T5 D Tabla 3.10: Asignación de tareas en el método MCBUC para un cubo de datos de cuatro dimensiones. 91 La principal ventaja que proporciona el hecho de que MCBUC y BUC generen al cubo de datos de comenzando por los cuboides de menos detalle en la lattice es permitir la poda Apriori, que consiste en podar todas aquellas particiones que no cumplen con el umbral de soporte mı́nimo en cubos de tipo iceberg. Esta estrategia reduce la cantidad de cálculos, se explicará posteriormente con más detalle. El algoritmo BUC [3] tiene la desventaja de que gran parte del tiempo se desperdicia realizando operaciones de ordenamiento, es susceptible a datos con mediana dimensionalidad y baja cardinalidad, ya que conforme aumenta la dimensionalidad en los datos hay mas vistas a generar y la baja cardinalidad generalmente conlleva a que los grupos a agregarse sean grandes, impidiendo que el método pueda utilizar la poda, para mejorar el desempeño en estas situaciones, MCBUC integra un paralelismo a nivel de tarea. A diferencia de los otros métodos presentados en esta tesis, MCBUC no utiliza almacenamiento en memoria lineal, el ordenamiento se realiza mediante el algoritmo counting sort [42], y la partición se realiza secuencialmente de manera similar a la de [40]. Más especı́ficamente, cuando de ejecuta una cierta tarea, la recursividad del BUC ordena y particiona a los datos en bruto, respecto a un solo atributo, entonces se toma a la primera partición de los datos para ser agregada. Posteriormente, la recursividad procede ordenando y particionando sobre la misma sección de los datos pero cada vez con más detalle, produciendo tuplas para los cuboides involucrados. Una vez terminado el calculo de las tuplas correspondientes a una partición, se procede a evaluar la siguiente partición de los datos en bruto, como se observa en la Figura 3.20. La recursividad sobre una partición termina al no satisfacerse el umbral de soporte mı́nimo del cubo iceberg. Figura 3.20: Particionamiento usado en los métodos BUC [3] y MCBUC de un conjunto de datos de cuatro dimensiones. Los ai son valores del atributo A, los valores bi corresponden al atributo B y ası́ sucesivamente. 92 Poda Apriori La propiedad Apriori en el contexto de cubos de datos dice lo siguiente: “Si una celda dada no satisface el soporte mı́nimo, entonces ninguno de sus ancestros (datos a mayor detalle) lo hará”, esta propiedad se utilizará para reducir el cálculo en los cubos iceberg y fue propuesta en el algoritmo Apriori para minerı́a de reglas de asociación [2]. En otras palabras si una condición es violada por alguna celda c entonces todo ancestro de c también la violará. Las medidas que obedecen este principio son conocidas como antimonotónicas. Véase la Figura 3.21 Figura 3.21: Ejemplo de poda Apriori. Los grupos que no cumplen con la cláusula iceberg son ignorados al construir un cuboide. Para este ejemplo, se ignoran los grupos con menos de dos tuplas. Algoritmo El Algoritmo 4 muestra el pseudocódigo del método recursivo BUC [3]. Como puede observarse, el algoritmo genera el cuboide o vista de menos detalle en la jerarquı́a de lattice y posteriormente se invoca recursivamente a BottomUpCube() iniciando en cada dimensión del cubo de datos. 93 Algoritmo 4 BottomUpCube Entrada: entrada: Relación a ser agregada dim: Dimensión inicial de la iteración global constante numDims: El número total de dimensiones global constante cardinalidad[numDims]: La cardinalidad de cada dimensión global constante minsup: Número mı́nimo de tuplas en una partición para ser procesada global salida: El registro de salida actual global conteoDatos[numDims]: Almacena el tamaño de cada partición conteoDatos[i] es una lista de enteros de tamaño cardinalidad[i] Salida: Un registro que es la agregación de la entrada Recursivamente se invoca BottomUpCube(dim, ..., numDims) sobre entrada (cumpliendo con minsup) 1: Agregar(entrada) . Situar el resultado en salida 2: Si entrada.conteo() == 1 Entonces . Optimización 3: EscribirAncestros(entrada[0], dim) 4: Regresar 5: Fin Si 6: Escribir salida 7: Para d ← dim; d < numDims; d + + hacer 8: Sea C ← cardinalidad[d] 9: Particionar(entrada, d, C, conteoDatos[d]) 10: Sea k ← 0 11: Para i ← 0; i < C; i + + hacer . Para cada partición 12: Sea c ← conteoDatos[d][i] 13: Si c ≥ minsup Entonces . BottomUpCube se detiene aquı́ 14: salida.dim[d] ← entrada[k].dim[d] 15: BottomUpCube(entrada[k, ..., k + c], d + 1) 16: Fin Si 17: k ←k+c 18: Fin Para 19: salida.dim[d] ← ALL 20: Fin Para 94 El Algoritmo 5 muestra un bosquejo del método MCBUC, simplemente un hilo de CPU se encarga de iniciar la recursividad para un cierto atributo del cubo de datos. Algoritmo 5 MCBUC Entrada: Re : Relación con n atributos o dimensiones (Ai , ..., An ), n es también el número de dimensiones del cubo global minsup: Umbral de soporte mı́nimo Salida: Cuboides o vistas del cubo de datos 1: Agregar la vista o cuboide menos detallada del cubo 2: Agrupación de vistas o cuboides en tareas: similar al algoritmo BUC, i.e., subarboles de procesamiento con raı́z en Ai . 3: Asignación de hilos: a cada sub árbol con raı́z en Ai (tareai ) se le asigna el hilo i. 4: Hacer en paralelo 5: Para cada sub árbol con raı́z en la dimensión Ai asignado al hilo hacer 6: BottomUpCube(Re , Ai ) . La salida se escribe en un buffer local 7: Fin Para 8: Fin Hacer 95 Ejemplo Para ejemplificar el flujo general de ejecución del método MCBUC, considere la siguiente instrucción en SQL de un cubo iceberg: SELECT A, B, COUNT(*) FROM R CUBE BY A, B HAVING COUNT(*) >= 1 El método MCBUC inicia por el cálculo del cuboide de menor detalle en la jerarquı́a de lattice, generando una sola tupla. Como se ha mencionado, por cada dimensión del cubo de datos hay una tarea donde el método MCBUC se ejecuta recursivamente a través del procesador que le fue asignado para producir las tuplas correspondientes. En cada fase de la recursividad, el método MCBUC particiona y agrega un grupo de tuplas hasta terminar con los cuboides correspondientes a la tarea. La Figura 3.22 muestra un esquema del flujo de ejecución del método MCBUC para la instrucción anterior sobre una relación con diez tuplas. Figura 3.22: Esquema conceptual de la ejecución del método MCBUC para la generación de un cubo de datos de dos dimensiones. Las flechas muestran el flujo de procesamiento recursivo de este método. 96 3.3.2. Método SPCube SPCube, es un método descendente ya que calcula los cuboides de mayor a menor detalle en la jerarquı́a de lattice. La idea principal es paralelizar el ordenamiento, partición, agregación y construcción de cuboides integrando la estrategia del calculo de cuboides a partir de ancestros en la jerarquı́a de lattice. La Figura 3.23 conceptualiza la idea general de este método ası́ como el uso de las primitivas. Figura 3.23: Esquema del método SPCube. La asignación de cuboides en tareas dentro de este método se realiza respecto a los niveles de la jerarquı́a de lattice, es decir, los cuboides son agrupados dependiendo del número de atributos que se tomarán en cuenta para calcular los agregados. Por tanto, a lo más pueden procesarse simultáneamente el número de cuboides de un cierto nivel en la jerarquı́a de lattice contenga. 97 Esta asignación de tareas permite calcular los cuboides en un cierto nivel de la lattice a partir de sus ancestros en la jerarquı́a. Por ejemplo, para un cubo de datos de cuatro dimensiones se tendrı́an la asignación de tareas como lo muestran la Tabla 3.11 y la Figura 3.24, permitiendo al método calcular los cuboides en una tarea a partir de los previamente calculados. Figura 3.24: Lattice para un cubo de datos de cuatro dimensiones. Las flechas indican rutas potenciales de cálculo para el método SPCube. Tareas Combinaciones T1 ABCD T2 ABC, ABD, ACD, BCD T3 AB, AC, AD, BC, BD, CD T4 A, B, C, D T5 ALL Tabla 3.11: Asignación de tareas en el método SPCube para un cubo de datos de cuatro dimensiones. Los cuboides son agrupados de acuerdo al número de atributos. 98 Con el fin de reducir cálculo, SPCube selecciona ciertas vistas del cubo de datos para generar a partir de ella otras más que se encuentran en niveles de menor detalle en la jerarquı́a de lattice. De la Figura 3.24 se puede observar que algunos vistas o cuboides podrı́an ser calculados a partir de varios ancestros, tal es el caso del cuboide B de la tarea 3 que comparte atributos con los cuboides más detallados AB, BC y BD, la selección de un ancestro adecuado implica la necesidad de conocer el costo de cada una de estas vistas. El costo de una vista o cuboide se refiere a su número de tuplas, en este método este número se estima a través de la cardinalidad de sus atributos, es decir, el número de tuplas en una vista está en función del producto de las cardinalidades de los atributos tomados en cuenta para generarla. Este número también puede estimarse mediante alguna técnica estadı́stica como [18, 44]. Además del costo, para determinar el ancestro más adecuado es necesario revisar si el cuboide a calcularse comparte ordenamiento con el ancestro. Considerando de nuevo a B, este cuboide podrı́a ser calculado directamente a partir de BC o BD sin necesidad de ordenar los datos, sin embargo si se calcula a partir de AB será necesario re-ordenar los datos para calcularlo. La Figura 3.25 muestra un ejemplo de árbol de procesamiento generado por el método SPCube a partir de la estimación de costos en la jerarquı́a de lattice. Los guiones en los arcos indican los casos en que es necesario ordenar el cuboide ancestro. Figura 3.25: Árbol de procesamiento generado por el método SPCube. Los números a la derecha de cada combinación de atributos indican la cantidad de tuplas del cuboide, M y K indican millones y miles respectivamente. Estas cantidades permiten realizar una selección que producirá el menor costo al generar vistas del cubo a partir de otras más detalladas (ancestros en la jerarquı́a de lattice). 99 Algoritmo La selección del cuboide ancestro que produce el menor costo al calcular otro menos detallado en la jerarquı́a de lattice se realiza a través del método que se muestra en el Algoritmo 6. El algoritmo obtiene el costo de toda vista que comparte atributos con una vista v y devuelve la referencia de la que produjo el menor costo. Algoritmo 6 padreMenorCosto Entrada: global ndimensiones: Número de dimensiones en los datos cardinalidad[ndimensiones]: Cardinalidades de los atributos de la relación base vistas: Conjunto de las combinaciones de atributos correspondientes a cada vista del cubo v: Combinación de atributos de la vista a ser evaluada Salida: padreMı́nimo: Combinación de atributos del ancestro de costo mı́nimo para la vista v 1: padreMı́nimo ← cuboideBase . combinación de la vista más detallada en vistas 2: costoMı́nimo ← costo(padreMı́nimo) 3: Para cada vistai en vistas hacer 4: Si v ⊂ vistai y costoMı́nimo > costo(vistai ) Entonces 5: costoMı́nimo ← costo(vistai ) 6: padreMı́nimo ← vistai 7: Fin Si 8: Fin Para 9: Regresar padreMı́nimo 10: Función costo(vista) . Costo de calcular una vista a partir de esta 11: costo ← 1 12: Para cada Ai en vista hacer 13: costo ← cardinalidad[i] ∗ costo 14: Fin Para 15: Regresar costo 16: Fin Función 100 El Algoritmo 7 muestra el flujo general del método SPCube ası́ como el uso de las primitivas paralelas. Como puede observarse, SPCube calcula grupos de vistas del cubo de datos usando la referencia del ancestro de menor costo proporcionada por el Algoritmo 6. Si el orden de los atributos de un cuboide ancestro lo permite, el ordenamiento se omite. En caso de que la instrucción de cubo de datos requiera la aplicación de una función como SUM, MAX, MIN o AVG esta se realiza mediante las primitivas paralelas de reducción. Algoritmo 7 SPCube Entrada: Re : Vector de tuplas global minsup: Umbral de soporte mı́nimo Salida: Cuboides o vistas del cubo de datos 1: Agrupar vistas en tareas y calcular la vista o cuboide base 2: Estimar de cardinalidad para cada atributo de los datos: cardinalidad[ndimensiones] 3: Para cada tareai en tareas hacer 4: Para cada vistaj en tareai hacer 5: padreMı́nimo ← padreMenorCosto(vistaj , cardinalidad) 6: Si vistaj y padreMı́nimo no comparten orden Entonces 7: ordenar tuplas de padreMı́nimo 8: Fin Si 9: Efectuar particiónLocal() para vistaj 10: Efectuar particiónCuboide() empleando el resultado de particiónLocal() 11: Efectuar reducción() o reducciónSegmentada() sobre el atributo de medida de padreMı́nimo . Agregar (GPU) 12: Fin Para 13: Hacer en paralelo . Generación de cuboides en paralelo (CPU multinúcleo) 14: Para cada vistaj en tareai hacer 15: Emplear el resultado de reducción() y reducciónSegmentada() para construir las tuplas de vistaj que cumplen minsup, indexando Re de acuerdo con los marcadores generados por particiónCuboide(). 16: Escribir tuplas de la vistaj en memoria secundaria 17: Fin Para 18: Fin Hacer 19: Fin Para 101 Ejemplo Para ejemplificar el flujo general de ejecución del método SPCube, considere la siguiente instrucción para generar un cubo de datos de tres dimensiones: SELECT A, B, C, SUM(D) FROM R CUBE BY A, B, C El método SPCube inicia por realizar un ordenamiento a los datos en bruto para calcular el cuboide más detallado de cubo de datos, en este ejemplo, es el cuboide ABC. A partir de este momento se inicia la fase de construcción del plan de construcción de cubo de datos, donde se estima el costo los cuboides restantes y se determina a partir de que ancestro se generará cada cuboide restante. Se toma en cuenta también si es posible evitar el ordenamiento del ancestro al generar un nuevo cuboide. La Figura 3.26 muestra el flujo de ejecución del algoritmo SPCube para la instrucción anterior. Como la función de agregación del cubo es SUM, las primitivas de reducción se encargarı́an de producir los valores agregados correspondientes a cada cuboide. Figura 3.26: Esquema de ejecución del método SPCube para un cubo de tres dimensiones. Los cuboides son generados a partir de otros más detallados en lugar de los datos en bruto. 102 3.3.3. Método GPUgenCube En está sección se presenta el método GPUgenCube para cálculo de cubos de datos completos y de tipo iceberg, el cual está diseñado con la idea de paralelizar el proceso de construcción del cubo de datos ejecutando varios cuboides simultáneamente usando hilos de CPU y delegando el cálculo de agregados como SUM, MAX, MIN y AVG a una GPU. La idea general del método ası́ como el uso de primitivas se esquematiza en la Figura 3.27. Figura 3.27: Esquema del método GPUgenCube. Este método inicia con una fase de generación de tareas, donde cada uno de los 2n cuboides son asignados a una determinada tarea. Posteriormente, si se requiere de ejecutar una función como SUM, MAX, MIN o AVG, las primitivas de reducción entran en funcionamiento para producir valores agregados. Las tuplas para los cuboides en las tareas son armadas simultáneamente usando hilos de CPU y los valores previamente producidos (en el caso de SUM, MAX, MIN y AVG). 103 El algoritmo de generación de tareas utilizado en GPUgenCube organiza los cuboides de acuerdo a sus atributos con el fin de reducir operaciones de ordenamiento, por ejemplo, considere la generación de un cubo de datos con las dimensiones A, B, C y D, si se ordenan los datos en bruto respecto a la combinación de atributos ABCD, entonces es posible calcular los cuboides ABCD, ABC, AB, A y ALL en paralelo y sin ordenamientos adicionales. Ordenar los datos en bruto respecto a la combinación de atributos del cuboide de más detalle en una tarea permite también calcular las tuplas correspondientes a todos los cuboides de dicha tarea en una sola pasada al flujo de entrada. Sin embargo, en primera instancia, este agrupamiento produce algunas tareas que podrı́an ser re-agrupadas, es decir, tareas donde los cuboides comparten los mismos atributos pero están organizados inversamente comenzando de izquierda a derecha, como los cuboides ABD, BD y D de la Figura 3.28. Un número elevado de tareas implica realizar más escaneos a memoria secundaria para calcular la totalidad de los cuboides. Para reducir el impacto de está situación, las tareas de un elemento son reagrupadas a través de un cambio en el sentido de las combinaciones de sus cuboides como se explica a continuación: 1. Seleccionar las tareas de 1 elemento. 2. Seleccionar el cuboide c de más detalle en las tareas de 1 elemento. 3. Invertir el la combinación de atributos de c. 4. Buscar agrupar c con otros cuboides invirtiendo sus combinaciones de atributos en caso de ser necesario. 5. Repetir procedimiento con los cuboides restantes Considerando de nuevo los cuboides ABD, BD y D de la Figura 3.28, ordenar los datos se respecto a la combinación ABD o DBA produce el mismo resultado, entonces, es posible calcular ABD, BD y D en una sola pasada a los datos en bruto. La Figura 3.28 muestra una lattice para un cubo de cuatro dimensiones, seguido de ella se encuentra la Tabla 3.12 que recopila las listas de cuboides agrupados como se mencionó. Estas listas serán ejecutadas una a una, generando en paralelo las tuplas de los cuboides en ellas. 104 Figura 3.28: Lattice para un cubo de datos de cuatro dimensiones. Tareas Combinaciones T1 ABCD, ABC, AB, A, ALL T2 BCD, BC, B T3 ACD, AC T4 CD, C T5 DBA, DB, D T6 AD Tabla 3.12: Asignación de tareas en el método GPUgenCube para un cubo de datos de cuatro dimensiones. Las tareas son procesadas una a una, empleando paralelismo de grano fino a través de las primitivas paralelas y generando tuplas en paralelo para los cuboides de una cierta tarea en paralelo. Nótese de la Tabla 3.12 que los cuboides BCD, BC y B por ejemplo, no podrı́an ser agrupados junto con los cuboides de T1 , ya que aunque se tienen atributos en común, el ordenamiento respecto a la combinación ABCD producirı́a incorrectamente más particiones para BCD, BC y B por incluir adicionalmente al atributo A. Algoritmo A continuación se presentan los detalles generales del método GPUgenCube. Este método a diferencia de SPCube no reduce cálculos usando ancestros en la jerarquı́a de lattice, en su lugar la agrupación de los cuboides en tareas permite reducir las operaciones de ordenamiento. 105 En caso de que la instrucción de cubo de datos requiera la aplicación de una función como SUM, MAX, MIN o AVG esta se realiza mediante las primitivas paralelas de reducción. Para el caso de la función COUNT, no se requieren operaciones de reducción, en su lugar, los conteos son realizados al vuelo cuando se construyen las tuplas de un cuboide usando operaciones aritméticas y la información de particionamiento. Algoritmo 8 GPUgenCube Entrada: Re : Vector de tuplas global minsup: Umbral de soporte mı́nimo Salida: Cuboides o vistas del cubo de datos 1: Agrupar cuboides en tareas 2: Para cada (tareai en tareas) hacer 3: Ordenar tuplas respecto a la vista de más detalle en tareai 4: Efectuar particiónLocal() respecto a la vista de más detalle en tareai 5: Para cada (vistaj en tareai ) hacer 6: Efectuar particiónCuboide() empleando el resultado de particiónLocal() 7: Si se incluyen funciones como SUM, MAX MIN o AVG Entonces . Agregar (GPU) 8: Efectuar reducción() o reducciónSegmentada() sobre el atributo de medida de Re 9: Fin Si 10: Fin Para 11: Hacer en paralelo . Generación de cuboides en paralelo (CPU multinúcleo) 12: Para cada vistaj en tareai hacer 13: Si se incluyen funciones como SUM, MAX MIN o AVG Entonces 14: Emplear el resultado de reducción() y reducciónSegmentada() para construir las tuplas de vistaj que cumplen minsup, indexando Re de acuerdo con los marcadores generados por particiónCuboide(). 15: Escribir tuplas de la vistaj en memoria secundaria 16: Si no 17: Construir las tuplas de vistaj que cumplen minsup, indexando Re de acuerdo con los marcadores generados por particiónCuboide(). 18: Escribir tuplas de la vistaj en memoria secundaria 19: Fin Si 20: Fin Para 21: Fin Hacer 22: Fin Para 106 Ejemplo Para ejemplificar el flujo general de ejecución del método GPUgenCube, considere la siguiente instrucción SQL para construir un cubo de 3 dimensiones: SELECT A, B, C, SUM(D) FROM R CUBE BY A, B, C El método GPUgenCube inicia por agrupar los cuboides en tareas de acuerdo a la combinación sus atributos como se explicó anteriormente. Las tareas se ejecutan una a una, ordenando los datos solo una vez por cada tarea (respecto al cuboide de más detalle en una cierta tarea). Los cuboides de cada tarea son construidos en paralelo utilizando hilos de CPU y en el caso de que se requiera resolver funciones como SUM, MAX, MIN o AVG, las primitivas de reducción se encargan de calcular los valores agregados correspondientes. La Figura 3.29 muestra el flujo general del método GPUgenCube en la construcción de un cubo de datos de tres dimensiones usando la función de agregación SUM. Al ejecutar una tarea, los datos en bruto son ordenados, particionados, agregados y posteriormente un conjunto de hilos se encarga de construir y escribir a disco las tuplas de los cuboides en la tarea. Figura 3.29: Esquema de ejecución del método GPUgenCube para un cubo de tres dimensiones. Las tareas son ejecutadas una a una por GPUgenCube. Los cuboides de una cierta tarea son generados y escritos simultáneamente a memoria secundaria por un hilo de CPU. 107 3.3.4. Comparativa de los métodos para cubos de datos La Tabla 3.13 muestra una comparativa entré las caracterı́sticas con las que cuentan los métodos presentados. En la columna “Uso de memoria”, el indicador Bajo se refiere a que el método solo requiere realizar un ordenamiento a la relación base para calcular los cuboides de una cierta tarea, Medio indica que el método requiere re-ordenar varias fuentes de datos (cuboides) para calcular una tarea, Alto indica que el método necesita replicar y re-ordenar la relación base para ejecutar el cálculo de las tareas. Las funciones implementadas para los métodos se presentan en la Tabla 3.14. Método Uso de memoria Estrategias para cubos completos Poda en cubos iceberg Usa primitivas paralelas MCBUC Alto No Si No SPCube Medio Si No Si Bajo Si No Si GPUgenCube Tabla 3.13: Resumen de caracterı́sticas para los métodos SPCube, MCBUC y GPUgenCube. En cuanto a funcionalidad, las implementaciones de los métodos incluyen las funciones de agregación que muestran una marca de verificación en la Tabla 3.14. Método SUM MAX MIN MCBUC COUNT AVG X SPCube X X X GPUgenCube X X X X X Tabla 3.14: Funciones de agregación en los métodos MCBUC, SPCube y GPUgenCube. Las marcas de verificación indican las funciones implementadas para cada método. Ası́ mismo, los métodos SPCube y GPUgenCube constan de 2 modos de escritura, CSV y SQLite. El formato CSV (en inglés, Comma-Separated Values) permite guardar las vistas del cubo de datos en formato tabular usando un archivo de texto en el que las columnas se separan mediante una coma, esta opción escribe cada vista del cubo de datos en un archivo con este formato. La opción SQLite produce un archivo con las vistas del cubo de datos para ser consultado usando el lenguaje SQL a través del motor de base de datos conocido como SQLite [45]. 108 Capı́tulo 4 Pruebas y resultados En esta sección se presenta una evaluación de los métodos propuestos para verificar su eficiencia y escalabilidad. El software desarrollado en este trabajo fue comparado contra otros algoritmos de cubos de datos bien conocidos en la literatura de bases de datos como BUC [3] y MM-Cubing [43], cuyas implementaciones se encuentran disponibles en el paquete Illimine [46] de la universidad de Illinois en Urbana-Champaign. A continuación se describe el escenario donde se realizó la experimentación. En primera instancia se da una descripción de la configuración de hardware del sistema de pruebas y posteriormente se dan las caracterı́sticas de los conjuntos de datos utilizados. 109 4.1. Equipo de pruebas Todos los métodos fueron programados en los lenguajes C/C++ y CUDA C sobre un sistema AMD Athlon II X3. La Tabla 4.1 presenta una especificación detallada de las caracterı́sticas del sistema donde se realizaron las pruebas. Software Sistema operativo Compilador de C Versión de CUDA Linux v3.0.0 GCC v4.4.3 5.5 CPU Modelo Número de núcleos Tamaño de caché L1 y L2 Memoria RAM AMD Athlon II X3 455 3 a 3.3GHZ 128KB, 1.5MB 4GB DDR3 (1333MHZ) GPU Modelo Número de multiprocesadores Total de núcleos Tamaño de memoria compartida caché L1 y caché L2 Memoria de video NVIDIA GTX 460 7 336 a 1.35GHZ 48KB, 16KB, 512KB 1GB (1800MHZ) Tabla 4.1: Especificación técnica del sistema de pruebas. 4.2. Conjunto de datos de prueba Las pruebas fueran realizadas utilizando datos de tipo entero para generar los cubos de datos. Los conjuntos de datos fueron producidos de manera sintética a través del generador de datos incluido en el paquete Illimine [46]. Este generador permite variar nivel del sesgo en los datos respecto al factor de Zipf, especificar de manera individual la cardinalidad de los atributos o dimensiones de los datos (número de valores diferentes por columna) ası́ como ajustar el rango en el cual se generarán los valores de dichos atributos. 110 4.3. Desempeño del ordenamiento Se probó la implementación del algoritmo de ordenamiento radix sort paralelo en una GPU contra otros algoritmos de ordenamiento por clave para CPU. Las implementaciones de los algoritmos para CPU fueron obtenidas de varias bibliotecas para C++ disponibles en la web, como son: la biblioteca GNU estándar de C/C++ [15], Boost [6] y Thrust [22]. Las Figuras 4.1 y 4.2 muestran el desempeño de los algoritmos sobre conjuntos de datos de hasta 120 millones de elementos. GPU radix sort Thrust (CPU) GNU Std C++ library Boost 9 8 7 Tiempo (S) 6 5 4 3 2 1 0 10 20 30 40 50 60 70 80 90 100 110 120 Millones de enteros Figura 4.1: Desempeño de algoritmos de ordenamiento sobre conjuntos de datos distribuidos aleatoriamente. GPU radix sort Thrust (CPU) GNU Std C++ library Boost 9 8 7 Tiempo (S) 6 5 4 3 2 1 0 10 20 30 40 50 60 70 80 90 100 110 120 Millones de enteros Figura 4.2: Desempeño de algoritmos de ordenamiento sobre conjuntos de enteros en orden decreciente. 111 La Figura 4.2 muestra la ejecución de los algoritmos de ordenamiento incremental sobre varios conjuntos de enteros de 32 bits en un rango de 1 a 1000 previamente ordenados de manera decreciente. En la Figura 4.1 puede observarse el tiempo promedio de varios grupos de ejecuciones sobre conjuntos de 1 a 120 millones de enteros de 32 bits generados aleatoriamente en un rango de 0 a 1000. En todos los casos el ordenamiento incremental más rápido se realizó utilizando la GPU. 4.4. Desempeño de los algoritmos de cubos de datos Las pruebas de esta sección consistieron en medir el tiempo de ejecución de los métodos propuestos en esta tesis en la generación de cubos de datos completos y de tipo iceberg. Se utilizaron conjuntos de hasta 10 millones de tuplas variando parámetros como número de tuplas, dimensiones (5 - 10 dimensiones), cardinalidad (20 - 100 valores diferentes por dimensión) y distribución (0 - 5 respecto al factor de Zipf). Los conjuntos de datos utilizados son sintéticos y constan de la misma cardinalidad en todos sus atributos, los valores para un atributo son enteros en un rango de 1 a la cardinalidad del atributo. Las gráficas incluyen el tiempo de acceso a memoria secundaria. En las gráficas de esta de esta sección, dimensiones denota el número de dimensiones para los cubos de datos (se producen 2d vistas para un cubo con d dimensiones), cardinalidad indica el número de valores por cada cada dimensión o atributo de los datos, tuplas se refiere a la cantidad de registros de los datos en bruto (m=millones) utilizados para la generación de los cubos, minsup es el umbral de soporte mı́nimo que toda tupla de una vista del cubo iceberg debe cumplir y por ultimo, sesgo el nivel de sesgo o valor de Zipf de los datos. Cuando el sesgo es cero, los datos están distribuidos de manera uniforme; conforme el sesgo aumenta los datos se encuentran más sesgados. El valor de sesgo se aplica a todas las dimensiones. Las gráficas se encuentran clasificadas de acuerdo a las siguientes cuatro categorı́as: Cubo completo: Gráficas sobre la generación de todas las vistas del cubo de datos. Se compara al método GPUgenCube presentado en esta tesis contra los métodos BUC [3] y MM-Cubing [43] utilizando la función COUNT(*). Cubo iceberg: Gráficas sobre la generación de las vistas o la parte de las vistas del cubo de datos que cumplen con un umbral de soporte mı́nimo (minsup) previamente especificado. Se compara al método GPUgenCube presentado en esta tesis contra los métodos BUC [3] y MM-Cubing [43] utilizando la función COUNT(*) y la clausula iceberg COUNT(*) ≥ minsup. 112 Sesgo: Gráficas donde el desempeño del proceso de generación del cubo se mide respecto a variaciones en el nivel de sesgo de los datos utilizando la función COUNT(*) y la clausula iceberg COUNT(*) ≥ minsup. Se compara al método GPUgenCube presentado en esta tesis contra los métodos BUC [3] y MM-Cubing [43]. Funciones de agregación: Gráficas donde se muestra el desempeño de los métodos SPCube y GPUgenCube presentados en esta tesis en la generación de cubos iceberg utilizando las funciones de agregación SUM, MAX, MIN, COUNT y AVG. Las gráficas de funciones de agregación se muestran por separado a falta de implementaciones completas de los métodos BUC [3] y MM-Cubing [43] (solo realizan COUNT(*)). 4.4.1. Cubo completo El siguiente grupo de gráficas muestra el desempeño de GPUgenCube, BUC [3] y MMCubing [43], en la generación de cubos de datos completos (se generan todas las vistas del cubo de datos). Se utiliza COUNT(*) como función de agregación. El eje horizontal de las gráficas indica el nivel de variación de un cierto parámetro al generar los cubos de datos. GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 2 4 6 8 10 Tuplas (Millones) Figura 4.3: Generación del cubo completo, dimensiones = 7, cardinalidad = 10, sesgo = 0. 113 GPUgenCube BUC MMCubing 1000 900 800 Tiempo (S) 700 600 500 400 300 200 100 0 10 20 30 40 50 Cardinalidad Figura 4.4: Generación del cubo completo, tuplas = 10m, dimensiones = 7, sesgo = 0. GPUgenCube BUC MMCubing 9000 8000 7000 Tiempo (S) 6000 5000 4000 3000 2000 1000 0 5 6 7 8 9 10 Dimensiones Figura 4.5: Generación del cubo completo, tuplas = 10m, cardinalidad = 20, sesgo = 0. 114 GPUgenCube BUC MMCubing 500 450 400 Tiempo (S) 350 300 250 200 150 100 50 0 2 4 6 8 10 Tuplas (Millones) Figura 4.6: Generación del cubo completo, dimensiones = 8, cardinalidad = 10, sesgo = 0. 330 GPUgenCube BUC MMCubing 300 270 Tiempo (S) 240 210 180 150 120 90 60 30 0 10 20 30 40 50 Cardinalidad Figura 4.7: Generación del cubo completo, tuplas = 10m, dimensiones = 7, sesgo = 0, sin considerar el tiempo de escritura a disco. 115 2200 GPUgenCube BUC MMCubing 2000 1800 Tiempo (S) 1600 1400 1200 1000 800 600 400 200 0 5 6 7 8 9 10 Dimensiones Figura 4.8: Generación del cubo completo, tuplas = 10m, cardinalidad = 20, sesgo = 0, sin considerar el tiempo de escritura a disco. 4.4.2. Cubo iceberg El siguiente grupo de gráficas muestra el desempeño de los métodos GPUgenCube, BUC [3] y MM-Cubing [43] en la generación de cubos de datos iceberg utilizando COUNT(*) como función de agregación y COUNT(*) ≥ minsup como clausula iceberg. El eje horizontal de las gráficas indica el nivel de variación de un cierto parámetro al generar los cubos de datos. 116 GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 20 40 60 80 100 Cardinalidad Tiempo (S) Figura 4.9: Generación del cubo iceberg, tuplas = 10m, dimensiones = 7, sesgo = 0, minsup = 100. 300 280 260 240 220 200 180 160 140 120 100 80 60 40 20 0 GPUgenCube BUC MMCubing 20 40 60 80 100 Cardinalidad Figura 4.10: Generación del cubo iceberg, tuplas = 10m, dimensiones = 8, sesgo = 0, minsup = 100. 117 GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 50 100 150 200 250 Minsup Figura 4.11: Generación del cubo iceberg, tuplas = 10m, cardinalidad = 20, dimensiones = 7, sesgo = 0. GPUgenCube BUC MMCubing 400 360 320 Tiempo (S) 280 240 200 160 120 80 40 0 10 20 30 40 50 60 70 80 90 100 110 Minsup Figura 4.12: Generación del cubo iceberg, tuplas = 10m, cardinalidad = 20, dimensiones = 8, sesgo = 0. 118 4.4.3. Sesgo El siguiente grupo de gráficas muestra el desempeño de los métodos GPUgenCube, BUC [3] y MM-Cubing [43] en la generación de cubos de datos iceberg, utilizando conjuntos de datos enteros distribuidos de manera distinta. Los datos fueron sesgados utilizando valores que van de cero a cinco respecto a Zipf. Conforme el valor de Zipf aumenta, los datos están más sesgados. Se utiliza COUNT(*) como función de agregación y COUNT(*) ≥ minsup como clausula iceberg. El eje horizontal de las gráficas indica el nivel de variación de un cierto parámetro al generar los cubos de datos. GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 0 1 2 3 4 5 Sesgo Figura 4.13: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. 119 GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 0 1 2 3 4 5 Sesgo Figura 4.14: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 40, minsup = 50. GPUgenCube BUC MMCubing 200 180 160 Tiempo (S) 140 120 100 80 60 40 20 0 0 1 2 3 4 5 Sesgo Figura 4.15: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 100, minsup = 50. 120 GPUgenCube BUC MMCubing 400 360 320 Tiempo (S) 280 240 200 160 120 80 40 0 0 1 2 3 4 5 Sesgo Figura 4.16: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 8, cardinalidad = 100, minsup = 10. 4.4.4. Funciones de agregación El siguiente grupo de gráficas muestra el desempeño de los métodos SPCube y GPUgenCube presentados en esta tesis en la generación de cubos de datos iceberg usando las funciones SUM, MAX MIN y AVG. Cada gráfica de esta sección muestra la generación de un grupo de cubos de datos donde se utiliza distinta función de agregación pero una misma clausula iceberg. El eje horizontal de las gráficas indica el nivel de variación de un cierto parámetro al generar los cubos de datos. 121 Tiempo (S) SPCube resolviendo SUM, MAX, MIN y usando la clausula iceberg SUM() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN 5 6 7 8 9 10 Dimensiones Figura 4.17: SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. SUM MAX MIN 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.18: SPCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. 122 Tiempo (S) SPCube resolviendo SUM, MAX, MIN y usando la clausula iceberg MAX() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN 5 6 7 8 9 10 Dimensiones Figura 4.19: SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. SUM MAX MIN 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.20: SPCube: Generación del cubo iceberg variando en el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. 123 Tiempo (S) SPCube resolviendo SUM, MAX, MIN y usando la clausula iceberg MIN() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN 5 6 7 8 9 10 Dimensiones Figura 4.21: SPCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. SUM MAX MIN 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.22: SPCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. 124 Tiempo (S) GPUgenCube resolviendo SUM, MAX, MIN, COUNT, AVG y usando la clausula iceberg SUM() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN COUNT(*) AVG 5 6 7 8 9 10 Dimensiones Figura 4.23: GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. SUM MAX MIN COUNT(*) AVG 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.24: GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. 125 Tiempo (S) GPUgenCube resolviendo SUM, MAX, MIN, COUNT, AVG y usando la clausula iceberg MAX() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN COUNT(*) AVG 5 6 7 8 9 10 Dimensiones Figura 4.25: GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. SUM MAX MIN COUNT(*) AVG 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.26: GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. 126 Tiempo (S) GPUgenCube resolviendo SUM, MAX, MIN, COUNT, AVG y usando clausula iceberg MIN() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN COUNT(*) AVG 5 6 7 8 9 10 Dimensiones Figura 4.27: GPUgenCube: Generación del cubo iceberg, AVG, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. SUM MAX MIN COUNT(*) AVG 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.28: GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. 127 Tiempo (S) GPUgenCube resolviendo SUM, MAX, MIN, COUNT, AVG y usando la clausula iceberg COUNT(*) ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN COUNT(*) AVG 5 6 7 8 9 10 Dimensiones Figura 4.29: GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 100. SUM MAX MIN COUNT(*) AVG 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.30: GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 100. 128 Tiempo (S) GPUgenCube resolviendo SUM, MAX, MIN, COUNT, AVG y usando la clausula iceberg AVG() ≥ minsup 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 SUM MAX MIN COUNT(*) AVG 5 6 7 8 9 10 Dimensiones Figura 4.31: GPUgenCube: Generación del cubo iceberg, tuplas = 10m, sesgo = 1, cardinalidad = 20, minsup = 20. SUM MAX MIN COUNT(*) AVG 150 Tiempo (S) 120 90 60 30 0 0 1 2 3 4 5 Sesgo Figura 4.32: GPUgenCube: Generación del cubo iceberg variando el nivel de sesgo (Zipf) de los datos, tuplas = 10m, dimensiones = 7, cardinalidad = 20, minsup = 20. 129 4.4.5. Observaciones Cubo completo En el conjunto de gráficas del cubo completo (Figuras 4.3, 4.4, 4.5 y 4.6) muestra una ligera diferencia entre los métodos GPUgenCube y MM-Cubing, como puede observarse en la Figuras 4.3 y 4.6 donde los tiempos entre ejecuciones de GPUgenCube y MM-Cubing difieren de manera casi constante. La Figura 4.6 muestra una diferencia máxima de 65 segundos entre las ejecuciones de ambos métodos, mientras que en la Figura 4.3 la diferencia máxima es apenas de 13 segundos. En primera instancia esto se debe a que el tiempo este proceso está dominado por las operaciones de entrada/salida a memoria secundaria. El algoritmo BUC obtiene un buen desempeño en ciertos casos solamente (Figuras 4.4 y 4.5), sobre todo en cubos de datos donde el tamaño del flujo de salida fue más elevado. Esto se puede observar con claridad en la Figura 4.5, donde el tiempo del BUC para la generación del cubo de datos de 10 dimensiones es ligeramente superior a 6000 segundos. GPUgenCube obtuvo un desempeño regular en este conjunto de experimentos (Figuras 4.3, 4.4, 4.5 y 4.6), esto se debe en primera instancia a que GPUgenCube paraleliza la escritura, contrastando con BUC y MM-Cubing, donde las operaciones de entrada/salida a memoria secundaria se realizan de manera serializada, lo cual favorece ligeramente al almacenamiento mecánico utilizado en las pruebas. Para dar una idea del tamaño del flujo de salida producido en este grupo de experimentos, la generación de un cubo de datos completo, a partir de 10 millones de tuplas, 10 dimensiones y una cardinalidad de 20 en cada atributo produjo un resultado que ocupó 120GB de espacio en disco (4,504,968,176 tuplas). Las Figuras 4.7 y 4.8 muestran el desempeño de GPUgenCube, BUC y MMCubing en la generación del cubo completo sin considerar el tiempo de escritura a disco. Cubo iceberg De las Figuras 4.9, 4.10, 4.11 y 4.12 puede observase que GPUgenCube obtiene un desempeño superior en todos los casos, superando al BUC a pesar su estrategia de poda Apriori y a MMCubing que reduce cálculos en base a frecuencia. En el problema del cubo iceberg la escritura a disco se ve reducida, acentuando la aceleración proporcionada por la paralelización de los cálculos en el método GPUgenCube. 130 Sesgo Las gráficas de la sección “Sesgo” (Figuras 4.13, 4.14, 4.15 y 4.16) muestran un experimentos realizados sobre conjuntos de datos distribuidos utilizando valores de 0 a 5 respecto al factor de Zipf para controlar el sesgo de los datos. El método GPUgenCube se muestra estable sobre esta variación como resultado del ordenamiento paralelo. Sin embargo, el sesgo beneficia a MMCubing, desempeñándose bastante bien en las ejecuciones donde los datos se encuentran más sesgados. En el caso del BUC, aunque también se beneficia del sesgo, obtiene el peor desempeño en todo el grupo de experimentos. Funciones de agregación La primitivas de reducción permiten a los métodos SPCube y GPUgenCube resolver funciones de agregación distributivas y algebraicas en un tiempo logarı́tmico. En la mayorı́a de los casos, la función AVG tomó más tiempo, como puede observarse en las Figuras 4.17 - 4.22 y 4.23 - 4.32, esto se debe en parte a que implica el calculo de SUM y COUNT. Por otro lado, considerando que los datos usados para generar el cubo de datos son enteros, la escritura a memoria secundaria producida por la función AVG es de tipo flotante y por tanto tiene un volumen mayor en comparación con los valores enteros producidos como salida por las funciones SUM, MAX, MIN y COUNT. Acceso a disco Cuando se lee un conjunto de datos de un archivo en disco, el procesador necesita esperar que la lectura finalice para iniciar el procesamiento sobre ellos. De manera similar sucede para cuando el procesador realiza una operación de escritura a disco, el procesador permanece en estado de espera mientras se realiza la escritura. Este tiempo de acceso es un problema para los métodos paralelos de esta tesis, como los discos duros son mecánicos, es necesario esperar a que el disco rote al sector que se requiere, limitando el desempeño de los múltiples hilos escritores. La latencia de acceso a disco es de alrededor de 13 milisegundos (varı́a en función de la velocidad de rotación del disco) mientras que la latencia de acceso a la memoria RAM es de aproximadamente 83 nanosegundos. 131 Capı́tulo 5 Conclusiones En este trabajo se presentaron tres métodos paralelos para generación de cubos de datos: MCBUC, SPCube y GPUgenCube, que aprovechan las ventajas de los CPUs multinúcleo y recientes GPUs a través de un conjunto de primitivas paralelas. En el capı́tulo 1 de la tesis se mencionó que se tenia por objetivo general el diseño e implementación de métodos paralelos para generación de cubos de datos completos y de tipo iceberg, aprovechando las ventajas del procesamiento en GPUs y CPUs multinúcleo. El objetivo general se cumplió con el diseño de los métodos antes mencionados. Ası́ también, se dijo que como objetivos particulares de la tesis se tenia: Diseñar e implementar un conjunto de primitivas paralelas que permitan el manejo eficiente de datos aprovechando el paralelismo de GPUs y CPUs multinúcleo, ası́ como los niveles de memoria de la GPU. Diseñar e implementar métodos paralelos de generación de cubos de datos completos y de tipo iceberg empleando tecnologı́a multihilo y primitivas paralelas. Diseñar e Implementar funciones de agregación distributivas y algebraicas para los métodos de cubos de datos. El primer objetivo particular se cumplió con la implementación de las primitivas que aprovechan el paralelismo de GPUs y CPUs multinúcleo ası́ como la memoria compartida de la GPU. El segundo objetivo particular se cumplió con el uso de hilos POSIX y de las primitivas paralelas del capı́tulo 3 para ejecutar subrutinas en los métodos de generación de cubos de datos. El tercer objetivo particular se cumplió con la implementación de las funciones distributivas SUM, MAX, MIN, COUNT y la función algebraica AVG. 132 Además, se pudo observar mediante las pruebas, que de los métodos presentados en este trabajo, GPUgenCube obtuvo el mejor desempeño, superando también en la mayorı́a de los casos a los algoritmos BUC [3] y MM-Cubing [43]. Cabe destacar que solo se probó a GPUgenCube contra MM-Cubing y BUC ejecutando la función COUNT esto a falta de funcionalidad en las implementaciones de MM-Cubing y BUC; por lo que las gráficas de las secciones “Cubo completo”, “Cubo iceberg” y “Sesgo” no muestran la ventaja del paralelismo en la ejecución de funciones de agregación. GPUgenCube tiene un manejo de memoria económico, ya que la división de tareas permite calcular un grupo de cuboides en una sola pasada a disco, además de ser capaz de resolver funciones agregación distributivas y algebraicas de manera eficiente. En lo que respecta al método SPCube, obtuvo un desempeño inferior al de GPUgenCube a pesar de utilizar una estrategia para reducir cálculo, esto a causa de las lecturas y ordenamientos adicionales a las vistas del cubo. Ası́ también, como resultado de la implementación paralela del ordenamiento y la reducción, los métodos GPUgenCube y SPCube demostraron experimentalmente ser poco susceptibles al sesgo y cardinalidad. Por otra parte, el formato para almacenamiento de tuplas en memoria lineal utilizado por los métodos SPCube y GPUgenCube promueve la utilización del ancho de banda del bus PCI Express al acceder y transferir secciones contiguas de memoria entre la RAM y la memoria global de la GPU, reduciendo ası́ los tiempos de transferencia entre estas memorias. Ası́ mismo, este formato en combinación con un ordenamiento externo permite fácilmente extender los métodos para calcular el cubo de datos a partir de conjuntos de datos que no caben en memoria principal. En cuanto a las limitaciones de este trabajo, a pesar del paralelismo con el que cuentan los métodos presentados en esta tesis, la implementación está limitada por la tecnologı́a de almacenamiento utilizada durante las pruebas, es decir, la alta cantidad y paralelización de operaciones de entrada/salida en discos duros normalmente conlleva a una degradación en el desempeño general del software, causada por la baja velocidad de acceso a disco y el movimiento continuo de la cabeza lectora del disco. Por ello, la escritura a memoria secundaria es un factor crı́tico en el desempeño de los métodos paralelos de cubos de datos, ya que de no ser implementada de manera cuidadosa puede resultar en un desempeño pobre en situaciones donde las operaciones de entrada y salida a memoria secundaria predominan, como es el caso del cálculo del cubo completo, donde la salida ocupa cientos de gigabytes de espacio en disco. Esta situación conllevó a obtener un mejor desempeño general en la construcción de cubos iceberg, donde la cantidad de información a escribir en memoria secundaria se reduce y los cálculos aumentan. 133 5.1. Trabajo futuro Entre los trabajos que pueden considerarse para extender el alcance de esta investigación se encuentran: Incluir un ordenamiento externo para extender la funcionalidad de los métodos paralelos a conjuntos de datos que no caben en memoria. La construcción del cubo de datos empleando jerarquı́as en las dimensiones, esto es, con frecuencia se requiere analizar un conjunto de datos a diferentes niveles de detalle con respecto a un cierto atributo (e.g., campos como la fecha o la región geográfica), lo que implica la necesidad de manejar de jerarquı́as en los datos. Incluir el operador CUBE BY en el dialecto SQL de algún manejador de base de datos relacional como [45] para generación de cubos de datos empleando los métodos presentados en este trabajo. Agregar soporte a los métodos para actualización incremental de cubos de datos. Extender el diseño e implementación los métodos paralelos presentados en este trabajo a clusters de computadoras o GPUs conectados a través de un bus de alta velocidad utilizando almacenamiento paralelo. 134 Referencias [1] Sameet Agarwal, Rakesh Agrawal, Prasad M. Deshpande, Ashish Gupta, Jeffrey F. Naughton, Raghu Ramakrishnan, y Sunita Sarawagi. On the computation of multidimensional aggregates. En IN PROCEEDINGS OF THE INTERNATIONAL CONFERENCE ON VERY LARGE DATABASES, págs. 506–521. 1996. [2] Rakesh Agrawal y Ramakrishnan Srikant. Fast algorithms for mining association rules in large databases. En Proceedings of the 20th International Conference on Very Large Data Bases, VLDB ’94, págs. 487–499. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 1994. ISBN 1-55860-153-8. URL http://dl.acm.org/citation.cfm?id=645920. 672836. [3] Kevin Beyer y Raghu Ramakrishnan. Bottom-up computation of sparse and iceberg cube. SIGMOD Rec., 28(2):359–370, 1999. ISSN 0163-5808. doi:10.1145/304181.304214. URL http://doi.acm.org/10.1145/304181.304214. [4] Guy E. Blelloch. Vector models for data-parallel computing. MIT Press, Cambridge, MA, USA, 1990. ISBN 0-262-02313-X. [5] Guy E. Blelloch, Charles E. Leiserson, Bruce M. Maggs, C. Greg Plaxton, Stephen J. Smith, y Marco Zagha. A comparison of sorting algorithms for the connection machine cm-2. En Proceedings of the third annual ACM symposium on Parallel algorithms and architectures, SPAA ’91, págs. 3–16. ACM, New York, NY, USA, 1991. ISBN 0-89791-4384. doi:10.1145/113379.113380. URL http://doi.acm.org/10.1145/113379.113380. [6] Boost. Boost c++ library. 2013. URL http://www.boost.org/. [7] Alejandro Botello, Adolfo Guzmán Arenas, y Renato Barrera. Query resolution in independent databases by partial integration. En International Congress on Data Mining and Information Systems, ICDIS 2007. Centro de Investigación en Computación - Instituto Politécnico Nacional, 2007. 135 [8] Ying Chen, F. Dehne, T. Eavis, y A. Rau-Chaplin. Building large rolap data cubes in parallel. En Database Engineering and Applications Symposium, 2004. IDEAS ’04. Proceedings. International, págs. 367–377. 2004. ISSN 1098-8068. doi:10.1109/IDEAS.2004.1319810. [9] Edgar Chávez, Karina Figueroa, y Gonzalo Navarro. Proximity searching in high dimensional spaces with a proximity preserving order. En Alexander F. Gelbukh, Alvaro de Albornoz, y Hugo Terashima-Marı́n, eds., MICAI, tomo 3789 de Lecture Notes in Computer Science, págs. 405–414. Springer, 2005. ISBN 3-540-29896-7. URL http://dblp.uni-trier.de/db/conf/micai/micai2005.html#ChavezFN05. [10] Jeffrey Dean y Sanjay Ghemawat. Mapreduce: simplified data processing on large clusters. Commun. ACM, 51(1):107–113, 2008. ISSN 0001-0782. doi:10.1145/1327452.1327492. URL http://doi.acm.org/10.1145/1327452.1327492. [11] Andrea C. Dusseau, David E. Culler, Klaus Erik Schauser, y Richard P. Martin. Fast parallel sorting under logp: Experience with the cm-5. IEEE Transactions on Parallel and Distributed Systems, 7:791–805, 1996. [12] Message Passing Interface Forum. Mpi standard. URL http://www.mpi-forum.org/docs/ docs.html. [13] The Apache Software Foundation. Apache hadoop. URL http://hadoop.apache.org/. [14] Veronica Gil-Costa, Cesar Ochoa, y A. Marcela Printista. Suffix array performance analisis for multi-core platforms. Computación y Sistemas, 17(3):391–399, 2013. ISSN 1405-5546. [15] GNU. Standard c++ library. 2013. URL http://gcc.gnu.org/libstdc++/. [16] Jim Gray, Surajit Chaudhuri, Adam Bosworth, Andrew Layman, Don Reichart, Murali Venkatrao, Frank Pellow, y Hamid Pirahesh. Data cube: A relational aggregation operator generalizing group-by, cross-tab, and sub-totals. Data Min. Knowl. Discov., 1(1):29–53, 1997. ISSN 1384-5810. doi:10.1023/A:1009726021843. URL http://dx.doi.org/10.1023/ A:1009726021843. [17] William Gropp, Ewing Lusk, y Anthony Skjellum. Using MPI (2nd ed.): portable parallel programming with the message-passing interface. MIT Press, Cambridge, MA, USA, 1999. ISBN 0-262-57132-3. [18] Peter J. Haas, Jeffrey F. Naughton, S. Seshadri, y Lynne Stokes. Sampling-based estimation of the number of distinct values of an attribute. En Proceedings of the 21th International Conference on Very Large Data Bases, VLDB ’95, págs. 311–322. Morgan 136 Kaufmann Publishers Inc., San Francisco, CA, USA, 1995. ISBN 1-55860-379-4. URL http://dl.acm.org/citation.cfm?id=645921.673295. [19] Jiawei Han, Jian Pei, Guozhu Dong, y Ke Wang. Efficient computation of iceberg cubes with complex measures. SIGMOD Rec., 30(2):1–12, 2001. ISSN 0163-5808. doi:10.1145/ 376284.375664. URL http://doi.acm.org/10.1145/376284.375664. [20] Bingsheng He, Naga K. Govindaraju, Qiong Luo, y Burton Smith. Efficient gather and scatter operations on graphics processors. En Proceedings of the 2007 ACM/IEEE Conference on Supercomputing, SC ’07, págs. 46:1–46:12. ACM, New York, NY, USA, 2007. ISBN 978-1-59593-764-3. doi:10.1145/1362622.1362684. URL http://doi.acm.org/10. 1145/1362622.1362684. [21] J. L. Hennessy y D. A. Patterson. Computer Architecture: A quantitative approach. Morgan Kauffman Publishers Inc., 2002. [22] Jared Hoberock y Nathan Bell. Thrust: A parallel template library. 2012. URL http: //thrust.github.io/. [23] IEEE. Ieee std 1003.1. URL http://standards.ieee.org/findstds/standard/1003. 1-2008.html. [24] Donald E. Knuth. The Art of Computer Programming, Volume III: Sorting and Searching. Addison-Wesley, 1973. ISBN 0-201-03803-X. [25] Mariela Lopresti, Natalia Miranda, Fabiana Piccoli, y Nora Reyes. Solving multiple queries through the permutation index in gpu. Computación y Sistemas, 17(3):341–356, 2013. ISSN 1405-5546. [26] Hua Luan, Xiao-Yong Du, y Shan Wang. Cache-conscious data cube computation on a modern processor. Journal of Computer Science and Technology, 24:708–722, 2009. ISSN 1000-9000. URL http://dx.doi.org/10.1007/s11390-009-9253-0. 10.1007/s11390-0099253-0. [27] Gilberto Martı́nez y Adolfo Guzmán. Búsqueda de patrones de comportamiento en cubos de datos. En Segundo taller internacional de minerı́a de datos, MINDAT 2000, págs. 163– 179. Colegio de Postgraduados y Centro de Investigación en Computación, Texcoco, estado de México., 2000. [28] Microsoft. Directx developer center. 2012. URL http://msdn.microsoft.com/en-us/ directx/. 137 [29] Open MPI. A high performance message passing library. URL http://www.open-mpi.org. [30] Raymond T. Ng, Laks V. S. Lakshmanan, Jiawei Han, y Alex Pang. Exploratory mining and pruning optimizations of constrained associations rules. SIGMOD Rec., 27(2):13–24, 1998. ISSN 0163-5808. doi:10.1145/276305.276307. URL http://doi.acm.org/10.1145/ 276305.276307. [31] Raymond T. Ng, Alan Wagner, y Yu Yin. Iceberg-cube computation with pc clusters. SIGMOD Rec., 30(2):25–36, 2001. ISSN 0163-5808. doi:10.1145/376284.375666. URL http://doi.acm.org/10.1145/376284.375666. [32] NVIDIA. Fermi compute architecture whitepaper. URL http://www.nvidia. com/content/PDF/fermi_white_papers/NVIDIA_Fermi_Compute_Architecture_ Whitepaper.pdf. [33] NVIDIA. What is cuda. 2012. URL http://developer.nvidia.com/what-cuda. [34] NVIDIA. Cuda c programming guide. 2013. URL http://docs.nvidia.com/cuda/ cuda-c-programming-guide/. [35] NVIDIA. Nvidia corporation. 2013. URL http://www.nvidia.com/page/home.html. [36] OpenGL. About opengl. 2012. URL http://www.opengl.org/about/. [37] Stefan Popov, Johannes Günther, Hans-Peter Seidel, y Philipp Slusallek. Stackless kd-tree traversal for high performance GPU ray tracing. Computer Graphics Forum, 26(3):415–424, 2007. (Proceedings of Eurographics). [38] Angel Omar Cervantez Ramı́rez. Paralelización de un subconjunto de consultas SQL con unión natural utilizando una GPU. Tesis de maestrı́a, Centro de Investigación en Computación - Instituto Politécnico Nacional, 2012. [39] Jun Rao y Kenneth A. Ross. Cache conscious indexing for decision-support in main memory. En Proceedings of the 25th International Conference on Very Large Data Bases, VLDB ’99, págs. 78–89. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 1999. ISBN 1-55860-615-7. URL http://dl.acm.org/citation.cfm?id=645925.671362. [40] Kenneth A. Ross y Divesh Srivastava. Fast computation of sparse datacubes. En Proceedings of the 23rd International Conference on Very Large Data Bases, VLDB ’97, págs. 116–125. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 1997. ISBN 1-55860470-7. URL http://dl.acm.org/citation.cfm?id=645923.670993. 138 [41] Nadathur Satish, Mark Harris, y Michael Garland. Designing efficient sorting algorithms for manycore gpus. En Proceedings of the 2009 IEEE International Symposium on Parallel&Distributed Processing, IPDPS ’09, págs. 1–10. IEEE Computer Society, Washington, DC, USA, 2009. ISBN 978-1-4244-3751-1. doi:10.1109/IPDPS.2009.5161005. URL http://dx.doi.org/10.1109/IPDPS.2009.5161005. [42] Robert Sedgewick. Algorithms in C. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1990. ISBN 0-201-51425-7. [43] Zheng Shao, Jiawei Han, y Dong Xin. Mm-cubing: Computing iceberg cubes by factorizing the lattice space. En Proceedings of the 16th International Conference on Scientific and Statistical Database Management, SSDBM ’04, págs. 213–. IEEE Computer Society, Washington, DC, USA, 2004. ISBN 0-7695-2146-0. doi:10.1109/SSDBM.2004.53. URL http://dx.doi.org/10.1109/SSDBM.2004.53. [44] Amit Shukla, Prasad M. Deshpande, Jeffrey F. Naughton, y Karthikeyan Ramasamy. Storage estimation for multidimensional aggregates in the presence of hierarchies. págs. 522–531. 1996. [45] SQLite. About sqlite. URL http://www.sqlite.org/about.html. [46] UIUC. Illimine. URL http://illimine.cs.uiuc.edu/. [47] Baoyuan Wang y Yizhou Yu. Parallel h-tree based data cubing on graphics processors. Int. J. Software and Informatics, 6(1):61–87, 2012. [48] Hwu Wen-mei, Kurt-Keutzer, y Timothy G. Mattson. The concurrency challenges. IEEE Design & Test of Computers, 25(4):312 – 320, 2008. ISSN 07407475. URL http://search.ebscohost.com/login.aspx?direct=true&db=iih&AN= 33558699&lang=es&site=ehost-live. [49] Dong Xin, Jiawei Han, Xiaolei Li, y Benjamin W. Wah. Star-cubing: computing iceberg cubes by top-down and bottom-up integration. En Proceedings of the 29th international conference on Very large data bases - Volume 29, VLDB ’03, págs. 476–487. VLDB Endowment, 2003. ISBN 0-12-722442-4. URL http://dl.acm.org/citation.cfm?id=1315451. 1315493. [50] Marco Zagha y Guy E. Blelloch. Radix sort for vector multiprocessors. En In Proceedings Supercomputing ’91, págs. 712–721. 1991. 139 [51] Guoliang Zhou y Hong Chen. Parallel cube computation on modern cpus and gpus. The Journal of Supercomputing, 61:394–417, 2012. ISSN 0920-8542. doi:10.1007/ s11227-011-0575-7. URL http://dx.doi.org/10.1007/s11227-011-0575-7. [52] Kun Zhou, Qiming Hou, Rui Wang, y Baining Guo. Real-time kd-tree construction on graphics hardware. ACM Trans. Graph., 27(5):126:1–126:11, 2008. ISSN 0730-0301. doi: 10.1145/1409060.1409079. URL http://doi.acm.org/10.1145/1409060.1409079. 140