Maison > Java > javaDidacticiel > Amélioration des performances grâce à l'analyse statique, à l'initialisation d'images et à la capture instantanée de tas

Amélioration des performances grâce à l'analyse statique, à l'initialisation d'images et à la capture instantanée de tas

PHPz
Libérer: 2024-09-03 10:30:59
original
283 Les gens l'ont consulté

Des structures monolithiques au monde des systèmes distribués, le développement d'applications a parcouru un long chemin. L’adoption massive du cloud computing et de l’architecture des microservices a considérablement modifié l’approche de création et de déploiement des applications serveur. Au lieu de serveurs d'applications géants, nous disposons désormais de services indépendants, déployés individuellement, qui entrent en action
au fur et à mesure des besoins.

Cependant, un nouvel acteur susceptible d'avoir un impact sur ce bon fonctionnement pourrait être les 'démarrages à froid'. Les démarrages à froid surviennent lorsque la première requête est traitée sur un travailleur fraîchement généré. Cette situation nécessite l’initialisation du runtime du langage et de la configuration du service avant de traiter la demande réelle. L'imprévisibilité et la lenteur d'exécution associées aux démarrages à froid peuvent enfreindre les accords de niveau de service d'un service cloud. Alors, comment contrer cette inquiétude croissante ?

Image native : optimisation du temps de démarrage et de l'empreinte mémoire

Pour lutter contre les inefficacités des démarrages à froid, une nouvelle approche a été développée impliquant l'analyse des points d'accès, l'initialisation de l'application au moment de la construction, la capture instantanée du tas et la compilation (AOT) à l'avance. Cette méthode fonctionne dans le cadre d'un monde fermé, exigeant que toutes les classes Java soient prédéterminées et accessibles au moment de la construction. Au cours de cette phase, une analyse complète des points à atteindre détermine tous les éléments de programme accessibles (classes, méthodes, champs) pour garantir que seules les méthodes Java essentielles sont compilées.

Le code d'initialisation de l'application peut s'exécuter pendant le processus de construction plutôt qu'au moment de l'exécution. Cela permet la pré-allocation d'objets Java et la construction de structures de données complexes, qui sont ensuite mises à disposition au moment de l'exécution via un « tas d'images ». Ce tas d'images est intégré à l'exécutable, offrant une disponibilité immédiate au démarrage de l'application. Le
l'exécution itérative de l'analyse des points de destination et de la capture instantanée se poursuit jusqu'à ce qu'un état stable (point fixe) soit atteint, optimisant à la fois le temps de démarrage et la consommation des ressources.

Flux de travail détaillé

L'entrée de notre système est le bytecode Java, qui peut provenir de langages comme Java, Scala ou Kotlin. Le processus traite uniformément l'application, ses bibliothèques, le JDK et les composants de la machine virtuelle pour produire un exécutable natif spécifique à un système d'exploitation et à une architecture, appelé « image native ». Le processus de création comprend une analyse itérative des points d'accès et une capture instantanée du tas jusqu'à ce qu'un point fixe soit atteint, permettant à l'application de participer activement via des rappels enregistrés. Ces étapes sont collectivement connues sous le nom de processus de création d'image native (Figure 1)

Enhancing Performance with Static Analysis, Image Initialization and Heap Snapshotting

Figure 1 – Processus de création d'images natives (source : redhat.com)

Analyse des points à effectuer

Nous utilisons une analyse de points pour vérifier l'accessibilité des classes, des méthodes et des champs pendant l'exécution. L'analyse des points de destination commence par tous les points d'entrée, tels que la méthode principale de l'application, et traverse de manière itérative toutes les méthodes accessibles de manière transitive jusqu'à atteindre un point fixe (Figure 2).

Enhancing Performance with Static Analysis, Image Initialization and Heap Snapshotting

Figure 2 – Points d'analyse

Notre analyse des points d'accès exploite le frontal de notre compilateur pour analyser le bytecode Java dans la représentation intermédiaire de haut niveau du compilateur (IR). Par la suite, l'IR est transformé en un graphe de type-flux. Dans ce graphique, les nœuds représentent des instructions opérant sur des types d'objets, tandis que les arêtes désignent des arêtes d'utilisation dirigée entre les nœuds, pointant de la définition vers l'utilisation. Chaque nœud conserve un état de type, composé d'une liste de types pouvant atteindre le nœud et d'informations nulles. Les états de type se propagent à travers les bords d'utilisation ; si l'état de type d'un nœud change, ce changement est diffusé à tous les usages. Il est important de noter que les états de type ne peuvent que se développer ; de nouveaux types peuvent être ajoutés à un état de type, mais les types existants ne sont jamais supprimés. Ce mécanisme garantit que le
l'analyse converge finalement vers un point fixe, conduisant à la terminaison.

Exécuter le code d'initialisation

L'analyse des points d'arrivée guide l'exécution du code d'initialisation lorsqu'il atteint un point fixe local. Ce code trouve ses origines dans deux sources distinctes : les initialiseurs de classe et le lot de code personnalisé exécuté au moment de la construction via une interface de fonctionnalités :

  1. Initialiseurs de classe : Chaque classe Java peut avoir un initialiseur de classe indiqué par une méthode , qui initialise les champs statiques. Les développeurs peuvent choisir les classes à initialiser au moment de la construction ou au moment de l'exécution.

  2. Rappels explicites : Les développeurs peuvent implémenter du code personnalisé via des hooks fournis par notre système, s'exécutant avant, pendant ou après les étapes d'analyse.

