Profiling
CPU Profiling
Hay dos enfoques principales para el perfilado de CPU en código Julia:
Via @profile
Donde el perfilado está habilitado para una llamada dada a través del macro @profile
.
julia> using Profile
julia> @profile foo()
julia> Profile.print()
Overhead ╎ [+additional indent] Count File:Line; Function
=========================================================
╎147 @Base/client.jl:506; _start()
╎ 147 @Base/client.jl:318; exec_options(opts::Base.JLOptions)
...
Triggered During Execution
Las tareas que ya están en ejecución también se pueden perfilar durante un período de tiempo fijo en cualquier momento activado por el usuario.
Para activar el perfilado:
- MacOS y FreeBSD (plataformas basadas en BSD): Usa
ctrl-t
o envía una señalSIGINFO
al proceso de julia, es decir,% kill -INFO $julia_pid
- Linux: Envía una señal
SIGUSR1
al proceso de julia es decir,% kill -USR1 $julia_pid
- Windows: No compatible actualmente.
Primero, se muestra un único rastreo de pila en el instante en que se lanzó la señal, luego se recopila un perfil de 1 segundo, seguido del informe del perfil en el siguiente punto de ceder, que puede ser al completar la tarea para código sin puntos de ceder, por ejemplo, bucles ajustados.
Opcionalmente, establece la variable de entorno JULIA_PROFILE_PEEK_HEAP_SNAPSHOT
en 1
para también recopilar automáticamente un heap snapshot.
julia> foo()
##== the user sends a trigger while foo is running ==##
load: 2.53 cmd: julia 88903 running 6.16u 0.97s
======================================================================================
Information request received. A stacktrace will print followed by a 1.0 second profile
======================================================================================
signal (29): Information request: 29
__psynch_cvwait at /usr/lib/system/libsystem_kernel.dylib (unknown line)
_pthread_cond_wait at /usr/lib/system/libsystem_pthread.dylib (unknown line)
...
======================================================================
Profile collected. A report will print if the Profile module is loaded
======================================================================
Overhead ╎ [+additional indent] Count File:Line; Function
=========================================================
Thread 1 Task 0x000000011687c010 Total snapshots: 572. Utilization: 100%
╎147 @Base/client.jl:506; _start()
╎ 147 @Base/client.jl:318; exec_options(opts::Base.JLOptions)
...
Thread 2 Task 0x0000000116960010 Total snapshots: 572. Utilization: 0%
╎572 @Base/task.jl:587; task_done_hook(t::Task)
╎ 572 @Base/task.jl:879; wait()
...
Customization
La duración del perfilado se puede ajustar a través de Profile.set_peek_duration
El informe de perfil se desglosa por hilo y tarea. Pasa una función sin argumentos a Profile.peek_report[]
para anular esto. es decir, Profile.peek_report[] = () -> Profile.print()
para eliminar cualquier agrupación. Esto también podría ser anulado por un consumidor de datos de perfil externo.
Reference
Profile.@profile
— Macro@profile
@profile <expresión>
ejecuta tu expresión mientras toma rastros de pila periódicos. Estos se añaden a un búfer interno de rastros de pila.
Los métodos en Profile
no están exportados y deben ser llamados, por ejemplo, como Profile.print()
.
Profile.clear
— Functionclear()
Limpia cualquier traza de retroceso existente del búfer interno.
Profile.print
— Functionprint([io::IO = stdout,] [data::Vector = fetch()], [lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data)]; kwargs...)
Imprime los resultados de perfilado en io
(por defecto, stdout
). Si no proporcionas un vector data
, se utilizará el búfer interno de trazas de pila acumuladas.
Los argumentos de palabra clave pueden ser cualquier combinación de:
format
– Determina si las trazas de pila se imprimen con (por defecto,:tree
) o sin (:flat
) sangrías que indican la estructura del árbol.C
– Si estrue
, se muestran las trazas de pila del código C y Fortran (normalmente están excluidas).combine
– Si estrue
(por defecto), se fusionan los punteros de instrucción que corresponden a la misma línea de código.maxdepth
– Limita la profundidad superior amaxdepth
en el formato:tree
.sortedby
– Controla el orden en el formato:flat
.:filefuncline
(por defecto) ordena por la línea de origen,:count
ordena en orden del número de muestras recolectadas, y:overhead
ordena por el número de muestras incurridas por cada función por sí misma.groupby
– Controla la agrupación sobre tareas e hilos, o sin agrupación. Las opciones son:none
(por defecto),:thread
,:task
,[:thread, :task]
, o[:task, :thread]
donde los dos últimos proporcionan agrupación anidada.noisefloor
– Limita los marcos que exceden el umbral de ruido heurístico de la muestra (solo se aplica al formato:tree
). Un valor sugerido para probar esto es 2.0 (el valor por defecto es 0). Este parámetro oculta muestras para las cualesn <= noisefloor * √N
, donden
es el número de muestras en esta línea, yN
es el número de muestras para el llamado.mincount
– Limita la impresión solo a aquellas líneas con al menosmincount
ocurrencias.recur
– Controla el manejo de la recursión en el formato:tree
.:off
(por defecto) imprime el árbol como normal.:flat
en su lugar comprime cualquier recursión (por ip), mostrando el efecto aproximado de convertir cualquier auto-recursión en un iterador.:flatc
hace lo mismo pero también incluye la colapsación de marcos C (puede hacer cosas extrañas alrededor dejl_apply
).threads::Union{Int,AbstractVector{Int}}
– Especifica qué hilos incluir en las instantáneas del informe. Ten en cuenta que esto no controla qué hilos se recolectan muestras (que también pueden haberse recolectado en otra máquina).tasks::Union{Int,AbstractVector{Int}}
– Especifica qué tareas incluir en las instantáneas del informe. Ten en cuenta que esto no controla en qué tareas se recolectan muestras.
Los argumentos de palabra clave groupby
, threads
y tasks
se introdujeron en Julia 1.8.
El perfilado en Windows está limitado al hilo principal. Otros hilos no han sido muestreados y no aparecerán en el informe.
print([io::IO = stdout,] data::Vector, lidict::LineInfoDict; kwargs...)
Imprime los resultados de perfilado en io
. Esta variante se utiliza para examinar los resultados exportados por una llamada anterior a retrieve
. Proporcione el vector data
de trazas de pila y un diccionario lidict
de información de línea.
Consulte Profile.print([io], data)
para una explicación de los argumentos de palabra clave válidos.
Profile.init
— Functioninit(; n::Integer, delay::Real)
Configura el delay
entre las trazas de retorno (medido en segundos) y el número n
de punteros de instrucción que se pueden almacenar por hilo. Cada puntero de instrucción corresponde a una sola línea de código; las trazas de retorno generalmente consisten en una larga lista de punteros de instrucción. Ten en cuenta que se utilizan 6 espacios para punteros de instrucción por traza de retorno para almacenar metadatos y dos marcadores de fin NULL. La configuración actual se puede obtener llamando a esta función sin argumentos, y cada uno se puede establecer de forma independiente utilizando palabras clave o en el orden (n, delay)
.
Profile.fetch
— Functionfetch(;include_meta = true) -> data
Devuelve una copia del búfer de perfiles de backtraces. Ten en cuenta que los valores en data
solo tienen significado en esta máquina en la sesión actual, porque dependen de las direcciones de memoria exactas utilizadas en la compilación JIT. Esta función es principalmente para uso interno; retrieve
puede ser una mejor opción para la mayoría de los usuarios. Por defecto, se incluye metadatos como threadid y taskid. Establece include_meta
en false
para eliminar los metadatos.
Profile.retrieve
— Functionretrieve(; kwargs...) -> data, lidict
"Exports" los resultados de perfilado en un formato portátil, devolviendo el conjunto de todas las trazas de retroceso (data
) y un diccionario que mapea los punteros de instrucción (específicos de la sesión) en data
a valores de LineInfo
que almacenan el nombre del archivo, el nombre de la función y el número de línea. Esta función te permite guardar los resultados de perfilado para un análisis futuro.
Profile.callers
— Functioncallers(funcname, [data, lidict], [filename=<filename>], [linerange=<start:stop>]) -> Vector{Tuple{count, lineinfo}}
Dada una ejecución de perfilado anterior, determina quién llamó a una función particular. Suministrar el nombre del archivo (y opcionalmente, el rango de números de línea sobre los cuales se define la función) te permite desambiguar un método sobrecargado. El valor devuelto es un vector que contiene un conteo del número de llamadas e información de línea sobre el llamador. Se puede suministrar opcionalmente un data
de backtrace obtenido de retrieve
; de lo contrario, se utiliza el búfer de perfil interno actual.
Profile.clear_malloc_data
— Functionclear_malloc_data()
Limpia cualquier dato de asignación de memoria almacenado al ejecutar julia con --track-allocation
. Ejecuta el/los comando(s) que deseas probar (para forzar la compilación JIT), luego llama a clear_malloc_data
. Luego ejecuta tu(s) comando(s) nuevamente, cierra Julia y examina los archivos *.mem
resultantes.
Profile.get_peek_duration
— Functionget_peek_duration()
Obtiene la duración en segundos del perfil "peek" que se activa a través de SIGINFO
o SIGUSR1
, dependiendo de la plataforma.
Profile.set_peek_duration
— Functionset_peek_duration(t::Float64)
Establece la duración en segundos del perfil "peek" que se activa a través de SIGINFO
o SIGUSR1
, dependiendo de la plataforma.
Memory profiling
Profile.Allocs.@profile
— MacroProfile.Allocs.@profile [sample_rate=0.1] expr
Perfila las asignaciones que ocurren durante expr
, devolviendo tanto el resultado como una estructura AllocResults.
Una tasa de muestreo de 1.0 registrará todo; 0.0 no registrará nada.
julia> Profile.Allocs.@profile sample_rate=0.01 peakflops()
1.03733270279065e11
julia> results = Profile.Allocs.fetch()
julia> last(sort(results.allocs, by=x->x.size))
Profile.Allocs.Alloc(Vector{Any}, Base.StackTraces.StackFrame[_new_array_ at array.c:127, ...], 5576)
Consulta el tutorial de perfilado en la documentación de Julia para más información.
Las versiones anteriores de Julia no podían capturar tipos en todos los casos. En versiones anteriores de Julia, si ves una asignación de tipo Profile.Allocs.UnknownType
, significa que el perfilador no sabe qué tipo de objeto fue asignado. Esto sucedió principalmente cuando la asignación provenía de código generado producido por el compilador. Consulta issue #43688 para más información.
Desde Julia 1.11, todas las asignaciones deberían tener un tipo reportado.
El perfilador de asignaciones se agregó en Julia 1.8.
Los métodos en Profile.Allocs
no están exportados y deben ser llamados, por ejemplo, como Profile.Allocs.fetch()
.
Profile.Allocs.clear
— FunctionProfile.Allocs.clear()
Borra toda la información de asignación previamente perfilada de la memoria.
Profile.Allocs.print
— FunctionProfile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...)
Imprime los resultados de perfilado en io
(por defecto, stdout
). Si no proporcionas un vector data
, se utilizará el búfer interno de trazas acumuladas.
Consulta Profile.print
para una explicación de los argumentos de palabra clave válidos.
Profile.Allocs.fetch
— FunctionProfile.Allocs.fetch()
Recupera las asignaciones registradas y las decodifica en objetos de Julia que se pueden analizar.
Profile.Allocs.start
— FunctionProfile.Allocs.start(sample_rate::Real)
Comienza a registrar las asignaciones con la tasa de muestreo dada. Una tasa de muestreo de 1.0 registrará todo; 0.0 no registrará nada.
Profile.Allocs.stop
— FunctionProfile.Allocs.stop()
Detener la grabación de asignaciones.
Heap Snapshots
Profile.take_heap_snapshot
— FunctionProfile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false)
Escribe una instantánea del montón, en el formato JSON esperado por el visor de instantáneas de montón de Chrome Devtools (extensión .heapsnapshot) a un archivo ($pid_$timestamp.heapsnapshot
) en el directorio actual por defecto (o en tempdir si el directorio actual no es escribible), o en dir
si se proporciona, o en la ruta de archivo completa dada, o en un flujo de IO.
Si all_one
es verdadero, entonces informa el tamaño de cada objeto como uno para que puedan ser contados fácilmente. De lo contrario, informa el tamaño real.
Si streaming
es verdadero, transmitiremos los datos de la instantánea en cuatro archivos, utilizando filepath como prefijo, para evitar tener que mantener toda la instantánea en memoria. Esta opción debe usarse para cualquier configuración donde tu memoria esté limitada. Estos archivos pueden ser reensamblados llamando a Profile.HeapSnapshot.assemble_snapshot(), lo que se puede hacer fuera de línea.
NOTA: Recomendamos encarecidamente establecer streaming=true por razones de rendimiento. Reconstruir la instantánea a partir de las partes requiere mantener toda la instantánea en memoria, por lo que si la instantánea es grande, puedes quedarte sin memoria mientras la procesas. La transmisión te permite reconstruir la instantánea fuera de línea, después de que tu carga de trabajo haya terminado de ejecutarse. Si intentas recopilar una instantánea con streaming=false (el valor predeterminado, por compatibilidad hacia atrás) y tu proceso es terminado, ten en cuenta que esto siempre guardará las partes en el mismo directorio que tu filepath proporcionado, por lo que aún puedes reconstruir la instantánea después del hecho, a través de assemble_snapshot()
.
Los métodos en Profile
no están exportados y deben ser llamados, por ejemplo, como Profile.take_heap_snapshot()
.
julia> using Profile
julia> Profile.take_heap_snapshot("snapshot.heapsnapshot")
Rastrea y registra objetos de Julia en el heap. Esto solo registra objetos conocidos por el recolector de basura de Julia. La memoria asignada por bibliotecas externas no gestionadas por el recolector de basura no aparecerá en la instantánea.
Para evitar OOM al grabar la instantánea, añadimos una opción de transmisión para enviar la instantánea del montón a cuatro archivos,
julia> using Profile
julia> Profile.take_heap_snapshot("snapshot"; streaming=true)
donde "snapshot" es la ruta del archivo como prefijo para los archivos generados.
Una vez que se generan los archivos de instantánea, se pueden ensamblar sin conexión con el siguiente comando:
julia> using Profile
julia> Profile.HeapSnapshot.assemble_snapshot("snapshot", "snapshot.heapsnapshot")
El archivo de instantánea de heap resultante se puede cargar en chrome devtools para ser visualizado. Para más información, consulta el chrome devtools docs. Una alternativa para analizar instantáneas de heap de Chromium es con la extensión de VS Code ms-vscode.vscode-js-profile-flame
.
Las instantáneas de heap de Firefox son de un formato diferente, y actualmente Firefox no puede ser utilizado para ver las instantáneas de heap generadas por Julia.