<commit_before>\section{Construction du profiling}

L'objectif de cette partie était d'analyser et d'interpréter les traces de sortie d'exécution du code à compiler.\\

Nous avons donc développé un parseur LEX et YACC capable d'analyser les traces statiques et dynamiques issues du code instrumenté.\\

\subsection{Parsing LEX et YACC}

Les traces issues de l'instrumentation dynamique se présentent sous la forme suivante:
\begin{verbatim}
Appel X à la fonction main entrée cycle WW sortie cycle YY
Appel X à la fonction f1 entrée cycle WW sortie cycle YY
Appel X à la fonction f2 entrée cycle WW sortie cycle YY
\end{verbatim}

La grammaire reconnaissant les traces dynamiques est la suivante:
\begin{verbatim}
CALL FUNCTION NAME ENTERCYCLE NUMERIC EXITCYCLE NUMERIC RETLINE
\end{verbatim}

ENTERCYCLE et EXITCYCLE correspondent aux temps de début et de fin d'exécution d'une fonction.\\

\subsection{Profilage exclusif}

Le profilage issu du fichier de trace est inclusif, dans le sens où le temps total d'exécution d'une fonction inclut les temps d'exécution des fonctions appelées depuis la fonction parente.\\
L'analyse du fichier de traces se propose de produire un profilage exclusif, c'est-à-dire le temps d'exécution d'une fonction seule.\\

Pour pouvoir construire le profilage exclusif des fonctions, nous avions besoin de connaître les imbrications entre elles, et nous avons opté pour une construction sous forme d'arbres n-aires, où chaque noeud représente une fonction.\\

La première fonction analysée constitue la racine de l'arbre (il s'agira du point d'entrée \verb#main()# par exemple). Ensuite, pour chaque nouvelle fonction reconnue par l'analyseur, celui-ci créé un noeud qui est comparé aux noeuds précédents, en fonction des temps d'entrée et de sortie de la fonction.\\
Le noeud précédent le plus récent contenant une mesure d'entrée inférieure et une mesure de sortie supérieure au noeud dernièrement créé devient le parent de celui-ci. Le temps exclusif de la fonction parente est alors calculée en soustrayant son temps inclusif par le temps inclusif de la fonction enfant.\\

\begin{center}
  \begin{dot2tex}[neato,mathmode]
    digraph {
       label="main->exclusive time = main->inclusive time - func1->inclusive time"
      "main" -> "func1" 
      "func1" [style="fill=red!80"]
    }
  \end{dot2tex}
  \captionof{figure}{Add a first node}
\end{center}

Si un autre noeud ayant le même parent est ajouté, alors on soustrait à nouveau le temps exclusif du parent par le temps inclusif du nouveau noeud ajouté.\\

\begin{center}
  \begin{dot2tex}[neato,mathmode]
    digraph {
   label="main->exclusive time = main->exclusive time - func2->inclusive time"
   "main" -> "func1" 
   "main" -> "func2"
   "func2" [style="fill=red!80"]
    }
  \end{dot2tex}
  \captionof{figure}{Add a second node}
\end{center}

L'opération est répétée jusqu'à la fin de l'analyse du fichier de traces, jusqu'à obtenir le temps exclusif de toutes les fonctions.\\

\subsection{Gestion des instances}

L'un des objectifs du profiling est de pouvoir comparer les temps d'exécution de différentes instances d'une fonction.\\ 
Jusqu'à maintenant, tous les appels d'une même fonction sont chacun représentés par un noeud unique (on rajoute un noeud à chaque ligne lue par YACC).\\

\begin{center}
	\includegraphics[scale=0.50]{images/liste1}
	\captionof{figure}{Nodes list}
\end{center}

Nous avons avant tout besoin de détecter les fonctions redondantes du fichier d'entrée, puis les considérer comme des instances différentes d'une même fonction.
Pour établir le nombre d'instances par fonction, l'analyseur reparcourt la liste des noeuds, identifie celles ayant le même nom, puis les stocke dans une structure, en renseignant le nombre d'instances.\\

\begin{center}
	\includegraphics[scale=0.50]{images/ListIntances}
	\captionof{figure}{Nodes list with instances}
\end{center}

Les données recueillies sont ainsi sérialisées dans un fichier de sortie, puis interprétées en vue de produire un diagramme gnuplot \\

\subsection{Corrélation des instrumentations statique et dynamique}

La seconde partie du profiler consiste à corréler les données statiques et dynamiques d'une fonction, à savoir confronter le temps d'exécution et le nombre de load/store. Dans cette optique, nous avons construit une grammaire lui permettant de reconnaître les données issues de l'instrumentation statique:

\begin{verbatim}<commit_msg>Modification de la partie profiler<commit_after>\section{Construction du profiling}

L'objectif de cette partie était d'analyser et d'interpréter les traces de sortie d'exécution du code à compiler.\\

