Algorithme d'élimination d'horloge Java manuscrit Redis (11) détaillé et implémentation à partir de zéro

Préface

Java implémente redis à la main à partir de zéro (1) Comment obtenir un cache de taille fixe?

Java implémente Redis à la main à partir de zéro (trois) principe d'expiration de Redis Expire

Java implémente redis à la main à partir de zéro (3) Comment redémarrer sans perdre de données mémoire?

Java implémente Redis à partir de zéro à la main (quatre) Add Listener

Une autre façon de mettre en œuvre la stratégie d'expiration de redis (5) à partir de zéro

Java implémente Redis à la main à partir de zéro (6) Principe de persistance AOF détaillé et implémentation

Redis manuscrite Java à partir de zéro (7) Explication détaillée de la stratégie d'élimination du cache LRU

Auparavant, nous avons mis en œuvre des stratégies d'élimination communes telles que FIFO / LRU / LFU, mais dans le système d'exploitation, l'algorithme de remplacement de page d'horloge est en fait utilisé.

Les performances de LRU sont vraiment bonnes, mais elles consomment plus de mémoire et sont plus difficiles à mettre en œuvre.

L'algorithme de remplacement de page d'horloge est une implémentation d'algorithme similaire à LRU, qui peut être considérée comme une amélioration de l'algorithme FIFO.

Algorithme de remplacement de page d'horloge

Pourquoi l'algorithme d'horloge est-il nécessaire?

Les performances de l'algorithme LRU sont proches de celles de l'OPT, mais il est difficile à implémenter et a une surcharge importante; l'algorithme FIFO est simple à implémenter, mais ses performances sont médiocres.

Par conséquent, les concepteurs du système d'exploitation ont essayé de nombreux algorithmes, essayant d'approcher les performances de LRU avec une surcharge relativement faible. Ces algorithmes sont toutes des variantes de l'algorithme CLOCK.

Étant donné que cet algorithme vérifie la condition de chaque page de manière cyclique, il est appelé l'algorithme CLOCK, également connu sous le nom d'algorithme de non récemment utilisé (NRU).

L'idée de base

Le bit d'accès de l'entrée de la table de pages est nécessaire. Lorsqu'une page est chargée en mémoire, le bit est initialisé à 0, puis en cas d'accès à la page (lecture / écriture), le matériel la met à 1.

