Profiling

CPU Profiling

Il existe deux approches principales pour le profilage CPU du code Julia :

Via @profile

Là où le profilage est activé pour un appel donné via le 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

Les tâches qui sont déjà en cours peuvent également être profilées pendant une période de temps fixe à tout moment déclenché par l'utilisateur.

Pour déclencher le profilage :

  • MacOS et FreeBSD (plateformes basées sur BSD) : Utilisez ctrl-t ou envoyez un signal SIGINFO au processus julia, c'est-à-dire % kill -INFO $julia_pid
  • Linux : Envoyez un signal SIGUSR1 au processus julia c'est-à-dire % kill -USR1 $julia_pid
  • Windows : Pas actuellement pris en charge.

Tout d'abord, une seule trace de pile au moment où le signal a été lancé est affichée, puis un profil de 1 seconde est collecté, suivi du rapport de profil au prochain point de yield, qui peut être à l'achèvement de la tâche pour du code sans points de yield, par exemple des boucles serrées.

Optionnellement, définissez la variable d'environnement JULIA_PROFILE_PEEK_HEAP_SNAPSHOT à 1 pour également collecter automatiquement 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 durée du profilage peut être ajustée via Profile.set_peek_duration

Le rapport de profil est divisé par fil et tâche. Passez une fonction sans argument à Profile.peek_report[] pour remplacer cela. c'est-à-dire Profile.peek_report[] = () -> Profile.print() pour supprimer tout regroupement. Cela pourrait également être remplacé par un consommateur de données de profil externe.

Reference

Profile.@profileMacro
@profile

@profile <expression> exécute votre expression tout en prenant des traces de pile périodiques. Celles-ci sont ajoutées à un tampon interne de traces de pile.

source

Les méthodes dans Profile ne sont pas exportées et doivent être appelées par exemple comme Profile.print().

Profile.printFunction
print([io::IO = stdout,] [data::Vector = fetch()], [lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data)]; kwargs...)

Imprime les résultats de profilage dans io (par défaut, stdout). Si vous ne fournissez pas de vecteur data, le tampon interne des backtraces accumulés sera utilisé.

Les arguments de mot-clé peuvent être n'importe quelle combinaison de :

  • format – Détermine si les backtraces sont imprimés avec (par défaut, :tree) ou sans (:flat) indentation indiquant la structure de l'arbre.
  • C – Si true, les backtraces du code C et Fortran sont affichés (normalement, ils sont exclus).
  • combine – Si true (par défaut), les pointeurs d'instruction sont fusionnés correspondant à la même ligne de code.
  • maxdepth – Limite la profondeur supérieure à maxdepth dans le format :tree.
  • sortedby – Contrôle l'ordre dans le format :flat. :filefuncline (par défaut) trie par la ligne source, :count trie par ordre du nombre d'échantillons collectés, et :overhead trie par le nombre d'échantillons encourus par chaque fonction elle-même.
  • groupby – Contrôle le regroupement sur les tâches et les threads, ou aucun regroupement. Les options sont :none (par défaut), :thread, :task, [:thread, :task], ou [:task, :thread] où les deux dernières fournissent un regroupement imbriqué.
  • noisefloor – Limite les frames qui dépassent le seuil de bruit heuristique de l'échantillon (s'applique uniquement au format :tree). Une valeur suggérée à essayer pour cela est 2.0 (la valeur par défaut est 0). Ce paramètre cache les échantillons pour lesquels n <= noisefloor * √N, où n est le nombre d'échantillons sur cette ligne, et N est le nombre d'échantillons pour le callee.
  • mincount – Limite l'impression uniquement à ces lignes avec au moins mincount occurrences.
  • recur – Contrôle la gestion de la récursion dans le format :tree. :off (par défaut) imprime l'arbre normalement. :flat compresse à la place toute récursion (par ip), montrant l'effet approximatif de la conversion de toute auto-récursion en un itérateur. :flatc fait la même chose mais inclut également l'effondrement des frames C (peut faire des choses étranges autour de jl_apply).
  • threads::Union{Int,AbstractVector{Int}} – Spécifiez quels threads inclure des instantanés dans le rapport. Notez que cela ne contrôle pas quels threads les échantillons sont collectés (qui peuvent également avoir été collectés sur une autre machine).
  • tasks::Union{Int,AbstractVector{Int}} – Spécifiez quelles tâches inclure des instantanés dans le rapport. Notez que cela ne contrôle pas quelles tâches les échantillons sont collectés à l'intérieur.
Julia 1.8

Les arguments de mot-clé groupby, threads, et tasks ont été introduits dans Julia 1.8.

