A medida que los tamaños de los datos han crecido en empresas de todas las industrias, Parquet Apache se ha convertido en un formato prominente para almacenar datos.
Apache Parquet es un formato de almacenamiento columnar diseñado para un procesamiento de datos eficiente a escala. Al organizar los datos por columnas en lugar de filas, Parquet permite consultas y análisis de alto rendimiento, ya que solo puede leer las columnas necesarias para una consulta en lugar de escanear filas completas de datos. El diseño de datos eficiente de Parquet, lo ha convertido en una opción popular en el ecosistema de análisis moderno, específicamente con las cargas de trabajo de Apache Spark.
El RAPIDS Accelerator para Apache Spark construido sobre cuDF admite Parquet como formato de datos para leer y escribir datos de manera acelerada en GPU. Para muchas cargas de trabajo de Spark a gran escala donde los tamaños de entrada de datos están en terabytes, tener escaneos de parquet eficientes es fundamental para lograr un buen rendimiento en tiempo de ejecución.
En esta publicación, discutimos cómo mitigar las limitaciones de ocupación causadas por usos de registro más altos y compartir resultados de referencia.
Formato de datos de parquet Apache
El formato de archivo Parquet permite almacenar los datos en formato columnar utilizando trozos de columna ensamblados en grupos de filas. Los metadatos son distintos de los datos para permitir la división de columnas en varios archivos según sea necesario (Figura 1).

El formato Parquet admite una variedad de tipos de datos. Los metadatos especifican cómo deben interpretarse estos tipos, lo que permite que estos tipos representen tipos lógicos más complejos, como marcas de tiempo, cadenas, decimales, etc.
También puede usar metadatos para especificar estructuras más complejas, como tipos anidados y listas. Los datos se pueden codificar en una variedad de formatos diferentes, como valores simples, diccionarios, codificación de longitud de ejecución, empaque de bits y más.
- BOOLEAN: 1 bit boolean - INT32: 32 bit signed ints - INT64: 64 bit signed ints - INT96: 96 bit signed ints - FLOAT: IEEE 32-bit floating point values - DOUBLE: IEEE 64-bit floating point values - BYTE_ARRAY: arbitrarily long byte arrays - FIXED_LEN_BYTE_ARRAY: fixed length byte arrays |
Parquet sobre limitaciones de ocupación de GPU
Antes de la RAPIDS Accelerator para Apache Spark, la implementación anterior de los escaneos de parquet fue un núcleo cuDF monolítico que admitía todos los tipos de columnas de parquet en un solo conjunto de código de procesamiento.
A medida que la adopción de Spark en GPU aumentó para los clientes con datos de Parquet, se invirtió más tiempo en comprender las características de rendimiento de los escaneos de Parquet dado el componente crítico del rendimiento que representaba. Hay varios recursos generales que tienen en cuenta la eficiencia con la que un kernel puede operar:
- Microprocesadores de transmisión (SM): Unidad de procesamiento principal de la GPU, que es responsable de ejecutar tareas informáticas.
- Memoria compartida: Memoria en chip de GPU que se asigna por bloque de subprocesos, de modo que todos los subprocesos en el mismo bloque puedan acceder a la misma memoria compartida.
- Registros: Memoria GPU rápida en chip que almacena información utilizada por un solo hilo para las operaciones de cómputo que ejecutan los SM.
A medida que analizamos los escaneos de parquet, observamos que la ocupación general de la GPU era menor de lo deseado debido a los límites de registro. El uso del registro está determinado por cómo el compilador CUDA genera código basado en la lógica del kernel más la gestión de datos.
Para el núcleo monolítico de Parquet, la complejidad para admitir todos los tipos de columnas creó un núcleo grande y complejo con alta memoria compartida y uso de registro. Si bien un solo núcleo monolítico puede haber consolidado el código juntos, su complejidad limitó los posibles tipos de optimizaciones y dio como resultado limitaciones de rendimiento a escala.

