web-dev-qa-db-fra.com

Ignorer la liste par rapport à l'arbre de recherche binaire

Je suis récemment tombé sur une structure de données appelée liste à ignorer . Son comportement semble très similaire à celui d'un arbre de recherche binaire.

Pourquoi voudriez-vous jamais utiliser une liste de saut sur un arbre de recherche binaire?

207
Claudiu

Les listes de sauts sont plus susceptibles d’être modifiées/modifiées Herb Sutter a écrit un article sur la structure de données dans des environnements concurrents. Il a plus d'informations en profondeur.

L’implémentation la plus fréquemment utilisée d’un arbre de recherche binaire est un arbre rouge-noir . Les problèmes concomitants surviennent lorsque l’arbre est modifié, il faut souvent le rééquilibrer. L'opération de rééquilibrage peut affecter de grandes parties de l'arborescence, ce qui nécessiterait un verrou mutex sur de nombreux nœuds d'arborescence. L'insertion d'un nœud dans une liste de sauts est beaucoup plus localisée, seuls les nœuds directement liés au nœud affecté doivent être verrouillés.


Mise à jour des commentaires de Jon Harrops

J'ai lu le dernier article de Fraser et Harris Programmation simultanée sans verrous . Vraiment bien si vous êtes intéressé par les structures de données sans verrouillage. Le papier se concentre sur Transactional Memory et une opération théorique MCAS multi-mots-comparer-et-swap. Les deux sont simulés par logiciel car aucun matériel ne les prend en charge pour le moment. Je suis assez impressionné par le fait qu'ils ont été capables de construire MCAS dans un logiciel.

Je n'ai pas trouvé le contenu de la mémoire transactionnelle particulièrement convaincant, car il nécessite un ramasse-miettes. En outre, mémoire transactionnelle logicielle est en proie à des problèmes de performances. Cependant, je serais très heureux si la mémoire transactionnelle matérielle devient un jour commun. En fin de compte, il reste encore de la recherche et ne sera pas utile pour le code de production avant une dizaine d'années.

Dans la section 8.2, ils comparent les performances de plusieurs implémentations d'arborescence simultanée. Je vais résumer leurs conclusions. Il vaut la peine de télécharger le pdf car il contient des graphiques très instructifs aux pages 50, 53 et 54.

  • Le verrouillage des listes de sauts est incroyablement rapide. Ils s'adaptent incroyablement bien avec le nombre d'accès simultanés. C'est ce qui rend les listes de sauts spéciales, d'autres structures de données basées sur des verrous ont tendance à trembler sous la pression.
  • Les listes de saut sans verrouillage sont toujours plus rapides que le verrouillage des listes de saut, mais à peine.
  • Les listes de sauts transactionnels sont toujours 2 à 3 fois plus lentes que les versions verrouillées et non verrouillables.
  • verrouiller les arbres rouge-noir croassent sous un accès simultané. Leurs performances se dégradent de manière linéaire avec chaque nouvel utilisateur simultané. Parmi les deux implémentations d'arborescence de verrouillage rouges-noires connues, l'une a essentiellement un verrou global lors du rééquilibrage de l'arborescence. L'autre utilise l'escalade de verrous sophistiqué (et compliqué) mais n'effectue toujours pas de manière significative l'exécution de la version de verrouillage global.
  • Les arbres rouge-noir sans verrouillage n'existent pas (n'est plus vrai, voir Mise à jour).
  • Les arbres rouge-noirs transactionnels sont comparables aux listes sautées transactionnelles. C'était très surprenant et très prometteur. Mémoire transactionnelle, bien que plus lente si beaucoup plus facile à écrire. Cela peut être aussi simple que la recherche rapide et le remplacement de la version non simultanée.

Mise à jour
Voici du papier sur les arbres sans cadenas: Arbres sans verrou rouge-noir avec CAS .
Je n'ai pas examiné la question en profondeur, mais à première vue, elle semble solide.

239
deft_code

Premièrement, vous ne pouvez pas comparer équitablement une structure de données randomisée avec une structure qui vous donne les garanties les plus défavorables.

Une liste de sauts équivaut à un arbre de recherche binaire (RBST) équilibré de manière aléatoire, comme expliqué plus en détail dans Dean et Jones "Exploration de la dualité entre les listes de sauts et les arbres de recherche binaire" .

À l’inverse, vous pouvez également avoir des listes de saut déterministes qui garantissent les performances dans le pire des cas, cf. Munro et al.

