|
Publicité ' | ||||||||||||||||||||||||
|
|
#121 | |
|
Inactif
Inscription : juillet 2005 Messages : 1 958 ![]() |
Citation:
|
|
|
|
00
|
|
|
#122 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Je parlais plus des algorithmes en eux-mêmes que des codes. Knuth possède une façon de raisonner quasi-exclusivement tournée vers l'impérativité, et programme selon des dogmes qui aujourd'hui sont faux : gestion de pile à la main parce que c'est soi-disant plus rapide, tableaux dans tous les sens, méthode de la sentinelle qui n'est quasiment jamais applicable, etc...
Pour ses codes, je suis d'accord, ils sont tout simplement affreux. Mais je ne suis pas sûr qu'ils aient marché du premier coup lorsqu'il les a écrits ! Cependant, si il y a une chose a retenir des publications de Knuth sur le sujet, c'est la borne supérieure pour la hauteur d'un AVL : c'est très intéressant pour connaître la taille maximale, à l'octet près, de la pile. C'est nécessaire lorsque l'on doit programmer des fonctions que l'on ne peut faire sans gestion de pile explicite, même en style fonctionnel, comme la comparaison de deux AVL.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#123 | ||||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
C'est pareil du côté de Niklaus Wirth (Algorithms and Data Structures), pas utilisable car le code est trop impératif pour du Caml.
Citation:
On peut éventuellement coder l'équilibrage sur 2 bits (et même placer cette information dans les bits de poids faible du pointeur puisque tout pointeur 32bits est un multiple de 4). J'ai opté pour une solution différente, à savoir utiliser set_left et set_right de façon à ne pas calculer la hauteur des arbres vides. À mon avis ça rend le code plus lisible, c'est important pour une rubrique "code source". Pour ce qui est de la performance :
Il faut arrêter l'exigence kafkaienne des benchs que je devrais faire contre du code qui n'a rien à voir ou contre le code que vous gardez pour vous. Citation:
Citation:
Citation:
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||||
|
00
|
|
|
#124 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
A tes commentaires je vois que, malgré ta vraie volonté de bien faire et surtout de progresser, tu manques cruellement d'expérience.
Je vais essayer de répondre en plusieurs points. 1- J'ai rien compris quand tu parles de set_left ou set_right. Bien-sûr qu'il ne faut pas calculer la hauteur (qui est une opération en Thêta(log n) dans ce cas, par opposition aux arbres binaires normaux) ! Mais la SEULE information dont tu as besoin est l'équilibrage : mettre la hauteur, c'est tout simplement ajouter de la complexité à des algorithmes qui n'en ont pas besoin (car à chaque rééquilibrage il faudra faire attention à mettre à jour la donnée). Mettre à jour un équilibrage, c'est 1000 fois plus simple. De plus, n'oublie pas que les gens regardent le code avant de l'utiliser (c'est mon cas) : si tu tombes sur des gens qui s'y connaissent (et il y en a un paquet) et qui tombent sur des AVL stockant la hauteur à chaque noeud, je peux te garantir qu'ils ne chercheront pas à aller plus loin. Pourquoi ? Tout simplement parce que stocker une hauteur quand tu as besoin de stocker 3 valeurs au plus, c'est stocker des données inutiles, et ça ne jette pas beaucoup de crédit sur le développeur... 2- Concernant la performance Ne fait surtout pas l'impasse sur les tests de performance. Si une librairie AVL (je précise bien AVL) n'est pas performante, ou si il existe un gros gap par rapport, par exemple, à une fonctionnalité équivalente de la librairie standard, tu peux être sûr que personne ne l'utilisera. Personne n'utilisera une librairie AVL tout simplement parce que c'est des AVL et que ça fait joli. On utilise des AVL parce qu'il s'agit de la structure de données entièrement dynamique (donc hors tables de hachage) la plus performante dans le cas général. C'est pas pour rien si on va l'utiliser jusques dans les OS comme Solaris ! Pour procéder, je te conseille de commencer par tester ton implantation (impérative) contre l'implantation de la librairie standard (entièrement fonctionnelle, donc en principe beaucoup plus lente). Si tu ne gagnes rien en termes de performances (utilise des données TRES grandes, jusqu'à bourrer toute ta mémoire, mais sans arriver au swap), alors ton code est intrinsèquement lent et mauvais. Sinon, si ton code est plus rapide, je te conseille d'en implanter une version entièrement fonctionnelle. Si elle est plus rapide, c'est bon, sinon, il faudra que tu travailles jusqu'à être plus rapide que la librairie standard (il le faut, sinon ça sert à rien !). Tu pourras alors répercuter les modifications sur la version impérative : tu devrais alors beaucoup gagner en vitesse. Les tests doivent être de toutes natures : aléatoires, éléments répétés (pour voir si ils sont dupliqués ou non), en ordre croissant ou pas, ensembles vides... enfin tout, quoi ! Et pour insérer des éléments croissants (ou décroissants), je connais une structure imbattable : la liste ! Pour ce qui est des autres arbres cités, je pense que si on les coupait on ferait un joli feu de bois ! Garde à l'esprit que personne n'utilisera une librairie AVL si elle n'est pas plus rapide que ce qui existe déjà. 3- Il te manque ENCORE la suppression. Crois-moi, c'est beaucoup plus délicat que l'insertion. Une fonction de validation te sera d'un grand secours. D'autant plus que sans cette fonction, on ne peut rien faire. L'autre fonction difficile (très courte quand elle est bien écrite, mais délicate) est la comparaison de deux ensembles. L'union, l'intersection etc... devraient prendre moins de temps qu'ouvrir une canette de Coca.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#125 | |||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
Au point où j'en suis je vais me contenter d'une version correcte.
On verra plus tard pour les perfs. Est-ce que ce code te paraît correct (moyennant les fonctions d'équilibrage appropriées) Code :
Citation:
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
|||
|
00
|
|
|
#126 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Je suis réellement désolé SpiceGuid, mais ce que tu as fait n'a réellement rien à voir avec des AVL, même si je n'en conteste absolument pas la cohérence et le fait que le code soit juste. Je me demande même si on parle bien de la même chose.
Les AVL sont caractérisés par le fait que sous-arbre droit et gauche sont de hauteur différant d'au plus un, de sorte que l'on conserve la donnée d'équilibrage au niveau des noeuds. Pas la hauteur, car ça sert à rien. L'insertion et suppression demandent de faire des rotations en cascade suivant les positions et équilibres des noeuds courants N et de ceux aux niveaux N-1 et N-2. Je ne reconnais pas vraiment ces opérations dans ton code. Pour ce qui est de l'algorithme de suppression, je confirme qu'il est beaucoup plus long et difficile que ce que tu as fait. Au passage, si je retourne sur la Linux de mon vieil ordinateur, je pourrais te faire suivre ce que j'avais fait à l'époque, tant en C qu'en OCaml. Enfin, concernant l'union et ses copains, une simple itération sur les éléments du premier AVL composée avec l'insertion dans le deuxième arbre suffit à avoir des résultats corrects. Il existe peut-être, pour les AVL, des algorithmes plus fins, mais j'aimerais bien savoir si en termes de complexité temporelle, donc asymptotique, on y gagne vraiment, du fait des nombreuses rotations que l'on doit opérer pour maintenir la structure équilibrée. Naïvement, on est en O(n log m), ce qui me laisse penser qu'il s'agit d'une complexité déjà optimale étant donné que la structure finale DOIT ETRE un arbre ed tri... donc qu'il s'agit, indirectement, d'un algorithme de pseudo-tri.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#127 | |
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
Citation:
C'est juste que c'est mon style qui perturbe tes repères. Les algos sont exactement les mêmes que pour un arbre non-équilibré, il suffit simplement d'ajouter un appel à une fonction d'équilibrage. La suppression ne présente aucune difficulté particulière, c'est l'algo habituel pour un arbre binaire. C'est pareil dans OCaml-Reins, dans les tutoriels en Java et partout ailleurs où l'on cherche la lisibilité maximale. http://prevert.upmf-grenoble.fr/Prog...arbresAVL.html Les opérations ensemblistes rapides sont codées à l'aide de l'opération de fission (voir mon code merge/divide du post #79). Et elles sont vraiment plus rapides que la version naive. L'union rapide est particulièrement utile puisqu'elle permet d'accélérer l'insertion par lot. Quand tu dois fusionner deux ensembles triés cette opération n'est pas un tri (c'est le principe à la base du tri-fusion).
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
|
|
00
|
|
|
#128 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Tu as raison pour l'union : ce n'est pas un algorithme de tri.
Par contre, je ne comprends pas ton raisonnement en ce qui concerne les AVL. Où effectues-tu les opérations d'équilibrage et de rotation des sous-arbres ? Je continue à dire, parce que je l'ai vu de nombreuses fois, parce que je l'ai pratiqué et parce que j'ai vu beaucoup de biblitohèques sans cette fonction, que la suppression est de loin plus délicate que l'insertion. Même Knuth, dont le style de programmation est contestable mais dont le travail sur la question en termes de résultats et d'algorithmes est remarquable, la qualifie de complexe. Tu ne peux pas la coder en deux coups de cuillère à pot.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#129 | ||||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
L'astuce c'est d'utiliser un constructeur "équilibré".
Code :
Les "1" désignent le différentiel maximal de hauteur entre les sous-arbres l et r. Après c'est exactement le même code que pour un arbre binaire quelconque. Sauf que tu remplace tous les constructeurs par rotated, ça suffit pour équilibrer ton arbre, du bas vers le haut. Code :
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||||
|
00
|
|
|
#130 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
C'est un peu plus clair comme ça, même si je ne suis pas allé vérifier chaque ligne de code.
Cependant, quel avantage à mettre 2 à la place de 1 ?
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#131 | ||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
![]() http://caml.inria.fr/pub/ml-archives...e0a88c.en.html Une autre astuce (utilisée par OCaml-Reins) c'est d'ajouter un variant Leaf pour les feuilles, du coup il y a moins de Empty à tester, ça soulage le GC. Code :
De cette façon Ord.compare n'est accédée qu'une seule fois (l'appel dans un 'foncteur' est légèrement plus coûteux que l'appel direct). Si tu n'as pas utilisé ces deux dernières astuces dans ton code alors il te reste encore un peu de marge de progression en performances.
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||
|
00
|
|
|
#132 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Moi je retiens de l'article cité plus haut...
Light experimentation suggested that imbalance <= 2 is globally more efficient than imbalance <= 1 J'essaye de retrouver mon code, alors, car je veux en être convaincu.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#133 | |||
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Citation:
Je donne ici l'implantation que j'avais faite à l'époque, il y a 3 ou 4 ans environ. Je l'ai conservée intacte. Entre autres, il y a tout plein de commentaires plus passionnants les uns que les autres, une licence de la mort-qui-tue, et un style mêlant encore Anglais et Français. Il y a même une fote de conjuguèson. Le code marche toujours. Je viens de le tester sur OCaml 3.09.02 en bytecode et ça marche. Le test consistait en l'insertion des entiers 1 à 5 000 000 dans un Set de int, en ordre croissant. Set standard OCaml : 1 minute 42 secondes Set AVL fonctionnel : 1 minute 10 secondes Set AVL impératif : 1 minute 1 seconde J'attire l'attention sur le fait que les résultats de la version impérative sont peut-être, comparativement aux versions fonctionnelles, en deçà de ce que l'on serait en mesure d'attendre. Le fait vient de l'état de la mémoire au moment des tests des versions fonctionnelles de la librairie standard et de la librairie AVL. La mémoire était, en effet, complètement vide, et le GC majeur n'a certainement pas dû se déclencher très souvent. @SpiceGuid : cette implantation peut déjà te servir de base pour des tests. Voilà. Toutes les remarques sont les bienvenues.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|||
|
|
00
|
|
|
#134 | ||||||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
Beaucoup de warnings dans ton code.
J'ai testé mon code (uniquement l'insertion): les arbres générés sont corrects et équilibrés. Mais les benchs sont décevants. Pourtant avant de les faire j'avais ajouté une optimisation inspirée par ton code: l'équilibrage est court-circuité quand le sous-arbre modifié ne change pas de hauteur. Avec ça j'espérais dépasser la stdlib mais apparemment la performance dépend de bien d'autres facteurs que l'algorithmique pure. La 1ière colonne est le temps en secondes pour l'insertion de 5 millions d'entiers décroissants. La 2nd colonne est le temps en secondes pour l'insertion de 5 millions d'entiers Random.bits. Chaque test est précédé d'un Gc.full_major(). En bytecode (linux x86, ocaml 3.10) : Code :
Code :
Conclusion:
D'une manière générale je trouve que la performance est difficilement prédictible. Par exemple, en randomisé, ma version fonctionnelle met 111.4 contre 16.5s en impératif, alors que les structures de données et l'algo sont exactement les mêmes. D'ailleurs en entiers décroissants le temps est le même dans les deux cas. Difficile à expliquer. Même chose avec impAVLMap.Make, plus lent que Map.Make sur les entiers décroissants mais plus rapide avec les randomisés, pourquoi ? Mystère. Ce bench ne me conforte pas dans l'idée de faire des implantations mixtes qui partageraient un maximum de code et d'optimisations. C'était pourtant une idee élégante. Dommage. Le code du test : Code :
Autres benchs :
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||||||
|
00
|
|
|
#135 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
J'ai développé le code, à l'époque, avec OCaml 3.07. La compilation effectuée avec ma 3.09 dimanche montrait que l'ajout du nouveau warning E dans la distribution faisait apparaître de nombreux warning ne servant à rien. L'option -A est utilisée, donc il est possible que bon nombre de warning inutiles soient lancés à la compilation, surtout si ils ont été ajoutés aux dernières versions. En tous cas, sur la 3.09.02, ça compile sans warning.
Pour ce qui est des tests, ma remarque allait dans le sens du commentaire de SpiceGuid. A mon avis, il serait intéressant d'étudier ce qui se passe dans le cas croissant ou décroissant. Par contre, pour le random, les résultats sont parfaitement prédictibles. Ce qui est intéressant également est la différence de performance délirante en natif de la version décroissant, entre fonctionnel pur et impératif. Peut-être est-ce un signe que le compilateur optimise mieux de tous petits codes fonctionnels que de gros tas d'instructions effectuées les unes après les autres... mais c'est juste une piste de réflexion. En ce qui concerne l'implantation, j'espère t'avoir convaincu qu'implanter une structure AVL réellement performante est une tâche pas si facile que ça. P.S. : j'ai la même en C, au cas où ça intéresserait quelqu'un.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#136 | ||||||||||||||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
Après avoir tatonné pendant plusieurs heures (bench puis recodage, en boucle) j'ai finalement réussi à dépasser Map.Make. Ça m'a pris pas mal de temps parce que j'ai suivi toutes les fausses pistes imaginables. Par exemple j'ai essayé de suivre le conseil de John Harrop (ajouter un variant Leaf pour les feuilles) mais ça n'a pas eu d'effet bénéfique en code natif, bien au contraire. J'ai été bien inspiré d'avoir deux benchs (je dirais même qu'il en faudrait encore plus) parce qu'un code qui améliore un bench le fait assez souvent au détriment de l'autre.
Code :
Code :
Code :
Maintenant que j'ai un peu plus confiance dans mes perfs brutes je vais pouvoir retourner à des considérations un peu plus algorithmiques. Par exemple ni Reins ni la Extlib n'ont le filtrage rapide des structures arborescentes. L'idée de base ça serait le code ci-dessous (où j'ai omis delete_min vu que je l'ai déjà posté) et de l'adapter pour les arbres équilibrés. Code :
Voici comment procéder pour équilibrer. Code :
Ce qu'on fait c'est qu'on remplace par un appel à un constructeur join qui créera le même noeud mais cette fois de manière équilibrante : Code :
Ce genre de constructeurs équilibrés a été introduit par Stephen Adams dans Implementing Sets Efficiently in a Functional Language. Leur code ci-dessous est inspiré de la source de Set.Make et adapté pour un container associatif implanté à l'aide d'enregistrements. Code :
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||||||||||||||
|
00
|
|
|
#137 |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Si j'en crois une remarque que tu avais faite précédemment, utiliser la fonction de comparaison par l'intermédiaire d'un foncteur est plus lent, même si je n'ai pas fait de test. Qu'en est-il de ton code avec cette façon de faire ? Je serais intéressé de voir la différence, car je n'ai aucune idée d'ordre de gandeur en tête.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
00
|
|
|
#138 | ||||||
![]() ![]() Damien GuichardInscription : juin 2007 Messages : 1 512 ![]() |
Pour être clair, ce que j'appelle "défonctorisé" c'est à la façon OCaml-Reins (une implantation défonctorisée), pas à la façon ExtLib (une interface défonctorisée).
Plus concrêtement, au lieu de faire ceci : Code :
Code :
Code :
Il n'y a qu'un seul moyen de faire un bon AVL, c'est de tout de suite tester l'équilibrage et de ne faire du pattern matching que s'il y a une rotation. Mon code initial, comme celui d'OCaml-Reins, mélangeait le pattern matching et la comparaison de hauteur. C'est dévastateur pour la performance. Il m'a fallu trop de tentatives pour comprendre que la déficience venait du réflexe conditionné qui consiste à filtrer avant toute autre chose. L'AVL d'OCaml-Reins aggrave son cas :
John Harrop dit avoir un AVL 30% plus rapide, je n'y crois pas fort :
Il est clair pour moi que stocker la hauteur est une source (mineure) d'inefficacité, je ne le fais que parce que :
__________________
Du même auteur: le cours OCaml, le dernier article publié, le projet, le blog dvp et le jeu vidéo. Avant de poser une question je lis les règles du forum. |
||||||
|
00
|
|
|
#139 | |
|
Membre Expert
![]() ![]() Inscription : septembre 2006 Messages : 1 036 ![]() |
Citation:
Je pense sincèrement qu'en termes d'AVL, on a tout inventé il y a très longtemps déjà. Tu ne fais là que confirmer ce que l'on sait déjà depuis longtemps : rien, mis à part la table de hachage, ne peut battre un vrai AVL.
__________________
When Colt produced the first practical repeating handgun, it gave rise to the saying God created men, but Colt made them equal. |
|
|
|
00
|
|
|
#140 | ||
|
Invité régulier
![]() Inscription : novembre 2004 Messages : 8 ![]() |
voici une utilisation du moteur d'inférence en DrScheme
Code :
|
||
|
|
00
|
Copyright © 2000-2013 - www.developpez.com