当前位置:网站首页>Thème technique d'optimisation - cadre de messagerie haute performance entre les Threads - utilisation et principe de disruptor

Thème technique d'optimisation - cadre de messagerie haute performance entre les Threads - utilisation et principe de disruptor

2021-09-15 07:41:19 Tarzan d'Ali

Résumé des prémisses

Un bref examen jdk Dans la file d'attente

Bloquer la file d'attente:

ArrayBlockingQueuePrincipalement par:Tableau(Object[])+ Compteur(count)+ ReetrantLockDeCondition (notEmpty:Non vide、notFull:Non saturé)Pour bloquer.

Opérations en équipe:

  • Fonctionnement non obstrué:
    • add:Impossible d'ajouter,Il retourne directement à.
    • offer:Après l'échec de l'ajout(C'est plein.)Lancer une exception directement,Attention!:offer(E o, long timeout, TimeUnit unit):Vous pouvez définir le temps d'attente,Si, dans un délai déterminé,Impossible d'ajouterBlockingQueue,Échec du retour.
  • Opération bloquée:
    • put:C'est plein.,AdoptionCondition:notFull.await()Bloquer les informations actuelles sur les données,Réveillez - vous lorsque vous quittez la file d'attente et supprimez les éléments put Fonctionnement.

Opérations hors ligne:

  • Fonctionnement non obstrué:
    • poll:Retour direct quand vide null.poll(long timeout, TimeUnit unit):DeBlockingQueuePrenez un objet en tête de file d'attente,Si, dans un délai déterminé,File d'attente une fois que les données sont disponibles,Renvoie immédiatement les données dans la file d'attente.Sinon, l'heure,Il n'y a pas de données disponibles pour le délai,Échec du retour.
    • remove:Supprimer l'état de l'élément contrôle de l'information de l'élément pertinent,InterruptExceptionAnomalie
  • Opération bloquée:
    • take:Quand c'est vide,notEmpty.await()(Réveillez - vous quand des éléments entrent dans l'équipe).
  • drainTo():Une fois deBlockingQueueObtenir tous les objets de données disponibles(Vous pouvez également spécifier le nombre de données à obtenir),Par cette méthode,Améliore l'efficacité de l'acquisition des données;Il n'est pas nécessaire de verrouiller ou de déverrouiller plusieurs fois par lots.

AvecArrayBlockingQueueEn revancheLinkedBlockingQueue:Node Réalisation、Verrouillage(Lisez la serrure.、Écrire La séparation des serrures)、File d'attente limitée optionnelle.Les problèmes de mémoire réels doivent être pris en considération,Prévention des déversements.

Application pratique

File d'attente du pool de threads

Excutors La valeur par défaut est d'utiliser LinkedBlockingQueue,Mais en pratique,Vous devriez également créer manuellement un pool de Threads pour utiliser des files d'attente limitées,Empêcher les producteurs de produire trop rapidement,Provoquer un débordement de mémoire.

File d'attente retardée(ScheduleServiceLa file d'attente retardée est également utilisée.!):

