Las aplicaciones informáticas de alto rendimiento que se ejecutan en supercomputadoras masivamente paralelas consisten en programas concurrentes diseñados utilizando modelos multiproceso y multihilo . Las aplicaciones pueden constar de varias construcciones ( hilos , procesos locales, procesos distribuidos, etc.) con distintos grados de paralelismo. Aunque los programas concurrentes de alto rendimiento utilizan patrones de diseño, modelos y principios similares a los de los programas secuenciales, a diferencia de estos, suelen demostrar un comportamiento no determinista. La probabilidad de errores aumenta con el número de interacciones entre las diversas construcciones paralelas. Las condiciones de carrera , las carreras de datos, los bloqueos , las señales perdidas y el bloqueo en vivo son tipos de error comunes.
Los programas paralelos se pueden dividir en dos categorías generales: explícitamente paralelos e implícitamente paralelos . El uso de construcciones de lenguaje paralelo definidas para la creación, comunicación y sincronización de procesos hace que una aplicación sea explícitamente paralela. El uso de una herramienta o compilador paralelizador para convertir un programa serial en uno paralelo lo hace implícitamente paralelo. Ambas categorías son igualmente propensas a errores.
Las aplicaciones concurrentes deberían ejecutarse correctamente en cada posible programación de subprocesos en el sistema operativo subyacente. Sin embargo, los métodos de prueba tradicionales detectan pocos errores, principalmente debido al problema de los Heisenbugs [1] . Un Heisenbug es un error que cambia o desaparece cuando se intenta aislarlo y analizarlo mediante el depurador , agregando algunas construcciones como solicitudes de sincronización o declaraciones de demora.
Otro problema se debe al comportamiento impredecible del planificador . Las diferencias en la carga del sistema influyen en el comportamiento del planificador. Este comportamiento no se puede cambiar manualmente. Para contrarrestar esta indeterminación, el programa debe ejecutarse muchas veces en varios entornos de ejecución. Aun así, no se garantiza que se pueda reproducir un error. La mayoría de las veces, el programa se ejecuta correctamente y el error solo es visible cuando se cumplen condiciones específicas. Como resultado, la no repetibilidad de los programas concurrentes es una fuente importante de obstáculos para detectar errores. Como ejemplo, considere lo siguiente.
Es evidente que esto tiene el problema de provocar bloqueos. Sin embargo, puede provocar bloqueos en algunas ejecuciones del programa, mientras que en otras puede ejecutarse correctamente.
El efecto de sondeo se observa en programas paralelos cuando se insertan instrucciones de retardo en programas paralelos que enfrentan problemas de sincronización. Este efecto, al igual que los Heisenbugs, altera los cambios de comportamiento que pueden ocultar los problemas. Detectar la fuente de un efecto de sondeo es un gran desafío en las pruebas de aplicaciones paralelas.
La principal diferencia entre el efecto de sondeo y los Heisenbugs es que los Heisenbugs se observan cuando se agregan instrucciones de retardo adicionales y/o solicitudes de sincronización a la aplicación concurrente durante la prueba, mientras que el efecto de sondeo se observa cuando el desarrollador agrega instrucciones de retardo a aplicaciones concurrentes con mala sincronización.
Las diferencias entre los programas secuenciales y concurrentes conducen a las diferencias en sus estrategias de prueba. Las estrategias para programas secuenciales se pueden modificar para que sean adecuadas para aplicaciones concurrentes. También se han desarrollado estrategias especializadas. Convencionalmente, las pruebas incluyen el diseño de casos de prueba y la verificación de que el programa produce los resultados esperados. Por lo tanto, los errores en la especificación, funcionalidad, etc. se detectan ejecutando la aplicación y sometiéndola a métodos de prueba como pruebas funcionales , caja blanca , caja negra y caja gris . [2] El análisis estático también se utiliza para detectar errores en software de alto rendimiento utilizando métodos como análisis de flujo de datos , análisis de flujo de control , complejidades ciclomáticas , análisis de escape de subprocesos y análisis de corte estático para encontrar problemas. El uso del análisis estático antes de las pruebas de funcionalidad puede ahorrar tiempo. Puede detectar "cuál es el error" y encontrar la fuente del error. Las técnicas de análisis estático pueden detectar problemas como la falta de sincronización , sincronizaciones incorrectas, predecir la aparición de bloqueos y errores posteriores a la espera en las solicitudes de encuentro .
Detalles:
La indeterminación de la programación tiene dos fuentes. [1]
Para que los programas concurrentes sean repetibles, se utiliza un planificador externo. El programa en prueba está instrumentado para agregar llamadas a este planificador. Dichas llamadas se realizan al principio y al final de cada hilo, así como antes de cada solicitud de sincronización. Este planificador bloquea selectivamente los hilos de ejecución manteniendo un semáforo asociado con cada hilo, de modo que solo un hilo esté listo para la ejecución en un momento dado. De este modo, convierte una aplicación paralela no determinista en una secuencia de ejecución en serie para lograr la repetibilidad. El número de decisiones de planificación tomadas por el planificador serializador está dado por:
(N * K / P)*{(N + P)!}
Donde
N = número de hilos
K = posibles puntos de cambio de contexto
P = presupuesto de cambios de contexto preventivos
Para obtener resultados más precisos utilizando una programación determinista, se puede elegir un enfoque alternativo. Unas pocas preempciones ubicadas correctamente en el programa concurrente pueden detectar errores relacionados con las carreras de datos. [1] Los errores se encuentran en grupos. La existencia de un error establece una alta probabilidad de que haya más errores en la misma región del código. Por lo tanto, cada paso del proceso de prueba identifica secciones del código con errores. El siguiente paso examina más a fondo esas secciones agregando llamadas del programador a su alrededor. Permitir que las ubicaciones problemáticas se ejecuten en un orden diferente puede revelar un comportamiento inesperado.
Esta estrategia garantiza que la aplicación no sea propensa al efecto de sondeo. Las fuentes de errores que provocan el efecto de sondeo pueden ir desde problemas de creación de tareas hasta problemas de sincronización y comunicación. Requisitos de las pruebas relacionadas con el tiempo: [3]
El número de casos de prueba por conjunto de datos de entrada es:
nC1 + nC1 + … + nC1 = 2n - 1
Donde n = número total de llamadas de sincronización, creación de procesos y comunicación.
Esta ecuación tiene orden exponencial. Para reducir la cantidad de casos de prueba, se utiliza el método de ejecución determinista (DET) o la técnica de ejecución múltiple (MET). Se deben abordar varios problemas:
Este método aplica el concepto de par definición-uso, para determinar las rutas a probar.
La verificación de software es un proceso que demuestra que el software funciona correctamente y realiza la tarea prevista según lo diseñado.
Se proporciona una entrada al sistema para generar un resultado conocido. Este par de entrada-resultado se puede obtener a partir de resultados empíricos previos y/o cálculos manuales. [4] Esta es una prueba a nivel de sistema que se puede realizar solo cuando todos los módulos relevantes están integrados. Además, solo muestra que existen errores. No ofrece información detallada sobre la cantidad de errores, su ubicación o naturaleza.
Estas pruebas se utilizan principalmente para simulaciones científicas. A menudo, no se puede predecir el resultado de la simulación. Dado que estas simulaciones intentan describir leyes científicas, la simulación debe respetar cualquier simetría en la teoría. Por lo tanto, al variar las condiciones de entrada a lo largo de las líneas de simetría y luego comparar los resultados obtenidos con resultados derivados externamente, se puede detectar la existencia de errores. [4]
En la informática científica, la mayoría de los datos se encuentran en la región central de las condiciones de simulación. Como resultado, es difícil realizar pruebas de valores límite [2] con datos experimentales en tiempo real. Por lo tanto, el centro de la simulación (por ejemplo, para el valor de datos de 10 en la Figura 1) se desplaza a uno de los límites para probar la condición límite de manera efectiva.
Las pruebas de implementación paralela se utilizan generalmente para aplicaciones que utilizan modelos de programación de memoria distribuida , como el paso de mensajes . Estas pruebas se suelen aplicar a programas que utilizan cuadrículas regulares de procesadores. [4] [ Aclaración necesaria ]
Muchas bases de datos paralelas utilizan el procesamiento paralelo distribuido para ejecutar las consultas. Al ejecutar una función de agregación como suma, se utiliza la siguiente estrategia: [5]
El resultado final puede contener algún error de redondeo, ya que cada procesador redondea de forma independiente los resultados locales. Una prueba consiste en garantizar que no se produzcan dichos errores. Para ello, es necesario demostrar que la suma agregada es independiente de la descomposición. Un esquema de suma alternativo consiste en enviar todos los valores individuales a un procesador para sumarlos. Este resultado se puede comparar con el resultado distribuido para garantizar la coherencia.
Esta herramienta elimina la falta de determinación mediante una programación determinista. Realiza un seguimiento de las rutas de programación ejecutadas anteriormente y garantiza que cada vez se ejecute una nueva ruta de programación. [1] [ Aclaración necesaria ]