Aide-mémoire de l'abréviation C ++ et plus encore. Partie 2: «et pas seulement»

Ceci est la deuxième et dernière partie de ma feuille de triche abréviation qu'un développeur C ++ devrait connaître. C ++ n'est mentionné ici que parce que j'ai créé la feuille de triche principalement pour moi, et je suis juste le même développeur C ++.

En fait, cette partie contient des concepts dont la portée n'est pas limitée au C ++. La sélection peut donc intéresser un public plus large.



Comme dans la première partie , les abréviations sont regroupées, si cela a du sens. Si cela n'a aucun sens, ils sont classés par ordre alphabétique.

Opérations simultanées et atomiques:
CAS
ABA
FAA
RCU

Stockage des données:
ACIDE
CAP
PACELC
BASE

Principes de développement logiciel:
SEC
BAISER
YAGNI
NIH
FTSE
GRASP
SOLIDE

Autre:
ABI
VACHE
FBC, FBCP
LRU

Opérations simultanées et atomiques


Cas


Comparez et échangez. Comparaison avec l'échange. Il s'agit d'une instruction atomique avec trois arguments: variable atomique ou adresse mémoire, valeur attendue, nouvelle valeur. Si et seulement si la valeur de la variable correspond à la valeur attendue, la variable reçoit une nouvelle valeur et l'instruction se termine avec succès. CAS renvoie simplement une valeur booléenne (et peut ensuite être appelée Compare And Set), ou s'il échoue, il renvoie également la valeur actuelle du premier argument.

Pseudo code
bool cas(int* addr, int& expected, int new_value) { if (*addr != expected) { expected = *addr; return false; } *addr = new_value; return true; } 

En C ++, CAS est représenté par les familles de méthodes std::atomic<T>::compare_exchange_weak , std::atomic<T>::compare_exchange_strong et les fonctions libres std::atomic_compare_exchange_weak , std::atomic_compare_exchange_strong . La différence entre * faible et * fort est que le premier peut produire de faux résultats négatifs. C'est-à-dire si la valeur est attendue, ils renverront false et ne la remplaceront pas par une nouvelle. La raison de l'existence d'opérations * faibles est que sur certaines architectures * fortes sont relativement coûteuses. Dans la plupart des cas, les instructions CAS tournent en boucle (la soi-disant boucle CAS), donc l'utilisation de * faible au lieu de * fort ne changera pas la logique, mais cela peut améliorer les performances.

Les instructions CAS sont utilisées pour implémenter des primitives de synchronisation (telles que des mutex et des sémaphores) et des algorithmes sans verrouillage. Mène souvent à un problème ABA .

Lire la suite: une fois (russe) , deux (anglais)

ABA


Problème ABA. Problème ABA. Ce problème se pose dans les algorithmes parallèles basés sur des comparaisons avec des échanges (voir CAS ), par exemple, dans des algorithmes sans verrouillage. L'essentiel est que le thread lit la valeur de la variable atomique, fait autre chose et met à jour cette variable par comparaison avec l'échange. C'est-à-dire la logique de flux est la suivante: si la variable contient toujours la valeur précédente, alors rien n'a changé, tout est en ordre. Mais ce n'est peut-être pas le cas. Une description plus formelle du problème:

  • le thread 1 lit la valeur de la variable, elle est égale à A
  • le thread 1 est forcé, le thread 2 démarre
  • le thread 2 modifie la valeur de la variable de A à B, effectue un tas de changements (modifie une valeur associée à la variable ou libère simplement la mémoire), puis modifie à nouveau la valeur - de B à A
  • le thread 1 reprend l'opération, compare la valeur précédemment obtenue avec la valeur actuelle et conclut que rien n'a changé

