Nantes Université

Skip to content
Extraits de code Groupes Projets
Valider 7964adb7 rédigé par Loig JEZEQUEL's avatar Loig JEZEQUEL
Parcourir les fichiers

Un TD sur la pile et le tas

parent e266eda3
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
*.aux
*.fdb_latexmk
*.fls
*.log
*.out
*.pdf
*.synctex.gz
*.tgz
# R2.04 Communication et fonctionnement bas niveau
Matériel pédagogique pour la ressource Communication et fonctionnement bas niveau du BUT informatique à l'IUT de Nantes.
\ No newline at end of file
Matériel pédagogique pour la ressource Communication et fonctionnement bas niveau du BUT informatique à l'IUT de Nantes.
## Contenu du dépôt
- td-pile-tas : un TD présentant l'organisation générale de la mémoire d'un processus avec des exemples en Go.
package main
func main() {
foo()
}
func foo() {
bar()
}
func bar() {}
package main
func main() {
foo()
bar()
}
func foo() {
foobar()
}
func bar() {}
func foobar() {}
package main
import "log"
const a int = 3
var b int
func main() {
var c int = 1
for i := 0; i < a; i++ {
b += c
c++
}
log.Print("a vaut ", a, ", b vaut ", b, " et c vaut ", c)
}
package main
func main() {
a, b := 1, 2
_ = additionBasique(a, b)
_ = additionVersPointeur(a, b)
_ = additionDepuisPointeurs(&a, &b)
}
//go:noinline
func additionBasique(x, y int) int {
z := x + y
return z
}
//go:noinline
func additionVersPointeur(x, y int) *int {
z := x + y
return &z
}
//go:noinline
func additionDepuisPointeurs(x, y *int) int {
z := *x + *y
return z
}
package main
import (
"math/rand"
"testing"
)
func BenchmarkAdditionBasique(b *testing.B) {
for i := 0; i < b.N; i++ {
a, b := rand.Intn(1000), rand.Intn(1000)
additionBasique(a, b)
}
}
func BenchmarkAdditionVersPointeur(b *testing.B) {
for i := 0; i < b.N; i++ {
a, b := rand.Intn(1000), rand.Intn(1000)
additionVersPointeur(a, b)
}
}
func BenchmarkAdditionDepuisPointeurs(b *testing.B) {
for i := 0; i < b.N; i++ {
a, b := rand.Intn(1000), rand.Intn(1000)
additionDepuisPointeurs(&a, &b)
}
}
\documentclass[a4paper]{article}
\usepackage{fullpage}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}
\usepackage[hyperfootnotes=false]{hyperref}
\usepackage{xurl}
\usepackage{helvet}
\renewcommand{\familydefault}{\sfdefault}
%\usepackage[fontsize=18pt]{scrextend}
%\usepackage{setspace}
%\onehalfspacing
%boxes
\newcounter{saveFootnote}
\usepackage{fancybox}
\newcommand{\important}[1]{\setcounter{saveFootnote}{\value{footnote}}\vspace{0.3cm}\doublebox{\begin{minipage}{14.5cm}#1\end{minipage}}\vspace{0.3cm}}
\newcommand{\remarque}[1]{\setcounter{saveFootnote}{\value{footnote}}\vspace{0.3cm}\fbox{\begin{minipage}{14.5cm}#1\end{minipage}}\vspace{0.3cm}}
\newcounter{exnum}
\setcounter{exnum}{0}
\newcommand{\exercice}[1]{\stepcounter{exnum}\setcounter{saveFootnote}{\value{footnote}}\vspace{0.3cm}\shadowbox{\begin{minipage}{14.5cm}{\bf Exercice \arabic{exnum}. }\\#1\end{minipage}}\vspace{0.3cm}}
%code
\usepackage{listings}
\usepackage{color}
\usepackage{newfloat}
\usepackage{caption}
\DeclareFloatingEnvironment[fileext=frm,placement={htbp},name=Programme]{figprog}
\captionsetup[figprog]{labelfont=bf}
%% Golang definition for listings
%% http://github.io/julienc91/lstlistings-golang
\lstdefinelanguage{Golang}{
morekeywords=[1]{package,import,func,type,struct,return,defer,panic,recover,select,var,const,iota,},
morekeywords=[2]{string,uint,uint8,uint16,uint32,uint64,int,int8,int16,int32,int64,bool,float32,float64,complex64,complex128,byte,rune,uintptr, error,interface},
morekeywords=[3]{map,slice,make,new,nil,len,cap,copy,close,true,false,delete,append,real,imag,complex,chan,},
morekeywords=[4]{for,break,continue,range,go,goto,switch,case,fallthrough,if,
else,default,},
morekeywords=[5]{Println,Printf,Error,Print,Fatal,ReadFile,Open,NewScanner,Scan,Text,Err,Create,Close},
sensitive=true,
morecomment=[l]{//},
morecomment=[s]{/*}{*/},
morestring=[b]',
morestring=[b]",
morestring=[s]{`}{`}
}
\lstset{
extendedchars=\true,
inputencoding=utf8,
literate=%
{é}{{\'{e}}}1
{è}{{\`{e}}}1
{ê}{{\^{e}}}1
{ë}{{{e}}}1
{û}{{\^{u}}}1
{ù}{{\`{u}}}1
{â}{{\^{a}}}1
{à}{{\`{a}}}1
{î}{{\^{i}}}1
{ô}{{\^{o}}}1
{ç}{{\c{c}}}1
{Ç}{{\c{C}}}1
{É}{{\'{E}}}1
{Ê}{{\^{E}}}1
{À}{{\`{A}}}1
{Â}{{\^{A}}}1
{Î}{{\^{I}}}1,
frame=none,
xleftmargin=1cm,
basicstyle=\footnotesize,
keywordstyle=\bf\color{blue},
numbers=none,
numbersep=5pt,
showstringspaces=false,
stringstyle=\color{red},
tabsize=4,
language=Golang % this is it !
}
\newcommand{\sourcecode}[2]{\begin{minipage}{#2}\par\noindent\rule{\textwidth}{0.4pt}\lstinputlisting{src/#1}\par\noindent\rule{\textwidth}{0.4pt}
\end{minipage}}
% schémas
\usepackage{tikz}
% macros générales
\newcommand{\file}[1]{\textsf{#1}}
\newcommand{\madoc}{\textsc{Madoc}}
\newcommand{\term}[1]{\textsf{\underline{#1}}}
\newcommand{\inlinecode}[1]{\textsf{#1}}
\newcommand{\goout}[1]{\textsf{#1}} % retours de go, erreurs, etc
\title{TD 1~: la pile, le tas et le segment de données}
\author{Communication et fonctionnement bas niveau}
\date{BUT informatique, première année}
\begin{document}
\maketitle{}
Lorsqu'on exécute un programme informatique on le stocke, ainsi que les données qu'il manipule (les constantes et variables du programme), dans la mémoire de l'ordinateur.
Ce programme dispose alors d'un espace mémoire qui lui est réservé et qui s'organise en différentes zones (figure~\ref{fig:mem}).
\vspace{0.5cm}
\begin{minipage}{0.3\textwidth}
\centering
\begin{tikzpicture}
\draw[fill=red!15] (0,10) rectangle (4,8);
\draw[fill=orange!30] (0,6) rectangle (4,4);
\draw[fill=green!20] (0,4) rectangle (4,2);
\draw[fill=cyan!25] (0,2) rectangle (4,0);
\draw[thick] (0,0) rectangle (4,10);
\draw (0,8) -- (4,8);
\draw (0,2) -- (4,2);
\draw (0,4) -- (4,4);
\draw (0,6) -- (4,6);
\draw[-latex, thick] (2, 8) -- (2, 7.5);
\draw[-latex, thick] (2, 6) -- (2, 6.5);
\node (info) at (2,9) {Pile};
\node (info) at (2,5) {Tas};
\node (info) at (2,3) {Données};
\node (info) at (2,1) {Code};
\end{tikzpicture}
\captionof{figure}{Schéma de l'organisation de la mémoire d'un programme}
\label{fig:mem}
\end{minipage}
\begin{minipage}{0.7\textwidth}
La {\bf pile} est une zone mémoire dans laquelle seront stockées les données utilisées de manière temporaire par les fonctions (leurs paramètres par exemple) uniquement au moment où elles sont nécessaires.
\vspace{1cm}
Le {\bf tas} est une zone mémoire dans laquelle seront stockées les données créées durant l'exécution du programme mais dont la durée de vie n'est pas directement liée à celle d'une fonction (par exemple car elles continuent à être utilisées après la fin de la fonction qui les a créées).
\vspace{1cm}
Le segment de {\bf données} est une zone mémoire dans laquelle seront stockées les données créées à la compilation du programme (les constantes par exemple). Cette zone est en fait séparée en plusieurs parties (.data, .bss, etc).
\vspace{1cm}
Enfin, le {\bf code} du programme lui même est aussi stocké dans la mémoire.
\vspace{1.5cm}
\end{minipage}
\vspace{0.5cm}
\important{L'objectif de ce TD est de comprendre comment se fait en général le choix de stocker des données dans la pile, le tas ou le segment de données.}
\newpage{}
\section{La pile}
La pile (\emph{stack)} est une zone mémoire qui fonctionne comme un pile au sens algorithmique du terme (i.e. une structure de données LIFO).
À chaque appel de fonction, un espace appelé {\bf cadre} (\emph{frame}) de la fonction est créé au sommet de la pile (on utilise donc l'opération \emph{push}).
Ce cadre contient notamment les paramètres de la fonction et les variables déclarées dans celle-ci\footnote{En fait c'est un peu plus compliqué que ça~: les variables locales et, dans certaines architectures, les paramètres peuvent parfois être stockés dans des registres.}.
À chaque retour de fonction, le cadre de cette fonction est retiré de la pile (on utilise donc l'opération \emph{pop}) et on se retrouve donc avec le cadre de la fonction qui l'avait appelée au sommet de la pile.
\remarque{En pratique on n'enlève pas vraiment de données de la pile, on se contente de déplacer un pointeur indiquant le sommet de la pile. Ce pointeur est appelé \emph{stack pointer} (SP).}
La figure~\ref{fig:pile} montre à quoi peut ressembler la pile au cours de l'exécution d'un programme.
Ici, la fonction \inlinecode{main} a appelé la fonction \inlinecode{foo}, qui elle-même a appelé la fonction \inlinecode{bar}.
Au moment où la pile est représentée, la fonction \inlinecode{bar} vient de se terminer.
\begin{figure}[htbp]
\sourcecode{pile.go}{0.5\textwidth}
\begin{minipage}{0.5\textwidth}
\begin{center}
\begin{tikzpicture}
\draw[thick] (0,0) -- (0, 4) -- (3, 4) -- (3, 0);
\draw[fill=blue!25] (0.2, 3.8) rectangle (2.8,2.8);
\draw[fill=blue!25] (0.2, 2.6) rectangle (2.8,1.6);
\draw[fill=blue!10,dashed] (0.2, 1.4) rectangle (2.8,0.4);
\node (info) at (1.5, 3.3) {main};
\node (info) at (1.5, 2.1) {foo};
\node (info) at (1.5, 0.9) {bar};
\draw[-latex, thick] (3.5, 1.5) -- (3, 1.5);
\node (sp) at (3.8, 1.5) {SP};
\draw[-latex] (4.5, 3.5) -- (4.5, 0.5);
\node[rotate=-90] (info) at (4.7, 2.1) {\footnotesize Ordre d'empilement};
\end{tikzpicture}
\end{center}
\end{minipage}
\caption{Un programme avec plusieurs fonctions et un aperçu de l'état de la pile juste après le retour de la fonction \inlinecode{bar}}\label{fig:pile}
\end{figure}
\exercice{Récupérer, sur \madoc{}, le programme \file{pile2.go} puis dessiner l'état de la pile au début et à la fin de chaque appel de fonction.}
\newpage{}
\section{Le tas}
Comme on a pu le comprendre en étudiant le fonctionnement de la pile dans la partie précédente, une donnée qui appartient au cadre d'une fonction (donc qui est mise dans la pile au moment de l'appel à cette fonction) n'existe plus (ou en tout cas n'est plus accessible) une fois que cette fonction a fini de s'exécuter.
(On rappelle aussi que -- même si ce n'est pas évident puisque ces données existent dans la pile -- sans pointeurs\footnote{On reparlera plus en détails des pointeurs dans la suite de ce cours.} les données du cadre d'une fonction ne sont pas non plus accessibles directement par les autres fonctions, même si ce cadre est encore dans la pile.)
Ces données qui ne peuvent pas être placées dans la pile seront placées dans le tas (\emph{heap}).
\exercice{Récupérer, sur \madoc{}, le programme \file{tas.go} et le jeu de tests associé \file{tas\_test.go}, puis consulter ces programmes et expliquer leur fonctionnement.
\begin{itemize}
\item À votre avis, quelles variables devraient être placées dans le tas et quelles variables devraient être placées dans la pile ?
\end{itemize}}
\remarque{Le commentaire \verb;go:noinline; qu'on trouve au dessus de chacune des fonctions est une directive donnée au compilateur (on parle parfois de \emph{pragma}). Elle lui interdit de faire une optimisation particulière qui consiste à remplacer les appels à une fonction par le code de cette fonction (ce qui ferait notamment, dans le cas de ce programme, que tout serait dans le main et donc que l'utilisation du tas ne serait plus jamais nécessaire). À tout moment dans la suite de ce TD vous pouvez supprimer ce commentaire pour voir ce que cela change (mais n'oubliez pas de le remettre ensuite).}
On rappelle qu'on peut exécuter les \emph{benchmarks} du fichier \file{tas\_test.go} par la commande
\begin{center}
\inlinecode{go test -bench=. tas.go tas\_test.go}
\end{center}
En ajoutant l'option \inlinecode{-benchmem} à cette commande on obtient, en plus des informations sur les temps d'exécution, des informations sur la gestion de la mémoire~:
\begin{center}
\inlinecode{go test -bench=. -benchmem tas.go tas\_test.go}
\end{center}
\exercice{Exécuter les \emph{benchmarks} du fichier \file{tas\_test.go} avec l'option \inlinecode{-benchmem}. Expliquer les résultats obtenus (pour cela il ne faut pas hésiter à se documenter sur la signification des informations affichées par \inlinecode{go test}).
\begin{itemize}
\item Quel(s) appel(s) de fonction(s) utilise(nt) le tas ?
\item Qu'est-ce qui vous semble plus efficace en termes de temps de calcul entre l'utilisation du tas et l'utilisation de la pile ?
\end{itemize}}
On peut obtenir plus d'informations sur les variables exactes qui sont placées dans le tas on peut compiler le programme avec l'option \inlinecode{-gcflags -m} :
\begin{center}
\inlinecode{go build -gcflags -m tas.go}
\end{center}
\remarque{Quand on utilise la commande \inlinecode{go build} avec \inlinecode{-gcflags} comme argument, on passe des options de compilation (\emph{flags}) au compilateur Go (\inlinecode{gc} signifie ici \emph{Go compiler}).
En effet, la commande \inlinecode{go build} est quelque chose de plus général que simplement un compilateur (elle va vérifier les dépendances, gérer les modules, etc) mais elle utilise un compilateur pour produire un exécutable.
Ici, on passe l'option \inlinecode{-m} qui permet d'afficher des informations sur les décisions d'optimisation (comme par exemple le choix de mettre une variable dans le tas ou dans la pile)\footnotemark{}.}
\stepcounter{saveFootnote}
\footnotetext[\value{saveFootnote}]{On peut obtenir plus d'informations en utilisant \inlinecode{go help build} qui nous explique que \inlinecode{-gcflags} passe des arguments à \inlinecode{go tool compile}.
En faisant \inlinecode{go help tool} on apprend qu'on peut obtenir des explication plus précise en utilisant \inlinecode{go doc cmd/compile}.
Cette dernière commande nous donne la signification exacte de \inlinecode{-m} et nous indique aussi toutes les autres options possibles.
On y trouve aussi des infos sur \verb;go:noinline; dont on a parlé plus tôt.
Et bien sûr, une version en ligne de cette page de documentation existe~: \url{https://pkg.go.dev/cmd/compile}}
\exercice{Utiliser la commande \inlinecode{go build -gcflags -m tas.go}.
\begin{itemize}
\item Quelles sont les variables qui sont placées dans le tas ? Expliquer.
\end{itemize}}
\remarque{Vous aurez peut-être noté que, quand on utilise \inlinecode{go build -gcflags -m tas.go} et \inlinecode{go test -bench=. -benchmem tas.go tas\_test.go} ce n'est pas le même code qui est analysé~: dans le premier cas c'est la fonction main qui est le point de départ du programme alors que dans le deuxième ce sont les fonction de \emph{benchmark} situées dans le fichier \file{tas\_test.go}}
On a observé des variables qui sont stockées dans le tas et des variables qui sont stockées dans la pile.
Cependant, ce qu'on a constaté dans l'exercice précédent n'est pas un règle absolue et, en pratique, les décisions prises par le compilateur Go sont plus complexes que cela.
Il peut être difficile de prévoir à l'avance si une variable va être dans le tas ou non (et cela peut varier d'une version à l'autre du compilateur).
On peut obtenir un peu plus d'informations dans la FAQ de Go
\exercice{Consulter le point sur l'allocation de la mémoire dans la FAQ de Go~: \url{https://go.dev/doc/faq\#stack_or_heap}.
\begin{itemize}
\item Est-ce que seules les variables dont on récupère l'adresse dans un programme sont susceptibles d'être stockées dans le tas ?
\item Est-ce qu'une variable dont on récupère l'adresse dans un programme est obligatoirement stockée dans le tas ?
\end{itemize}}
\remarque{Les espaces mémoires qui sont occupés dans le tas doivent être libérés quand ils ne sont plus utilisés pour éviter des fuites de mémoire (\emph{memory leaks}) menant à une occupation trop importante de la mémoire et donc, lorsqu'il n'y a plus d'espace disponible, à des ralentissements importants des programmes, voir à un blocage complet de l'ordinateur.
Par rapport à d'autres langages de bas niveau (comme le C) où le programmeur doit lui même réserver et libérer la mémoire, le Go a une différence majeure~: il y a un ramasse-miettes (\emph{garbage collector}) qui se charge de temps en temps de libérer la mémoire qui n'est plus utile (les espaces du tas qui ne seront plus jamais utilisés dans le reste du programme).
Ceci simplifie la vie au programmeur mais ajoute un surcoût à l'utilisation du tas.
Le fonctionnement du ramasse-miettes est assez complexe et nous ne l'aborderons pas dans ce cours.}
\newpage{}
\section{Le segment de données}
Le segment de données contient les données qui peuvent être déterminées à la compilation (donc dont on connaît la taille et dont on sait qu'elles existeront dans toute exécution sans avoir a exécuter le programme).
On peut voir ces données, ainsi que le code assembleur du programme \file{segment\_donnees.go} en utilisant la commande suivante~:
\begin{center}
\inlinecode{go tool compile -S -S segment\_donnees.go}
\end{center}
Attention, il y a bien deux fois le \inlinecode{-S}, en le mettant une seule fois on ne voit que le code et pas les données.
\exercice{Récuperer le fichier \file{segment\_donnees.go} sur \madoc{} et tester la commande \inlinecode{go tool compile -S -S segment\_donnees.go} et repérer la partie données et la partie code dans le résultat produit. Dans le code, SP signifie \emph{stack pointer} et SB signifie \emph{static base pointer}.
\begin{itemize}
\item La variable b est-elle allouée statiquement ?
\item La variable c est-elle allouée statiquement ?
\item Que remarquez-vous sur la constante a ?
\item Que remarquez-vous sur les chaînes de caractères utilisées dans le programme ?
\end{itemize}}
\vfill{}
\section*{Références}
\begin{enumerate}
\item \url{https://povilasv.me/go-memory-management/}~: des explications générales sur la gestion de la mémoire, mises en lien avec le cas particulier de Go.
\item \url{https://medium.com/eureka-engineering/understanding-allocations-in-go-stack-heap-memory-9a2631b5035d}~: des explications sur les choix faits par Go d'allouer la mémoire dans la pile ou dans le tas.
\item \url{https://go.dev/doc/asm}~: quelques informations sur le langage assembleur utilisé par le compilateur Go.
\item \url{https://go.dev/doc/faq}~: la FAQ officielle du langage Go.
\end{enumerate}
\end{document}
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter