El software científico no es muy diferente de otro software, en cuanto a cómo saber qué necesita ajuste.
El método que uso es una pausa aleatoria . Estas son algunas de las aceleraciones que ha encontrado para mí:
Si se pasa una gran parte del tiempo en funciones como log
y exp
, puedo ver cuáles son los argumentos de esas funciones, en función de los puntos desde los que se les llama. A menudo se les llama repetidamente con el mismo argumento. Si es así, la memorización produce un factor de aceleración masiva.
Si estoy usando las funciones BLAS o LAPACK, puedo encontrar que se gasta una gran parte del tiempo en rutinas para copiar matrices, multiplicar matrices, transformar Choleski, etc.
La rutina para copiar matrices no está ahí por la velocidad, está ahí por conveniencia. Puede encontrar que hay una manera menos conveniente, pero más rápida, de hacerlo.
Las rutinas para multiplicar o invertir matrices, o tomar transformaciones choleski, tienden a tener argumentos de caracteres que especifican opciones, como 'U' o 'L' para el triángulo superior o inferior. Nuevamente, esos están ahí por conveniencia. Lo que encontré fue que, dado que mis matrices no eran muy grandes, las rutinas pasaban más de la mitad de su tiempo llamando a la subrutina para comparar caracteres solo para descifrar las opciones. Escribir versiones para propósitos especiales de las rutinas matemáticas más costosas produjo una aceleración masiva.
Si solo puedo ampliar el último: la rutina de matriz de multiplicación DGEMM llama a LSAME para decodificar sus argumentos de caracteres. Si se analiza el porcentaje de tiempo inclusivo (la única estadística que vale la pena mirar), los perfiladores considerados "buenos" podrían mostrar que DGEMM usa un porcentaje del tiempo total, como el 80%, y LSAME usa un porcentaje del tiempo total, como el 50%. Mirando lo primero, te sentirías tentado a decir "bueno, debe estar muy optimizado, así que no puedo hacer mucho al respecto". Mirando lo último, te sentirías tentado a decir "¿Eh? ¿De qué se trata todo eso? Es solo una pequeña rutina. ¡Este perfilador debe estar equivocado!"
No está mal, simplemente no te dice lo que necesitas saber. Lo que muestra una pausa aleatoria es que DGEMM está en el 80% de las muestras de pila y LSAME está en el 50%. (No necesita muchas muestras para detectar eso. 10 generalmente es suficiente.) Además, en muchas de esas muestras, DGEMM está en el proceso de llamar a LSAME desde un par de líneas de código diferentes.
Así que ahora sabes por qué ambas rutinas están tomando tanto tiempo inclusivo. También sabe de qué parte de su código están siendo llamados para pasar todo este tiempo. Es por eso que uso pausas aleatorias y tomo una visión icónica de los perfiladores, sin importar qué tan bien estén. Están más interesados en obtener mediciones que en contarle lo que está sucediendo.
Es fácil suponer que las rutinas de la biblioteca matemática se han optimizado en el enésimo grado, pero de hecho se han optimizado para ser utilizables para una amplia gama de propósitos. Necesita ver lo que realmente está sucediendo, no lo que es fácil de asumir.
AGREGADO: Entonces, para responder sus dos últimas preguntas:
¿Cuáles son las cosas más importantes para probar primero?
Tome 10-20 muestras de pila, y no solo las resuma, entienda lo que cada una le está diciendo. Haga esto primero, último y en el medio. (No hay "intento", joven Skywalker).
¿Cómo sé cuánto rendimiento puedo obtener?
xβ(s+1,(n−s)+1)sn1/(1−x)n=10s=5x
xx
Como te he señalado antes, puedes repetir todo el procedimiento hasta que no puedas más, y la relación de aceleración compuesta puede ser bastante grande.
(s+1)/(n+2)=3/22=13.6%.) La curva inferior en el siguiente gráfico es su distribución:
Considere si tomamos hasta 40 muestras (más de las que he tenido al mismo tiempo) y solo vimos un problema en dos de ellas. El costo estimado (modo) de ese problema es del 5%, como se muestra en la curva más alta.
¿Qué es un "falso positivo"? Es que si soluciona un problema, se da cuenta de una ganancia tan pequeña de lo esperado, que lamenta haberlo solucionado. Las curvas muestran (si el problema es "pequeño") que, si bien la ganancia podría ser menor que la fracción de las muestras que lo muestran, en promedio será mayor.
Existe un riesgo mucho más grave: un "falso negativo". Eso es cuando hay un problema, pero no se encuentra. (Esto contribuye al "sesgo de confirmación", donde la ausencia de evidencia tiende a tratarse como evidencia de ausencia).
Lo que obtienes con un generador de perfiles (uno bueno) es que obtienes una medición mucho más precisa (por lo tanto, menos posibilidades de falsos positivos), a expensas de una información mucho menos precisa sobre cuál es realmente el problema (por lo tanto, menos posibilidades de encontrarlo y obtener cualquier ganancia). Eso limita la aceleración general que se puede lograr.
Animaría a los usuarios de los perfiladores a informar los factores de aceleración que realmente obtienen en la práctica.
Hay otro punto para hacerse re. La pregunta de Pedro sobre falsos positivos.
Mencionó que podría haber una dificultad para resolver problemas pequeños en un código altamente optimizado. (Para mí, un pequeño problema es uno que representa el 5% o menos del tiempo total).
Dado que es completamente posible construir un programa que sea totalmente óptimo, excepto el 5%, este punto solo puede abordarse empíricamente, como en esta respuesta . Para generalizar a partir de la experiencia empírica, es así:
Un programa, tal como está escrito, generalmente contiene varias oportunidades para la optimización. (Podemos llamarlos "problemas", pero a menudo son un código perfectamente bueno, simplemente capaces de una mejora considerable). Este diagrama ilustra un programa artificial que tarda cierto tiempo (por ejemplo, 100 s) y contiene los problemas A, B, C, ... que, cuando se encuentra y repara, ahorra 30%, 21%, etc. de los 100 originales.
Observe que el problema F cuesta el 5% del tiempo original, por lo que es "pequeño" y difícil de encontrar sin 40 o más muestras.
Sin embargo, las primeras 10 muestras encuentran fácilmente el problema A. ** Cuando eso se arregla, el programa solo toma 70 segundos, para una aceleración de 100/70 = 1.43x. Eso no solo hace que el programa sea más rápido, sino que aumenta, en esa proporción, los porcentajes tomados por los problemas restantes. Por ejemplo, el problema B originalmente tomó 21 segundos, que era el 21% del total, pero después de eliminar A, B toma 21 de 70, o 30%, por lo que es más fácil de encontrar cuando se repite todo el proceso.
Una vez que el proceso se repite cinco veces, ahora el tiempo de ejecución es de 16,8 segundos, de los cuales el problema F es del 30%, no del 5%, por lo que 10 muestras lo encuentran fácilmente.
Entonces ese es el punto. Empíricamente, los programas contienen una serie de problemas que tienen una distribución de tamaños, y cualquier problema encontrado y solucionado hace que sea más fácil encontrar los restantes. Para lograr esto, ninguno de los problemas se puede omitir porque, si lo están, se sientan allí tomando tiempo, limitando la aceleración total y sin poder magnificar los problemas restantes.
Por eso es muy importante encontrar los problemas que se esconden .
Si se encuentran y solucionan los problemas A a F, la aceleración es 100 / 11.8 = 8.5x. Si se pierde uno de ellos, por ejemplo D, entonces la aceleración es solo 100 / (11.8 + 10.3) = 4.5x.
Ese es el precio pagado por los falsos negativos.
Entonces, cuando el generador de perfiles dice "no parece haber ningún problema significativo aquí" (es decir, buen codificador, este es un código prácticamente óptimo), tal vez sea correcto, y tal vez no lo sea. (Un falso negativo .) No sabe con certeza si hay más problemas que solucionar, para una mayor velocidad, a menos que pruebe con otro método de creación de perfiles y descubra que los hay. En mi experiencia, el método de creación de perfiles no necesita una gran cantidad de muestras, resumidas, sino una pequeña cantidad de muestras, donde cada muestra se entiende lo suficiente como para reconocer cualquier oportunidad de optimización.
2/0.3=6.671 - pbinom(1, numberOfSamples, sizeOfProblem)
1 - pbinom(1, 20, 0.3) = 0.9923627
xβ(s+1,(n−s)+1)nsy1/(1−x)xyy−1Distribución BetaPrime . Lo simulé con 2 millones de muestras, llegando a este comportamiento:
distribution of speedup
ratio y
s, n 5%-ile 95%-ile mean
2, 2 1.58 59.30 32.36
2, 3 1.33 10.25 4.00
2, 4 1.23 5.28 2.50
2, 5 1.18 3.69 2.00
2,10 1.09 1.89 1.37
2,20 1.04 1.37 1.17
2,40 1.02 1.17 1.08
3, 3 1.90 78.34 42.94
3, 4 1.52 13.10 5.00
3, 5 1.37 6.53 3.00
3,10 1.16 2.29 1.57
3,20 1.07 1.49 1.24
3,40 1.04 1.22 1.11
4, 4 2.22 98.02 52.36
4, 5 1.72 15.95 6.00
4,10 1.25 2.86 1.83
4,20 1.11 1.62 1.31
4,40 1.05 1.26 1.14
5, 5 2.54 117.27 64.29
5,10 1.37 3.69 2.20
5,20 1.15 1.78 1.40
5,40 1.07 1.31 1.17
(n+1)/(n−s)s=ny
Este es un gráfico de la distribución de los factores de aceleración y sus medias para 2 aciertos de 5, 4, 3 y 2 muestras. Por ejemplo, si se toman 3 muestras, y 2 de ellas son coincidencias con un problema, y ese problema puede eliminarse, el factor de aceleración promedio sería 4x. Si se ven los 2 aciertos en solo 2 muestras, la aceleración promedio no está definida, ¡conceptualmente porque existen programas con bucles infinitos con probabilidad distinta de cero!