Solutions possibles au problème:

  1. Le plus simple et le plus évident est d'utiliser des verrous. Cela se traduira par l'algorithme thread-safe habituel avec des sections critiques. Mais il cessera d'être exempt de verrous. Mais s'il s'agit de CAS et ABA , ce n'est probablement pas une option.
  2. Ajoutez des étiquettes spéciales aux valeurs comparées. Par exemple, un compteur du nombre de changements. D'une part, ce compteur peut déborder, mais d'autre part, les processeurs x86_64 modernes prennent en charge les opérations CAS 128 bits. C'est-à-dire lorsque vous comparez des pointeurs à un compteur, vous pouvez donner jusqu'à 64 bits, et quelqu'un a pensé que cela suffisait pour 10 ans de fonctionnement continu de l'algorithme.
  3. Certaines architectures (ARM, par exemple) fournissent des instructions LL / SC (charge liée, stockage conditionnel) qui vous permettent non seulement d'obtenir la valeur actuelle de l'adresse en mémoire, mais également de comprendre si cette valeur a changé depuis la dernière lecture.

Pour utiliser des structures de données telles qu'une pile, une liste ou une file d'attente, qui sont exemptes de verrous, en général, lorsqu'il existe un risque de rester avec un pointeur suspendu vers un nœud distant, il existe toute une famille de solutions au problème ABA basées sur la suppression différée des nœuds. Cela inclut le garbage collector, les pointeurs de danger et le mécanisme de lecture-modification-écriture (voir RCU ).

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

FAA


Récupérer et ajouter. Ahem ... obtenez et ajoutez (il semble que ce concept ne soit jamais traduit en russe). Une opération atomique avec deux arguments: une variable atomique ou une adresse en mémoire, et la valeur par laquelle cette variable doit être modifiée. Si l'architecture le permet, l'opération renvoie la valeur précédente de la variable modifiée (x86 le permet depuis i486). Contrairement à la CAS , la FAA est toujours couronnée de succès.

Pseudo code
 int faa(int* addr, int diff) { int value = *addr; *addr = value + diff; return value; } 

En C ++, il est implémenté comme les familles de méthodes std::atomic<T>::fetch_add , fetch_sub , fetch_and , fetch_or , fetch_xor et les fonctions libres correspondantes std::atomic_fetch_add , etc.

Comme il sied à une instruction atomique, le FAA est utilisé dans les implémentations de primitives de synchronisation et d'algorithmes et de structures de données sans verrouillage.

Lire la suite: une fois (russe) , deux (anglais)

RCU


Lecture-copie-mise à jour. Lecture-modification-écriture. Il s'agit d'un mécanisme non bloquant pour synchroniser l'accès à la structure de données (sans verrouillage bien sûr). Il est utilisé dans les cas où la vitesse de lecture est critique. C'est un exemple de compromis entre le temps et la mémoire (compromis espace-temps).

L'idée de RCU est que le flux d'écriture ne modifie pas les données en place, mais crée une copie, y apporte les modifications nécessaires et échange atomiquement les données actuelles et la copie modifiée. En même temps, les fils de lecture ont constamment accès aux données - anciennes ou nouvelles, quel que soit le temps. Lorsqu'il ne reste aucun lecteur travaillant avec la version obsolète, le rédacteur supprime les données qui ne sont plus nécessaires, libérant ainsi de la mémoire.

RCU très simplifié fonctionne comme ceci:

  • Beaucoup de lecteurs, un écrivain.
  • La lecture et le changement se produisent simultanément.
  • Les lecteurs utilisent une synchronisation très légère. En fait, le lecteur n'a qu'à avertir l'écrivain au moment d'entrer dans la section critique et au moment de la quitter. Le travail avec les données synchronisées se produit uniquement dans la section critique.
  • L'écrivain, dès qu'il remplace atomiquement les données par une copie, annonce le début de la soi-disant période de grâce (période de grâce). La période de grâce se termine lorsque tous les lecteurs qui se trouvaient dans des sections critiques au début de cette période ont quitté leurs sections critiques. Désormais, le rédacteur peut supprimer en toute sécurité les données obsolètes. Il est sous-entendu que toutes les sections critiques sont finies, ce qui garantit la finitude du délai de grâce.

