Changement de contexte : le coût caché du multitâche

📖 16min read

Si le serveur est lent, augmenter les threads ?

Lorsque j’ai rejoint l’entreprise, le serveur API Spring Boot dont j’étais responsable ralentissait à chaque fois qu’il y avait beaucoup de trafic. Comme je n’en connaissais pas la cause, j’ai commencé par chercher sur Google.

« Réponse lente du serveur Spring Boot » « Optimisation des performances de Tomcat »

À la suite de la recherche, les conseils les plus courants trouvés sur les blogs et les communautés étaient simples. ‘Augmentez la taille du pool de threads de Tomcat. Votre demande est en attente car il n’y a pas assez de travailleurs.’

Je me suis dit : « Aha, nous manquons de travailleurs ! » J’ai pensé simplement. J’ai immédiatement ouvert les paramètres application.yml et augmenté le nombre de threads de la valeur par défaut de 200 à 2 000. D’après mes calculs, le nombre de travailleurs a été multiplié par 10, la vitesse de traitement a donc dû être plus rapide.

Mais après avoir vu l’écran de surveillance après le déploiement, je me suis figé. Le serveur s’est en fait déplacé plus lentement, l’utilisation du processeur a grimpé en flèche, mais le nombre de requêtes traitées a en réalité diminué. C’était comme si les ouvriers pelletaient dans les airs sans faire aucun travail.

Pourquoi diable le nombre d’ouvriers a-t-il augmenté, mais l’usine est devenue plus lente ? En cherchant la raison, je suis tombé sur le coût le plus élevé du système d’exploitation, le « changement de contexte ».

Avoir trop de travailleurs n’est pas une bonne chose. Le coût de leur rotation peut être plus élevé.

Avis : discussions, vous vous souvenez ?

Pour ceux qui lisent cet article pour la première fois, ou pour ceux qui ne connaissent pas le contenu de l’article précédent (Partie 4 : Processus et threads), revenons en arrière un instant.

Dans notre vision du monde du « centre logistique numérique » :

Spring Boot est fondamentalement multi-thread. Chaque fois qu’une demande arrive, un travailleur (thread) est affecté à l’exécution du travail. Alors j’ai simplement pensé : « S’il y a plus de travailleurs, plus de demandes seront traitées simultanément, n’est-ce pas ? »

Mais il y a quelque chose que j’ai oublié. Il s’agit du nombre de cœurs CPU, les éléments clés de notre usine.

Travail posté dans un centre de distribution numérique

En fait, le cœur du processeur, le cœur de métier d’un ordinateur, ne peut effectuer une seule tâche à la fois. (Basé sur un seul cœur) Cependant, nous écoutons des chansons, codons et utilisons KakaoTalk en même temps. Comment est-ce possible ?

Cela est dû au fait que le directeur de l’usine (OS) ordonne aux travailleurs (CPU) de « travailler par équipes » à une vitesse incroyablement rapide. « Jouez la chanson pendant 0,001 seconde et arrêtez ! Envoyez KakaoTalk pendant 0,001 seconde suivante et arrêtez ! »

Il s’agit du « Partage de temps », et le processus par lequel le travailleur dépose l’outil et en prend un nouvel outil est le « Changement de contexte ».

Le prix du changement de tâche : il est temps de changer de vêtements

C’est ce qui s’est passé lorsque j’ai augmenté le nombre de threads à 2 000.

Le processeur est un corps unique, avec 2 000 threads qui crient : « Débarrassez-vous-en ! » Le CPU rencontre 2 000 personnes en permanence, à tour de rôle, pour traiter le travail de manière équitable.

Le problème est qu’un « temps de préparation » est nécessaire pour passer du travail du travailleur A au travail du travailleur B.

