Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Tutoriel : Réduire les allocations de mémoire avec
Souvent, le réglage des performances pour une application .NET implique deux techniques. Tout d’abord, réduisez le nombre et la taille des allocations de tas. Ensuite, réduisez la fréquence à laquelle les données sont copiées. Visual Studio fournit d’excellents outils qui permettent d’analyser la façon dont votre application utilise la mémoire. Une fois que vous avez déterminé où votre application effectue des allocations inutiles, vous apportez des modifications pour réduire ces allocations. Vous convertissez les types class
en types struct
. Vous utilisez ref
des fonctionnalités de sécurité pour préserver la sémantique et réduire la copie supplémentaire.
Utilisez Visual Studio 17.5 pour une expérience optimale avec ce didacticiel. L’outil d’allocation d’objets .NET utilisé pour analyser l’utilisation de la mémoire fait partie de Visual Studio. Vous pouvez utiliser Visual Studio Code et la ligne de commande pour exécuter l’application et apporter toutes les modifications. Toutefois, vous ne pourrez pas voir les résultats d’analyse de vos modifications.
L’application que vous allez utiliser est une simulation d’une application IoT qui surveille plusieurs capteurs pour déterminer si un intrus a entré une galerie secrète avec des objets de valeur. Les capteurs IoT envoient constamment des données qui mesurent la combinaison d’oxygène (O2) et de dioxyde de carbone (CO2) dans l’air. Ils signalent également la température et l’humidité relative. Chacune de ces valeurs varie légèrement tout le temps. Toutefois, lorsqu’une personne entre dans la pièce, le changement un peu plus, et toujours dans la même direction : l’oxygène diminue, le dioxyde de carbone augmente, la température augmente, comme l’humidité relative. Lorsque les capteurs combinent pour afficher des augmentations, l’alarme d’intrus est déclenchée.
Dans ce tutoriel, vous allez exécuter l’application, prendre des mesures sur les allocations de mémoire, puis améliorer les performances en réduisant le nombre d’allocations. Le code source est disponible dans le navigateur d’exemples.
Explorer l’application de démarrage
Téléchargez l’application et exécutez l’exemple de démarrage. L’application de démarrage fonctionne correctement, mais parce qu’elle alloue de nombreux petits objets avec chaque cycle de mesure, ses performances se dégradent lentement au fil du temps.
Press <return> to start simulation
Debounced measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Average measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Debounced measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
Average measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
De nombreuses lignes ont été supprimées.
Debounced measurements:
Temp: 67.597
Humidity: 46.543%
Oxygen: 19.021%
CO2 (ppm): 429.149
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
Debounced measurements:
Temp: 67.602
Humidity: 46.835%
Oxygen: 19.003%
CO2 (ppm): 429.393
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
Vous pouvez explorer le code pour découvrir le fonctionnement de l’application. Le programme principal exécute la simulation. Une fois que vous appuyez <Enter>
sur , il crée une salle et collecte des données de base initiales :
Console.WriteLine("Press <return> to start simulation");
Console.ReadLine();
var room = new Room("gallery");
var r = new Random();
int counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
Console.WriteLine();
counter++;
return counter < 20000;
});
Une fois que les données de référence ont été établies, elles exécutent la simulation sur la salle, où un générateur de nombres aléatoires détermine si un intrus est entré dans la salle :
counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
room.Intruders += (room.Intruders, r.Next(5)) switch
{
( > 0, 0) => -1,
( < 3, 1) => 1,
_ => 0
};
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
Console.WriteLine();
counter++;
return counter < 200000;
});
D’autres types contiennent les mesures, une mesure de réponse qui est la moyenne des 50 dernières mesures et la moyenne de toutes les mesures prises.
Ensuite, exécutez l’application à l’aide de l’outil d’allocation d’objets .NET. Vérifiez que vous utilisez la Release
build, et non la Debug
build. Dans le menu Débogage , ouvrez le profileur de performances. Cochez l’option .NET Object Allocation Tracking , mais rien d’autre. Exécutez votre application pour terminer. Le profileur mesure les allocations d’objets et signale les allocations et les cycles de nettoyage de la mémoire. Vous devez voir un graphique similaire à l’image suivante :
Le graphique précédent montre que le travail de réduction des allocations offre des avantages en matière de performances. Vous voyez un modèle en dents de scie dans le graphique des objets en temps réel. Cela vous indique que de nombreux objets sont créés qui deviennent rapidement garbage. Ils sont collectés ultérieurement, comme le montre le graphe delta de l'objet. Les barres rouges orientées vers le bas montrent un cycle de ramasse-miettes.
Examinez ensuite l’onglet Allocations sous les graphiques. Ce tableau indique quels types sont alloués le plus :
Le type System.String représente la plupart des allocations. La tâche la plus importante doit être de réduire la fréquence des allocations de chaînes. Cette application affiche constamment de nombreux affichages formatés sur la console. Pour cette simulation, nous voulons conserver les messages. Nous allons donc nous concentrer sur les deux lignes suivantes : le SensorMeasurement
type et le IntruderRisk
type.
Double-cliquez sur la SensorMeasurement
ligne. Vous pouvez voir que toutes les allocations ont lieu dans la static
méthode SensorMeasurement.TakeMeasurement
. Vous pouvez voir la méthode dans l’extrait de code suivant :
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
Chaque mesure alloue un nouvel SensorMeasurement
objet, qui est un class
type. Chaque SensorMeasurement
créé provoque une allocation de tas.
Modifier les classes en structs
Le code suivant montre la déclaration initiale de SensorMeasurement
:
public class SensorMeasurement
{
private static readonly Random generator = new Random();
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
private const double CO2Concentration = 409.8; // increases with people.
private const double O2Concentration = 0.2100; // decreases
private const double TemperatureSetting = 67.5; // increases
private const double HumiditySetting = 0.4500; // increases
public required double CO2 { get; init; }
public required double O2 { get; init; }
public required double Temperature { get; init; }
public required double Humidity { get; init; }
public required string Room { get; init; }
public required DateTime TimeRecorded { get; init; }
public override string ToString() => $"""
Room: {Room} at {TimeRecorded}:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
Le type a été créé à l’origine en tant que class
parce qu’il contient de nombreuses double
mesures. Il est plus grand que ce que vous souhaitez copier dans des chemins chauds. Toutefois, cette décision signifiait un grand nombre d’allocations. Remplacez le type d’un class
par un struct
.
La modification d’une class
pour une struct
introduit quelques erreurs de compilateur, car le code d’origine utilisait des vérifications de référence null
à quelques endroits. La première est dans la DebounceMeasurement
classe, dans la AddMeasurement
méthode :
public void AddMeasurement(SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i] is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
Le DebounceMeasurement
type contient un tableau de 50 mesures. Les lectures d’un capteur sont signalées comme la moyenne des 50 dernières mesures. Cela réduit le bruit dans les lectures. Avant que 50 lectures complètes aient été prises, ces valeurs sont null
. Le code vérifie la null
référence pour signaler la moyenne correcte au démarrage du système. Après avoir modifié le SensorMeasurement
type en struct, vous devez utiliser un autre test. Le type SensorMeasurement
inclut un identificateur de pièce string
. Vous pouvez donc utiliser ce test à la place :
if (recentMeasurements[i].Room is not null)
Les trois autres erreurs du compilateur sont toutes dans la méthode qui prend à plusieurs reprises des mesures dans une salle :
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
Dans la méthode de démarrage, la variable locale pour l’objet SensorMeasurement
est une référence nullable :
SensorMeasurement? measure = default;
Maintenant que le SensorMeasurement
est un struct
au lieu d'un class
, le nullable est un type de valeur nullable. Vous pouvez modifier la déclaration en type valeur pour corriger les erreurs restantes du compilateur :
SensorMeasurement measure = default;
Maintenant que les erreurs du compilateur ont été traitées, vous devez examiner le code pour vous assurer que la sémantique n’a pas changé. Étant donné que struct
les types sont passés par valeur, les modifications apportées aux paramètres de méthode ne sont pas visibles une fois la méthode retournée.
Importante
La modification d’un type d’un class
à un struct
peut modifier la sémantique de votre programme. Lorsqu’un class
type est passé à une méthode, toutes les mutations effectuées dans la méthode sont apportées à l’argument. Lorsqu’un type struct
est passé à une méthode, les mutations effectuées dans la méthode portent sur une copie de l’argument. Cela signifie que toute méthode qui modifie ses arguments par conception doit être mise à jour pour utiliser le ref
modificateur sur n’importe quel type d’argument que vous avez changé d’un class
à un struct
.
Le SensorMeasurement
type n’inclut aucune méthode qui change d’état, ce qui n’est pas un problème dans cet exemple. Vous pouvez prouver cela en ajoutant le readonly
modificateur à la SensorMeasurement
structure :
public readonly struct SensorMeasurement
Le compilateur impose la nature readonly
de la structure SensorMeasurement
. Si votre inspection du code a manqué une méthode qui a modifié l’état, le compilateur vous indiquerait. Votre application se compile toujours sans erreurs, donc ce type est readonly
. L’ajout du readonly
modificateur lorsque vous modifiez un type d’un class
à un struct
peut vous aider à trouver des membres qui modifient l’état du struct
.
Éviter d’effectuer des copies
Vous avez supprimé un grand nombre d’allocations inutiles de votre application. Le SensorMeasurement
type n’apparaît pas dans le tableau n’importe où.
À présent, il effectue une copie supplémentaire de la SensorMeasurement
structure chaque fois qu’elle est utilisée comme paramètre ou valeur de retour. Le SensorMeasurement
struct contient quatre doubles, a DateTime et a string
. Cette structure est sensiblement plus grande qu’une référence. Ajoutons les modificateurs ref
ou in
aux emplacements où le type SensorMeasurement
est utilisé.
L’étape suivante consiste à rechercher des méthodes qui retournent une mesure ou à prendre une mesure en tant qu’argument et à utiliser des références dans la mesure du possible. Commencez dans la structure SensorMeasurement
. La méthode statique TakeMeasurement
crée et retourne un nouveau SensorMeasurement
:
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
Laissons celui-ci en l’état, en retournant par valeur. Si vous avez essayé de retourner par ref
, vous obtiendriez une erreur du compilateur. Vous ne pouvez pas retourner ref
à une structure nouvellement créée localement dans la méthode. La conception de la structure immuable signifie que vous pouvez uniquement définir les valeurs de la mesure lors de sa création. Cette méthode doit créer une nouvelle structure de mesure.
Examinons à nouveau DebounceMeasurement.AddMeasurement
. Vous devez ajouter le in
modificateur au measurement
paramètre :
public void AddMeasurement(in SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i].Room is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
Cela économise une opération de copie. Le in
paramètre est une référence à la copie déjà créée par l’appelant. Vous pouvez également enregistrer une copie avec la TakeMeasurement
méthode dans le Room
type. Cette méthode illustre la façon dont le compilateur assure la sécurité lorsque vous passez des arguments par ref
. La méthode initiale TakeMeasurement
dans le Room
type prend un argument de Func<SensorMeasurement, bool>
. Si vous essayez d’ajouter le modificateur in
ou ref
à cette déclaration, le compilateur signale une erreur. Vous ne pouvez pas passer d’argument ref
à une expression lambda. Le compilateur ne peut pas garantir que l’expression appelée ne copie pas la référence. Si l’expression lambda capture la référence, la référence peut avoir une durée de vie plus longue que la valeur à laquelle elle fait référence. L’accès à celui-ci en dehors de son contexte de sécurité ref entraîne une altération de la mémoire. Les ref
règles de sécurité ne l’autorisent pas. Vous pouvez en savoir plus dans la vue d’ensemble des fonctionnalités de sécurité ref.
Conserver la sémantique
Les derniers ensembles de modifications n’ont pas d’impact majeur sur les performances de cette application, car les types ne sont pas créés dans les chemins chauds. Ces modifications illustrent certaines des autres techniques que vous utiliseriez dans votre réglage des performances. Examinons la classe initiale Room
:
public class Room
{
public AverageMeasurement Average { get; } = new ();
public DebounceMeasurement Debounce { get; } = new ();
public string Name { get; }
public IntruderRisk RiskStatus
{
get
{
var CO2Variance = (Debounce.CO2 - Average.CO2) > 10.0 / 4;
var O2Variance = (Average.O2 - Debounce.O2) > 0.005 / 4.0;
var TempVariance = (Debounce.Temperature - Average.Temperature) > 0.05 / 4.0;
var HumidityVariance = (Debounce.Humidity - Average.Humidity) > 0.20 / 4;
IntruderRisk risk = IntruderRisk.None;
if (CO2Variance) { risk++; }
if (O2Variance) { risk++; }
if (TempVariance) { risk++; }
if (HumidityVariance) { risk++; }
return risk;
}
}
public int Intruders { get; set; }
public Room(string name)
{
Name = name;
}
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
}
Ce type contient plusieurs propriétés. Certaines sont des types class
. La création d’un Room
objet implique plusieurs allocations. Un pour le Room
lui-même, et un pour chacun des membres d’un class
type qu’il contient. Vous pouvez convertir deux de ces propriétés de types class
en types struct
: les types DebounceMeasurement
et AverageMeasurement
. Examinons cette transformation avec les deux types.
Remplacez le type DebounceMeasurement
de class
par struct
. Cela introduit une erreur CS8983: A 'struct' with field initializers must include an explicitly declared constructor
du compilateur . Vous pouvez résoudre ce problème en ajoutant un constructeur sans paramètre vide :
public DebounceMeasurement() { }
Vous pouvez en savoir plus sur cette exigence dans l’article de référence du langage sur les structures.
Le Object.ToString() remplacement ne modifie aucune des valeurs du struct. Vous pouvez ajouter le readonly
modificateur à cette déclaration de méthode. Le DebounceMeasurement
type est mutable, vous devez donc vous assurer que les modifications n’affectent pas les copies qui sont supprimées. La AddMeasurement
méthode modifie l’état de l’objet. Elle est appelée à partir de la Room
classe, dans la TakeMeasurements
méthode. Vous souhaitez que ces modifications persistent après l’appel de la méthode. Vous pouvez modifier la Room.Debounce
propriété pour renvoyer une référence à une seule instance du DebounceMeasurement
type :
private DebounceMeasurement debounce = new();
public ref readonly DebounceMeasurement Debounce { get { return ref debounce; } }
Il existe quelques modifications dans l’exemple précédent. Tout d’abord, la propriété est une propriété en lecture seule qui retourne une référence en lecture seule à l’instance appartenant à cette pièce. Il est maintenant soutenu par un champ déclaré qui est initialisé lorsque l’objet Room
est instancié. Après avoir apporté ces modifications, vous allez mettre à jour l’implémentation de la méthode AddMeasurement
. Il utilise le champ de stockage privé, debounce
et non la propriété Debounce
en lecture seule . Ainsi, les modifications se produisent sur l’instance unique créée lors de l’initialisation.
La même technique fonctionne avec la Average
propriété. Tout d’abord, vous modifiez le AverageMeasurement
type d’un class
à un struct
, puis ajoutez le readonly
modificateur à la ToString
méthode :
namespace IntruderAlert;
public struct AverageMeasurement
{
private double sumCO2 = 0;
private double sumO2 = 0;
private double sumTemperature = 0;
private double sumHumidity = 0;
private int totalMeasurements = 0;
public AverageMeasurement() { }
public readonly double CO2 => sumCO2 / totalMeasurements;
public readonly double O2 => sumO2 / totalMeasurements;
public readonly double Temperature => sumTemperature / totalMeasurements;
public readonly double Humidity => sumHumidity / totalMeasurements;
public void AddMeasurement(in SensorMeasurement datum)
{
totalMeasurements++;
sumCO2 += datum.CO2;
sumO2 += datum.O2;
sumTemperature += datum.Temperature;
sumHumidity+= datum.Humidity;
}
public readonly override string ToString() => $"""
Average measurements:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
Ensuite, vous modifiez la Room
classe en suivant la même technique que celle que vous avez utilisée pour la Debounce
propriété. La propriété Average
retourne un readonly ref
au champ privé pour la mesure moyenne. La AddMeasurement
méthode modifie les champs internes.
private AverageMeasurement average = new();
public ref readonly AverageMeasurement Average { get { return ref average; } }
Éviter la boxe
Il y a un changement final pour améliorer les performances. Le programme principal imprime des statistiques pour la pièce, y compris l’évaluation des risques :
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
L’appel au ToString
généré boxe la valeur d’énumération. Vous pouvez éviter cela en écrivant un remplacement dans la Room
classe qui met en forme la chaîne en fonction de la valeur du risque estimé :
public override string ToString() =>
$"Calculated intruder risk: {RiskStatus switch
{
IntruderRisk.None => "None",
IntruderRisk.Low => "Low",
IntruderRisk.Medium => "Medium",
IntruderRisk.High => "High",
IntruderRisk.Extreme => "Extreme",
_ => "Error!"
}}, Current intruders: {Intruders.ToString()}";
Ensuite, modifiez le code dans le programme principal pour appeler cette nouvelle ToString
méthode :
Console.WriteLine(room.ToString());
Exécutez l’application à l’aide du profileur et examinez la table mise à jour pour les allocations.
Vous avez supprimé de nombreuses allocations et fourni à votre application une amélioration des performances.
Utilisation de la sécurité de référence dans votre application
Ces techniques sont une optimisation des performances à un niveau bas. Ils peuvent augmenter les performances de votre application lorsqu’elles sont appliquées à des chemins d’accès chauds, et lorsque vous avez mesuré l’impact avant et après les modifications. Dans la plupart des cas, le cycle que vous allez suivre est le suivant :
- Mesurer les allocations : déterminez les types qui sont le plus alloués et quand vous pouvez réduire les allocations de tas.
-
Convertir la classe en struct : plusieurs fois, les types peuvent être convertis d’un
class
à unstruct
. Votre application utilise de l’espace de pile au lieu d’effectuer des allocations de tas. -
Conserver la sémantique : la conversion d’un
class
en unstruct
peut avoir un impact sur la sémantique des paramètres et des valeurs de retour. Toute méthode qui modifie ses paramètres doit maintenant marquer ces paramètres avec leref
modificateur. Cela garantit que les modifications sont apportées à l’objet correct. De même, si une valeur de retour de propriété ou de méthode doit être modifiée par l’appelant, ce retour doit être marqué avec leref
modificateur. -
Évitez les copies : lorsque vous passez un struct volumineux en tant que paramètre, vous pouvez marquer le paramètre avec le
in
modificateur. Vous pouvez passer une référence en moins d’octets et vous assurer que la méthode ne modifie pas la valeur d’origine. Vous pouvez également retourner des valeurs en renvoyantreadonly ref
une référence qui ne peut pas être modifiée.
Ces techniques vous permettent d’améliorer les performances dans les parties critiques de votre code.