Pregunta Evitar regresiones de rendimiento en R


¿Qué es un buen flujo de trabajo para detectar regresiones de rendimiento en paquetes R? Idealmente, estoy buscando algo que se integre con R CMD check eso me alerta cuando introduje una regresión de rendimiento significativa en mi código.

¿Qué es un buen flujo de trabajo en general? ¿Qué otros idiomas proporcionan buenas herramientas? ¿Es algo que se puede construir en la unidad de prueba superior, o que se suele hacer por separado?


32
2017-12-11 15:17


origen


Respuestas:


Esta es una pregunta muy desafiante, y con la que estoy tratando con frecuencia, ya que cambio un código diferente en un paquete para acelerar las cosas. A veces, una regresión de rendimiento viene junto con un cambio en los algoritmos o la implementación, pero también puede surgir debido a cambios en las estructuras de datos utilizadas.

¿Qué es un buen flujo de trabajo para detectar regresiones de rendimiento en paquetes R?

En mi caso, tiendo a tener casos de uso muy específicos que estoy tratando de acelerar, con diferentes conjuntos de datos fijos. Como Spacedman escribió, es importante tener un sistema informático fijo, pero eso es casi inviable: a veces, una computadora compartida puede tener otros procesos que ralentizan las cosas un 10-20%, incluso cuando parece bastante inactivo.

Mis pasos:

  1. Estandarice la plataforma (por ejemplo, una o algunas máquinas, una máquina virtual en particular, o una máquina virtual + infraestructura específica, como los tipos de instancias de EC2 de Amazon).
  2. Estandarice el conjunto de datos que se usará para la prueba de velocidad.
  3. Cree scripts y datos de salida intermedios fijos (es decir, guardados en archivos .rdat) que impliquen transformaciones de datos mínimas. Mi atención se centra en algún tipo de modelado, en lugar de manipulación o transformación de datos. Esto significa que quiero dar exactamente el mismo bloque de datos a las funciones de modelado. Sin embargo, si el objetivo es la transformación de datos, asegúrese de que los datos pretratados / manipulados estén lo más cerca posible del estándar en las pruebas de diferentes versiones del paquete. (Ver esta pregunta para ejemplos de memorización, caché, etc., que se pueden usar para estandarizar o acelerar cómputos no focales. Hace referencia a varios paquetes por el OP.)
  4. Repita las pruebas varias veces.
  5. Escala los resultados relativos a los puntos de referencia fijos, p. el momento de realizar una regresión lineal, ordenar una matriz, etc. Esto puede permitir variaciones "locales" o transitorias en la infraestructura, como pueden ser debido a E / S, el sistema de memoria, paquetes dependientes, etc.
  6. Examine la salida del perfil lo más vigorosamente posible (ver esta pregunta para algunas ideas, también haciendo referencia a las herramientas del OP).

    Idealmente, estoy buscando algo que se integre con la verificación R CMD que me alerta cuando introduje una regresión de rendimiento significativa en mi código.

    Lamentablemente, no tengo una respuesta para esto.

    ¿Qué es un buen flujo de trabajo en general?

    Para mí, es bastante similar a la prueba de código dinámico general: ¿el resultado (tiempo de ejecución en este caso) es reproducible, óptimo y transparente? La transparencia proviene de la comprensión de lo que afecta el tiempo general. Aquí es donde las sugerencias de Mike Dunlavey son importantes, pero prefiero ir más allá, con un perfilador de líneas.

    En cuanto a un perfilador de línea, consulte mi pregunta anterior, que se refiere a las opciones en Python y Matlab para otros ejemplos. Es más importante examinar el tiempo del reloj, pero también es muy importante seguir la asignación de memoria, la cantidad de veces que se ejecuta la línea y la profundidad de la pila de llamadas.

    ¿Qué otros idiomas proporcionan buenas herramientas?

    Casi todos los demás idiomas tienen mejores herramientas. :) Los lenguajes interpretados como Python y Matlab tienen buenos y posiblemente familiares ejemplos de herramientas que se pueden adaptar para este propósito. Aunque el análisis dinámico es muy importante, el análisis estático puede ayudar a identificar dónde puede haber algunos problemas graves. Matlab tiene un gran analizador estático que puede informar cuando los objetos (por ejemplo, vectores, matrices) están creciendo dentro de los bucles, por ejemplo. Es terrible encontrar esto solo a través del análisis dinámico: ya has desperdiciado tiempo de ejecución para descubrir algo como esto, y no siempre es discernible si tu contexto de ejecución es bastante simple (por ejemplo, solo unas pocas iteraciones u objetos pequeños).

    En cuanto a los métodos independientes del idioma, puedes mirar:

    1. Valgrind y cachegrind
    2. Monitoreo de E / S de disco, búferes sucios, etc.
    3. Monitoreo de la memoria RAM (Cachegrind es útil, pero solo puedes monitorear la asignación de RAM, y muchos detalles sobre el uso de RAM)
    4. Uso de múltiples núcleos

    ¿Es algo que se puede construir en la unidad de prueba superior, o que se suele hacer por separado?

    Esto es difícil de responder. Para el análisis estático, puede ocurrir antes de la prueba unitaria. Para el análisis dinámico, es posible que desee agregar más pruebas. Piense en ello como un diseño secuencial (es decir, a partir de un marco de diseño experimental): si los costos de ejecución parecen ser, dentro de algunas tolerancias estadísticas para la variación, lo mismo, entonces no se necesitan más pruebas. Sin embargo, si el método B parece tener un costo de ejecución promedio mayor que el método A, entonces se deben realizar pruebas más intensivas.


Actualización 1: si puedo ser tan audaz, hay otra pregunta que recomendaría incluir, que es: "¿Cuáles son algunos inconvenientes al comparar el tiempo de ejecución de dos versiones de un paquete?" Esto es análogo a suponer que dos programas que implementan el mismo algoritmo deberían tener los mismos objetos intermedios. Eso no es exactamente cierto (ver esta pregunta - no es que esté promoviendo mis propias preguntas, aquí - es solo un trabajo duro para hacer las cosas mejor y más rápido ... dando lugar a múltiples preguntas sobre este tema :)). De manera similar, dos ejecuciones del mismo código pueden diferir en el tiempo consumido debido a factores distintos a la implementación.

Por lo tanto, algunos errores que pueden ocurrir, ya sea dentro del mismo idioma o en diferentes idiomas, dentro de la misma instancia de ejecución o en instancias "idénticas", que pueden afectar el tiempo de ejecución:

  1. Recolección de basura: diferentes implementaciones o idiomas pueden afectar la recolección de basura bajo diferentes circunstancias. Esto puede hacer que dos ejecuciones parezcan diferentes, aunque puede depender mucho del contexto, los parámetros, los conjuntos de datos, etc. La ejecución obsesiva del GC parecerá más lenta.
  2. Almacenamiento en caché a nivel del disco, placa base (por ejemplo, cachés L1, L2, L3) u otros niveles (por ejemplo, memoria). A menudo, la primera ejecución pagará una penalización.
  3. Escalado de voltaje dinámico - Esta es una mierda. Cuando hay un problema, esta puede ser una de las bestias más difíciles de encontrar, ya que puede desaparecer rápidamente. Parece cacheing, pero no lo es.
  4. Cualquier administrador de prioridad de trabajo que no conozca.
  5. Un método utiliza múltiples núcleos o hace algunas cosas inteligentes sobre cómo se reparte el trabajo entre núcleos o CPU. Por ejemplo, conseguir un proceso bloqueado en un núcleo puede ser útil en algunos escenarios. Una ejecución de un paquete R puede ser más afortunada a este respecto, otro paquete puede ser muy inteligente ...
  6. Variables no utilizadas, transferencia de datos excesiva, cachés sucias, memorias intermedias no eliminadas, ... la lista continúa.

El resultado clave es: idealmente, ¿cómo deberíamos probar las diferencias en los valores esperados, sujeto a la aleatoriedad creada debido a los efectos del pedido? Bueno, bastante simple: regrese al diseño experimental. :)

Cuando las diferencias empíricas en los tiempos de ejecución son diferentes de las diferencias "esperadas", es genial haber habilitado el monitoreo adicional del sistema y la ejecución para que no tengamos que volver a ejecutar los experimentos hasta que tengamos el rostro azul.


17
2017-12-31 18:41



La única forma de hacer algo aquí es hacer algunas suposiciones. Así que supongamos una máquina sin cambios, o si no, requiramos una 'recalibración'.

A continuación, utilice un marco similar de prueba de unidad, y considere que "se debe hacer en X unidades de tiempo" como otro criterio de prueba que se debe cumplir. En otras palabras, haz algo como

 stopifnot( timingOf( someExpression ) < savedValue plus fudge)

entonces tendríamos que asociar tiempos previos con expresiones dadas. También se podrían usar comparaciones de pruebas de igualdad de cualquiera de los tres paquetes de pruebas unitarias existentes.

Nada que Hadley no pudiera manejar, así que creo que casi podemos esperar un nuevo paquete timr después de la próxima pausa académica larga :). Por supuesto, esto tiene que ser opcional porque en una máquina "desconocida" (piense: CRAN probando el paquete) no tenemos un punto de referencia, o el factor dulce tiene que "ir a 11" para aceptar automáticamente en una máquina nueva .


10
2017-12-11 16:43



Un cambio reciente anunciado en la alimentación R-devel podría dar una medida cruda para esto.

CAMBIOS EN LOS SERVICIOS PÚBLICOS DE R-DEVEL

'R CMD check' puede informar opcionalmente los tiempos en varias partes de la comprobación: esto está controlado por variables de entorno documentadas en 'Writing R Extensions'.

Ver http://developer.r-project.org/blosxom.cgi/R-devel/2011/12/13#n2011-12-13

El tiempo total dedicado a ejecutar las pruebas podría verificarse y compararse con valores anteriores. Por supuesto, agregar nuevas pruebas aumentará el tiempo, pero aún se pueden ver regresiones de rendimiento dramáticas, aunque de forma manual.

Esto no es tan detallado como el soporte de tiempo dentro de suites de prueba individuales, pero tampoco depende de ningún conjunto de pruebas específico.


4
2017-12-13 20:16