Organisez chaque page dans une liste circulaire liée (semblable à une surface d'horloge), et pointez le pointeur vers la page la plus ancienne (la plus avancée);

Lorsqu'une interruption de défaut de page se produit, la page la plus ancienne pointée par le pointeur est examinée et si son accès est 0, elle est immédiatement éliminée. Si l'accès est 1, la position est définie sur 0, puis le pointeur se déplace d'un espace vers le bas. Faites cela jusqu'à ce que vous trouviez la page éliminée, puis déplacez le pointeur vers sa grille suivante.

Des doutes personnels

(1) Et si les éléments sont tous 1 après avoir recherché un cercle?

Est-il juste de prendre le premier élément directement par défaut? Ceci est considéré comme un retour au mécanisme FIFO simple.

(2) Problèmes de performances d'accès

Le parcours ici peut être considéré comme une liste chaînée circulaire:

Contenu de chaque nœud:

K key;
boolean accessFlag;

Le FIFO naïf est très simple, il suffit de jeter des éléments directement dans la file d'attente, puis d'éliminer l'élément le plus ancien.

Si la liste chaînée est vraiment utilisée comme structure de données, la complexité du temps de recherche et de mise à jour est O (N), évidemment les performances sont moyennes.

La solution envisageable est de stocker les nœuds clé + liste doublement liés dans HashMap.

Par rapport à la version améliorée des performances de LRU, chaque mise à jour n'effectue pas d'ajustements de suppression de nœud, mais met uniquement à jour les bits d'indicateur correspondants.

Algorithme CLOCK simple

C'est en associant un bit supplémentaire (bit de référence) à chaque page visitée, qui est également appelé bit d'utilisation à certains endroits.

Son idée principale est la suivante: lorsqu'une page est chargée dans la mémoire principale, le bit d'utilisation est initialisé à 0; si la page est accédée plus tard, le bit d'utilisation est toujours marqué comme 1.

Pour l'algorithme de remplacement de page, l'ensemble de trames candidat peut être considéré comme un tampon circulaire, et un pointeur est associé au tampon. Lorsqu'un remplacement de page est rencontré, le pointeur pointe vers le cadre suivant dans la mémoire tampon.

Si cette page entre dans la mémoire principale et qu'il n'y a pas de trame libre (frame), c'est-à-dire que les bits d'utilisation de toutes les pages sont 1, alors un tampon est mis en boucle à partir du pointeur à ce moment, et les bits d'utilisation précédents sont remis à 0 et laissés au début À la position, permutez la page correspondant au cadre.

ps: S'il n'y a pas de trame libre trouvée ici, tous les bits utilisés seront effacés.

exemple

Prenez le processus de remplacement de page suivant comme exemple, les pages visitées sont: 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5.

Il y a 4 cadres libres dans la mémoire principale, et la structure correspondante de chaque page est (numéro de page, bit d'utilisation).

Le premier numéro de page 1 entre dans la mémoire principale, il y a des trames inactives dans la mémoire principale et son bit d'utilisation est marqué 1. Puisqu'il n'y a pas de page 1 dans la mémoire principale auparavant, une interruption de défaut de page se produira.

De la même manière, les pages 2, 3 et 4 suivantes entrent dans la mémoire principale et leurs bits d'utilisation sont enregistrés comme 1, et une interruption de défaut de page se produit.

Lorsque les pages suivantes 1, 2 entrent dans la mémoire principale, les pages 1 et 2 étant déjà dans la mémoire principale, elles ne sont pas traitées.

Lorsque la page suivante 5 entre dans la mémoire principale, il n'y a pas de trame libre dans la mémoire principale. À ce stade, tout le tampon est déplacé de manière circulaire avec le pointeur, et tous les bits utilisés de la page précédente sont remis à 0, c'est-à-dire les pages 1, 2, 3, 4 Les bits utilisés correspondants sont tous à 0, le pointeur revient à la position d'origine, la page 1 est remplacée, la page 5 est échangée dans la mémoire principale et les bits utilisés sont marqués comme 1.

Par analogie, on peut voir que CLOCK a 10 interruptions de défaut de page.

Entrez la description de l'image

Gclock (algorithme de remplacement de page d'horloge généralisé)

Idée d'algorithme

Cet algorithme est une variante de Clock.

Comparé au bit indicateur d'horloge utilisant les valeurs binaires 0 et 1, le bit indicateur Gclock utilise un entier, ce qui signifie qu'il peut théoriquement être augmenté à l'infini.

principe de fonctionnement

(1) Lorsque l'objet à mettre en cache est dans le cache, ajoutez 1 à la valeur de son bit d'indicateur. En même temps, le pointeur pointe vers l'objet suivant de l'objet.

(2) S'il n'est pas dans le cache, vérifiez le bit de marque de l'objet vers lequel pointe le pointeur. S'il vaut 0, remplacez l'objet par l'objet à mettre en cache; sinon, réduisez la valeur du bit d'indicateur de 1 et le pointeur pointe vers l'objet suivant. Et ainsi de suite jusqu'à ce qu'un objet soit éliminé. Etant donné que la valeur du bit d'indicateur peut être supérieure à 1, le pointeur peut effectuer une boucle plusieurs fois pour éliminer un objet.

ps: C'est un peu similaire à la version simplifiée de LFU, qui compte les occurrences correspondantes.

WSclock (algorithme de remplacement de page d'horloge de jeu de travail)

Idée d'algorithme

Cet algorithme est également une variante de l'horloge, et peut être l'algorithme le plus largement utilisé dans la pratique.

Il utilise le principe de l'horloge et est une version améliorée de l'algorithme ws.

La structure de données de l'algorithme est une liste circulaire liée. Chaque objet de cache enregistre le "temps récemment utilisé" rt et le drapeau R de "s'il faut faire référence", et utilise un temporisateur périodique t. l'âge est exprimé comme la différence entre l'heure actuelle et rt

principe de fonctionnement

(1) Lorsque l'objet à mettre en cache est dans le cache, mettez à jour rt à l'heure actuelle. En même temps, le pointeur pointe vers l'objet suivant de l'objet.

(2) S'il n'existe pas dans le cache, si le cache n'est pas plein, rt à l'emplacement pointé par le pointeur de mise à jour est l'heure actuelle et R est 1. En même temps, le pointeur pointe vers l'objet suivant. S'il est plein, un objet doit être éliminé. Vérifiez l'objet pointé par le pointeur,

  • R est 1, indiquant que l'objet est dans le jeu de travail, réinitialise R à 0 et le pointeur pointe vers l'objet suivant.

  • R est égal à 0. Si l'âge est supérieur à t, indiquant que l'objet n'est pas dans le jeu de travail, remplacez l'objet et définissez R sur 1 et rt sur l'heure actuelle. Si l'âge n'est pas supérieur à t, continuez à rechercher les objets éliminés. Si vous revenez à la position où le pointeur a commencé et que l'objet éliminé n'a pas été trouvé, le premier objet avec R étant 0 est éliminé.

Méthode de la seconde chance (ou horloge améliorée)

Algorithme CLOCK amélioré

Idée: réduire la surcharge de traitement des défauts de page liée à la modification des pages

Modifiez l'algorithme d'horloge pour permettre aux pages sales d'être toujours conservées dans un balayage de la tête d'horloge, tout en utilisant le bit sale (également appelé bit d'écriture) et le bit d'utilisation pour guider le remplacement

Flux d'algorithme

Dans l'algorithme CLOCK précédent, en plus du bit utilisé, un bit modifié a été ajouté, qui est également appelé bit sale à certains endroits.

Maintenant, chaque page a deux états, à savoir (utiliser le bit, modifier le bit), qui peuvent être divisés dans les quatre cas suivants:

(0,0): Non utilisé ou modifié récemment, la meilleure condition!

(0,1): Modifié mais non utilisé récemment, sera écrit

(1,0): Utilisé mais non modifié, il sera réutilisé au tour suivant

(1,1): Utilisé et modifié, la dernière sélection du prochain cycle de remplacement de page

exemple

Prenons l'exemple du processus de remplacement de page suivant:

Les pages visitées sont: 0,1,3,6,2,4,5,2,5,0,3,1,2,5,4,1,0. Le numéro rouge indique la page à modifier, à savoir Leur bit modifié sera mis à 1. Dans la figure ci-dessous, ces pages sont indiquées en italique, et les bits utilisés et les bits modifiés sont indiqués dans la figure ci-dessous. Le "défaut?" Suivant signifie le nombre de fois où trouver des cadres libres lorsqu'une page est défectueuse.

Entrez la description de l'image

Commande de remplacement

  1. Partir de la position actuelle du pointeur pour trouver la page de la mémoire principale qui satisfait (utiliser bit, modifier bit) comme (0,0);

  2. Si la condition n'est pas trouvée à l'étape 1, recherchez la page dont l'état est (0,1);

  3. S'il n'est toujours pas trouvé, le pointeur revient à sa position d'origine et les bits d'utilisation de toutes les pages de l'ensemble sont mis à 0. Répétez l'étape 1 et si nécessaire, répétez l'étape 2 pour trouver la page à remplacer.

java implémente l'algorithme d'horloge

La description

Cet article implémente principalement une version simple de l'algorithme d'horloge et ajoute certaines optimisations de performances à l'implémentation conventionnelle. (L'ensemble du réseau peut être exclusif, ou le premier à y parvenir)

L'optimisation est principalement basée sur des considérations de performances, similaires à l'optimisation des performances précédente pour LRU, qui optimise les opérations de requête de O (N) à O (1).

Idée de réalisation

Nous définissons une liste chaînée circulaire qui répond au scénario commercial actuel (cette dernière peut également être indépendante, et il est temps d'écrire un projet de structure de données distinct pour une réutilisation facile)

Définissez le nœud contenant le accessFlag.

Nous utilisons une liste à double chaînage au lieu d'une liste à liaison unique, les performances de suppression sont donc les meilleures.

Utilisez la carte pour enregistrer les informations clés, évitez de boucler toute la liste chaînée pour déterminer si la clé existe et échangez de l'espace contre du temps.

Eh bien, la prochaine étape est la bonne étape de codage.

Code

Définition du nœud

/**
 * 循环链表节点
 * @author binbin.hou
 * @since 0.0.15
 * @param <K> key
 * @param <V> value
 */
public class CircleListNode<K,V> {

    /**
     * 键
     * @since 0.0.15
     */
    private K key;

    /**
     * 值
     * @since 0.0.15
     */
    private V value = null;

    /**
     * 是否被访问过
     * @since 0.0.15
     */
    private boolean accessFlag = false;

    /**
     * 后一个节点
     * @since 0.0.15
     */
    private CircleListNode<K, V> pre;

    /**
     * 后一个节点
     * @since 0.0.15
     */
    private CircleListNode<K, V> next;

    //getter & setter
}

Voici quelques éléments simples: clé, valeur, accessFlag (identification de l'accès ou non), puis ensuite, les pré-utilisateurs implémentent une liste doublement liée.

Implémentation de liste double chaînée

Attributs de base

Afin d'être cohérent avec la liste d'origine Lru doublement liée, nous implémentons l'interface d'origine.

public class LruMapCircleList<K,V> implements ILruMap<K,V> {

    private static final Log log = LogFactory.getLog(LruMapCircleList.class);

    /**
     * 头结点
     * @since 0.0.15
     */
    private CircleListNode<K,V> head;

    /**
     * 映射 map
     * @since 0.0.15
     */
    private Map<K, CircleListNode<K,V>> indexMap;

    public LruMapCircleList() {
        // 双向循环链表
        this.head = new CircleListNode<>(null);
        this.head.next(this.head);
        this.head.pre(this.head);

        indexMap = new HashMap<>();
    }

}

Initialisez le nœud Head et l'utilisateur d'indexMap enregistre la relation entre la clé et le nœud bidirectionnel.

Supprimer l'élément

/**
 * 移除元素
 *
 * 1. 是否存在,不存在则忽略
 * 2. 存在则移除,从链表+map中移除
 *
 * head==>1==>2==>head
 *
 * 删除 2 之后:
 * head==>1==>head
 * @param key 元素
 * @since 0.0.15
 */
@Override
public void removeKey(final K key) {
    CircleListNode<K,V> node = indexMap.get(key);
    if(ObjectUtil.isNull(node)) {
        log.warn("对应的删除信息不存在:{}", key);
        return;
    }
    CircleListNode<K,V> pre = node.pre();
    CircleListNode<K,V> next = node.next();
    //1-->(x2)-->3  直接移除2
    pre.next(next);
    next.pre(pre);
    indexMap.remove(key);
    log.debug("Key: {} 从循环链表中移除", key);
}

Il n'est pas difficile de supprimer des nœuds. Supprimez simplement les nœuds de la liste circulaire liée et supprimez les informations de l'indexMap.

Mise à jour

La même méthode est utilisée pour mettre / obtenir ici. En fait, si vous voulez implémenter une version améliorée de l'algorithme d'horloge, il vaut mieux faire la distinction entre les deux, mais je pense que le principe est similaire, donc je ne l'implémenterai plus ici. On estime que cela est éliminé La dernière section de l'algorithme.

/**
 * 放入元素
 *
 * 类似于 FIFO,直接放在队列的最后
 * 
 * head==>1==>head
 * 加入元素:
 *
 * head==>1==>2==>head
 *
 * (1)如果元素不存在,则直接插入。
 * 默认 accessFlag = 0;
 * (2)如果已经存在,则更新 accessFlag=1;
 *
 * @param key 元素
 * @since 0.0.15
 */
@Override
public void updateKey(final K key) {
    CircleListNode<K,V> node = indexMap.get(key);
    // 存在
    if(ObjectUtil.isNotNull(node)) {
        node.accessFlag(true);
        log.debug("节点已存在,设置节点访问标识为 true, key: {}", key);
    } else {
        // 不存在,则插入到最后
        node = new CircleListNode<>(key);
        CircleListNode<K,V> tail = head.pre();
        tail.next(node);
        node.pre(tail);
        node.next(head);
        head.pre(node);
        // 放入 indexMap 中,便于快速定位
        indexMap.put(key, node);
        log.debug("节点不存在,新增节点到链表中:{}", key);
    }
}

Il s'agit principalement de distinguer si le nœud suivant existe déjà.

(1) Existant, récupérez le nœud directement, mettez à jour accessFlag = true;

(2) N'existe pas: insérez un nouveau nœud, accessFlag = false

Données obsolètes

/**
 * 删除最老的元素
 *
 * (1)从 head.next 开始遍历,如果元素 accessFlag = 0,则直接移除
 * (2)如果 accessFlag=1,则设置其值为0,循环下一个节点。
 *
 * @return 结果
 * @since 0.0.15
 */
@Override
public ICacheEntry<K, V> removeEldest() {
    //fast-fail
    if(isEmpty()) {
        log.error("当前列表为空,无法进行删除");
        throw new CacheRuntimeException("不可删除头结点!");
    }
    // 从最老的元素开始,此处直接从 head.next 开始,后续可以考虑优化记录这个 key
    CircleListNode<K,V> node = this.head;
    while (node.next() != this.head) {
        // 下一个元素
        node = node.next();
        if(!node.accessFlag()) {
            // 未访问,直接淘汰
            K key = node.key();
            this.removeKey(key);
            return CacheEntry.of(key, node.value());
        } else {
            // 设置当前 accessFlag = 0,继续下一个
            node.accessFlag(false);
        }
    }
    // 如果循环一遍都没找到,直接取第一个元素即可。
    CircleListNode<K,V> firstNode = this.head.next();
    return CacheEntry.of(firstNode.key(), firstNode.value());
}

Traversez le nœud directement et éliminez-le lorsque accessFlag = 0.

Si accessFlag = 1, définissez sa valeur sur 0, puis passez à la suivante. (Voici un peu le sentiment que la médaille d'or ne peut être utilisée qu'une seule fois)

Il n'a pas été trouvé après la boucle. En fait, il suffit de récupérer directement head.next et de revenir à FIFO. Bien sûr, parce que nous avons mis à jour accessFlag = 0, nous pouvons en fait continuer la boucle.

  • Lacunes de mise en œuvre

Il y a un point à améliorer: on ne démarre pas forcément la boucle à chaque fois. En fait, les lacunes sont plus évidentes. Par conséquent, l'élément qui entre en premier dans la file d'attente doit être éliminé pour la deuxième fois, et d'autres éléments non visités peuvent toujours exister. Vous pouvez vous souvenir de cette position avec un élément. (Le nœud suivant du nœud qui a été éliminé la dernière fois), je pense que cela est plus conforme à l'idée de l'algorithme d'horloge.

Une autre méthode consiste à ne pas définir le accessFlag accédé à 0, et aucun élément ne peut être trouvé dans une boucle et rétrograder directement à FIFO, mais après l'accès à la plupart des éléments, les performances se détérioreront. Il est donc recommandé de marquer la position du dernier cycle.

transfert

Lorsque le cache est plein, nous pouvons appeler la liste liée circulaire actuelle:

import com.github.houbb.cache.api.ICache;
import com.github.houbb.cache.api.ICacheEntry;
import com.github.houbb.cache.api.ICacheEvictContext;
import com.github.houbb.cache.core.model.CacheEntry;
import com.github.houbb.cache.core.support.struct.lru.ILruMap;
import com.github.houbb.cache.core.support.struct.lru.impl.LruMapCircleList;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;

/**
 * 淘汰策略-clock 算法
 *
 * @author binbin.hou
 * @since 0.0.15
 */
public class CacheEvictClock<K,V> extends AbstractCacheEvict<K,V> {

    private static final Log log = LogFactory.getLog(CacheEvictClock.class);

    /**
     * 循环链表
     * @since 0.0.15
     */
    private final ILruMap<K,V> circleList;

    public CacheEvictClock() {
        this.circleList = new LruMapCircleList<>();
    }

    @Override
    protected ICacheEntry<K, V> doEvict(ICacheEvictContext<K, V> context) {
        ICacheEntry<K, V> result = null;
        final ICache<K,V> cache = context.cache();
        // 超过限制,移除队尾的元素
        if(cache.size() >= context.size()) {
            ICacheEntry<K,V>  evictEntry = circleList.removeEldest();;
            // 执行缓存移除操作
            final K evictKey = evictEntry.key();
            V evictValue = cache.remove(evictKey);

            log.debug("基于 clock 算法淘汰 key:{}, value: {}", evictKey, evictValue);
            result = new CacheEntry<>(evictKey, evictValue);
        }

        return result;
    }

    /**
     * 更新信息
     * @param key 元素
     * @since 0.0.15
     */
    @Override
    public void updateKey(final K key) {
        this.circleList.updateKey(key);
    }

    /**
     * 移除元素
     *
     * @param key 元素
     * @since 0.0.15
     */
    @Override
    public void removeKey(final K key) {
        this.circleList.removeKey(key);
    }

}

En fait, il n'y a aucune difficulté à appeler le lieu, il suffit d'appeler la méthode directement.

tester

Bon, vérifions-le brièvement après l'écriture du code.

Code de test

ICache<String, String> cache = CacheBs.<String,String>newInstance()
        .size(3)
        .evict(CacheEvicts.<String, String>clock())
        .build();
cache.put("A", "hello");
cache.put("B", "world");
cache.put("C", "FIFO");
// 访问一次A
cache.get("A");
cache.put("D", "LRU");
Assert.assertEquals(3, cache.size());
System.out.println(cache.keySet());

Journal

[DEBUG] [2020-10-07 11:32:55.396] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.updateKey] - 节点不存在,新增节点到链表中:A
[DEBUG] [2020-10-07 11:32:55.398] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.updateKey] - 节点不存在,新增节点到链表中:B
[DEBUG] [2020-10-07 11:32:55.401] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.updateKey] - 节点不存在,新增节点到链表中:C
[DEBUG] [2020-10-07 11:32:55.403] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.updateKey] - 节点已存在,设置节点访问标识为 true, key: A
[DEBUG] [2020-10-07 11:32:55.404] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.removeKey] - Key: B 从循环链表中移除
[DEBUG] [2020-10-07 11:32:55.406] [main] [c.g.h.c.c.s.e.CacheEvictClock.doEvict] - 基于 clock 算法淘汰 key:B, value: world
[DEBUG] [2020-10-07 11:32:55.410] [main] [c.g.h.c.c.s.l.r.CacheRemoveListener.listen] - Remove key: B, value: world, type: evict
[DEBUG] [2020-10-07 11:32:55.411] [main] [c.g.h.c.c.s.s.l.i.LruMapCircleList.updateKey] - 节点不存在,新增节点到链表中:D
[D, A, C]

Répondez à nos attentes.

Comparaison de LRU, FIFO et Clock

LRU et FIFO sont essentiellement des idées premier entré premier sorti, mais LRU est basé sur le temps d'accès le plus récent des pages à trier, il est donc nécessaire d'ajuster dynamiquement l'ordre de chaque page lors de chaque accès à la page (le plus récent de chaque page) Le temps d'accès a changé); tandis que la FIFO trie l'heure d'entrée de la page en mémoire. Cette heure est fixe, donc la séquence des pages est fixe.