Ce « temps nécessaire pour enregistrer, ranger et lire » est appelé « coût de changement de contexte (frais généraux) ». Lorsqu’il y a suffisamment de travailleurs, ce coût est négligeable. Mais que se passe-t-il s’il y a trop de travailleurs ? Le CPU se retrouve dans une situation où il organise les registres des travailleurs toute la journée, mais est incapable d’effectuer le moindre « travail réel (calcul) ». C’était la véritable raison pour laquelle mon serveur était lent.

Pendant le temps de changement de tâche, l’usine est arrêtée.

[Vérification du code] Est-ce nécessairement plus rapide simplement parce qu’il y a beaucoup de threads ?

Voir vaut la peine d’être entendu. Prouvons-le avec du code. Comparons la vitesse d’exécution du même nombre d’opérations d’addition en utilisant un thread et en le divisant en 1 million de threads. Le bon sens suggère qu’un million d’unités devrait être plus rapide, mais la réalité est différente.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ContextSwitchingTest {
    private static final int TASK_COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 1. Traitement en thread unique (sans rotation)
        long start = System.currentTimeMillis();
        for (int i = 0; i < TASK_COUNT; i++) {
            simpleTask();
        }
        System.out.println("Temps thread unique : " + (System.currentTimeMillis() - start) + "ms");

        // 2. Traitement avec de nombreux threads (provoque du context switching)
        // Creer un pool de threads illimite (attention : l ordinateur peut freezer)
        ExecutorService executor = Executors.newCachedThreadPool();
        start = System.currentTimeMillis();
        for (int i = 0; i < TASK_COUNT; i++) {
            executor.submit(() -> simpleTask());
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.HOURS);
        System.out.println("Temps multi-thread : " + (System.currentTimeMillis() - start) + "ms");
    }

    private static void simpleTask() {
        int a = 1 + 1; // Tres leger comme travail
    }
}

Exemples de résultats (varie selon l’environnement) :

Analyse : la tâche elle-même (1+1) est si simple qu’elle peut être réalisée en un clin d’œil. Cependant, dans la méthode multithread, le coût de création d’un million de threads et de basculement du système d’exploitation entre eux est des milliers de fois plus élevé que le temps d’opération. Le nombril est plus gros que le ventre

Leçons tirées de la pratique : Trouver le juste milieu

Alors, combien de threads sont appropriés pour un serveur Spring Boot ? La réponse dépend de « ce que fait le serveur ».

Cependant, comme dans la situation que j’ai vécue, augmenter aveuglément ce nombre à 2 000 est trop. En effet, à mesure que le nombre de threads augmente, davantage de mémoire (pile) est consommée et le processeur est surchargé en raison des coûts de changement de contexte.

Récemment, des technologies non bloquantes telles que Node.js et WebFlux de Spring attirent l’attention pour résoudre ce problème. Ils utilisent la stratégie suivante : « n’augmentez pas le nombre de threads, laissez une seule personne traiter rapidement sans s’arrêter ».

Il n’y a pas de réponse inconditionnelle. Vous devez choisir selon que votre service nécessite beaucoup de « changements » ou de « précipitation ».

Clôture : Il n’y a pas de déjeuner gratuit

Nous pensons souvent à tort que « traiter les choses en même temps est plus rapide ». Cependant, dans le monde des ordinateurs, le terme « simultané » n’est en réalité qu’un travail posté à grande vitesse, ce qui est presque une astuce.

Une fois que vous aurez compris le changement de contexte, vous comprendrez pourquoi le réglage du serveur ne consiste pas seulement à « augmenter les chiffres ». L’augmentation inconsidérée des threads peut en fait devenir un poison qui étouffe le serveur.

Maintenant, les usines internes de l’ordinateur (CPU, RAM, Process) semblent plutôt bien fonctionner. Maintenant, ouvrons la porte de l’usine et sortons. Comment envoyer les données créées dans notre usine à une autre usine (client) éloignée ?

La prochaine fois, nous parlerons des réseaux, de HTTP et de ce réseau routier invisible.

Laisser un commentaire