DelayQueue : PriorityQueue (File d'attente prioritaire) + Lock.condition (Délai d'attente) + leader (Évitez d'attendre inutilement).

Principales méthodes:

  • getDelay() Délai.

  • compareTo() Cette méthode permet de comparerPriorityQueueValeur intérieure.

En ligne.:

AvecBlockingQueueC'est très similaire.,add、put、offer:Les Threads en attente se réveillent en entrant dans l'équipe,Effectuer une sortie d'équipe.

Dehors!:

  • S'il n'y a pas de données dans la file d'attente,Les éléments sont réveillés quand ils entrent dans l'équipe.

  • S'il y a des données dans la file d'attente,Sera bloqué jusqu'à ce que le temps soit écoulé.

    • take-Blocage:
    • poll-La file d'attente de satisfaction a des données et delay Temps inférieur à0Les éléments sont retirés,Sinon, revenez immédiatement. null Peut être préempté leader.

Scénario d'application:

  • Tâches retardées:Définir la durée du délai d'exécution des tâches;Traitement nécessitant une valeur d'expiration,Par exemple, le cache expire.

  • Mode de réalisation:Chaque fois getDelay() La méthode fournit une différence entre le temps de création du cache et l'heure actuelle,Hors de l'équipe compareTo() Méthode la plus petite différence.Chaque fois que vous entrez dans la file d'attente, vous récupérez la plus petite différence dans la file d'attente pour le traitement.

  • Les files d'attente sont plus comme des producteurs、Le scénario du consommateur,La plupart de ces scénarios exigent une vitesse de traitement élevée.,Donc nous utilisons la technologie Multithreading.

  • La concurrence peut se produire avec plusieurs threads,Pour éviter les erreurs,Nous sélectionnerons une file d'attente Thread - SAFE.

    • ArrayBlockingQueue、LinkedBlockingQueue Ou ConcurrentLinkedQueue.Les deux premiers sont réalisés par verrouillage,Le dernier est passé par cas Pour assurer la sécurité des fils.

    • Tenir compte du débordement de mémoire que le producteur peut créer trop rapidement,On dirait que ArrayBlockingQueue Est le plus satisfaisant.

Mais parce que l'efficacité de verrouillage ralentit,C'est ce qui a conduit:DisruptorCadre de service !


DisruptorIntroduction

  • DisruptorSource deGitAdresse de l'entrepôt:GitHub - LMAX-Exchange/disruptor: High Performance Inter-Thread Messaging Library
  • DisruptorDéfinition du concept:Un cadre de messagerie haute performance pour les systèmes asynchrones
  • DisruptorL'idée centrale de:Convertir les problèmes de sécurité de thread pour l'écriture simultanée Multithread en écriture locale thread,C'est - à - dire::Pas besoin de synchronisation,Aucune opération de verrouillage n'est autorisée.

DisruptorPrésentation des avantages

  • Très léger,Mais c'est très puissant.,Grâce à son excellente conception et à son application aux principes de base de l'ordinateur
    • Un seul thread peut gérer des dépassements par seconde600WDonnées(DisruptorOui.1En quelques secondes600WTransmission des données aux consommateurs,Le niveau actuel de matériel sera bien au - dessus de ce niveau!)
  • Modèle axé sur les événements,Pas besoin que les consommateurs prennent l'initiative de tirer des messages
  • QueJDKDeArrayBlockingQueueHaute performance d'un ordre de grandeur

Pourquoi si vite?

  • Clôture non verrouillée
  • Remplissage des lignes de cache,Supprimer le pseudo - partage
  • Pré - allocation de mémoire
  • File d'attente circulaireRingBuffer

DisruptorConcept de base

  • RingBuffer(File d'attente circulaire):Cache de niveau de mémoire basé sur un tableau,Est de créersequencer(Numéro de série)Et définitionsWaitStrategy(Politique de rejet)Entrée.

  • Disruptor(Portail général d'exécution):C'est exact.RingBufferLe paquet de,DétentionRingBuffer、Pool de fils de consommationExecutor、Ensemble de consommationConsumerRepositoryAutres citations.

  • Sequence(Distributeur de numéros de série):C'est exact.RingBufferÉléments marqués par un numéro de série,Gérer les données échangées par incréments séquentiels(Événements/Event),UnSequenceVous pouvez suivre les progrès du traitement qui identifient un événement,Peut également éliminer le pseudo - partage.

  • Sequencer(Transmetteur de données):SequencerIl contientSequence,- Oui.DisruptorAu cœur de,SequencerIl y a deux classes d'implémentation:SingleProducerSequencer(Réalisation par un seul producteur)、MultiProducerSequencer(Réalisation Multi - producteurs),SequencerLe rôle principal est d'accélérer les échanges entre les producteurs et les consommateurs.、Algorithme simultané pour la transmission correcte des données

  • SequenceBarrier(Barrière des consommateurs):Pour le contrôleRingBufferDeProducerEtConsumerUn équilibre entre,Et a décidéConsumerY a - t - il une logique pour les événements gérables?.

  • WaitStrategy(Stratégie d'attente des consommateurs):Il détermine comment les consommateurs attendent que les producteursEventProgrès de la productionDisruptor,WaitStrategyIl existe plusieurs stratégies de mise en œuvre,Respectivement.:

    1. BlockingWaitStrategy(Stratégie la plus stable):Mode de blocage,Moins efficace,Mais oui.cpuFaible consommation,Les mécanismes de verrouillage et de variable conditionnelle typiques sont utilisés à l'interne(javaDeReentrantLock),Pour gérer le réveil du fil,Cette stratégie estdisruptorL'une des stratégies d'attente les plus lentes,Mais la consommation la plus prudentecpuUne utilisation de,Et la plus grande cohérence de performance dans différents environnements de déploiement. Mais,Au fur et à mesure que nous pouvons optimiser les performances supplémentaires en fonction de l'environnement de service déployé.
    2. BusySpinWaitStrategy(Stratégie la plus performante):Mode de spin,Pas de serrure,BusySpinWaitStrategyEst la politique d'attente la plus performante,Mais plus la dépendance est limitée par l'environnement de déploiement. Seulement sieventCette politique d'attente ne devrait être utilisée que lorsque le nombre de fils de traitement est inférieur au nombre de noyaux physiques. Par exemple,Hyperthread ne peut pas être activé.
    3. LiteBlockingWaitStrategy(Presque rien.,Le mécanisme stratégique le plus proche de l'original):BlockingWaitStrategyVersion variante de,Ne semble pas recommandé pour le moment
    4. LiteTimeoutBlockingWaitStrategy:LiteBlockingWaitStrategyVersion timeout de
    5. PhasedBackoffWaitStrategy(MinimumCPUPolitique configurée):Spin + yield + Politique personnalisée,Lorsque le débit et la faible latence sont inférieurs àCPURessources importantes,CPUManque de ressources,Cette politique peut être utilisée.
    6. SleepingWaitStrategy:Mode de sommeil par spin(Pas de serrure),Performance etBlockingWaitStrategyPresque.,Mais cela a le moins d'impact sur les fils du producteur,Il utilise unloopBoucle d'attente occupée,Mais au milieu de la boucle, il appelleLockSupport.parkNanos(1).
      • En général,linuxLe système interrompt le fil d'environ60Microsecondes.Mais l'avantage est que,Les fils du producteur n'ont pas besoin de compteurs d'accumulation supplémentaires,Il n'est pas non plus nécessaire de générer des frais généraux de sémaphore variables conditionnels.
      • Les effets négatifs sont les suivants:,Passer entre le fil producteur et le fil consommateureventLe retard des données augmente.Alors...SleepingWaitStrategyConvient pour les applications sans faible latence, Mais nécessite un faible impact sur le fil du producteur.Un cas typique est la journalisation asynchrone.
    7. TimeoutBlockingWaitStrategy:BlockingWaitStrategyMode de blocage des délais
    8. YieldingWaitStrategy(Réalisation intégraleCPUStratégie de performance du débit):Mode de compétition de commutation de fil de spin(Thread.yield()),La façon la plus rapide,Pour les systèmes à faible délai,Lorsque des performances extrêmement élevées sont requises et que le nombre de lignes de traitement d'événements est inférieur àCPUCette politique est recommandée dans les scénarios avec des noyaux logiques,Il utilise pleinement la pressecpuPour atteindre l'objectif de réduction des retards.
      • Attendre en boucle constantesequencePour augmenter à une valeur appropriée. Dans la circulation,AppelezThread.yield()Pour permettre à d'autres Threads de file d'attente d'exécuter. C'est une sorte deevent handlerMoins de threadscpuPolitiques recommandées pour le nombre de noyaux logiques.
      • Dis - le ici.YieldingWaitStrategyUtiliser avec précaution,Lorsque les performances ne sont pas spécifiquement requises,Utiliser avec prudence,Sinon, le service commencera.cpuLa montée en flèche,Parce que son implémentation interne est faite par un thread100Et puisThread.yield(),Peut pressercpuPerformance en échange de vitesse.

Attention!:Hyperthread estintelUn type de recherche et développementcpuTechnique,Permet à un noyau de fournir deux fils logiques,Par exemple,4Après l'hyperthread Central8Threads.


  • Event:Unités de données traitées dans le processus du producteur au consommateur,EventPersonnalisé par l'utilisateur.
  • EventHandler:Mise en œuvre personnalisée par l'utilisateur,C'est là que nous écrivons la logique du consommateur.,Ça représenteDisruptorInterface avec un consommateur.
  • EventProcessor:C'est une interface de gestionnaire d'événements,C'est fait.Runnable,Gérer le cycle des événements majeurs,TraitementEvent,Propriétaire du consommateurSequence,Cette interface a2Principales réalisations:
    • WorkProcessor:Mise en œuvre du traitement Multithread,Dans le modèle multi - producteurs et multi - consommateurs,Assurez - vous que chaquesequencePar un seulprocessorConsommation,Dans le mêmeWorkPoolMoyenne,Assurer plusieursWorkProcessorNe consommera pas la même chosesequence
    • BatchEventProcessor:Mise en œuvre du traitement par lots à fil unique,InclusEvent loopMise en œuvre efficace,Et rappelé à unEventHandlerObjet de mise en œuvre de l'interface,Cette interface est en fait réécriterunMéthodes,Sondage pour obtenir des objets de données,Et mettre les données à la disposition des consommateurs par une stratégie d'attente.

DisruptorStructure générale

Voyons voir. Disruptor Comment faire sans blocage、Production multiple、Multi - consommation.

  • Construire Disruptor Chaque paramètre de ringBuffer La construction de:
    • EventFactory:Créer un événement(Mission)Classe d'usine pour.
    • ringBufferSize:Longueur du conteneur.
    • Executor:Pool de fils de consommation,Le thread qui exécute la tâche.
    • ProductType:Type de producteur:Producteur unique、Producteurs multiples.
    • WaitStrategy:Politique d'attente.
    • RingBuffer:Conteneur pour les données.
    • EventHandler:Gestionnaire d'événements.

DisruptorComment utiliser

mavenDépendance:

xml

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

Exemple de mode simple de production et de consommation:

EventModèle de données

java

import lombok.Data;
@Data
public class SampleEventModel {
    private String data;
}

EventModèle d'événementFactoryClasse d'usine

java

import com.lmax.disruptor.EventFactory;
/**
 * Usine de production d'objets de message
 */
public class SampleEventModelFactory implements EventFactory<SampleEventModel> {
    @Override
    public SampleEventModel newInstance() {
        //Renvoie des données d'objet de message videsEvent
        return new SampleEventModel();
    }
}

EventHandlerFonctionnement du processeur

java

import com.lmax.disruptor.EventHandler;
/**
 * Gestionnaire d'événements de message
 */
public class SampleEventHandler implements EventHandler<SampleEventModel> {
    /**
     * Mode événementiel
     */
    @Override
    public void onEvent(SampleEventModel event, long sequence, boolean endOfBatch) throws Exception {
        // do ...
        System.out.println("Données sur le traitement de la consommation des consommateurs:" + event.getData());
    }
}

EventProducerFonctionnement du processeur de service aux producteurs d'usine

java

import com.lmax.disruptor.RingBuffer;
/**
 * Message envoyé
 */
public class SampleEventProducer {
    private RingBuffer<SampleEventModel> ringBuffer;
    public SampleEventProducer(RingBuffer<SampleEventModel> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }
    /**
     * Publier des informations sur les données
     * @param data
     */
    public void publish(String data){
        //DeringBufferObtenir disponiblesequenceNuméro de série
        long sequence = ringBuffer.next();
        try {
            //SelonsequenceAccèssequenceCorrespondantEvent 
			//C'estEventEst un objet sans données spécifiques assignées
            TestEvent testEvent = ringBuffer.get(sequence);
            testEvent.setData(data);
        } finally {
            //Soumettre pour publication
            ringBuffer.publish(sequence);
        }
    }
}

EventProducerFonctionnement du processeur de service aux producteurs d'usine

java

public class TestMain {
    public static void main(String[] args) {
        SampleEventModelFactory eventFactory = new SampleEventModelFactory();
        int ringBufferSize = 1024 * 1024;
		//Ce pool de Threads est mieux personnalisé
        ExecutorService executor = Executors.newCachedThreadPool();
        //Instanciationdisruptor
        Disruptor<SampleEventModel> disruptor = new Disruptor<SampleEventModel>(
                eventFactory,                   //Usine de messages
                ringBufferSize,                 //ringBufferLongueur maximale du conteneur
                executor,                       //Pool de Threads,Il est préférable de personnaliser un
                ProducerType.SINGLE,            //Mode producteur unique
                new BlockingWaitStrategy()      //Politique d'attente
        );
        //Ajouter l'écoute des consommateurs Prends ça.TestEventHandlerLié àdisruptor
        disruptor.handleEventsWith(new SampleEventHandler());
        //Démarragedisruptor
        disruptor.start();
        //Obtient le conteneur dans lequel les données sont réellement stockéesRingBuffer
        RingBuffer<SampleEventModel> ringBuffer = disruptor.getRingBuffer();
        //Données d'envoi de la production
        SampleEventProducer producer = new SampleEventProducer(ringBuffer);
        for(long i = 0; i < 100; i ++){
            producer.publish(i);
        }
        disruptor.shutdown();
        executor.shutdown();
    }
}

DisruptorAnalyse des principes de

  • L'utilisation d'un tableau circulaire au lieu d'un modèle producteur - consommateur de file d'attente est naturellement inséparable de la file d'attente,Utiliser des données pré - remplies pour éviter GC;
  • UtiliserCPULa façon dont les lignes de cache sont remplies pour éviter la dégradation des performances due à la contestation des données dans des cas extrêmes;
  • Techniques de codage pour éviter les conflits de verrouillage dans la programmation multithreadée.

DisruptorNous a donné une idée et une pratique,Mise en œuvre du tableau circulaire de base,Définir un tableau,La longueur est2Puissance de.

Téléchargement en cours…Télécharger à nouveau annuler​

Tableau circulaire

  • Définir un drapeau numérique pour indiquer l'emplacement actuel disponible(Peut être obtenu à partir de0C'est parti.).

    • Le BIT du drapeau d'en - tête indique la prochaine position qui peut être insérée.
      • Le BIT de drapeau d'en - tête ne peut pas être plus long qu'un tableau du BIT de drapeau de queue(Parce que cela fait que la position insérée et la position lue se chevauchent, ce qui entraîne une perte de données).
    • Le chiffre de queue indique la prochaine position lisible.
      • Le BIT de queue ne peut pas être égal au BIT de tête(Parce que les données ainsi lues sont en fait de vieilles données du cycle précédent) Pré - Remplissage pour améliorer les performances,Nous savons quejavaSi vous créez un grand nombre d'objets, jetez - les après utilisation.,JVM Sera effectué en temps opportunGCFonctionnement.
  • Lorsque ce symbole numérique augmente continuellement au - dessus de la longueur du tableau, l'opération de fusion avec la longueur du tableau est effectuée,Le nouveau nombre qui en résulte est toujours dans la plage de longueur du tableau,Vous pouvez l'insérer à nouveau.

  • C'est comme si on l'insérait jusqu'à la fin du tableau et qu'on recommence à zéro.,C'est ce qu'on appelle un tableau circulaire.. Un tableau circulaire général a deux bits de drapeau.C'est comme une file d'attente..

Tableau circulaire(Initialisation des informations sur les données)

Dans un tableau circulaire,Vous pouvez remplir le tableau de données à l'avance.Une fois que de nouvelles données sont générées,Il suffit de modifier certaines valeurs d'attribut dans un bit du tableau.Cela évite la création fréquente de données et le rejet de données GC.

C'est mieux que la file d'attente.. Un seul bit de drapeau est réservé,Multithreading dans la file d'attente,Un tableau circulaire est bon.,Il doit y avoir une concurrence pour les bits de drapeau.Que vous utilisiez des serrures pour éviter la concurrence,Toujours utiliser CAS Pour effectuer un algorithme sans verrouillage.

Tant qu'il y a des arguments,Et beaucoup de fils,Il y aura une consommation constante de ressources.Plus d'objets contestés,Plus de ressources sont dépensées dans la course.

Pour éviter une telle situation,Réduire les ressources disponibles est un moyen.Par exemple, un seul bit de drapeau est réservé dans un tableau circulaire,C'est - à - dire le prochain bit de drapeau qui peut être écrit à l'emplacement des données.Les bits de drapeau arrière sont stockés dans les fils de consommation individuels(Techniques de programmation spécifiques).

Tableau de boucle dans un seul thread

  • Utilisation de tableaux circulaires dans un seul thread,S'il est établi qu'il n'y a qu'un seul producteur,C'est - à - dire qu'il n'y a qu'un seul fil d'écriture.L'utilisation dans les tableaux circulaires est plus simple.

  • Plus précisément, les bits de drapeau sur un tableau de mise à jour monothread,C'est ce qui s'est passé.,Il n'est pas nécessaire d'utiliser des bits de signalisationCASÉcrire pour déterminer la prochaine position inscriptible,Il suffit d'effectuer une mise à jour normale en un seul thread.

Tableau de boucle dans plusieurs fils

  • L'utilisation de tableaux circulaires dans le Multithreading,S'il y a plus d'un producteur,Les bits de drapeau inscriptibles doivent être utilisésCAS Algorithme pour rivaliser,Éviter l'utilisation de serrures.

  • Plusieurs fils passentCASObtenez un numéro de séquence unique et non conflictuel à écrire suivant,Parce qu'un numéro de séquence est nécessaire pour écrire,Ce n'est que lorsque l'écriture est terminée que le thread consommateur peut consommer.

  • C'est pour ça qu'après avoir obtenu le numéro de série,,Avant de terminer l'écriture,Il doit y avoir un moyen pour le consommateur de vérifier si cela est fait.

  • Pour empêcher les consommateurs d'obtenir des bits de tableau qui n'ont pas encore été remplis. Pour atteindre cet objectif,Être simple—Inefficacité et complexité—Deux façons d'être efficace.

Utiliser deux bits de drapeau d'une manière simple mais potentiellement inefficace.

  • prePut:Indique la prochaine position disponible pour le producteur;

    • Plusieurs producteurs passent par CAS Obtenir prePut Différentes valeurs pour,Après avoir obtenu le numéro de série et terminé l'écriture des données,Oui. put Valeur de CAS Mode incrémental(Par exemple, le numéro de série obtenu est7,Seulement put - Oui.6Le réglage réussi n'est autorisé que lorsque),Appelé publication.
    • Il y a un inconvénient à cette approche,Si plusieurs Threads écrivent simultanément,Accès prePut La valeur de ne pas bloquer,Supposons que l'un des producteurs écrive les données un peu plus lentement,D'autres Threads ne peuvent pas terminer la publication après avoir écrit.Provoque une attente cyclique,C'est du gâchis. CPU Performance.
  • put:Indique où le dernier producteur a été placé.

  • Une approche complexe mais potentiellement efficace,De la manière ci - dessus,Les principaux segments de compétition sont axés sur la publication multithreadée,La publication d'un thread avec un grand numéro de séquence doit attendre que la publication d'un thread avec un petit numéro de séquence soit terminée avant la publication.Alors notre point d'optimisation est ici aussi..

  • Cela évite la concurrence pour la publication. Mais ça pose un autre problème.,Quel nombre indique si la publication est terminée?Si seulement0Et1,C'est écrit.1Après le tour,Tous les bits du tableau des drapeaux1C'est.Je ne peux pas faire la différence..

  • Donc le nombre sur le tableau des drapeaux devrait être différent à chaque cycle du tableau des boucles.

Comme au début.-1,Au premier tour0A été publié,Au deuxième tour0Indique qu'il n'a pas été publié,- Oui.1A été publié.

Remplissage des lignes de cache

Pour en savoir plus sur la suppression des pseudo - actions de remplissage de ligne de cache,D'abord, apprenez ce qu'est une ligne de cache système:

  • CPU Pour une exécution plus rapide du Code.Donc quand vous lisez les données de la mémoire,Ne lisez pas seulement ce que vous voulez.Au lieu de cela, lisez assez d'octets pour remplir la ligne de cache.Selon CPU ,La taille de la ligne de cache est différente.Par exemple: X86 - Oui. 32BYTES ,Et ALPHA - Oui. 64BYTES .Et toujours en 32 Octets ou 64 Aligner en octets.Voilà.,Quand CPU Lors de l'accès aux données adjacentes,Il n'est pas nécessaire de lire à partir de la mémoire à chaque fois,Augmentation de la vitesse. Parce que l'accès à la mémoire prend beaucoup plus de temps que l'accès au cache.

  • Ce cache estCPUCache interne,L'Unit é de cache interne est une ligne,Appelé ligne de cache.Se produit dans un environnement multicentriqueCPUProblèmes de synchronisation de la mémoire entre(Par exemple, un noyau charge un cache,L'autre noyau utilise les mêmes données.),Si chaque noyau accède à la mémoire chaque fois que nécessaire(Un cache en lecture,Un lors de l'écriture du cache,Cause incohérence des données),Cela entraîne une perte de performance relativement importante.

  • Les données ne sont pas stockées dans le cache en tant qu'éléments séparés,Si ce n'est pas une variable séparée,Ni un seul pointeur.Le cache se compose de lignes de cache,En général.64Octets(Traduction:Les lignes de cache des processeurs couramment utilisés au moment de la publication de cet article sont:64Octets,Les anciennes lignes de cache du processeur sont32Octets),Et il fait effectivement référence à une adresse dans la mémoire principale.UnJavaDelongLe type est8Octets,Pour que vous puissiez mettre en cache8- Oui.longVariable de type.

  • Lorsqu'une valeur dans le tableau est chargée dans le cache,Il va charger plus7- Oui..Donc vous pouvez traverser ce tableau très rapidement.En fait,Vous pouvez parcourir très rapidement n'importe quelle structure de données allouée dans des blocs de mémoire consécutifs.

  • Donc si les éléments de votre structure de données ne sont pas contigus en mémoire,Vous n'obtiendrez pas les avantages du chargement gratuit du cache.Et dans chacune de ces structures de données, il peut y avoir des erreurs de cache.

  • Imaginez le vôtre.longLes données de type ne font pas partie du tableau.Imaginez qu'il ne s'agisse que d'une seule variable.Appelons çahead,Il n'y a aucune raison de l'appeler comme ça..Puis Imaginez qu'il y ait une autre variable dans votre classe juste à côté d'elle.Appelons - le directementtail.Maintenant,Quand vous chargezheadQuand il est temps de mettre en cache,Vous l'avez chargé gratuitement aussi.tail

版权声明
本文为[Tarzan d'Ali]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/09/20210915073252805H.html

随机推荐