Si le programme est local, LRU sera bon. Si toutes les pages de la mémoire n'ont pas été accédées, elles dégénéreront en FIFO (par exemple, la page n'a pas été accédée après l'entrée en mémoire, et le dernier temps d'accès est le même que l'heure à laquelle la mémoire est entrée).

Les performances de l'algorithme LRU sont meilleures, mais la surcharge système est relativement importante; la surcharge système de l'algorithme FIFO est relativement faible, mais le phénomène Belady peut se produire.

Par conséquent, le meilleur choix est l'algorithme Clock. Il n'a pas besoin d'ajuster dynamiquement l'ordre des pages dans la liste liée à chaque fois qu'une page est accédée. Au lieu de cela, il se contente de marquer et d'attendre qu'une erreur de page se produise avant de la déplacer vers la liste liée. À la fin.

Pour les pages qui ne sont pas accédées dans la mémoire, l'algorithme Clock fonctionne aussi bien que LRU, mais pour les pages qui ont été accédées, il ne peut pas se souvenir de leur séquence d'accès exacte comme LRU.

Supplément d'algorithme de remplacement

Nous avons essentiellement couvert les algorithmes de remplacement courants.

Cependant, il existe de nombreuses variantes d'algorithmes, et il existe de nombreux algorithmes dans différents scénarios. Ici, l'algorithme qui n'est pas expliqué en détail est ajouté, et l'implémentation correspondante ne sera pas effectuée ici.