RCU est idéal pour les données qui sont souvent lues et rarement mises à jour. Ce mécanisme est activement utilisé dans le noyau Linux, où il est assez simple de déterminer la fin de la période de grâce.

Inconvénients:

  • Mauvais pour synchroniser l'accès aux données fréquemment modifiées.
  • Difficile à mettre en œuvre dans l'espace utilisateur.
  • Cela dépend de la capacité de changer atomiquement des pointeurs vers une adresse en mémoire, mais toutes les architectures ne fournissent pas cette capacité.

Lire la suite: une fois (russe) , deux (anglais)

Stockage de données


ACIDE


Atomicité, cohérence, isolement, durabilité. Atomicité, consistance, isolement, durabilité. Il s'agit d'un ensemble d'exigences de transaction dans un SGBD. ACID fournit un fonctionnement SGBD fiable et prévisible même en cas d'erreur.

  • L'atomicité garantit que la transaction se termine complètement ou ne fait rien. Un état intermédiaire est impossible, il ne sera pas tel qu'une opération de transaction a réussi, et l'autre non. Tout ou rien.
  • La cohérence garantit que toutes les données de la base de données satisfont à toutes les règles et restrictions spécifiées à la fois avant le début de la transaction et après son achèvement. Pendant l'exécution de la transaction, la cohérence peut être violée.
  • L'isolement garantit que les transactions simultanées ne s'influencent pas mutuellement. Aucune transaction n'a accès à des données incohérentes traitées par une autre transaction.
  • La durabilité signifie que le résultat d'une transaction réussie est stocké dans la base de données et ne peut pas être perdu, quoi qu'il arrive à la base de données immédiatement après la transaction.

Tous les principaux SGBD relationnels prennent entièrement en charge ACID . Dans le monde NoSQL, une telle prise en charge complète est probablement une exception.

En savoir plus: une fois (anglais) , deux (anglais)

Casquette


Théorème CAP. Théorème CAP. Le théorème stipule que tout système distribué ne peut pas avoir plus de deux propriétés de la liste: cohérence des données ( cohérence ), disponibilité ( disponibilité ), résistance à la séparation ( tolérance de partition ).

  • La cohérence dans ce cas signifie une cohérence cohérente (simplifiée). C'est-à-dire dès que l'opération de mise à jour des données sur un nœud s'est terminée avec succès, tous les autres nœuds ont déjà ces données mises à jour. Par conséquent, tous les nœuds sont dans un état cohérent. Ce n'est pas la cohérence requise par ACID .
  • La disponibilité signifie que chaque nœud défaillant renvoie une réponse correcte à chaque demande (à la fois en lecture et en écriture) dans un délai raisonnable. Il n'y a aucune garantie que les réponses des différents nœuds correspondent.
  • La résistance à la séparation signifie que le système continuera de fonctionner correctement en cas de perte d'un nombre arbitraire de messages entre ses nœuds.

Parce que les trois propriétés sont inaccessibles; du point de vue du théorème CAP , tous les systèmes distribués se répartissent en trois classes: CA , CP et AP . Les systèmes CA n'ont évidemment pas de résistance de séparation. Parce que dans la grande majorité des cas, la distributivité implique la distributivité sur un réseau réel, et dans un réseau réel il y a toujours une probabilité non nulle de perdre un paquet, alors les systèmes d' AC sont de peu d'intérêt.

Le choix se fait entre CP et AP , c'est-à-dire entre cohérence et disponibilité. Les SGBD relationnels traditionnels qui suivent les principes ACID préfèrent la cohérence. Alors que de nombreuses solutions NoSQL choisissent l'accessibilité et la BASE .

Dans le cas d'un fonctionnement normal du réseau, c'est-à-dire lorsqu'il n'y a pas de séparation de réseau, le théorème CAP n'impose aucune restriction de cohérence et de disponibilité. C'est-à-dire le don de quelque chose n'est pas nécessaire.

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

