¿Te importa demasiado si hablo un poco acerca de los perfiles, qué funciona y qué no?
Compongamos un programa artificial, algunas de cuyas declaraciones están haciendo un trabajo que puede optimizarse, es decir, no son realmente necesarias. Son "cuellos de botella".
La subrutina foo
ejecuta un ciclo de CPU que demora un segundo. Supongamos también que las instrucciones CALL y RETURN de la subrutina toman un tiempo insignificante o cero, en comparación con todo lo demás.
Subrutina bar
llamadas foo
10 veces, pero 9 de esas veces son innecesarias, que no sabe con anticipación y no puede decir hasta que su atención se dirige hacia allí.
subrutinas A
, B
, C
, ..., son J
10 subrutinas, y que cada llamada bar
vez.
La rutina de nivel superior main
llama cada una de A
a J
una vez.
Así el árbol total de la llamada tiene el siguiente aspecto:
main
A
bar
foo
foo
... total 10 times for 10 seconds
B
bar
foo
foo
...
...
J
...
(finished)
¿Cuánto tiempo se tome todo? 100 segundos, obviamente.
Ahora veamos las estrategias de creación de perfiles. Las muestras de apilamiento (como, por ejemplo, 1000 muestras) se toman a intervalos uniformes.
¿Hay algún tiempo para usted? Sí. foo
toma el 100% del tiempo de autoaprendizaje. Es un verdadero "punto caliente". ¿Eso te ayuda a encontrar el cuello de botella? No. Porque no está en foo
.
¿Cuál es el camino caliente?Así, las muestras de pila aspecto:
principal -> A -> Bar -> foo (100 muestras, o el 10%)
principal -> B -> Bar -> foo (100 muestras, o el 10%)
...
principal -> J -> bar -> foo (100 muestras, o el 10%)
Hay 10 caminos calientes, y ninguno de ellos se ven lo suficientemente grande como para tener que mucho acelerar.
SI SUCEDE A ADIVINAR, y SI EL PERFILADOR LO PERMITE, puede hacer que bar
sea la "raíz" de su árbol de llamadas. De allí tendría que ver esto:
bar -> foo (1000 samples, or 100%)
entonces sabría que foo
y bar
eran cada uno independientemente responsable del 100% de las veces y por lo tanto son lugares en busca de la optimización. Miras foo
, pero por supuesto sabes que el problema no está allí. Luego mira bar
y ve las 10 llamadas al foo
, y ve que 9 de ellas son innecesarias. Problema resuelto.
SI no sucedió de adivinar, y en su lugar el perfilador simplemente le mostraron el porcentaje de muestras que contienen cada rutina, se podría ver esto:
main 100%
bar 100%
foo 100%
A 10%
B 10%
...
J 10%
que le indica a la vista main
, bar
, y foo
. Usted ve que main
y foo
son inocentes. Miras dónde bar
llama al foo
y ves el problema, así que está resuelto.
Es aún más claro si, además de mostrarle las funciones, se le pueden mostrar las líneas donde se llaman las funciones. De esta forma, puede encontrar el problema sin importar cuán grandes sean las funciones en términos de texto fuente.
AHORA, cambiemos foo
para que no entre en la CPU sleep(oneSecond)
. ¿Cómo cambia eso las cosas?
Lo que significa es que aún tarda 100 segundos en el reloj de pared, pero el tiempo de CPU es cero. El muestreo en una muestra solo de CPU mostrará nada.
Ahora se le indica que pruebe la instrumentación en lugar de muestrear. Incluido entre todas las cosas que te dice, también te dice los porcentajes que se muestran arriba, por lo que en este caso podrías encontrar el problema, asumiendo que bar
no era muy grande. (Puede haber razones para escribir pequeñas funciones, pero ¿debería ser uno de ellos el que satisface el generador de perfiles?)
En realidad, el problema principal con la muestra fue que no puede muestrear durante sleep
(o E/S u otra bloqueo), y no muestra porcentajes de línea de código, solo porcentajes de función.
Por cierto, 1000 muestras le dan agradables porcentajes de aspecto preciso. Supongamos que tomas menos muestras. ¿Cuántos necesitas para encontrar el cuello de botella? Bueno, dado que el cuello de botella está en la pila el 90% del tiempo, si solo tomaste 10 muestras, sería en aproximadamente 9 de ellas, por lo que aún lo verías. Si incluso tomó tan solo 3 muestras, la probabilidad de que aparezca en dos o más de ellas es del 97.2%.**
Las frecuencias de muestreo altas están demasiado sobrevaloradas, cuando su objetivo es encontrar cuellos de botella.
De todos modos, es por eso que confío en random-pausing.
** ¿Cómo obtuve el 97.2 por ciento? Piense que es como tirar una moneda 3 veces, una moneda muy injusta, donde "1" significa ver el cuello de botella. Hay 8 posibilidades:
#1s probabality
0 0 0 0 0.1^3 * 0.9^0 = 0.001
0 0 1 1 0.1^2 * 0.9^1 = 0.009
0 1 0 1 0.1^2 * 0.9^1 = 0.009
0 1 1 2 0.1^1 * 0.9^2 = 0.081
1 0 0 1 0.1^2 * 0.9^1 = 0.009
1 0 1 2 0.1^1 * 0.9^2 = 0.081
1 1 0 2 0.1^1 * 0.9^2 = 0.081
1 1 1 3 0.1^0 * 0.9^3 = 0.729
por lo que la probabilidad de ver lo tienen 2 o 3 veces es .081 * 3 + .729 = .972
Instrumentación parece ser más hacia lo que necesito, puedo ver exactamente cuánto tiempo pasó dentro de cada función. ¡Gracias de nuevo! –
@Peter Huene: un poco fuera del tema, pero tengo curiosidad por saber si es posible obtener información de cobertura del código fuente para el código nativo y .net en una sola ejecución. Mi principal ejecutor es un .exe nativo que usa .net dlls – Chubsdad
@Chubsdad: Lo es. Si usa VS 2010 o una versión anterior, debe instrumentar cada ejecutable, tanto nativo como administrado mediante VSInstr y recopilar mediante VSPerfMon. En 2012, la herramienta de cobertura de código (CodeCoverage.exe) instrumentará tanto ejecutables nativos como administrados sobre la marcha (en memoria), siempre que sus archivos .pdbs estén presentes en el momento de la recolección. –