Le but est d'améliorer le système cognitif de tout l'algorithme d'élimination.

Algorithme de permutation optimale (OPT)

Les pages éliminées sélectionnées par l'algorithme de remplacement Optimal (OPT) ne seront jamais utilisées à l'avenir, ou les pages qui ne seront pas accessibles dans le laps de temps le plus long, de sorte que le taux d'erreur de page le plus bas puisse être garanti.

Cependant, étant donné que les gens ne peuvent pas prédire laquelle des milliers de pages de la mémoire du processus ne sera pas consultée le plus longtemps à l'avenir, cet algorithme ne peut pas être mis en œuvre.

Le meilleur algorithme de remplacement peut être utilisé pour évaluer d'autres algorithmes. Supposons que le système alloue trois blocs physiques à un processus et tenez compte de la chaîne de référence de numéro de page suivante:

7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1

Lorsque le processus est en cours d'exécution, les trois pages 7, 0 et 1 sont chargées séquentiellement dans la mémoire.

Lorsqu'un processus veut accéder à la page 2, une interruption de défaut de page est générée. Selon le meilleur algorithme de remplacement, la page 7 qui doit être transférée pour le 18ème accès est sélectionnée et supprimée.

Ensuite, lors de l'accès à la page 0, il n'est pas nécessaire de générer une interruption de défaut de page car elle est déjà en mémoire. Lors de l'accès à la page 3, la page 1 sera éliminée selon le meilleur algorithme de remplacement ... et ainsi de suite, comme le montre la Figure 3-26.