Pacelc


Théorème de PACELC. Théorème de PACELC. Il s'agit d'une extension du théorème CAP , qui stipule qu'un système distribué dans le cas d'une partition réseau ( Partition ) est obligé de choisir entre la disponibilité ( cohérence ), et dans le cas d'un fonctionnement réseau normal ( Else ), vous devez choisir entre la latence ( cohérence) )

En conséquence, si le théorème de CAP distingue 2 classes de systèmes stables avec la séparation de réseaux, PACELC en a 4: PA / EL , PA / EC , PC / EL et PC / EC . Certaines bases de données NoSQL peuvent changer de classe en fonction des paramètres.

Lire la suite: une fois (russe) , deux (anglais)

BASE


Fondamentalement disponible, état souple, cohérence éventuelle. Disponibilité de base, état fragile, cohérence à long terme. Selon le théorème de la PAC dans les systèmes distribués, quelque chose devra être abandonné. La cohérence stricte est généralement abandonnée au profit de la cohérence à long terme. Ce qui signifie qu'en l'absence de modifications des données, le système finira un jour par atteindre un état cohérent.

Pour indiquer un tel compromis, l'abréviation BASE a commencé à être utilisée de manière quelque peu stricte , et un jeu de termes chimiques s'est avéré ( ACIDE - acidité, BASE - basicité).

  • Fondamentalement disponible signifie que le système garantit la disponibilité des données, il répond à chaque demande. Mais la réponse peut être des données obsolètes ou incohérentes (ou leur absence)
  • L'état souple signifie que l'état du système peut changer au fil du temps, même en l'absence de demandes de modification des données. Parce qu'à tout moment, les données peuvent être mises dans un état cohérent.
  • La cohérence finale signifie que si les données cessent de changer, elles finiront bien sûr dans un état cohérent. C'est-à-dire la même demande à différents nœuds conduira aux mêmes réponses.

En savoir plus: une fois (anglais) , deux (anglais)

Principes de développement de logiciels


SEC


Ne vous répétez pas. Ne répétez pas. C'est le principe du développement logiciel, dont l'idée principale est de réduire la quantité d'informations dupliquées dans le système, et l'objectif est de réduire la complexité du système et d'augmenter sa gérabilité.

Dans l'original ( The Pragmatic Programmer livre, par Andrew Hunt et David Thomas ), ce principe est formulé comme suit: «Chaque élément de connaissance doit avoir une représentation unique, cohérente et faisant autorité au sein du système. Dans ce cas, la connaissance signifie toute partie d'un domaine ou d'un algorithme: un code, un schéma de base de données, un certain protocole d'interaction, etc. Ainsi, pour apporter une modification au système, une seule «connaissance» doit être mise à jour en un seul endroit.

Un exemple stupide: le client et le serveur se transmettent des données structurées. Parce que ce sont des applications différentes qui s'exécutent sur des machines différentes, alors elles doivent toutes deux avoir leurs propres implémentations de ces structures. Si quelque chose change, des changements devront être effectués à deux endroits. Une étape évidente pour éviter cette répétition consiste à allouer le code commun dans une bibliothèque distincte. L'étape suivante consiste à le générer en fonction de la description des structures (Google Protocol Buffers, par exemple), afin de ne pas écrire le même type de code pour accéder aux champs des structures.

La duplication de code n'est qu'un cas particulier de violation DRY . Et ce n'est pas toujours le cas. Si deux morceaux de code se ressemblent, mais implémentent chacun leur propre logique métier, DRY n'est pas interrompu.

Comme tout autre principe, DRY est un outil, pas un dogme. Plus le système est grand, plus il est facile de violer ce principe. Premièrement, l'idéal est inaccessible. Et deuxièmement, si suivre aveuglément DRY conduit à un code plus compliqué et le rend plus difficile à comprendre, alors il vaut mieux l'abandonner.