Note

Le profilage sur Windows est limité au thread principal. D'autres threads n'ont pas été échantillonnés et ne figureront pas dans le rapport.

source
print([io::IO = stdout,] data::Vector, lidict::LineInfoDict; kwargs...)

Imprime les résultats de profilage dans io. Cette variante est utilisée pour examiner les résultats exportés par un appel précédent à retrieve. Fournissez le vecteur data des backtraces et un dictionnaire lidict des informations de ligne.

Voir Profile.print([io], data) pour une explication des arguments de mot-clé valides.

source
Profile.initFunction
init(; n::Integer, delay::Real)

Configurez le delay entre les backtraces (mesuré en secondes), et le nombre n de pointeurs d'instruction qui peuvent être stockés par thread. Chaque pointeur d'instruction correspond à une seule ligne de code ; les backtraces consistent généralement en une longue liste de pointeurs d'instruction. Notez que 6 espaces pour les pointeurs d'instruction par backtrace sont utilisés pour stocker des métadonnées et deux marqueurs de fin NULL. Les paramètres actuels peuvent être obtenus en appelant cette fonction sans arguments, et chacun peut être défini indépendamment en utilisant des mots-clés ou dans l'ordre (n, delay).

source
Profile.fetchFunction
fetch(;include_meta = true) -> data

Renvoie une copie du tampon des traces de profil. Notez que les valeurs dans data n'ont de signification que sur cette machine dans la session actuelle, car elles dépendent des adresses mémoire exactes utilisées dans la compilation JIT. Cette fonction est principalement destinée à un usage interne ; retrieve peut être un meilleur choix pour la plupart des utilisateurs. Par défaut, des métadonnées telles que threadid et taskid sont incluses. Définissez include_meta sur false pour supprimer les métadonnées.

source
Profile.retrieveFunction
retrieve(; kwargs...) -> data, lidict

"Exports" les résultats de profilage dans un format portable, retournant l'ensemble de toutes les traces de retour (data) et un dictionnaire qui associe les pointeurs d'instruction (spécifiques à la session) dans data à des valeurs LineInfo qui stockent le nom de fichier, le nom de fonction et le numéro de ligne. Cette fonction vous permet de sauvegarder les résultats de profilage pour une analyse future.

source
Profile.callersFunction
callers(funcname, [data, lidict], [filename=<filename>], [linerange=<start:stop>]) -> Vector{Tuple{count, lineinfo}}

Étant donné une exécution de profilage précédente, déterminez qui a appelé une fonction particulière. Fournir le nom de fichier (et éventuellement, la plage de numéros de ligne sur laquelle la fonction est définie) vous permet de désambiguïser une méthode surchargée. La valeur retournée est un vecteur contenant un compte du nombre d'appels et des informations de ligne sur l'appelant. On peut optionnellement fournir un data de backtrace obtenu à partir de retrieve; sinon, le tampon de profilage interne actuel est utilisé.

source
Profile.clear_malloc_dataFunction
clear_malloc_data()

Efface toutes les données d'allocation de mémoire stockées lors de l'exécution de julia avec --track-allocation. Exécutez la ou les commandes que vous souhaitez tester (pour forcer la compilation JIT), puis appelez clear_malloc_data. Ensuite, exécutez à nouveau votre ou vos commandes, quittez Julia et examinez les fichiers *.mem résultants.

source
Profile.get_peek_durationFunction
get_peek_duration()

Obtenez la durée en secondes du profil "peek" qui est déclenché via SIGINFO ou SIGUSR1, selon la plateforme.

source
Profile.set_peek_durationFunction
set_peek_duration(t::Float64)

Définir la durée en secondes du profil "peek" qui est déclenché via SIGINFO ou SIGUSR1, selon la plateforme.

source

Memory profiling

Profile.Allocs.@profileMacro
Profile.Allocs.@profile [sample_rate=0.1] expr

Profilez les allocations qui se produisent pendant expr, retournant à la fois le résultat et une structure AllocResults.

Un taux d'échantillonnage de 1.0 enregistrera tout ; 0.0 n'enregistrera rien.

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)

Voir le tutoriel de profilage dans la documentation Julia pour plus d'informations.

Julia 1.11

Les versions antérieures de Julia ne pouvaient pas capturer les types dans tous les cas. Dans les versions antérieures de Julia, si vous voyez une allocation de type Profile.Allocs.UnknownType, cela signifie que le profileur ne sait pas quel type d'objet a été alloué. Cela se produisait principalement lorsque l'allocation provenait de code généré par le compilateur. Voir issue #43688 pour plus d'infos.

