Recientemente me encontré con retrasos de varios minutos en mi estación de trabajo. Después de la investigación, resultó que la causa del problema era un bloqueo, que podía durar cinco minutos, durante los cuales la fuente del bloqueo básicamente giraba en un ciclo de nueve instrucciones.
Es muy importante para mí seleccionar buenos encabezados para mis publicaciones, pero inmediatamente recordé que el nombre apropiado "48 núcleos están bloqueados por nueve instrucciones"
ya fue
tomado [
traducción en Habré] por una publicación escrita hace menos de un mes. La cantidad de procesadores bloqueados es diferente, y el ciclo es un poco más largo, pero de hecho, todo esto te hace experimentar un deja vu. Por lo tanto, mientras explico el nuevo problema encontrado, quería reflexionar sobre
por qué sucede esto todo el tiempo .
¿Por qué está pasando esto?
Hablando en términos generales, tales problemas surgen como resultado de una observación que llamaré
la Primera Ley de Computación de Dawson: O (n 2 ) es un imán para algoritmos que no escalan bien : son lo suficientemente rápidos como para entrar en producción, pero lo suficientemente lentos como para estropear todo, cuando lleguen
O (n 2 ) en acción: datos tomados de mi casoQue esta pasando El desarrollador escribe el código y usa el algoritmo O (n
2 ). Quizás no se da cuenta de esto, o el algoritmo se convierte en O (n
2 )
debido a un error , o el desarrollador sabe que él es O (n
2 ), pero cree que nunca será importante. En condiciones de laboratorio, la velocidad del código es aceptable, y es aceptable para la mayoría de los usuarios en el mundo real, pero luego alguien crea
7,000 procesos con App Verifier activado o crea un
archivo binario con 180,000 elementos CFG , o recopila una DLL tan grande que es
constantemente se escanea una lista enlazada , que toma todo el procesador. Al trabajar en el tiempo de inicio del motor Valve Source 2, encontré
muchos algoritmos O (n
2 ), cada uno de los cuales agregó aproximadamente 30 segundos al tiempo de inicio del motor, es decir, este problema ocurre con desarrolladores muy diferentes.
O (n 2 ) es un imán para algoritmos que no escalan bien: son lo suficientemente rápidos como para entrar en producción, pero lo suficientemente lentos como para estropear todo cuando llegan allí.Exactamente
Por ejemplo, la
creación de los nombres de los archivos de registro del verificador de la aplicación se realiza en tiempo lineal para cada proceso en ejecución, y esto es normal hasta que se da cuenta de que esto conduce a O (n
2 ) si se están ejecutando muchos procesos. A veces ni siquiera es obvio que hay bucles anidados, o que
formalmente no es O (n
2 ), o no es obvio que los bucles pueden funcionar durante tanto tiempo que puede afectar significativamente la velocidad ...
Por lo tanto, tenga en cuenta esta característica, piense en ello cuando escriba código, monitoree cómo se escala el rendimiento bajo cargas pesadas y examine partes sospechosas del código al perfilar esas cargas pesadas. O déjemelo a mí para que pueda buscarlos y escribir artículos en mi blog.
Volvamos a nuestras quejas habituales.
Como de costumbre, me dediqué a trabajar en mi estación de trabajo desactualizada pero poderosa con 48 procesadores lógicos y 96 GB de RAM. Introduje
ninja chrome para construir Chromium, pero ... no pasó nada. Miré y esperé veinte segundos, pero la asamblea nunca comenzó. Entonces, por supuesto,
cambié a
UIforETW para grabar el rastro
ETW . Más precisamente, traté de hacerlo. Al intentar iniciar la grabación de seguimiento,
UIforETW se congela . ¡Por primera vez en mi práctica, un error utilizó medidas de protección para evitar que lo explorara!
Después de uno o dos minutos, comenzó el ensamblaje de Chromium, y
UIforETW comenzó la traza, pero comenzó demasiado tarde y no tenía la más mínima información sobre lo que sucedió.
Opciones de UIforETW con seguimiento de búfer circular seleccionadoCuando sucedió lo mismo un par de días después,
UIforETW nuevamente no pudo hacer nada. Esta vez, dejé el rastro para trabajar en memorias intermedias circulares, para estar preparados para el hecho de que el bloqueo se produciría por tercera vez. Sin embargo, esto redujo en gran medida la velocidad de mis herramientas de construcción, así que después de unas horas me di por vencido.
Entonces esta situación se repitió
nuevamente . Esta vez ejecuté la herramienta de
registro de rastreo ETW creada por Microsoft -
wprui , y pude comenzar a grabar. ¡Aproximadamente 40 segundos después, el ensamblaje comenzó a funcionar y obtuve un rastro!
¿Puedo comenzar la investigación ahora?
Anteriormente, noté en el "Administrador de tareas" que
WinMgmt.exe se estaba
ejecutando durante estas
congelaciones . Al
observar los datos de Uso de CPU (Preciso) en WPA, me convencí de que después de más de cuarenta segundos, durante los cuales
WinMgmt.exe fue casi el único proceso de trabajo, mi máquina cobró vida después de que
WinMgmt.exe se apagara :
Esperamos el despertar de los procesos después de que se complete WinMgmt.exeTodo esto es bastante sospechoso, pero mis sabios lectores saben que "después" no significa "debido a", y requerirán pruebas.
Como la
última vez , aproximé el momento del desbloqueo en el gráfico, clasificando los cambios de contexto por
Tiempo de cambio y buscando el primer cambio con un valor de
Tiempo desde el último largo (que indica el período de tiempo durante el cual el hilo no se ejecutó). Habiendo perdido una docena de hilos que solo fueron cortos tiempos de inactividad, encontré el primero de muchos que esperó 41.57 segundos. El hilo dormido no despertó
WinMgmt.exe , pero rápidamente descubrí que se despertó con el hilo que despertó
WinMgmt.exe una fracción de milisegundo antes.
Para obtener una explicación de los gráficos de Uso de CPU (Preciso) y los conceptos de preparación de subprocesos / subprocesos nuevos, consulte este tutorial o esta documentación .
En la captura de pantalla con los datos de cambio de contenido, la línea 17 contiene la secuencia
72.748 (WinMgmt.exe) , que activa la secuencia
74.156 (svchost.exe). A continuación, en la línea 19, el subproceso
74,156 (svchost.exe) activa el subproceso
58,704 (svchost.exe) , que esperaba 41,57 segundos. Este es el primer hilo que se despierta después de un largo sueño y a partir de esto continúa la cadena de activación de los flujos. Los subprocesos que se acaban de activar se pueden ver en la columna
Nuevo Id. De subproceso , y luego bajar unas líneas y verlos en la columna
Id. De subproceso de preparación , activando otro subproceso. Los nombres y las ID de proceso lo ayudan a comprender el contexto. La línea 17 está asociada con las líneas 18 y 19, la línea 19 está asociada con 20, que está asociada con la línea 23, que está asociada con la línea 27, y así sucesivamente; cada hilo es activado por el hilo anterior en la cadena:
El monstruo despierta: los largos hilos inactivos cobran vida41.57 es mucho tiempo para bloquear un hilo, pero en realidad se bloquearon cientos de hilos, y se bloquearon
mucho más tiempo. La única razón por la que su valor
Tiempo desde el último es aproximadamente 41.5 segundos es porque la longitud del rastreo antes de que se resuelva el bloqueo.
Parece que los resultados son consistentes con la teoría de que el problema está en
WinMgmt.exe , pero no lo prueban. Mi confianza aumentó cuando miré
svchost.exe (3024) en
Seguimiento-> Configuración del sistema-> Servicios y descubrí que es un servicio
Winmgmt , pero aún necesitaba más certeza.
Habiendo hurgado un poco más (avanzando y retrocediendo en el tiempo), decidí que las interacciones son demasiado complicadas para analizarlas en detalle, especialmente sin
nombres de flujo que podrían indicarnos qué están haciendo 25 hilos diferentes en
svchost.exe (3024) .
Prueba!
Entonces decidí
acercarme a la prueba de culpabilidad
WinMgmt.exe de manera diferente. Quizás valió la pena comenzar con esto, pero sería demasiado simple. Tomé la línea de comando
WinMgmt.exe de la tabla
Procesos en WPA y la inicié manualmente. El comando tiene la forma:
winmgmt.exe / verificadorepository
y tardó unos cinco minutos en completarse. Si bien funcionó (y tuve mucho tiempo), descubrí que no podía iniciar el seguimiento de ETW desde
UIforETW . Tal evidencia era mejor que cualquier análisis complejo que pudiera hacer.
Configuración para mostrar solo subprocesos inactivos largosLuego corrí repro nuevamente con el rastro ya corriendo; Después de analizar el rastro, descubrí más de
cien procesos cuyos hilos fueron bloqueados durante más de
cinco minutos.Y de nuevo al grano ...
Por costumbre, volví a mirar los datos de Uso de la CPU (Muestreo) para ver en qué
WinMgmt.exe estaba perdiendo el tiempo. Rápidamente descubrí que el 96.5% de las muestras estaban en
repdrvfs.dll! CPageCache :: Read () , llamado en cuatro pilas diferentes:
Cuatro caminos que me llevaron a CPageCache :: LeerAquí se muestra un árbol de pilas completas para esta función, principalmente para las personas de Microsoft que desean investigar este problema:
Pilas completas que conducen a CPageCache :: Leer de tres manerasAgregué una columna de dirección y descubrí que el 95.3% de las muestras estaban en un ciclo de nueve instrucciones (las muestras siempre cayeron en solo siete de nueve instrucciones (si desea saber por qué, vea
aquí ), pero el depurador mostró el tamaño completo del ciclo) :
Muestras por dirección: siete direcciones muy "calientes"Luego comencé
winmgmt.exe / verifiedrepository manualmente, y al
mismo tiempo recopilé datos del contador de la CPU sobre las instrucciones de rama que se estaban ejecutando . A partir de esto, podría calcular aproximadamente cuántas veces se ejecutó el ciclo. Esto probablemente no era necesario, pero quería asegurarme de que el bucle se ejecutara muchas veces y que no se ejecutara lentamente (por alguna razón). Pensé que era genial poder hacerlo simplemente, solo hacer un
pequeño cambio en el archivo por lotes. Descubrí que
WinMgmt.exe ejecutó aproximadamente una instrucción de ramificación por ciclo, es decir, el ciclo (que, como ya sabía, consumía la mayor parte del tiempo de la CPU) fue extremadamente rápido, y la desaceleración se debió al hecho de que corrió cientos de millones tiempos
Retardo Xperf
Solo por meticulosidad, decidí ver por qué
UIforETW no podía comenzar a rastrear durante este incidente.
Resultó que
UIforETW estaba ejecutando
xperf , pero
xperf estuvo inactivo durante 41.5 segundos (en realidad más) en esta pila de llamadas:
xperf.exe! wmain
xperf.exe! CStopTrace :: Ejecutar
perfctrl.dll! LoggingSession :: EnumLoggers
perfctrl.dll! LoggingSession :: LoggingSession
perfctrl.dll! LoggingSession :: CreateProviderList
perfctrl.dll! GetProviderInfoCache
perfctrl.dll! CProviderInfoCache :: CProviderInfoCache
tdh.dll! TdhfEnumerateProviders
tdh.dll! TdhpWbemConnect
wbemprox.dll! CLocator :: ConnectServer
wbemprox.dll! CDCOMTrans :: DoActualConnection
En resumen,
xperf es llamado por
Wbem y, por lo tanto, está bloqueado por este problema.
xperf intenta detener el rastreo antes de iniciarlo, porque agregué este comportamiento para que el
inicio del rastreo sea más tolerante a fallas . Sospecho que aún se produciría un bloqueo, pero no estoy seguro.
Creamos gráficos de complejidad computacional.
Me di cuenta de que
WinMgmt.exe escanea el
directorio c: \ windows \ System32 \ wbem \ Repository , que tiene 1.9 GB en mi máquina, por lo que pedí en el trabajo y en Twitter que dijera cuánto tarda este directorio en obtener puntos de datos. También le pedí a la gente que arreglara el tiempo de ejecución de
winmgmt.exe / verificadorepository y comencé a programar. Aunque estas pruebas se llevaron a cabo en máquinas completamente diferentes con diferentes velocidades de CPU, el gráfico resultó ser bastante claro:
La relación entre la raíz cuadrada del tiempo y el tamaño del repositorioEste gráfico de la relación de sqrt (tiempo) al tamaño del repositorio es increíblemente ideal para los datos recibidos de seis máquinas diferentes y, sin embargo, es real. Obviamente, la función
VerifyRepository tiene un rendimiento O (n
2 ). Si n es el tamaño del directorio del repositorio en
GB , entonces
VerifyRepository demora aproximadamente 1.6 * n
2 minutos. Esta es una buena estimación aproximada para todos los valores, desde una fracción de segundo hasta diez minutos.
Relevancia
O tengo suerte o solo estoy atento, porque durante un par de semanas ya nadie ha encontrado este problema; pensé que cosas extrañas estaban sucediendo con mi automóvil. Pero de repente comencé a escuchar quejas sospechosamente similares de colegas. Uno de ellos tenía un repositorio de 2.6 GB, que tardó diez minutos en verificar. El problema afectó a algunos de nuestros desarrolladores de
CI y, en diferentes grados, a otras personas diferentes. Mis colegas generalmente saben que en caso de problemas con el rendimiento de las máquinas con Windows, tengo que decirme esto, sin embargo, probablemente hay muchos otros empleados de Google que trabajan con Windows que se ven impedidos por este error, pero no se dan cuenta de esto.
Afortunadamente, ya he comenzado a trabajar con nuestro departamento de TI. Encontré el script que lanzó
WinMgmt y descubrí que se ejecuta cada hora. Esto significaba que mi máquina estaba
ejecutando WinMgmt.exe / verificadorepository el 10% del tiempo, y algunos de mis colegas tenían más del 16% del tiempo. Hay una probabilidad bastante alta de obtener un retraso de diez minutos antes del montaje.
Para cuando comenzaron a llegar los informes, la solución ya estaba en camino a la producción. El script era opcional, y ciertamente no valía la pena por los problemas que causó, por lo que la solución fue desactivar su llamada.
Resumen
winmgmt.exe / verifiedrepository contiene un ciclo de nueve instrucciones, cuyo número de iteraciones de ejecución es proporcional al cuadrado del tamaño del
repositorio wbem . Debido a esto, la ejecución del comando puede tomar hasta diez minutos, aunque en realidad debería ejecutarse en solo unos segundos. Esto es malo en sí mismo.
Pero aún peor, el equipo realiza un bloqueo WMI (
Instrumental de administración de Windows ) durante su operación, por lo que cualquier proceso que realice operaciones WMI se congelará.
Acertijos increíbles
El script que
ejecutaba winmgmt.exe / verificadorepository cada hora hizo esto durante muchos años, pero el comportamiento problemático comenzó a aparecer hace solo uno o dos meses. Presumiblemente, esto significa que el repositorio wbem se ha vuelto mucho más grande recientemente. Los retrasos a 0.5 GB se pasan por alto fácilmente, pero a partir de 1.0 GB y más ya pueden forzar. Como se
sugirió en Twitter, ejecuté
strings.exe para el archivo
objects.data . Muchas de las cadenas más comunes contienen polmkr en el nombre, pero no sé qué significa eso.
Publiqué un informe de error en Twitter , y al principio causó
algunos movimientos del equipo de WMI , pero luego dejé de recibir respuestas, por lo que no sé cuál es la situación ahora.
Me gustaría ver una solución para el problema de rendimiento, y me gustaría que nuestro departamento de TI pueda encontrar y solucionar el problema que hace que nuestros repositorios wbem sean tan grandes. Pero por ahora, el departamento de TI ha prometido no ejecutar el comando / verifique el repositorio cada hora más, lo que debería ayudarnos a evitar los peores síntomas.
Referencias
- Una lista general de tutoriales, investigaciones y documentación de ETW está disponible aquí: https://tinyurl.com/etwcentral
- El tutorial sobre el uso de la CPU (muestreado) (para averiguar en qué se dedica el tiempo de la CPU) está aquí , y la documentación está aquí.
- El tutorial de Uso de CPU (Preciso) (para encontrar las razones por las que no se pueden ejecutar hilos) está aquí , y la documentación está aquí.
- Los enlaces a artículos individuales se encuentran en el cuerpo de la publicación, también puede encontrarlos en mi categoría Investigar informes
- Otras historias sobre algoritmos O (n 2 ) se pueden leer en Accidentally Quadratic
Una discusión del artículo sobre Reddit está
aquí , una discusión sobre noticias de hackers está
aquí , un hilo en Twitter está
aquí, y posiblemente
aquí