En savoir plus: un (russe) , deux (russe)

BAISER


Restez simple, stupide. Rendez-le plus facile (stupide dans ce cas n'est pas un appel). C'est le principe de conception selon lequel la plupart des systèmes fonctionnent mieux s'ils restent simples. Le principe trouve son origine dans l'industrie aéronautique et s'applique beaucoup partout, y compris dans le développement de logiciels.

Dans ce dernier cas, KISS est utile à la fois pour concevoir et pour écrire directement du code. Une architecture et un code simples sont non seulement plus faciles à comprendre, ils sont également plus faciles à utiliser, à entretenir et à développer. MapReduce ne devrait pas être dupe si une paire producteur-consommateur est suffisante. La magie de la métaprogrammation est excessivement complexe si vous pouvez faire avec quelques fonctions ordinaires et atteindre le niveau de performance requis.

Avec tout cela, il ne faut pas oublier que la simplicité n'est pas un objectif, mais seulement une exigence non fonctionnelle. L'essentiel est d'atteindre l'objectif de conception / mise en œuvre, et il est conseillé de le faire de la manière la plus simple possible.

En savoir plus: un (russe) , deux (russe) , trois (anglais)

YAGNI


Tu n'en auras pas besoin. Vous n'en avez pas besoin. C'est le principe du développement logiciel, dont l'idée principale est le rejet de fonctionnalités excessives, et l'objectif est d'économiser les ressources consacrées au développement.

YAGNI dit que vous n'avez pas besoin de concevoir ou d'implémenter des fonctions qui ne sont pas nécessaires pour le moment. Même si vous êtes sûr qu'ils seront nécessaires à l'avenir. Pas besoin d'ajouter des abstractions inutiles, dont les avantages apparaîtront un peu plus tard.

Le problème est que les gens ne prédisent pas bien l'avenir. Par conséquent, tout ce qui est fait "en réserve" ne sera probablement pas utile. Et il s'avère que le temps et l'argent dépensés pour ce développement, ces tests et cette documentation sont gaspillés. De plus, le logiciel est devenu plus compliqué; des ressources supplémentaires doivent être dépensées pour le soutenir à nouveau. Pire encore, quand il s'avère qu'il fallait faire différemment. De l'argent et du temps seront également consacrés à la correction.

Le principe YAGNI est un peu plus radical que DRY et KISS . S'ils divisent le système en parties compréhensibles et prennent des décisions simples, alors YAGNI supprime simplement les parties et les solutions inutiles.

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

Nih


Pas inventé ici. Pas inventé ici. Il s'agit d'un syndrome de rejet des évolutions d'autrui, une position qui frise pratiquement l'invention du vélo. Souvent, le syndrome s'accompagne de la conviction que la création de technologies au sein de l'entreprise sera à la fois plus rapide et moins chère, et que la technologie elle-même répondra mieux aux besoins de l'entreprise. Cependant, dans la plupart des cas, ce n'est pas le cas, et NIH est un anti-modèle.

Voici quelques cas justifiant NIH :

  • La qualité de la solution tierce n'est pas assez élevée ou le prix n'est pas assez bas.
  • Une solution tierce a des restrictions de licence.
  • L'utilisation d'une solution tierce crée une dépendance vis-à-vis de son fournisseur et menace ainsi l'entreprise.

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

Ftse


Théorème fondamental du génie logiciel. Théorème fondamental du développement logiciel. En fait, ce n'est pas un théorème, il n'a aucune preuve. C'est un dicton célèbre d'Andrew Koenig:
Tout problème peut être résolu en ajoutant une autre couche d'abstraction.
Parfois, ils ajoutent à cette phrase "... sauf pour le problème de trop de couches d'abstraction". En général, le «théorème» n'est pas une chose sérieuse, mais il vaut la peine de le savoir.

En savoir plus: une fois (anglais) , deux (anglais)

GRASP


Modèles de logiciel d'attribution de responsabilité générale. Modèles généraux d'attribution des responsabilités. Ces neuf modèles sont formulés dans Application de UML et Patterns par Craig Larman . Chaque modèle est une solution typique à un problème de conception de logiciel (mais plutôt général).

  1. Expert en information . Problème: quel est le principe général de la répartition des responsabilités entre les objets? Décision: attribuer un devoir à une personne qui dispose des informations nécessaires pour remplir cette obligation.
  2. Créateur Problème: qui devrait être responsable de la création d'un nouvel objet? Solution: la classe B doit créer des instances de la classe A si une ou plusieurs des conditions suivantes sont remplies:
    - la classe B agrège ou contient des instances de A
    - B écrit A
    - B utilise activement A
    - B a des données d'initialisation A
  3. Couplage bas Problème: comment réduire l'impact du changement? Comment augmenter les possibilités de réutilisation? Solution: répartissez les responsabilités afin que la connectivité soit faible. Le couplage est une mesure de la rigidité des éléments connectés, de la façon dont ils dépendent les uns des autres. C'est-à-dire Il est recommandé de connecter des objets pour qu'ils ne se connaissent que le minimum nécessaire.
  4. Engrenage élevé ( haute cohésion ). Problème: Comment gérer la complexité? Solution: répartissez les responsabilités afin de maintenir un engagement élevé. Un engagement élevé signifie que les responsabilités d'un élément sont concentrées sur un seul domaine.
  5. Contrôleur Problème: qui devrait être responsable de la gestion des événements d'entrée? Solution: attribuez une classe responsable, qui représente soit l'ensemble du système ou du sous-système dans son ensemble (contrôleur externe), soit un script spécifique (script ou contrôleur de session). Dans le même temps, le contrôleur ne met pas en œuvre de réaction aux événements, il la délègue aux exécuteurs concernés.
  6. Polymorphisme ( polymorphisme ). Problème: comment gérer différents comportements en fonction du type? : , , , .
  7. ( Pure Fabrication ). : , ? : , .
  8. ( Indirection ). : , Low Coupling ? : .
  9. Résistance aux changements ( Variations protégées ). Problème: comment concevoir des objets et des sous-systèmes de manière à ce que leurs modifications n'aient pas d'effet indésirable sur d'autres éléments? Solution: trouver d'éventuels points d'instabilité et créer une interface stable autour d'eux, communiquer uniquement via une telle interface.

Les modèles GRASP se croisent constamment avec les modèles Gang Four et les principes SOLID . C'est normal, car ils résolvent tous le problème commun - pour simplifier la création de logiciels de haute qualité.

En savoir plus: un (russe) , deux (anglais) , trois (russe)

SOLIDE


Les principes de SOLID. Ce sont les cinq principes de la programmation et de la conception orientées objet (l'abréviation est faite à partir des premières lettres de leurs noms). Il est devenu célèbre grâce à Robert Martin au début des années 2000. L'objectif principal de ces principes est de créer des logiciels faciles à comprendre, à maintenir et à développer.

  1. Le principe de la responsabilité unique ( responsabilité unique principe, la SRP ). Une classe ou un module ne devrait avoir qu'une seule responsabilité. "Faites une seule chose, mais faites-le bien."
  2. / ( Open Closed Principle, OCP ). (, , ) , . : , .
  3. ( Liskov Substitution Principle, LSP ). , . C'est-à-dire - .
  4. ( Interface Segregation Principle, ISP ). , . , . , , . , .
  5. Inversion des dépendances ( du inversion des dépendances, le DIP ). Les modules de niveau supérieur ne doivent pas dépendre de modules de niveau inférieur. Tous les modules doivent dépendre d'abstractions. Les abstractions ne devraient pas dépendre des détails. Les détails doivent dépendre des abstractions. Par exemple, ni les interfaces ni les classes concrètes ne doivent contenir ou accepter d'autres classes concrètes comme arguments de leurs méthodes, mais uniquement des interfaces (au sens de Java et C #).

En savoir plus: un (russe) , deux (anglais) , trois (anglais)

Autre


Abi


Interface binaire d'application. Interface d'application binaire. Il s'agit d'un ensemble de conventions qui définit l'interaction des modules binaires (fichiers exécutables, bibliothèques, OS). Deux modules doivent être créés en conformité avec un ABI - c'est une condition préalable à leur compatibilité binaire, dans ce cas, ils peuvent interagir sans problème (par exemple, le fichier exécutable est lié à la bibliothèque et exécuté par le système d'exploitation).

Les exemples d' ABI sont les formats de fichiers exécutables ELF sous Linux et PE sous Windows. Chaque OS s'attend à ce que les données nécessaires (ressources, point d'entrée, etc.) se trouvent dans un fichier binaire selon le format correspondant. De toute évidence, ELF et PE sont différents, car les programmes Linux ne s'exécutent pas directement sur Windows et vice versa.

Au niveau des bibliothèques et des fichiers exécutables, ABI peut déterminer le placement des champs au sein d'une classe, les classes de base à l'intérieur des descendants, le mécanisme de mise en œuvre des fonctions virtuelles, le format du cadre de la pile d'appels, les règles de passage des arguments à la fonction appelée, etc., etc.

C ++ n'a pas un seul ABI standard , ce qui n'est pas surprenant, car cela dépend de l'architecture et du système d'exploitation. Par exemple, les compilateurs C ++ pour de nombreux systèmes d'exploitation de type Unix (Linux, FreeBSD, MacOS) sur x86_64 suivent System V AMD64 ABI , sur ARM - ARM C ++ ABI . Visual C ++ ABI n'est pas officiellement publié, mais au moins partiellement repensé. Il est très différent du système V ABI, ils ont des règles complètement différentes pour cacher les noms (mangling) etpasser des arguments à la fonction appelée (Linux utilise 6 registres, Windows utilise 4 registres), et un tas d'autres différences.

Même si l'API et l' ABI restent les mêmes et que seuls les détails d'implémentation changent, la compatibilité binaire peut être rompue . Par exemple, en C ++ 11, les chaînes devaient stocker les caractères séquentiellement (comme dans un vecteur). Pour cette raison, GCC 5 a dû changer l'implémentation des chaînes ( COW y était utilisé auparavant ), ce qui a conduit à une incompatibilité binaire.

En savoir plus: une fois (russe) , deux (anglais) et tous les liens des deux paragraphes précédents.

Vache


Copie en écriture. Copie lors de l'enregistrement. Il s'agit d'un mécanisme de gestion des ressources, également appelé partage implicite et copie paresseuse. L'idée est que lorsqu'une copie est requise, la ressource n'est pas réellement copiée, mais un lien vers celle-ci est créé. Et ce n'est que lorsqu'il y a une demande de modification - dans l'original ou dans la «copie» - qu'alors la création d'une copie complète a lieu.

L'avantage de COW est évident: la copie de n'importe quel objet se fait instantanément. Si les objets sont souvent copiés mais rarement modifiés, les gains de performances peuvent être importants.

Exemples d'utilisation de COW :

  • Gestion de la mémoire des processus virtuels sous Linux. Lorsque les fork()pages sont appelées , la mémoire de processus n'est pas copiée, mais uniquement marquée comme partagée.
  • Instantanés dans certains systèmes de fichiers (Btrfs, ZFS) et bases de données (MS SQL Server).
  • Avant C ++ 11, certaines implémentations std::stringutilisaient COW . En C ++ 11, les exigences pour std::stringont changé (voir ABI ).
  • De nombreux types dans Qt utilisent COW .

En savoir plus: une fois (anglais) , deux (anglais)

FBC, FBCP


Classe de base fragile (problème). Le problème d'une classe de base fragile. Il s'agit d'un problème de POO fondamental, son essence est que le changement correct de la classe de base peut entraîner une erreur chez l'un des héritiers.

Par exemple, à une récursion infinie
 struct Base { virtual void method1() { // ... } virtual void method2() { // ... method1(); // <--      } }; struct Derived : Base { void method1() override { method2(); } }; 

Vous pouvez résoudre le problème FBC uniquement en abandonnant l'héritage au profit de la composition, par exemple, ou en étendant les interfaces dans la terminologie Java (en C ++, cela héritera uniquement des classes de base abstraites sans implémentations d'état et de méthode). Dans d'autres cas, vous ne pouvez essayer de minimiser la probabilité de FBCP qu'avec les conseils suivants:

  • Interdire l'héritage ou la redéfinition là où ils ne sont pas nécessaires (mot clé finalen C ++ et Java).
  • L'héritier ne doit pas avoir accès aux intérieurs de la classe de base, la communication ne peut passer que par l'interface publique.
  • La méthode heir ne peut appeler que les méthodes virtuelles qui sont appelées dans la méthode redéfinie de la classe de base et la méthode redéfinie elle-même.

En savoir plus: un (anglais) , deux (anglais) , trois (anglais)

LRU


Le moins récemment utilisé. L'extrusion est longtemps inutilisée. Il s'agit de l'un des algorithmes de mise en cache (ce sont également des politiques préventives). En général, le cache peut être considéré comme un stockage rapide de paires clé-valeur, dont l'une des principales caractéristiques est le taux de réussite. Plus ce niveau est élevé, plus la valeur souhaitée se trouve souvent dans le cache rapide et moins elle doit être recherchée dans un stockage lent. Mais comme la mémoire n'est jamais caoutchouteuse, la taille du cache doit être limitée. La tâche des algorithmes de mise en cache est de déterminer l'élément à jeter hors du cache rempli, si nécessaire, afin de maximiser le niveau de hits.

LRUremplace leur élément de cache, auquel personne n'a accédé le plus longtemps. C'est peut-être l'algorithme de mise en cache le plus célèbre. Probablement en raison de la combinaison d'efficacité et de simplicité. La consommation de mémoire de LRU est O (n), le temps d'accès moyen à la valeur est O (1), le temps moyen pour ajouter un élément est également O (1). Pour la mise en œuvre, une table de hachage et une liste doublement liée sont généralement utilisées.

Par exemple,
 template <class K, class V> class LRU { private: using Queue = std::list<std::pair<K, V>>; using Iterator = typename Queue::iterator; using Hash = std::unordered_map<K, Iterator>; Queue queue_; Hash hash_; const size_t limit_; public: LRU(size_t limit) : limit_(limit) { } std::optional<V> get(const K& key) { const auto it = hash_.find(key); if (it == hash_.end()) { return {}; } it->second = reorder(it->second); return { it->second->second }; } void add(K&& key, V&& value) { if (hash_.size() >= limit_) { pop(); } queue_.emplace_front(std::move(key), std::move(value)); const auto it = queue_.begin(); hash_[it->first] = it; } private: Iterator reorder(Iterator it) { queue_.emplace_front(std::move(it->first), std::move(it->second)); queue_.erase(it); return queue_.begin(); } void pop() { hash_.erase(queue_.back().first); queue_.pop_back(); } }; 

L'inconvénient évident de LRU est la consommation élevée de mémoire, car il utilise deux structures de n éléments chacune. En plus de LRU, il existe de nombreux autres algorithmes de mise en cache pour une variété de cas: MRU (le plus récemment utilisé), LFU (le moins fréquemment utilisé), LRU segmenté, 2Q, etc. Relisez

: une fois (anglais) , deux (russe) , trois

PS


Si j'ai raté quelque chose ou que je me suis trompé quelque part - écrivez dans les commentaires.

Source: https://habr.com/ru/post/fr470317/


All Articles