On peut voir sur la figure que le meilleur algorithme de remplacement est utilisé.

On peut voir que le nombre d'interruptions de défaut de page est de 9 et le nombre de remplacements de page est de 6.

Entrez la description de l'image

Bien sûr, il s'agit d'un algorithme théorique, qui est en réalité impossible à réaliser, car nous ne pouvons pas prédire comment les données ultérieures seront utilisées.

Algorithme de mise en mémoire tampon de page (PBA: algorithme de mise en mémoire tampon de page) 

Bien que les algorithmes de remplacement de LRU et d'horloge soient meilleurs que les algorithmes FIFO, ils nécessitent tous deux une certaine prise en charge matérielle et nécessitent plus de temps système. De plus, le remplacement d'une page modifiée est plus coûteux que le remplacement d'une page non modifiée.

L'algorithme de tampon de page (PBA) peut non seulement améliorer les performances du système de pagination, mais également adopter une stratégie de remplacement plus simple.

Le système d'exploitation VAX / VMS utilise l'algorithme de tampon de page. Il utilise les méthodes d'allocation de variables et de remplacement locales susmentionnées, et l'algorithme de remplacement utilise FIFO.

L'algorithme stipule qu'une page éliminée est placée dans l'une des deux listes chaînées, c'est-à-dire que si la page n'a pas été modifiée, elle est directement placée dans la liste chaînée libre; sinon, elle est placée dans la liste chaînée des pages modifiées. Il convient de noter que la page n'est pas physiquement déplacée dans la mémoire à ce moment, mais que seule l'entrée de la table de pages est déplacée vers l'une des deux listes liées.

La liste liée des pages gratuites est en fait une liste chaînée de blocs physiques gratuits. Chaque bloc physique qu'elle contient est gratuit, de sorte que des programmes ou des données peuvent y être chargés. Lorsqu'une page doit être lue, le premier bloc physique de la liste chaînée de blocs physiques libres peut être utilisé pour charger la page. Lorsqu'une page non modifiée doit être échangée, elle n'est pas réellement remplacée par la mémoire, mais le bloc physique où se trouve la page non modifiée est suspendu à la fin de la liste liée de la page gratuite.

De même, lors du remplacement d'une page modifiée, le bloc physique dans lequel elle se trouve est également accroché à la fin de la liste chaînée de la page modifiée. De cette manière, les pages qui ont été modifiées et les pages qui n'ont pas été modifiées sont toujours conservées en mémoire. Lorsque le processus accède à nouveau à ces pages dans le futur, il suffit d'une petite quantité de temps système pour que ces pages reviennent à l'ensemble résident du processus. Lorsque le nombre de pages modifiées atteint une certaine valeur, par exemple 64 pages, elles sont réécrites sur le disque ensemble, ce qui réduit considérablement le nombre d'opérations d'E / S disque.

Un algorithme de mise en mémoire tampon de page plus simple a été implémenté dans le système d'exploitation MACH, mais il ne fait pas la distinction entre les pages modifiées et les pages non modifiées.