Voici les API fournies pour l'intégration à notre système.

API passive (interroge l'état actuel de l'analyse)

boolean isReachable(Class<?> clazz);

boolean isReachable(Field field);

boolean isReachable(Executable method);
Copier après la connexion

Pour plus d'informations, reportez-vous à QueryReachabilityAccess

API active (enregistre les rappels pour les changements de statut d'analyse) :

void registerReachabilityHandler(Consumer<DuringAnalysisAccess> callback, Object... elements);

void registerSubtypeReachabilityHandler(BiConsumer<DuringAnalysisAccess, Class<?>> callback, Class<?> baseClass);

void registerMethodOverrideReachabilityHandler(BiConsumer<DuringAnalysisAccess, Executable> callback, Executable baseMethod);
Copier après la connexion

Pour plus d'informations, reportez-vous au BeforeAnalysisAccess

Au cours de cette phase, l'application peut exécuter du code personnalisé tel que l'allocation d'objets et l'initialisation de structures de données plus volumineuses. Il est important de noter que le code d'initialisation peut accéder à l'état actuel de l'analyse des points d'accès, permettant ainsi des requêtes concernant l'accessibilité des types, des méthodes ou des champs. Ceci est accompli à l'aide des différentes méthodes isReachable() fournies par DurantAnalysisAccess. En tirant parti de ces informations, l'application peut construire des structures de données optimisées pour les segments accessibles de l'application.

Instantané de tas

Enfin, la capture instantanée de tas construit un graphe d'objets en suivant des pointeurs racine comme des champs statiques pour créer une vue complète de tous les objets accessibles. Ce graphique remplit ensuite l'image native
tas d'images, garantissant que l'état initial de l'application est chargé efficacement au démarrage.

Pour générer la fermeture transitive des objets accessibles, l'algorithme parcourt les champs d'objets, lisant leurs valeurs par réflexion. Il est essentiel de noter que le générateur d'images fonctionne dans l'environnement Java. Seuls les champs d'instance marqués comme « lus » par l'analyse des points de destination sont pris en compte lors de ce parcours. Par exemple, si une classe a deux champs d'instance mais que l'un d'entre eux n'est pas marqué comme lu, l'objet accessible via le champ non marqué est exclu du tas d'images.

Lorsque vous rencontrez une valeur de champ dont la classe n'a pas été préalablement identifiée par l'analyse des points de destination, la classe est enregistrée en tant que type de champ. Cet enregistrement garantit que lors des itérations ultérieures de l'analyse des points à atteindre, le nouveau type est propagé à toutes les lectures de champs et utilisations transitives dans le graphe de flux de types.

L'extrait de code ci-dessous décrit l'algorithme de base pour la capture instantanée du tas :

Declare List worklist := []
Declare Set reachableObjects := []

Function BuildHeapSnapshot(PointsToState pointsToState)
For Each field in pointsToState.getReachableStaticObjectFields()
Call AddObjectToWorkList(field.readValue())
End For

    For Each method in pointsToState.getReachableMethods()
        For Each constant in method.embeddedConstants()
            Call AddObjectToWorkList(constant)
        End For
    End For

    While worklist.isNotEmpty
        Object current := Pop from worklist
        If current Object is an Array
            For Each value in current
                Call AddObjectToWorkList(value)
                Add current.getClass() to pointsToState.getObjectArrayTypes()
            End For
        Else
            For Each field in pointsToState.getReachableInstanceObjectFields(current.getClass())
                Object value := field.read(current)
                Call AddObjectToWorkList(value)
                Add value.getClass() to pointsToState.getFieldValueTypes(field)
            End For
        End If
    End While
    Return reachableObjects
End Function
Copier après la connexion

En résumé, l'algorithme de capture instantanée du tas construit efficacement un instantané du tas en parcourant systématiquement les objets accessibles et leurs champs. Cela garantit que seuls les objets pertinents sont inclus dans le tas d'images, optimisant ainsi les performances et l'empreinte mémoire de l'image native.

Conclusion

En conclusion, le processus de capture instantanée du tas joue un rôle essentiel dans la création d'images natives. En parcourant systématiquement les objets accessibles et leurs champs, l'algorithme de capture instantanée du tas construit un graphe d'objets qui représente la fermeture transitive des objets accessibles à partir de pointeurs racine tels que les champs statiques. Ce graphe d'objets est ensuite intégré dans l'image native en tant que tas d'images, servant de tas initial au démarrage de l'image native.

Tout au long du processus, l'algorithme s'appuie sur l'état de l'analyse des points à déterminer pour déterminer quels objets et champs sont pertinents pour être inclus dans le tas d'images. Les objets et les champs marqués comme « lus » par l'analyse des points de destination sont pris en compte, tandis que les entités non marquées sont exclues. De plus, lorsqu'il rencontre des types inédits, l'algorithme les enregistre pour propagation dans les itérations suivantes de l'analyse des points à atteindre.

Dans l'ensemble, la capture instantanée du tas optimise les performances et l'utilisation de la mémoire des images natives en garantissant que seuls les objets nécessaires sont inclus dans le tas d'images. Cette approche systématique améliore l'efficacité et la fiabilité de l'exécution des images natives.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal