2011-12-11 10 views
32

¿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 que me alerta cuando introduje una regresión de rendimiento significativa en mi código.Prevención de regresiones de rendimiento en R

¿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?

+2

Tricky. Puede que ni siquiera estés ejecutando las pruebas en la misma PC que anteriormente, por lo que tendrá que ser relativa a un punto de referencia estable ... – Spacedman

+4

* [Este método] (http://stackoverflow.com/questions/375913/what -can-i-use-to-profile-c-code-in-linux/378024 # 378024) * funciona en cualquier idioma, incluido R. No mide el tiempo con precisión; lo que hace es señalar con precisión el código que lleva tiempo, y da una estimación aproximada del porcentaje de tiempo que lleva. Si ve un cambio en lo que lleva tiempo, o un aumento significativo en su porcentaje, eso le indica cuál es la regresión. –

+0

... Si identifica algo que requiere un alto porcentaje, y lo arregla, verá que el porcentaje se reducirá y el tiempo total disminuirá. El porcentaje de otra cosa aumentará porque toma una fracción mayor de un total menor. Eso es de esperar. –

Respuesta

17

Esta es una pregunta muy desafiante, y una 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. estandarizar la plataforma (por ejemplo, una o unas pocas máquinas, una máquina virtual en concreto, o una máquina virtual + infraestructura específica, los tipos de instancias EC2 del A la Amazonas).
  2. Estandarice el conjunto de datos que se usará para la prueba de velocidad.
  3. Cree scripts y datos intermedios fijos de salida (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. (Consulte this question para ver 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. Escale 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 las debidas a E/S, el sistema de memoria, paquetes dependientes, etc.
  6. Examine la salida de generación de perfiles lo más vigorosamente posible (consulte this question para obtener información, también haciendo referencia a las herramientas desde 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: ¿la salida (tiempo de ejecución en este caso) es reproducible, óptima 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 generador de perfiles de línea, consulte my previous question, 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 los buenos 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 independiente del idioma, se puede ver en:

    1. Valgrind & Cachegrind
    2. Seguimiento de disco E/S, tampones sucios, etc.
    3. Monitoreo de RAM (es Cachegrind útiles, pero sólo podría supervisar la asignación de memoria RAM, y un montón de detalles sobre el uso de RAM)
    4. El 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 me permite el atrevimiento, hay otra pregunta que me gustaría recomendar que incluye, que es: "¿Cuáles son algunas trampas en 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 this question, no es que esté promoviendo mis propias preguntas, aquí, es solo un trabajo difícil hacer las cosas mejor y más rápido ... dando lugar a múltiples preguntas SO 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.

Así, algunas trampas que pueden ocurrir, ya sea dentro de la misma lengua o distintos idiomas, dentro de la misma instancia de ejecución oa través de instancias "idénticas", que pueden afectar el tiempo de ejecución:

  1. La recolección de basura - diferentes implementaciones o los 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, la placa base (por ejemplo, cachés L1, L2, L3) u otros niveles (por ejemplo, memorización). A menudo, la primera ejecución pagará una penalización.
  3. Dynamic voltage scaling - 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 usted no conozca.
  5. Un método utiliza varios 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 en este aspecto, otro paquete puede ser muy inteligente ...
  6. Variables no utilizadas, transferencia de datos excesiva, cachés sucios, 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 estemos azules en la cara.

10

La única manera 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 a la prueba de unidad, y considere que 'tiene que hacerse en X unidades de tiempo' como un criterio de prueba más para cumplir. En otras palabras, haga algo como

stopifnot(timingOf(someExpression) < savedValue plus fudge) 

, por lo que 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 pudo manejar, así que creo que casi podemos esperar un nuevo paquete timr después del próximo descanso académico prolongado :). 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 .

+0

¿O tal vez una extensión para probar eso? expect_that (foo (x), takes_less_than (bar (y)))? – Spacedman

+0

Con 'bar (y)' es la función que nos proporciona los tiempos previos de 'foo (x)' más alguna medida de tolerancia/fudge? Posiblemente. –

+0

no estoy seguro si esto está conectado, pero 'svUnit' captura la información de 'tiempo' en las pruebas de la unidad y la informa al registro. – Ramnath

4

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

CAMBIOS UTILIDADES R-devel

‘cheque R CMD’

pueden opcionalmente tiempos de informe en varias partes del cheque: esto es controlado por las variables de entorno documentados en ‘escritura R Extensiones’.

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

El tiempo total de ejecución de las pruebas se pudo comprobar y se compara con los 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 fino como el soporte de tiempo en suites de prueba individuales, pero tampoco depende de ningún conjunto de pruebas específico.