Contrairement à ce que certains prétendent ci-dessus, vous pouvez avoir des implémentations d'arbres de recherche binaires (BST) qui fonctionnent bien en programmation simultanée. Un problème potentiel avec les BST axés sur la concurrence est que vous ne pouvez pas obtenir facilement les mêmes garanties d'équilibrage que si vous utilisiez un arbre rouge-noir (RB). (Mais les listes de sauts "standard", c'est-à-dire aléatoires, ne vous donnent pas non plus ces garanties.) Il existe un compromis entre le maintien de l'équilibre à tout moment et un bon accès simultané (et facile à programmer), de sorte relaxé Les arbres RB sont généralement utilisés lorsque l'on souhaite une bonne concurrence. La détente consiste à ne pas rééquilibrer l'arbre tout de suite. Pour une enquête quelque peu datée (1998), consultez "La performance des algorithmes simultanés arbre rouge-noir" de Hanke [ps.gz] .

L’une des améliorations les plus récentes est l’arbre chromatique dit (vous avez un poids tel que le noir soit égal à 1 et le rouge à zéro). , mais vous autorisez également des valeurs entre). Et comment se comporte un arbre chromatique contre une liste à sauter? Voyons ce que Brown et al. "Une technique générale pour les arbres non bloquants" (2014) doivent dire:

avec 128 threads, notre algorithme surpasse de 13% à 156% le skipliste non bloquant de Java, l’arbre AVL basé sur le verrouillage de Bronson et al. de 63% à 224% et une RBT utilisant la mémoire logicielle transactionnelle (STM) de 13 à 134 fois

EDIT à ajouter: la liste de sauts basée sur les verrous de Pugh, référencée dans Fraser et Harris (2007) "Programmation simultanée sans verrouillage" comme se rapprochant de leur propre version sans verrouillage ( un point sur lequel insiste amplement la réponse du haut), est également modifié pour un bon fonctionnement simultané, cf. Pugh's "Maintenance simultanée des listes de refus" , bien que de manière plutôt légère. Néanmoins, un article plus récent/2009 "Un algorithme de liste de saut optimiste simple" de Herlihy et al., Qui propose une implémentation supposée plus simple (que celle de Pugh) de listes de saut simultanées , a critiqué Pugh pour ne pas avoir fourni une preuve d’exactitude suffisamment convaincante pour eux. Laissant de côté ce problème (peut-être trop pédant), Herlihy et al. montrent que leur implémentation simplifiée d’une liste de sautement basée sur le verrouillage échoue, de même que son implémentation sans verrouillage du JDK, mais uniquement en cas de forte contention (50% insertions, 50% suppressions et 0% recherches) ... quel Fraser et Harris n'a pas testé du tout; Fraser et Harris n'ont testé que 75% des recherches, 12,5% des insertions et 12,5% des suppressions (liste de sauts contenant environ 500 000 éléments). La mise en œuvre plus simple de Herlihy et al. se rapproche également de la solution sans verrouillage du kit JDK en cas de faible contention testée (70% recherches, 20% insertions, 10% suppressions); En fait, ils ont battu la solution sans verrou pour ce scénario quand ils ont rendu leur liste de saut assez grande, c’est-à-dire en passant de 200 000 éléments à 2 millions, de sorte que la probabilité de contestation de tout verrou devenait négligeable. Cela aurait été bien si Herlihy et al. avait surmonté leur problème de la preuve de Pugh et testé sa mise en œuvre aussi, mais hélas, ils ne l'ont pas fait.

EDIT2: J'ai trouvé un modèle (publié en 2015) de tous les tests de performance: Gramoli's "Plus que vous ne vouliez connaître la synchronisation. Synchrobench, mesure de l'impact de la synchronisation sur des algorithmes concurrents" : Voici une image extraite de cette question.

enter image description here

"Algo.4" est un précurseur (version 2011 plus ancienne) de Brown et al. Mentionné ci-dessus. (Je ne sais pas à quel point la version 2014 est meilleure ou pire). "Algo.26" est Herlihy mentionné ci-dessus; comme vous pouvez le constater, les mises à jour sont mises à la corbeille, ce qui est bien pire pour les processeurs Intel utilisés ici que pour les processeurs Sun à partir du papier d'origine. "Algo.28" est ConcurrentSkipListMap du JDK; il ne fait pas aussi bien qu'on l'aurait espéré comparé à d'autres implémentations de listes de sauts basées sur CAS. Les gagnants sous haute controverse sont "Algo.2", un algorithme basé sur le verrouillage (!!) décrit par Crain et al. in "Un arbre de recherche binaire favorable aux conflits" et "Algo.30" est le "skiplist en rotation" de "Structures de données logarithmiques pour les multicœurs" . "Algo.29" est le "Aucune liste de saut non bloquante" . Sachez que Gramoli est co-auteur de ces trois articles sur les algorithmes gagnants. "Algo.27" est l'implémentation C++ de la liste de sauts de Fraser.

La conclusion de Gramoli est qu'il est beaucoup plus facile de bousiller une implémentation d'arborescence simultanée basée sur CAS que de bousiller une liste de sauts similaire. Et sur la base des chiffres, il est difficile de ne pas être d'accord. Son explication pour ce fait est:

La difficulté de concevoir un arbre sans verrouillage tient à la difficulté de modifier plusieurs références de manière atomique. Les listes de sauts sont composées de tours reliées les unes aux autres par des pointeurs successeurs et dans lesquelles chaque nœud pointe vers le nœud immédiatement inférieur. Ils sont souvent considérés comme similaires aux arbres car chaque nœud a un successeur dans la tour suivante et, en dessous, une différence majeure est que le pointeur vers le bas est généralement immuable, ce qui simplifie la modification atomique d'un nœud. Cette distinction est probablement la raison pour laquelle les listes de sauts surpassent les arbres en conflit important, comme le montre la figure [ci-dessus].