Comparaison des algorithmes de remplacement

algorithme Commentaire
Algorithme optimal Non réalisable, mais peut être utilisé comme référence
Algorithme NRU (utilisé récemment) Approximation approximative de LRU
Algorithme FIFO Peut supprimer des pages importantes (souvent utilisées)
Algorithme de la seconde chance Grande amélioration par rapport au FIFO
Algorithme d'horloge réaliste
Algorithme LRU (le moins récemment utilisé) Très bon, mais difficile à réaliser
Algorithme NFU (le moins utilisé) Approximation de LRU
Algorithme de vieillissement Très similaire à LRU
Algorithme de jeu de travail Cher à mettre en œuvre
Algorithme d'horloge de réglage de travail Bon algorithme efficace

résumé

L'algorithme d'horloge est un compromis Dans les applications pratiques réelles, le système d'exploitation choisit cet algorithme.

L’avantage d’une compréhension personnelle de l’horloge est que vous n’avez pas besoin de mettre à jour la position de l’élément à chaque fois que vous le visitez fréquemment , vous n’avez besoin de le mettre à jour qu’une fois quand il est éliminé. C'est assez inutile.

L'algorithme d'élimination du cache a essentiellement pris fin ici. Merci pour votre soutien et j'espère que vous pourrez gagner quelque chose.

Adresse open source:https://github.com/houbb/cache

Si vous pensez que cet article vous est utile, n'hésitez pas à aimer, commenter, ajouter des favoris et suivre une vague. Votre encouragement est ma plus grande motivation ~

Je ne sais pas ce que vous avez gagné? Ou si vous avez plus d'idées, n'hésitez pas à discuter avec moi dans la zone de message et attendez avec impatience de rencontrer vos pensées.

L'apprentissage en profondeur

Je suppose que tu aimes

Origine blog.51cto.com/9250070/2540345
conseillé
Classement