Nous avons donc développé un parseur LEX et YACC capable d'analyser les traces statiques et dynamiques issues du code instrumenté.\\

\subsection{Parsing LEX et YACC}

Les traces issues de l'instrumentation dynamique se présentent sous la forme suivante:
\begin{verbatim}
Appel X à la fonction main entrée cycle WW sortie cycle YY
Appel X à la fonction f1 entrée cycle WW sortie cycle YY
Appel X à la fonction f2 entrée cycle WW sortie cycle YY
\end{verbatim}

La grammaire reconnaissant les traces dynamiques est la suivante:
\begin{verbatim}
CALL FUNCTION NAME ENTERCYCLE NUMERIC EXITCYCLE NUMERIC RETLINE
\end{verbatim}

ENTERCYCLE et EXITCYCLE correspondent aux temps de début et de fin d'exécution d'une fonction.\\

\subsection{Profilage exclusif}

Le profilage issu du fichier de trace est inclusif, dans le sens où le temps total d'exécution d'une fonction inclut les temps d'exécution des fonctions appelées depuis la fonction parente.\\
L'analyse du fichier de traces se propose de produire un profilage exclusif, c'est-à-dire le temps d'exécution d'une fonction seule.\\

Pour pouvoir construire le profilage exclusif des fonctions, nous avions besoin de connaître les imbrications entre elles, et nous avons opté pour une construction sous forme d'arbres n-aires, où chaque noeud représente une fonction.\\

La première fonction analysée constitue la racine de l'arbre (il s'agira du point d'entrée \verb#main()# par exemple). Ensuite, pour chaque nouvelle fonction reconnue par l'analyseur, celui-ci créé un noeud qui est comparé aux noeuds précédents, en fonction des temps d'entrée et de sortie de la fonction.\\
Le noeud précédent le plus récent contenant une mesure d'entrée inférieure et une mesure de sortie supérieure au noeud dernièrement créé devient le parent de celui-ci. Le temps exclusif de la fonction parente est alors calculée en soustrayant son temps inclusif par le temps inclusif de la fonction enfant.\\

\begin{center}
  \begin{dot2tex}[neato,mathmode]
    digraph {
       label="main->exclusive time = main->inclusive time - func1->inclusive time"
      "main" -> "func1" 
      "func1" [style="fill=red!80"]
    }
  \end{dot2tex}
  \captionof{figure}{Add a first node}
\end{center}

Si un autre noeud ayant le même parent est ajouté, alors on soustrait à nouveau le temps exclusif du parent par le temps inclusif du nouveau noeud ajouté.\\

\begin{center}
  \begin{dot2tex}[neato,mathmode]
    digraph {
   label="main->exclusive time = main->exclusive time - func2->inclusive time"
   "main" -> "func1" 
   "main" -> "func2"
   "func2" [style="fill=red!80"]
    }
  \end{dot2tex}
  \captionof{figure}{Add a second node}
\end{center}

L'opération est répétée jusqu'à la fin de l'analyse du fichier de traces, jusqu'à obtenir le temps exclusif de toutes les fonctions.\\

\subsection{Gestion des instances}

L'un des objectifs du profiling est de pouvoir comparer les temps d'exécution de différentes instances d'une fonction, et de pouvoir les visualiser.\\ 
Jusqu'à maintenant, tous les appels d'une même fonction sont chacun représentés par un noeud unique (on rajoute un noeud à chaque ligne lue par YACC).\\

\begin{center}
	\includegraphics[scale=0.50]{images/liste1}
	\captionof{figure}{Nodes list}
\end{center}

Nous avons avant tout besoin de détecter les fonctions redondantes du fichier d'entrée, puis les considérer comme des instances différentes d'une même fonction.
Pour établir le nombre d'instances par fonction, l'analyseur reparcourt la liste des noeuds, identifie celles ayant le même nom, puis les stocke dans une structure, en renseignant le nombre d'instances.\\

\begin{center}
	\includegraphics[scale=0.50]{images/ListIntances}
	\captionof{figure}{Nodes list with instances}
\end{center}

Les données recueillies sont ainsi sérialisées dans un fichier de sortie, puis interprétées en vue de produire un diagramme gnuplot \\

\subsection{Corrélation des instrumentations statique et dynamique}

La seconde partie du profiler consiste à corréler les données statiques et dynamiques d'une fonction, à savoir confronter le temps d'exécution et le nombre de load/store. Dans cette optique, nous avons construit une grammaire lui permettant de reconnaître les données issues de l'instrumentation statique:

\begin{verbatim}
{'fraction_non_alphanumeric': 0.06611291951958694, 'fraction_numerical': 0.005387810079694691, 'mean_word_length': 4.7335907335907335, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1505781', 'n_tokens_mistral': 2827, 'n_tokens_neox': 2862, 'n_words': 1162}