Surmonter cette difficulté était une préoccupation majeure dans les travaux récents de Brown et al. Ils ont un article séparé (2013) "# Primitives pragmatiques pour des structures de données non bloquantes" sur la construction de "primitives" composées multi-enregistrements LL/SC, qu'ils appellent LLX/SCX. , eux-mêmes mis en œuvre à l'aide de CAS (au niveau de la machine). Brown et al. a utilisé ce bloc constitutif LLX/SCX dans son implémentation d'arborescence simultanée de 2014 (mais pas de 2011).

Je pense que cela vaut peut-être aussi la peine de résumer ici les idées fondamentales de la liste de saut "no hot spot"/contention-friendly (CF) . Il ajoute une idée essentielle des arbres RB décontractés (et de structures de données similaires concrètement similaires): les tours ne sont plus construites immédiatement après leur insertion, mais sont retardées jusqu'à ce qu'il y ait moins de conflits. Inversement, la suppression d'une grande tour peut créer de nombreux conflits. cela a été observé depuis la publication simultanée de la liste prioritaire de Pugh en 1990, raison pour laquelle Pugh a inversé le pointeur lors de la suppression (une friandise que la page de Wikipédia sur les listes ignorées n'a toujours pas mentionnée à ce jour, hélas). La liste de sauts des FC va plus loin et retarde la suppression des niveaux supérieurs d'une tour haute. Les deux types d’opérations différées dans les listes de sauts CF sont exécutés par un fil distinct, semblable à un ramasse-miettes (basé sur CAS), que ses auteurs appellent le "fil adaptatif".

Le code Synchrobench (y compris tous les algorithmes testés) est disponible à l'adresse suivante: https: //github.com/gramoli/synchrobench . Le dernier Brown et al. La mise en oeuvre (non incluse dans ce qui précède) est disponible à l'adresse http: //www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.Java Quelqu'un at-il un noyau 32+? machine disponible? J/K Mon idée est que vous pouvez les gérer vous-mêmes.

75
Fizz

En outre, en plus des réponses données (facilité de mise en œuvre associée à des performances comparables à celles d’un arbre équilibré). Je trouve que l'implémentation du parcours dans l'ordre (en avant et en arrière) est beaucoup plus simple, car une liste de sauts contient effectivement une liste chaînée dans son implémentation.

12
Evan Teran

Dans la pratique, j'ai constaté que les performances B-tree de mes projets s'avéraient meilleures que les listes sautées. Les listes de sauts semblent plus faciles à comprendre, mais mettre en œuvre un arbre B n'est pas aussi difficile que .

Le seul avantage que je connaisse est que certaines personnes intelligentes ont découvert comment mettre en place une liste de sauts simultanés sans verrouillage qui utilise uniquement des opérations atomiques. Par exemple, Java 6 contient la classe ConcurrentSkipListMap et vous pouvez y lire le code source si vous êtes fou.

Mais il n’est pas trop difficile d’écrire une variante concurrente de l’arbre-B - je l’ai déjà vu faire par quelqu'un d’autre - si vous scindez et fusionnez de manière préventive des nœuds "au cas où" en descendant l’arbre, vous n’aurez plus à le faire. vous inquiétez des impasses et n’avez besoin que de garder un verrou à deux niveaux de l’arbre à la fois. La surcharge de la synchronisation sera un peu plus élevée mais le B-tree est probablement plus rapide.

9
Jonathan

De l'article Wikipedia vous avez cité:

(N) les opérations qui nous obligent à visiter chaque noeud dans un ordre croissant (comme l'impression de la liste complète) permettent d'effectuer une dérandomisation en coulisse de la structure de niveau de la liste de saut de manière optimale, ramenant la liste de sauts au temps de recherche O (log n). [...] Une liste de sauts sur laquelle nous n'avons pas effectué récemment [de telles opérations] Θ (n), ne fournit pas les mêmes garanties absolues de performance dans le pire des cas que les opérations équilibrées plus traditionnelles structures de données arborescentes , car il est toujours possible (avec une probabilité très faible) que les retournements utilisés pour construire la liste de saut produisent une structure mal équilibrée

EDIT: c’est donc un compromis: les listes d’évitement utilisent moins de mémoire au risque de dégénérer en un arbre déséquilibré.

8
Mitch Wheat

Les listes de sauts sont implémentées à l'aide de listes.

Des solutions sans verrouillage existent pour les listes à une ou deux listes liées - mais il n’existe pas de solutions sans verrouillage utilisant directement uniquement CAS pour toute structure de données O(logn).

Vous pouvez toutefois utiliser des listes basées sur CAS pour créer des listes de sauts.

(Notez que MCAS, créé à l'aide de CAS, autorise des structures de données arbitraires et qu'un arbre rouge-noir de validation technique a été créé à l'aide de MCAS).

Alors, aussi curieux soient-ils, ils s'avèrent très utiles :-)

2
Blank Xavier