La Figura 2 representa el bucle de procesamiento de datos de Parquet en la GPU. Cada uno de los bloques es una cantidad grande y compleja de código del kernel, que puede tener sus propios requisitos de memoria compartida. Muchos de los bloques dependen del tipo, lo que conduce a un núcleo hinchado cargado en la memoria.
Específicamente, una de las limitaciones era cómo se decodificaban los bloques de parquet dentro de las urdimbres. Había una dependencia en serie para que las urdimbres esperaran a que las urdimbres ordenadas previamente se completaran antes de procesar sus bloques de datos. Esto permitió que diferentes partes del proceso de decodificación ocurrieran en diferentes deformaciones, pero creó una dependencia ineficiente para el trabajo que se realizará en la GPU.
Pasar a un algoritmo de decodificación de todo el bloque fue crítico para el rendimiento, pero habría aumentado el recuento de registros y la ocupación limitada aún más debido a su intercambio de datos agregado y la complejidad de sincronización.
Microkernels de parquet en cuDF
Para mitigar las limitaciones de ocupación causadas por usos de registro más altos, probamos una idea inicial de un núcleo más pequeño para preprocesar datos de tipo de lista en Parquet. Separamos un fragmento de código del núcleo monolítico en un núcleo autónomo y los resultados fueron impresionantes. Los puntos de referencia generales mostraron tiempos de ejecución más rápidos y los rastros de GPU demostraron una mejor ocupación.
Después de eso, probamos el mismo enfoque para diferentes tipos de columnas. Los microkernels para varios tipos utilizaron plantillas C++ para reutilizar las capacidades. Esto simplificó el código para el mantenimiento y la depuración por tipo.

El enfoque de microkernel de Parquet aprovecha la optimización del tiempo de compilación para pasar solo por las rutas de código necesarias para procesar un tipo determinado. En lugar de un núcleo monolítico que contiene todas las rutas de código posibles, puede generar muchos microkernels individuales que contienen solo el código necesario para esa ruta.
Esto se puede hacer usando if constexpr
en el momento de la compilación, de modo que el código se lea normalmente, pero no incluya ninguna ruta de código que nunca se tome para una combinación particular de atributos de datos (cadenas o ancho fijo, listas o listas no, etc.).
Aquí hay un ejemplo simple para procesar columnas de tipo de ancho fijo. Puede ver que la mayoría del procesamiento no es necesario y se omite en el nuevo enfoque de microkernel. Solo se necesita la copia de datos para este tipo.

Para resolver el cuello de botella entre las deformaciones, los nuevos microkernels permitieron procesar bloques enteros en cada paso para que las urdimbre pudieran procesar datos de forma independiente de manera más eficiente. Esto fue especialmente significativo para que las cadenas permitieran que un bloque completo de 128 subprocesos en la GPU copiara cadenas donde la implementación anterior solo usaba una urdimbre para copiar cadenas.
Ejecutamos puntos de referencia locales utilizando una GPU NVIDIA RTX A5000 de 24GB con datos comprimidos de Parquet Snappy de 512 MB precargados en búferes de dispositivos. Para probar lecturas fragmentadas, leemos fragmentos de 500 KB a la vez. Los datos de prueba incluyeron algunas variaciones:
- Cardinalidad 0 y 1000
- Longitud de ejecución 1 y 32
- 1% nulos
- Uso adaptativo del diccionario para ser utilizado si se repitieron los datos
La Figura 5 muestra los resultados en términos de mejora del rendimiento en todos los tipos de columnas de parquet con el nuevo enfoque de microkernel en la GPU.

Una optimización para la lectura fragmentada de las columnas de la lista también mejoró el rendimiento en un 117% para las lecturas de 500 KB.
Comenzando con Apache Spark en GPU
El parquet es un formato de datos clave en amplia adopción para el procesamiento de datos grandes. La GPU puede acelerar el escaneo de datos de parquet en Apache Spark mediante el uso de microkernels optimizados en cuDF.
Las empresas pueden aprovechar el RAPIDS Accelerator para Apache Spark para migrar sin problemas las cargas de trabajo de Apache Spark a las GPU NVIDIA. RAPIDS Accelerator para Apache Spark aprovecha las GPU para acelerar el procesamiento al combinar la potencia de la biblioteca cuDF de RAPIDS y la escala del marco informático distribuido de Spark. Ejecute las aplicaciones Apache Spark existentes en GPU sin cambios de código iniciando Spark con RAPIDS Accelerator para el archivo JAR del complemento Apache Spark.
Obtenga información práctica con el procesamiento de escaneo de parquet y el acelerador RAPIDS para Apache Spark con el Spark RAPIDS Aceleración de parquet Cuaderno colab. NVIDIA Blog. M. A., P. M. y D- B. Traducido al español