Depuis Julia 1.11, toutes les allocations devraient avoir un type signalé.

Julia 1.8

Le profileur d'allocation a été ajouté dans Julia 1.8.

source

Les méthodes dans Profile.Allocs ne sont pas exportées et doivent être appelées par exemple comme Profile.Allocs.fetch().

Profile.Allocs.clearFunction
Profile.Allocs.clear()

Effacer toutes les informations d'allocation précédemment profilées de la mémoire.

source
Profile.Allocs.printFunction
Profile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...)

Imprime les résultats de profilage dans io (par défaut, stdout). Si vous ne fournissez pas de vecteur data, le tampon interne des backtraces accumulées sera utilisé.

Voir Profile.print pour une explication des arguments de mot-clé valides.

source
Profile.Allocs.fetchFunction
Profile.Allocs.fetch()

Récupérez les allocations enregistrées et décodez-les en objets Julia qui peuvent être analysés.

source
Profile.Allocs.startFunction
Profile.Allocs.start(sample_rate::Real)

Commence l'enregistrement des allocations avec le taux d'échantillonnage donné. Un taux d'échantillonnage de 1.0 enregistrera tout ; 0.0 n'enregistrera rien.

source

Heap Snapshots

Profile.take_heap_snapshotFunction
Profile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false)
Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false)

Écrivez un instantané du tas, dans le format JSON attendu par le visualiseur d'instantanés de tas des outils de développement Chrome (.heapsnapshot extension) dans un fichier ($pid_$timestamp.heapsnapshot) dans le répertoire courant par défaut (ou tempdir si le répertoire courant n'est pas accessible en écriture), ou dans dir si donné, ou le chemin de fichier complet donné, ou un flux IO.

Si all_one est vrai, alors signalez la taille de chaque objet comme un, afin qu'ils puissent être facilement comptés. Sinon, signalez la taille réelle.

Si streaming est vrai, nous allons diffuser les données de l'instantané dans quatre fichiers, en utilisant filepath comme préfixe, pour éviter de devoir conserver l'ensemble de l'instantané en mémoire. Cette option doit être utilisée pour tout paramètre où votre mémoire est limitée. Ces fichiers peuvent ensuite être réassemblés en appelant Profile.HeapSnapshot.assemble_snapshot(), ce qui peut être fait hors ligne.

REMARQUE : Nous recommandons fortement de définir streaming=true pour des raisons de performance. Reconstruire l'instantané à partir des parties nécessite de conserver l'ensemble de l'instantané en mémoire, donc si l'instantané est volumineux, vous pouvez manquer de mémoire lors de son traitement. Le streaming vous permet de reconstruire l'instantané hors ligne, après que votre charge de travail ait terminé de s'exécuter. Si vous essayez de collecter un instantané avec streaming=false (la valeur par défaut, pour des raisons de compatibilité ascendante) et que votre processus est tué, notez que cela enregistrera toujours les parties dans le même répertoire que votre chemin de fichier fourni, afin que vous puissiez toujours reconstruire l'instantané après coup, via assemble_snapshot().

source

Les méthodes dans Profile ne sont pas exportées et doivent être appelées par exemple comme Profile.take_heap_snapshot().

julia> using Profile

julia> Profile.take_heap_snapshot("snapshot.heapsnapshot")

Trace et enregistre les objets Julia sur le tas. Cela n'enregistre que les objets connus du ramasse-miettes Julia. La mémoire allouée par des bibliothèques externes non gérées par le ramasse-miettes ne s'affichera pas dans l'instantané.

Pour éviter un OOM lors de l'enregistrement du snapshot, nous avons ajouté une option de streaming pour diffuser le snapshot de tas dans quatre fichiers,

julia> using Profile

julia> Profile.take_heap_snapshot("snapshot"; streaming=true)

où "snapshot" est le chemin d'accès en tant que préfixe pour les fichiers générés.

Une fois les fichiers de snapshot générés, ils peuvent être assemblés hors ligne avec la commande suivante :

julia> using Profile

julia> Profile.HeapSnapshot.assemble_snapshot("snapshot", "snapshot.heapsnapshot")

Le fichier de snapshot de tas résultant peut être téléchargé dans les outils de développement Chrome pour être visualisé. Pour plus d'informations, voir le chrome devtools docs. Une alternative pour analyser les snapshots de tas Chromium est avec l'extension VS Code ms-vscode.vscode-js-profile-flame.

Les instantanés de tas de Firefox sont d'un format différent, et Firefox ne peut actuellement pas être utilisé pour visualiser les instantanés de tas générés par Julia.