Cette annexe contient le "fichier de log", dans lequel l'activité et les modifications du programme sont notées, tout comme les idées et les problèmes rencontrés, afin d'en garder une trace écrite pour ne pas refaire les mêmes erreurs ensuite et conserver les morceaux de codes valides hors du fichier source.
L'activité a été notée entre le 12 avril 1999 au 28 janvier 2000, le journal n'est donc pas exhaustif mais il a permis de garder la tête froide lors des phases les plus difficiles de la programmation. On y retrouve l'histoire et les détails absents du corps du mémoire pour ce qui concerne l'affichage, le codage, les listes de modifications, l'équation de détection...
vbe43.asm: montre les bizarreries de la prédiction statistique de branchements. premier minimum à strip=6 puis ensuite tous >=15. les autres sont étonnament désordonnés. Ces aléas de prédiction seront "cachés" par d'autres mécanismes plus importants comme le calcul et l'accès à la mémoire. t01.asm: montre que la vidéo est un goulet d'étranglement. le meilleur strip count est 1, mais les strip > 15 convergent vers cette valeur. t02.asm: montre l'intérêt de l'affichage à la fin de la fenêtre: décroissance monotone (1/x) du temps par cellule. Un affichage utile et rapide est donc possible avec le strip mining. rb07.asm: montre l'intérêt d'une fenêtre glissante. lorsque sa taille est > à la L2, la courbe remonte. ma théorie est donc confirmée. au niveau du speedup, on gagne d'un facteur 10 pour une taille de 950*730 environ (STRIP=19). peu d'opérations sont effectuées cependant, et l'affichage est lourd (la version finale ne sera qu'une copie de bloc vers la vidéo). rb08.asm: idem mais sans affichage, même conclusion, mais speedup moins important par rapport à STRIP=1 (1:5) sp02.asm: on peut observer le comportement pour des tailles TRES importantes, comme prévu le STRIP descend vite. time for a huge CRAY. la solution: un processeur par "bande". 6 juin 99: sp06.asm: l'image im000003.bmp est la première image valide que j'ai réussi à générer. l'essai porte sur une taille raisonnable, plus grosse que la L2. un speedup de 6,7 (2327006/346362) est obtenu avec une boucle interne assez raisonnable (translation horizontale) et affichage à taille normale. Il faudra étudier l'influence de: - la hiérarhie mémoire (PMMX contre PII: résultats étonnants) - la variation de la courbe de speedup avec la taille, hauteur, largeur. - l'affichage (influence du zoom) - l'alignement (enlever les "align") - etc im000001.bmp: expérience sur PMMX, 3072*2880 (8,8MB). la courbe montre le creux de la L1 à strip=5, puis une décroissance plus timide pour la L2. Par rapport au PII, le goulot d'étranglement de la mémoire est important car la L2 est externe. ccl: un strip petit est suffisant pour un PMMX, ou du moins quand la L2 est externe. l'expérience sur PII montre que la cache interne, par sa vitesse (bande passante), et son relais par la L1, favorise des strips plus nombreux, jusqu'à atteindre la limite de la L2. Dans tous les cas, le balayage linéaire (double boucle) est déconseillé. STRIP=1 montre bien un pic important. De plus, l'affichage simultané augmente la pénalité. Il faudra voir ce qui se passera quand le calcul sera plus lourd. Remarque: pour un gros tableau de 8,8MO, on arrive à 8847360/8184118=1.08104... cycles par cellule. à comparer avec d'autres programmes. le calcul n'est pas effectué, mais l'affichage et une partie de la translation est opérationnelle. on devrait "tuner" le code (alignement, pipeline scheduling, etc) pour grapiller une ou deux décimales mais on n'a pas le temps. ce sera pour une autre "rebuild". MESURES ET CONDITIONS: 1 cycle=5ns (200MHz) sur PMMX avec 256Ko de L2 et SDRAM fichier: taille: strip: temps/tableau temps/strip=1 t0001.bmp: 256*256 (1/16M) 32 33822 129203 384*384 27 70797 273786 t0003.bmp: 512*512 (1/4M) 21 123735 464412 768*768 15 301074 1365041 t0005.bmp: 1024*1024 (1M) 14 636469 3620042 1536*1536 9 1596089 7996253 t0007.bmp: 2048*2048 (4M) 7 3309295 14186996 3072*3072 5 8644870 31753869 4096*4096 3 20248441 55846489 Les résultats sont faibles à +/- 1% (ordre de grandeur des variations) Les mesures ont été faites par la boucle de calibration à zoom=4:1 (-> variation de la quantité de données à afficher entre les != tailles) CALCULS: taille: speedup larg*strip cycle/cel vitesse v. sans SM 256*256 3,82008 8192 0,51608 387Mc/s 101Mc/s 384*384 3,86719 10368 0,48012 416Mc/s 107Mc/s 512*512 3,67246 10752 0,47201 423Mc/s 112Mc/s 768*768 4,55391 11520 0,51044 391Mc/s 86Mc/s 1024*1024 5,68769 14336 0,60698 329Mc/s 57Mc/s 1536*1536 5,00990 13824 0,67651 295Mc/s 59Mc/s 2048*2048 4,28701 14336 0,78899 253Mc/s 59Mc/s 3072*3072 3,67315 15360 0,91604 218Mc/s 59Mc/s 4096*4096 2,75806 12288 1,20690 165Mc/s 60Mc/s Speedup: -------- Le speedup n'est pas lié à d'autes facteurs, et n'est pas un critère de performance absolu, mais relatif, permettant de comparer les algorithmes. Il montre que l'algorithme de trip mining est au moins deux fois supérieur au balayage linéaire. Il est probablement influencé par le ratio de fréquence entre le processeur et le bus: 200MHz par rapport à un bus à 64 bits @ 66MHz (rapport triple de fréquence). Le taux de transfert théorique de 66*8=528 MO/s est le double de la "vitesse" moyenne, car avant de charger une ligne dans la cache, il faut libérer de la place en écrivant une ligne vers la mémoire centrale. Mais comme une partie des opérations peuvent s'effectuer dans la cache interne, la vitesse "apparente" (avec le strip mining) est supérieure à 528/2. Le transfert maximum correspond à la taille 512*512=256KO, c'est à dire: la taille de la L2. La "ventilation" avec la mémoire centrale est donc minimale. Un algorithme de strip mining de 2ème "ordre" est donc envisageable mais des nombreuses contraintes rendent le gain trop "cher", sauf dans le cas d'accès à une mémoire de masse lente (disque dur par exemple). Le speedup encore accessible à ce moment reste du côté du calcul. La 'vitesse' sans strip mining est divisée par deux lorsque la taille du tableau est supérieure à la L2: de 112 à 57. l'algorithme de LRU n'est pas efficace pour gérer des tableaux plus larges que sa taille. un algorithme de remplacement aléatoire rendrait par contre le strip mining plus "statistique", moins déterministe. un mécanisme de "gel" de lignes de caches serait dans notre cas très efficace !. Q: la diminution de la vitesse par deux est-il dû à deux transferts au lieu d'un, ou à la latence de la mémoire ? Q: comment faire pour augmenter la vitesse de calcul des très gros tableaux sans complications ? cache warming ? faut-il mettre un WBINVD à la fin d'un calcul de tableau ? larg*strip: largeur * strip count --------------------------------- la valeur converge vers une valeur de 87% de la taille de la cache L1. cela veut dire que l'algorithme de strip mining fonctionne comme prévu. Il serait intéressant de connaître quelles données sont présentes en cache pour "pousser" le 87% vers le 100%. C'est très probablement les données correspondant aux ligne supérieures et inférieures dans lesquelles le programme écrit lors de la phase de déplacement. Il faudrait donc considérer la formule 'larg*(strip+2) à la place. Avec la "vitesses de traitement", cette grandeur larg*(strip+2) est probablement une bonne manière de juger (en temps réel dans le programme) si le strip count est adapté, et sinon de savoir dans quel régime le programme tourne (<L1:sous-utilisation, +/-=L1:OK, >>L1:L2). cycle/cel: nombre de cycles de processeur pour une cellule (un octet) --------- la valeur est bonne, comparée à un algorithme par "octets". On pourrait même comparer cette valeur au temps nécessaire pour consulter la table des collisions. Pour que le programme reste compétitif, il faut que le temps de calcul soit inférieur au temps de déplacement dans l'algorithme par octets. chaque médaille a son revers. vitesse: nombre de cellules (octets) transférés par seconde ------- La vitesse minimale du tableau (253 millions de cellules par seconde) est largement supérieure à mes espérances, et la pointe à 423 Mc/s est une heureuse surprise. j'ai l'impression d'avoir un superordinateur alors que ce n'est qu'un banal PC qu'on peut trouver dans un bureau. Et encore, dans les entreprises, ils changent leurs ordinateurs assez souvent. Mais attention ! le calcul en lui-même n'est pas effectué. la "vitesse" de calcul va chuter dramatiquement avec des lois de collisions "saturées". v. sans SM: vitesse sans strip mining ---------- elle est assez faible comparée à celle avec strip mining, mais elle correspond plus à la vitesse "normale" d'un tel programme. il y a deux raisons qui pondèrent la fiabilité de ce nombre: l'affichage occupe une grande partie du temps de transfert vers l'extérieur, et le mécanisme de balayage agit de manière plus complexe que ce qu'il effectue vraiment (for(i=0;i<1;i++){(calcul)} revient à (calcul) ). en fait, c'est un cas particulier de l'algorithme de strip mining, qui correspond dans la tâche effectuée à un algorithme linéaire, mais qui n'est pas fait pour cela. J'estime qu'une partie du travail a été accomplie. la cible de ce type de programme étant la famille Pentium sur socket (5ème génération de processeurs) je ne m'avance pas plus dans ce domaine. Le PII avec sa cache rapide et transactionnelle dans le même boîtier crée des effets plus complexes qu'il sera intéressant d'analyser : l'influence de la largeur des lignes reste à définir mais la L2 fait _presque_ office de L1 pour les très gros tableaux. Il faudra donc explorer les deux méthodes de biprocessing (vertical/horizontal) pour trouver la meilleure. La méthodologie de "l'extérieur vers l'intérieur" (de la boucle de calcul): on programme les opérations annexes pour qu'elles prennent un temps minimal. par exemple: les boucles de balayage sont "tunées" avant de mettre qqc dedans, qui sera lui aussi tuné avant d'être complet. Ainsi, l'intérieur de la boucle est dans des conditions autant optimales que possible: -pmode32 -> mode FLAT 32 bits -interface -> occupation mémoire -boucle de balayage -> prédiction de branchement, alignement des données -translation -> alignement, scheduling -calcul -> scheduling et opérations minimum. avec une méthode de conception "de l'intérieur vers l'extérieur", l'extérieur a souvent tendance à "s'encrasser" d'opérations peu importantes qui pouraient être effectuée dans des niveaux extérieurs par exemple. De toute manière, lorsqu'un problème se pose avec la première méthode, comme un problème d'allocation des registres, il est souvent très facile de réécrire la partie délicate afin de "repartir sur des bonnes bases". Vient maintenant la partie plus complexe de la translation verticale ! Mesures sur le PII: on aurait du faire des mesures intermédiaiares sur le P200MMX. PII biprocesseur (mode monoprocesseur) 266 MHz MGA millenium AGP 64MO SDRAM fichier: taille: strip: temps min. temps max (strip=1) remarques tst\PII0000.bmp 256*256 32 38889 106120 rem: <L1, STRIP "saturé" tst\PII0001.bmp 384*384 29 84737 237022 efficacité maximum (strip<=32) tst\PII0002.bmp 512*512 23 149876 411926 tst\PII0003.bmp 768*768 18 394849 2160390 tst\PII0004.bmp 1024*1024 14 790396 4868350 tst\PII0005.bmp 1536*1536 10 2072506 11023138 tst\PII0006.bmp 2048*2048 07 4473040 19606746 rem: La courbe redescend un peu tst\PII0007.bmp 3072*3072 32 10688105 44295856 rem: L2, strip saturé, la courbe vient de dépasser le minimal local de la L1 tst\PII0008.bmp 4096*4096 32 18935142 77168892 rem: L2, strip saturé tst\PII0009.bmp (idem pour la valeur maxi) taille*freqCPU/tps calculs: taille: MO(Ms) speedup larg*strip temps/tableau freq (x*y) (strip max/strip=1) tps min/freqCPU 1/precedent 256*256 0,0625 2,7283 8192 0,0001458 s 6857,1/s 384*384 0,1406 2,7971 11136 0,0003177 s 3146,9/s 512*512 0,25 2,7484 11776 0,0005620 s 1779,2/s 768*768 0,5625 5,4714 13824 0,0014806 s 675,3/s 1024*1024 1 6,1593 14336 0,0029639 s 333,3/s 1536*1536 2,25 5,3187 15360 0,0077718 s 128,6/s 2048*2048 4 4,3833 14336 0,0197739 s 59,6/s 3072*3072 9 4,1444 98304 0,0400803 s 24,9/s 4096*4096 16 4,0754 131072 0,0710067 s 14,1/s (suite) taille: cycle/cel vitesse v. sans SM tps/taille taille*freq 256*256 0,5933 449Mc/s 164Mc/s 384*384 0,5746 494Mc/s 165Mc/s 512*512 0,5717 466Mc/s 169Mc/s 768*768 0,6694 398Mc/s 72Mc/s 1024*1024 0,7537 353Mc/s 57Mc/s 1536*1536 0,8784 303Mc/s 57Mc/s 2048*2048 1,0664 250Mc/s 57Mc/s 3072*3072 1,1325 235Mc/s 56Mc/s 4096*4096 1,1286 236Mc/s 57Mc/s Les données sont plus complexes à décortiquer mais on peut remarquer certaines choses: Vitesse: on peut penser que la L2 est à la moitié de la fréquence de la CPU: décroissance de 500Mc/s à 250Mc/s (en gros) fréquence du bus: 66MHz=57MB/s*1,16, or l'affichage nécessite 1/16 de la bande passante (zoom 4:1, donc 4*4 moins de données à afficher) On remarque ainsi que le calcul de l'affichage n'est pas encore un problème. On peut considérer 3 phases lorsque la taille et la largeur croissent: 1-la taille est inférieure à la L1 2-La taille est supérieure à la L1 mais inférieure à la L2 3-la taille est supérieure à la L2 lorsque une des tailles (L1, L2) est dépassée, la performance chute rapidement car le type de balayage ne respecte pas beaucoup le mécanisme de LRU, de par le balayage en colonnes. Cela explique probablement le pic du speedup à 6,1 lorsque le type de mémoire utilisée changeait. Speedup: le début à 2,7 environ fut une surprise, mais comme la mémoire utilisée restait en L2 et que celle-ci est accédée plus vite que sur le PMMX, et que les calculs "normaux" se font sur des dimensions plus grandes, le gain de plus de la moitié reste satisfaisant. à 1MO, le pic de 6,1 n'est pas encore bien compris. mais ce qui compte reste la "vitesse" de calcul effective, pas l'accélération par rapport à un code qui ne sera pas utilisé. à partir de 4MO, le speedup est de 4 et semble se stabiliser. On tourne alors entièrement avec la L2. Le speedup diminuera probablement si la taille de la fenêtre dépasse la taille de la L2. Largeur*Strip: mêmes interprétation que sur le PMMX sauf que la L2 permet de s'adapter lorsque le calcul ne tourne plus en L1. On imagine donc que Larg*strip va converger vers 256Ko. temps par tableau: Il faut un temps très court pour accéder à tous les éléments du tableau grâce au strip mining. La vitesse de rafraichissement est donc très rapide. Attention cependant, car plus le tableau est grand, plus le temps de circulation augmente. le temps d'établissement d'un fluide peut être arbitrairement donné par 3*X*X*Y temps de cellule, donc une accélération de 4 permet de traiter un tableau seulement sqrt(2)*plus grand à même vitesse. freq. d'un tableau: attention, cette valeur est plus grande que la fréquence de rafraichissement de l'affichage. il faut fixer STRIP=1 pour avoir un affichage à chaque cycle, mais c'est environ 4*plus lent. un tableau de 512*512 est affiché environ à la fréquence du moniteur: 70Hz*23 strip=1610 tableaux, proche de la fréquence de 1779 calculée. Un tableau de 1024*1024 est affiché à 333/14=23,78Hz, ce qui est proche de la fréquence d'une pellicule de cinéma. Cycles CPU pour une cellule: le temps augmente de manière presque monotone de 0,6 à 1,2. cette augmentation correspondant à l'augmentation de la taille du domaine de calcul, on peut conclure que nous sommes toujours en "memory bound". Les calculs de vitesse le montrent clairement. La CPU passe donc beaucoup de temps à attendre la mémoire centrale. De plus, la fréquence d'horloge est plus grande et le processeur a besoin de nombreux étages de pipeline, le CPI augmente par rapport au PMMX avec ses 5 ou 6 étages. Un cache miss est donc plus pénalisant. Heureusement la grande cache L2 recolle un peu les pots cassés. Vitesse: Les valeurs sont comparables en ordre de grandeur, ce qui est surprenant car les architectures ne sont justement pas comparables. Le point commun reste le goulet d'étranglement numéro un: le bus d'accès à la mémoire large de 64 bits seulement à 66MHz. Il est étrange de dire qu'un Pentium MMX à 200MHz est aussi puissant qu'un Pentium II à 266 MHz et pourtant les chiffres le prouvent: ils sont similaires. La seule explication est que FHP EST MEMORY BOUND sur ce type de plateforme. Je ne suis donc pas sûr que l'utilisation du deuxième processeur soit utile si le code reste memory bound, car les deux compères partagent le même bus. Il faudra donc que le code soit très lourd en calcul pour que l'option du multiprocessing soit intéressante. remarque: la légère remontée de la vitesse pour 4096*4096 vient certainement du fait que l'affichage n'est pas complet, seuls trois quarts sont affichés (zoom=4:1, donc 4096*3040 max.). cela suffit à modifier de quelques % les résulats. Conclusions: Les chiffres ont parlé. L'algorithme de strip mining avait été prévu pour manipuler des tableaux de la taille de la mémoire centrale à la vitesse de la cache L1, pour des processeurs de type Pentium. Les mesures montrent que le calcul s'effectue à la vitesse prévue lorsque le tableau tient dans la L2. Or le PII a une architecture différente qui permet effectivement d'atteindre ce but : le pic local de la courbe de performance est oublié pour un minimum plus important correspondant à la cache L2. Le PII associe donc la capacité de la L2 avec la vitesse de la L1 grâce à des bus séparés. Mais la vitesse est alors limitée par celle de la cache L2, deux fois plus lente. Il faudrait un Xeon, cinq fois plus cher au moins, mais avec une L2 encore plus grande et à la vitesse du processeur, pour éviter ce désagrément. La performance atteinte avec le strip mining est atténuée pour l'utilisateur par le fait qu'il faille attendre plusieurs secondes pour traiter le tableau immense avec un nombre de strip maximal. Pour visualiser un événement de dimension temporelle fine, l'utilisateur peut toutefois revenir à un stip count plus faible mais avec une performance diminuée. Son intension est alors d'observer, non de calculer, donc cela n'est pas important. Les mesures ont prouvé que les programmes FHP sont "memory bound", et que le strip mining apporte une solution utile car le speedup en bande passante est compris entre 2,7 et 6,1, avec une moyenne de 4 environ sur les deux machines mesurées. Le goulet d'étranglement de la mémoire centrale n'est pourtant pas levé, et les PC de bureau ne vont pas être améliorés avant longtemps. La technologie RAMBUS par exemple fonctionne à 800MHz mais s'accompagnera probablement d'un bus moins marge. FHP étant "memory bound", un calcul sans strip mining aurait donc pu profiter de la latence de la mémoire pour effectuer les calculs booléens complexes. Les calculs peuvent donc être plus complexes et moins optimisés. Mais le strip mining change le problème en "CPU bound" pour les petites simulations, lever cette difficulté est le prochain but à atteindre. FHP étant "mémory bound", il se pose aussi le problème de savoir si l'usage d'un deuxième processeur peut encore améliorer les performances dans l'architecture considérée ici. La réponse est positive a priori, si le code devient "CPU bound". De plus, le chargement des données se faisant par paquets qui sont ensuite traités dans les mémoires caches, il est probable que les deux processeurs "entrelacent" leurs requêtes vers la mémoire lorsqu'un processeur est en phase de calcul. La boucle de recherche exhaustive permet de trouver des solutions imprévues, donc les meilleures dans n'importe quels cas, pour toutes les plateformes. Un strip mining plus complexe ne devient intéressant que pour des mémoires énormes et très lentes car cela nécessite beaucoup plus de stockage temporaire. L'organisation de la mémoire en niveaux de caches de plus en plus lents et de plus en plus larges et de moins en moins chers est basée sur le postulat que les données ont une localité temporelle et spaciale. Or les automates cellulaires et le modèle FHP ne correspondent pas à ces critères et sont peu efficaces sur les architectures classiques. le Strip mining permet de diminuer l'impact du balayage sur les performances, en réorganisant les données pour augmenter leur localité spatiale et temporelle. Il faut noter que le cas des automates cellulaires n'est pas isolé, et que des application courantes balaient linéairement des tableaux plus grands que leur mémoire cache. Ainsi, l'inversion de matrices est un cas important pour le calcul scientifique qui a fait l'objet d'optimisations, en particulier dans le compilateur des processeurs PowerPC qui dispose d'une option pour effectuer une transformation de la boucle de calcul. Le but de ces mesures est principalement de trouver la "barrière" supérieure des performances, afin de se donner pour objectif de ne pas descendre torp en-dessous lorsque le calcul sera en place. Une chute de 90% des performances par exemple montrera que le calcul est trop lourd ou mal effectué. De plus, la boucle de calcul n'a pas encore été optimisée. L'optimisation peut améliorer la performance en crête mais pas la performance sur les tableaux immenses (16Mo), on peut donc conclure que l'optimisation fait partie de la partie "CPU bound". voilà. 9 juin 1999: WBINVD placé au début de la boucle de calcul: sur un tableau de 4096*4096, on constate une amélioration de 0,3%, alors que sur un petit tableau de 256*256, il y a un ralentissement de 0,8%. Les avantages et inconvénients semblent trop minces pour s'y intéresser maintenant. Remarques pour la programmation sur x86: c'est vraiment l'architecture la plus difficile à programmer et à optimiser, car les règles de codage sont beaucoup trop nombreuses et inutiles. non seulement les instructions sont à 2 opérandes mais en plus il y a peu de registres. La pression sur l'allocation des registres est très fortes. le processeur Alpha est bien supérieur, non seulement pour son bus plus large et plus rapide, mais par son jeu d'instruction plus cohérent à 3 opérandes, ses registres plus nombreux et plus larges, et ses règles de codage simples. En plus, il peut effectuer quatre opérations par cycle conte 3 pour le PII et 2 pour le PMMX. (en crête...) A même fréquence d'horloge, le processeur ALPHA est donc bien supérieur aux processeurs x86. Optimisation de la boucle interne, déplacement horizontal: la méthode est changée. il y avait un grand nombre de dépendances de données. C'est dur aussi de coder avec les contraintes du PMMX et du PII à la fois ! Les deux codes de déplacement ont été optimisés et entrelacés. résultat : taille strip min. max 512*512 22 116370 446978 il y a une légère différence de strip count. speedup min: 123735/116370=1,0632 6% c'est déjà mieux. On atteint alors 450 Mc/s ! Ce sera la meilleure "vitesse" atteinte car les modifications à venir la diminueront. speedup max: 464412/446978=1,039 4% c'est pas trop mal, mais je me demande comment faire mieux. la solution est problement un "cache warming" mais le PMMX ne le supporte pas. Et surtout, j'ai passé du temps à optimiser le code mais il n'y a pas autant de gain qu'escompté. essayons avec une taille moins "memory bound": taille: strip: min: max: 235*256 32 31795 127187 speedup min=1,0637521 : idem. Le Pentium est vraiment plein de mécanismes lents à se mettre en branle. speedup max=1,01585 1,5%: ça devient ridicule. mais c'est vraiment toujours memory bound. --------- essayons sans accéder aux données: 8 instructions de (dé)chargement ont été supprimées. 256*256, strip=32, min=24679 et max=98330 29% du temps est donc utilisé pour les accès à la mémoire. il y a 21+8=29 instructions dans la boucle, soit 38%. la conclusion serait donc d'avoir moins de calculs. ------------- essayons: 18 instructions ont été mises en commentaire, les 3 restantes servent à contrôler la boucle. les instructions qui accèdent à la mémoire ont été rétablies. taille: 256*256, Strip=32, min=21857, max=124469 max normal/max sans calcul=127187/124469=1,021836 min normal/min sans calcul=31795/21857=1,45468 "max" est mémory bound sans l'ombre d'un doute. lorsque le calcul s'effectue dans la L1, il occupe "seulement" 45 % du temps. Cela contredit les observations des mesures précédentes (sans accès à la mémoire) mais il faut remarquer que la mémoire est toujours accédée pour interpréter les listes de modification. Conclusion: on peut rajouter des calculs dans la boucle sans trop influencer les performances. Le mouvement des bits au moins ne semble pas poser de problème, on verra ensuite pour le calcul des collisions. -------------- Il faut aussi remarquer que dans la mesure précédente, l'affichage était aussi effectué, ce qui explique le "saut" de 2 à 45% lorsque le strip count diminue, donc que l'affichage augmente. l'affichage non seulement accède aux données mais aussi à la mémoire vidéo. réessayons en désactivant l'affichage et la liste de modification: taille=256*256, strip=32, min=14906, max=38421 cela semble plus raisonnable mais je ne comprends pas le rapport de 2,5 entre les deux nombres, il ne devrait pas y avoir de différence notable. Peut-être s'agit-il de l'utilisation de la pile ? Mais le calcul prend 46% du temps en "CPU bound". Il reste donc de la place pour rajouter du calcul. Le rapport avec max est encore plus optimiste : 127187/38421=3,31. Le calcul ne prend plus qu'un tiers du temps en "memory bound". ---------------- fichier source renommé SP08.asm --------- Intro: Les ordinateurs modernes sont des êtres furieusement non-linéaires. Ils sont devenus tellement complexes qu'il est très difficile de prévoir les performances d'un code même très simple. Le strip mining est une méthode de réorganisation des données et du balayage pour les très gros tableaux et pour des calculs qui possèdent une certaine localité de traitement. Cependant la mise en place de balayages complexes suppose une complication du code et demande de nouveaux buffers. Un déplacement horizontal de bits a pu être mis en place car il est perpendiculaire au sens de déplacement de la fenêtre de strip mining, et n'apporte pas de complication notable. Le déplacement vertical est par contre plus beaucoup plus compliqué car il nécessite un buffer. Le déplacement vers le haut est encore assez simple puisqu'il est dans le sens inverse de la fenêtre. le problème se résume alors à une variable temporaire qui doit être initialisée et vidée correctement hors de la boucle interne. le résultat est dans SP08.asm. ---------------- fichier source renommé SP09.asm --------- Le buffer temporaire pour le balayage et l'affichage : Le graphe des dépendences des données a montré que pour chaque colonne balayée il faut un buffer aussi long que cette colonne. ce buffer est cependant accédé assez rarement, deux mots de 64 bits à la fin de chaque balayage de colonne. Le buffer nécessaire à cette fonction sera entrelacé avec un autre buffer qui mémorisera les résultats à afficher (densité, vorticité...) constitué par chaque calcul de cellule. Grâce au changement du sens de balayage de la double boucle interne, c'est à dire vertical à l'intérieur, le buffer horizontal (contenant une ligne) des programmes des premières générations a été supprimé pour la boucle interne, mais les dépendances de données existent encore et il faut en tenir compte lors des balayages verticaux. On ne peut plus utiliser la première garde horizontale pour le buffer circulaire de ligne. Il faut remarquer que même si le buffer nécessaire est par nature bidimensionnel, il est facilement linéarisable avec un buffer circulaire simple. Son organisation a été choisie pour diminuer son impact sur les performances, en occupation de mémoire cache et en instructions nécessaires à son traitement. Ainsi, même s'il nécessite plus de place mémoire, ce buffer n'est pas beaucoup plus complexe et gourmand que les buffers temporaires des premiers programmes. Chaque "cellule" de ce buffer comprend 10 mots de 64 bits: 2 pour le buffer vertical (de colonne), et 8 pour les 64 octets à afficher, soit 80 octets. Il faut d'abord trouver de l'espace pour contenir ((CEL+16)*XSIZE)*32 octets. On le place au-dessus du tableau de calcul, afin d'éviter les problèmes d'allocation mémoire avec MSDOS. En plus, la mémoire MSDOS ne peut pas en général contenir des tableaux de plus de 500Ko, ce qui est pourtant probablement nécessaire pour des très grosses simulations sur un PII notament. La formule de l'espace occupé est donc modifiée: TAILLE= ((XSIZE+CEL)*(YSIZE+2))+(((XSIZE*4)+XSIZE)*512) octets. Le buffer est géré comme un buffer circulaire classique dont la taille est ((CEL+16)/CEL)*STRIP*XSIZE. Le pointeur de début est décrémenté (au besoin) avant de lancer le calcul en double boucle, dans lequel un autre pointeur augmente (modulo TAILLE) à partir du pointeur de début. Un buffer circulaire est nécessaire à la fois pour réduire au minimum le déplacement des blocs d'informations (on change le pointeur à la place) et l'occupation en mémoire (et l'occupation en cache par la même occasion). L'organisation des cellules est directement liée à la réduction du nombre de pointeurs, car il ne reste plus qu'un seul registre disponible pour pointer (ESI). Or, le buffer remplit deux fonctions qui sont différentes de par l'occupation et le type de balayage qu'elles effectuent. Le buffer de balayage vertical est accédé une fois à la fin du balayage d'une colonne, alors que le buffer d'affichage est accédé pour chaque cellule. C'est donc cet usage qui prime sur le premier d'un facteur 4/5. Le balayage doit donc s'effectuer dans l'ordre privilégiant la localité maximale du buffer d'affichage. La perte en bande apssante et en occupation utile de la mémoire cache est d'un peu moins d'1/5 car le buffer vertical n'est utilisé qu'à la fin des colonnes. Les données sont cependant convoyées avec celles du buffer d'affichage, ce qui évite 4/5 des "cache miss" dans ce cas. Cela ressemble à du "cache warming" mais est dû à une restriction forte du nombre de registres pointeurs. Une fois que la fonction et la gestion du buffer sont dédinies, ont peut mettre au point le balayage dans ses détails. L'approche est de "suivre" le cheminement des données le long des XSIZE colonnes de STRIP mots, afin de profiter au maximum de la bande passante (car les blocs ne sont pas alignés sur 32 octets). Les colonnes ayant plus de localité que les lignes, elles sont ainsi la structure de base du tableau. A l'image du balayage du tableau central, le balayage du tableau temporaire est orienté colonnes. Le tableau est géré comme un buffer circulaire, afin de ne pas déplacer de données mais les pointeurs associés. Cela est aussi dû au fait que le balayage du tableau principal a un démarrage progressif, il faut faire attention pour que le balayage effectué avec un certain strip count (au démarrage ou à l'arrivée) puisse fonctionner au cycle suivant lorsqu'il augmente ou diminue. Ce sont des effets de bord importants qu'il faut gérer correctement. La stratégie du pointeur sur la pile résout facilement ce problème et est expliqué dans les paragraphes suivants. Au cours du balayage, le tableau temporaire est parcouru linéairement, en "suivant" le balayage du tableau principal. Mais au démarrage et à l'arrivée, lorsque la taille balayée change, il faut "sauter" des cellules. L'approche est "externe->interne" car on met en place la boucle externe d'abord. Elle gère l'aspect du strip mining de "haut niveau", dans son principe, du point de vue de la fenêtre de calcul glissante. Ensuite, la boucle secondaire gère le buffer de balayage vertical. Enfin, la boucle interne gère le buffer d'affichage. Il faut noter aussi que la synchronisation du tampon avec la procédure de balayage est importante. Elle est assurée par la boucle externe. Le tampon est balayé par un seul pointeur, ESI, mais dans chaque imbrication de boucle, il va être sauvegardé sur la pile et modifié de plus en plus finement: Pour la boucle extérieure, le pointeur est avancé de -80 octets (une cellule de buffer) modulo la taille du buffer, afin de faire "glisser" la fenêtre de calcul vers le bas. Pour la boucle intermédiaire, le pointeur est sauvé sur la pile et avancé à chaque colonne de STRIP*80/64, modulo la taille du buffer. La gestion du buffer de mouvement vertical est ici assurée. Pour la boucle interne, le pointeur est sauvé sur la pile et avancé de 80 octets pour "suivre" le balayage principal. Il est à noter que, contrairement à la boucle principale, le besoin en pointeurs en moindre car la boucle de contrôle est déjà en place. Nous pouvons donc nous servir de la pile tranquillement pour gérer le pointeur. Néanmoins, un DSP avec plusieurs générateurs d'adresses et un mécansime de boucle câblé aurait été plus performant et plus simple à programmer (comme quoi, ce n'est pas seulement un problème de langage de programmation). Le mécanisme ici décrit permet de limiter au maximum le trafic sur les bus mémoires, mais occupe au moins la moitié de la mémoire cache dans laquelle tourne le calcul. Ainsi, le trafic mémoire est augmenté car il y a moins de lignes de cache libres : on peut donc s'attendre à des performances diminuées de moitié par rapport aux mesures déjà effectuées. Les mesures diront si l'algorithme se comporte comme prévu. 0) vider temp dans la colonne: 1) lecture C 2) temp C -> C 3) calcul 4) resultat->tempC + 1 5) "decremente" ESI. il y a besoin effectivement de 2 mots par cellule temporaire, parce que les résultats n'appartiennent pas à la même génération du calcul. la perte n'est plus de 1/5 mais le nombre d'opérations augmente. nuit du samedi 12 juin: Aujourd'hui est historique, j'ai enfin réussi à comprendre et à programmer (le premier étant en fait bien plus difficile que le second) le déplacement vers le bas. c'est beaucoup plus vicelard que je pensais mais j'ai pris mon stylo à deux mains et j'ai fait "tourner" les bits sur le papier jusqu'à bien comprendre ce qu'ils faisaient et en déduire l'algorithme. la gestion des pointeurs est beaucoup plus simple que je l'avais imaginé, en remarquant certaines choses qui se dessinaient sur la feuille. Je l'ai codé avec assez peu de peine, juste un bug idiot avec ESI poussé sur la pile pour la liste de modification mais pas restoré après... je n'ai pas mis longtemps à trouver le pot aux roses avec un peu de logique, car j'ai analysé en partant de l'arrière (du code fautif) les dépendances de données pour trouver qu'il ne correspondait pas à ce qu'on s'attendait. A part cela, le déplacement vertical a fonctionné tout de suite ou presque. il ne faut pas non plus oublier de "vider" le buffer avant de calculer une fenêtre pour éviter de voir apparaître des particules imprévues, qui sont en fait les bits qui devraient disparaître en bas et qui "bouclent". J'ai ensuite mis en place les listes de modifications. avec un peu de peine car elles commencent à devenir complexes. comme je l'ai pensé depuis des années, il va falloir fabriquer un programme de génération de ces listes. Les listes actuelles font la réflexion des particules horizontales et verticales, mais ce n'est pas parfait car il manque quelques trucs dans les coins. pour l'instant ce n'est pas trop grave mais il ne faut pas l'oublier. J'ai enfin calibré la boucle : comme prévu, les performances sont divisées par 2. heureusement qu'il y a le strip mining ! un XEON serait aussi le bienvenu. mais le speedup entre la version "strip idéal" et "strip=1" est toujours environ 4. Prochaine étape: les obliques, où on se met (enfin) sur un réseau hexagonal. Il faudra en plus revoir l'organisation générale des tableaux. je pense maintenant que les gardes horizontales du bas et du haut sont inutiles. la garde verticale est cependant toujours importante. <remarque: la direction F écrit vers le haut, donc il faut la garde du haut> Les premières mesures avec le déplacement vertical indiquent en gros des performances réduites de moitié. On remarque aussi une redescente plus nette de la performance lorsque le best strip est dépassé, car il faut "swapper" en plus le buffer temporaire. PMMX 200MHz, L2=256Ko taille best strip best cycle cycles for strip=1 256*256 20 43269 125163 512*512 12 162093 450402 1024*1024 7 891808 3148301 2048*2048 3 5355502 13069373 calculs: strip*largeur: speedup(relatif): vitesse crete: vitesse non crete: 5120 2,89267 302 104 6144 2,77866 323 116 7168 3,53024 235 66 6144 2,44036 156 64 la vitesse lorsque strip=1 n'est pas surprenante et n'a pas changé par rapport aux autres versions plus "légères" du programme: on est encore en régime "memory bound". le strip*largeur ne dépassse pas 7168, soit moins de la moitié de la cache L1. l'autre moitié est utilisée par le buffer temporaire et par quelques variables temporaires, sans oublier les cellules "voisines" accédées lors du déplacement des particules. le speedup relatif est plus faible, indiquant que nous nous rapprochons de la limite CPU/memory bound. 2,5-3 en moyenne. mais comme l'indique le speedup, le strip mining permet toujours de calculer au moins deux fois plus vite qu'un code classique. La vitesse crête par contre n'est pas beaucoup altérée, la crête se situant toujours pour 512*512=taille L2. la meilleure performance atteinte sur le PMMX étant de 450Mc/s, le processeur ne fonctionne plus qu'à 70% de la performance crête. On s'attendait plutôt à 50%, ce qui est une bonne surprise. j'envisage donc une performance moyenne de 200Mc/s pour les programmes FHP sur le PMMX. En débutant ce projet sur un Pentium 100, j'envisageais plutôt une performance de 20Mc/s :-) Il faut dire merci à la plus grande cache L1 (16Ko au lieu de 8)[x2] et au MMX qui rajoute des registres qui permettent de travailler sur de plus grands nombres (64 bits)[x2]. le processeur plus rapide et l'utilisation du mode protégé expliquent enfin le dernier doublement de performance[2x2x2=8]. Par contre, la mémoire est toujours à 66MHz et 64 bits de large... (supériorité de l'esprit sur la matière ;-D) Notes: le fichier est maintenant SP10.asm. NASM en mode 16 bits n'arrive plus à l'assembler, j'ai donc chargé le nouveau nasm 0.98 en mode protégé pour digérer les 104Ko de fichier source (bon d'accord, 58Ko quand il n'y a plus de commentaires). le binaire fait 8Ko et le listing 234Ko. et j'ai fait tout çà à la main... Je suis à quelques centaines de lignes du but, et ce sont vraiment les plus difficiles ! La réunion de quelques variables que j'estime accédées le plus souvent, dans un même endroit, ne donne que très peu d'amélioration environ 1/1000 dans le meilleur des cas. je garde toutefois cette configuration. La réorganisation de la cellule (affectation de A, B, C,...) semble plus prometteuse, de l'ordre du %. Il faudra vérifier cela plus attentivement. SP11.asm: je mets en place le déplacement hexagonal en duplicant les déplacements verticaux. ça fonctionne bien car c'est du copier-coller. l'assignation des registres est un peu embêtante mais ça reste humain. A et D ne sont pas à modifier, les horizontaux fonctionnent bien, et des éléments de chacun vont être copiés pour ajouter de la complexité au balayage. nuit du lundi 14 juin: Enfer et damnation ! bonne mère, je suis dans un sacré pétrin. En mettant en place le déplacement en F (vers le haut à gauche) je me suis aperçu de comportements très bizarres des particules. ***** Après pas mal de questions j'ai fait un programme en Pascal afin de profiter du débugger (qui ne m'a finalement pas beaucoup servi). mais surtout Pascal est duffisament lent pour voir les choses se passer. résultat: - le déplacement vertical vers le bas est OK. - le déplacement vers le haut est refait et il est plus simple que je ne l'avais programmé même si ça fonctionnait. Je n'avais pas correctement analysé les dépendenaces de données dans le temps. - le gros problème cependant est la remise en question des listes des modifications. Elles ne permettent pas de modifier les particules allant vers le haut ! La solution pour ce problème est d'abandonner la structure prévue et de mettre en place un mécanisme plus fin, le plus simple étant un pointeur dans la partie WALL de chaque cellule. le programme en pascal donne un résultat positif. On peut aussi par la même occasion abandonner la garde verticale :-) bref la tempête a fait rage et a fait des dégâts. Je savais bien que mon truc était possible mais je me suis trompé sur le comment... nuit du mardi au mercredi 15 juin: j'ai remis de l'ordre dans le programme. les balayages nord, sud, est et ouest fonctionnent. le sud-ouest est opérationnel. le nord-ouest par contre est instable pour les strips>1. je me demande vraiment quand je m'en sortirai. je croyais pourtant que j'avais dompté l'espace-temps mais chaque pas vers la solution pose de nouveaux problèmes. je croyais avoir fait le plus difficile alors que le reste à venir est inconnu parce que pas encore programmé, donc imprévisible. a‹ïe. j'avais commencé à ébaucher une procédure de conversion vectorielle-> liste de modification, mais devant la complexité inutile pour les prototypes je suis revenu à la vieille méthode du tout cousu main sur mesure. Après de longs cafouillages, les listes de modification par cellule fonctionnent comme désiré. c'est maintenant le déplacement hexagonal qui laisse à désirer ! donc, retour à Turbo Pascal pour voir ce qui se passe vraiment. L'informatique est vraiment un monde étrange où ce qui est complexe semble plus important que le simple. Il me semble paradoxal de passer des années sur un code qui tournera quelques heures en tout... Il me semble paradoxal que les algoritmes les plus complexes soient les meilleurs. Peut-être parce que les idées, par leurs abstractions, n'arrivent pas à saisir la complexité du monde. lemonde est-il un assemblage d'idées ? je continue ma descente aux enfers. je n'arrive plus à dormir. c'est plus frustrant que la plupart des problèmes que j'ai eus, sauf peut-être lorsque j'ai découvert le FORTH, mais ici l'enjeu est différent. J'ai déjà réécouté tous mes CDs, je recommence à écouter des cassettes. Je n'ai pas d'échappatoire car je suis convaincu de pouvoir réussir. je croyais avoir résolu la plupart des problèmes mais la réalité a rattrapé la théorie. je reste optimiste car je pense avoir résolu la moitié des problèmes. mais je reste sans voix devant les nouveaux problèmes qui se posent. la pression est forte mais j'arrive à tenir car j'ai peu d'obligations "annexes". Yannick Sustrac m'a mailé les résultats du programme SP07.asm (je crois que c'est celui-là, du moins, il n'y avait pas le balayage horizontal optimisé, donc cela correspond aux premières mesures de cette page.) Son ordinateur est un Cyrix (IBM) avec (apparemment) 256 Ko de cache L2, vidéo S3 sur bus PCI, SDRAM. je ne suis pas sûr du reste. je ne connais même pas la fréquence réelle de la CPU. néanmoins on peut déjà exploiter quelques résultats: >> mes resultats (Les strips min ne correspondent pas du tout) >> fichier: taille: strip: temps min. temps max (strip=1) remarques >> tst\PII0000.bmp 256*256 32 49018 111305 >> tst\PII0001.bmp 384*384 32 101699 275009 >> tst\PII0002.bmp 512*512 32 171817 450830 >> tst\PII0003.bmp 768*768 32 373296 1253318 >> tst\PII0004.bmp 1024*1024 32 668435 3144734 >> tst\PII0005.bmp 1536*1536 31 1465001 6902381 >> tst\PII0006.bmp 2048*2048 30 2618118 12112979 >les deux colonnes de droite ont l'air OK. >mais je suis scié par la colonne du strip count. il reste tout le temps à 32 ! >ensuite il redescend après 1MO. tu aurais 1MO de cache ????? >il y a un truc que je pige pas. tu as bien marqué le numéro de la ligne sur >laquelle le signe || s'est arrêté ? je sais pas trop comment prendre ce >résultat. > >En tous cas, le speedup: >256²: 2,27 >384²: 2.7 >512²: 2,62 >768²: 3,35 >1024²: 4,7 >1536²: 4,71 >2048²: 4,62 > les nombres de cycles sont du même ordre de grandeur que sur les autres ordinateurs testés. Le speedup est similaire à ceux des Intel pour une plateforme a priori différente à plusieurs niveaux. Le strip mining montre son efficacité sur les très gros tableaux, qui sont le domaine choisi pour le projet. Et même sur les petits tableaux, le gain est intéressant. Après un reboot "forcé", le BIOS a eu un CRC error et je suis retourné dans le BIOS pour la première fois depuis longtemps. j'ai découvert qu'il est possible de configurer la SDRAM pour des facteurs dont j'ignorais l'existence, comme l'entrelacement de banques. je me demande ce que ça veut dire mais je me doute qu'il y a quelques % de performance à grapiller de ce côté-là. Comme je ne me souviens pas des anciens paramètres, j'en ai mis d'autres qui sont probablement bien différents. les performances d'aujourd'hui ne peuvent donc plus être comparées avec les anciennes, sauf en ce qui concerne les ordres de grandeur. Le BIOS devrait être pris en compte dans la caractérisations des mesures. J'en suis à SP15.asm. Nuit du mercredi au jeudi 16 juin: La solution du problème est finalement venue au petit matin. Un simple dessin, et je me suis aperçu de mon erreur. Cuisant échec d'une erreur trop belle, d'une conviction qui a duré quelques années. Parce que je n'étais pas allé jusqu'au bout du problème avant. La conclusion finalement c'est que le balayage vertical à l'intérieur de la boucle obligeait un "saut spatio-temporel" trop grand d'un pas de temps. La solution est de réorganiser la boucle le plus simplement du monde, c'est à dire avec la ligne à l'intérieur. Les choses redeviennent alors presque naturelles, du déjà fait, du connu. juste encore la diagonale qui est délicate (sud-est) mais le bon sens est efficace. Je reviens de loin, et il n'y a plus qu'à programmer. Pascal m'aura permis de tester les algorithmes rapidement, parce qu'il est lent, et parce que je n'ai pas à attendre l'affichage d'une ligne puisque les données sont en mémoire vidéo. Cette fois-ci je recommence à reprendre pied. le reste à venir devrait aller comme du papier à musique, c'est déjà préparé. Peut-être le passage en multiprocesseur sera plus délicat car moins préparé, mais il n'y a pas 36 solutions. Il faut donc réorganiser la boucle principale, puis implémenter le balayage progressivement pour l'hexagone. au boulot. SP17.asm: A et D sont en place. la boucle a été réorganisée sans problème, ce n'était que de la réécriture comme j'en ai l'habitude. sp18: mise en place des déplacements verticaux. jeudi 17 juin, 4h15 du matin: les 6 directions sont fonctionnelles et stables pour tous les strip counts. il reste encore le problème des parois qui foirent un peu... mais le plus dur est enfin fait !!!! sp18 est renommé sp19.asm. 512*512, strip=14, best=277675, s=1:528986 512*14=7168, best/s=1:1,905, vitesse max:188Mc/s, vitess s=1:99Mc/s cela donne un nouvel ordre de grandeur de la vitesse finale. le déplacement des particules n'est pas optimisé, il est juste posé et il fonctionne. je ferai le ménage lorsque le calcul sera au point. Je m'attends aussi à une autre baisse de performance lorsque la sommation sera au point, car elle utilisera le reste du buffer principal qui, je crois, n'est pas chargé complétement en mémoire cache (si on prend la granularité de 32 octets). 6h du mat. : corrigé un léger bug pour la direction E. je prends maintenant le problème des parois à bras le corps, car les "trous" laminent mes beaux blocs de particules... vendredi 18, 5H30 du mat: je me suis réveillé à 2H du mat, et j'ai tout de suite allumé l'ordinateur. la fabrication des listes de modification n'est pas évidente mais c'est du niveau d'un projet de licence de Jean Méhat. C'est en le programmant qu'on comprend mieux le programme, mais une avancée théorique a été effectuée hier (un peu de bon sens aide parfois) Lorsque deux segments perpendiculaires se croisent, il faut mettre un "point dur" (réflichissant total) à l'intersection pour éviter que des particules entrent "à l'intérieur" du segment. le cas n'est pas difficile à détecter mais un peu compliqué car il y a un grand nombre de cas particuliers à prendre en compte. Un point important n'est pas encore en place: la reconnaissance qu'une cellule existe déjà autre part. cela permettra de tirer parti au maximum de la méthode des pointeurs lors du calcul. Pour cela, la recherche de cellules existantes doit se faire dans le sens inverse de croissance du buffer de cellules. 10H du mat: je peux faire des points isolé et des lignes verticales dans certaines conditions. il reste encore 3 cas particuliers à traiter, ce qui prendra du temps supplémentaire, mais procurera un confort d'utilisation incontestable pour le programme :-) par contre, de 1: heureusement que j'ai Turbo Pascal, de 2: ça va être un beau merdier de passer ça en assembleur, mais de 3: j'ai un algo raisonnable. pour l'instant. "Ce travail consiste à inventer et programmer un ensemble d'algorithmes à applications scientifiques, industrielles, pédagogiques et ludiques (joujou de luxe ;-D) avec des implications/répercussions algorithmiques et architecturales (ordinateurs), pour la simulation interactive de phénomènes de mécanique des fluides." yannick sustrac wrote: > >le modèle que j'utilise a un temps entre deux collisions entre particules > >(le chemin que parcourt une particule entre deux collisions) assez grand, > >donc le programme passe son temps à "bouger des particules". si on veut > >optimiser le programme il faut donc commencer par là :-) > Ca me repelle la simulation N-body c'est fait pour simuler l'evolution des > galaxies en astrophysique a partir de particules de gaz animées de mouvement. moi, en plus, je ne fais que des "intéractions locales". en gros, N-body doit regarder tout le monde, alors que moi, je me contente seulement de l'état d'une seule zone très restreinte. ce qui accélère vraiment les chauses. > Il y a un article la dessus ds SCientific-Computing Wolrd (C'est gratis) > http://www.iop.org/Mags/SCW V voir... mais ça me dit qqc :-) > > ensuite une fois que les particules bougent correctement et assez vite, > Elle bougnet en 2D ou 3D tes particules? bah en 2 D. la 3D est vraiment trop chiante à comprendre, car c'est impossible à faire directement, alors on fait un détour par la 4è dimension et on "projette" le résultat en 3D. je te dis pas la mémoire que ça prend, et le temps de calcul en monopraucesseur... sans parler du reste. > > on rajoute le calcul > De quoi? des collisions. les particules bougent, mais elles intéragissent assez peu. si elles n'intéragissent pas, ça donne (pour la 1D horizontale) le programme que tu as. une fois qu'elles bougent correctement, on les fait intéragir (localement, càd site par site). là, ça devient "intéressant" pour les simulations. mais tant que ça bouge pas correctement, tu peux toujours courir, même si ça fait des trucs "marrants". > >au milieu > et pourquoi au milieu du calcul du chemin? le "calcul" se fait en 2 "étapes" principales: -déplacement -collision dans les boucles on effectue le déplacement. une fois que le déplacement "roule", on peut mettre la collision dans son "cocon", au coeur de la boucle. sinon, tu peux toujours essayer de commencer par le calcul de collision. mais faire des collisions sur des particules qui bougent pas, ça me ferait prendre encore plus pour un idiot qu'on le croit déjà... > > et le tour est joué. il faudra faire des mesures sur le ratio > > entre particules bougées et particules > >qui font une collision avec réorganisation. en tous cas, ça devrait tourner > >autour de 3 ou 5, donc si on bouge les particules plus vite, le programme > >tourne plus vite. Et je sais pas si le PIII supporte les instructions > >booléennes sur 128 bits mais dans ce cas ça ira simplement 2 fois plus vite > >au moins (sans parler du bus à 133MHz et de la fréquence d'horloge plus > >élevée :-D) > Si c'est pour trouver que ca va + vite avec des processuer + recent ... nan. c'est pour dire que ça va aussi vite que ce que le processeur permet. avec une technique "classique" tu l'as dans le baba. si tu avais plus de cache, ça servirait à rien. alors que tu as bien vu qu'avec mon "truc", tu vas 4* plus vite que normalement. sur PIII, tu ne bénéficierait que du bus 2* plus rapide, alors que ma méthode permet de profiter des autres améliorations. > >le programme que tu as permet de connaitre les caractéristiques de ton > >système, et permet de valider ma théorie selon laquelle le strip mining > >améliore la vitesse d'environ 3 fois par rapport à un balayage linéaire. > >et un calcul 3 fois plus rapide, ça fait de mal à personne (sauf à ma > >tête). c'est pour ça que je me casse le cul dessus. > alors maintenant tu n'a plus qu'a me donner q explication sur ton strip > mining... et les diff par rapport au balyage lin. ahem, ahem. je m'éclaircis la voix car c'est un truc assez ... tu vas voir. Lorsque la mémoire cache a été mise au point, son application a été basée sur le principe de localité spatiale et temporaire des données. en gros, il y avait de grandes chances pour que qu'en t+1, les données accédées soient proches des données accédées en t. C'est valable pour de nombreuses choses, comme les instructions: surtout les petites boucles. avec les programmes générés par compilateur, ça marche aussi car toutes les variables globales sont groupées. enfin il ne faut pas oublier la pile, intensément utilisée. pour plus de détails, on peut par exemple lire Patterson & Hennessy, disponible dans la bibliothèque de tout informaticien qui se respecte. or, moi je veux traiter des tableaux immenses, gros comme la mémoire centrale. donc si je balaie linéairement tout ce tableau, à chaque frontière de ligne de cache se produit un cache miss, qui prend du temps à se résoudre, encore plus si le système est en mémoire virtuelle et que la page n'est pas en mémoire, et que l'adresse doit être traduite... le problème est que le postulat d'en haut ne s'applique pas à mon cas. Pour accélérer le processus, il faut augmenter la "localité spatiotemporelle". je l'ai fait en réorganisant les boucles. la boucle externe balaie une fenêtre de calcul qui "glisse" sur le tableau. cette fenêtre contient un nombre variable de lignes, ce nombre de ligne je l'ai appelé "strip count". Et quand tu appuies sur le pied à coulisse, on effectue le calcul pour chaque valeur possible du strip count, de 1 à 32 (mais je vais probablement lever la limite de 32 vers 64). la calibration consiste à tester toutes les tailles de fenêtres pour voir laquelle est la plus rapide, objectivement, sans autre considération, ce qui permet de trouver des résultats inattendus comme sur ton ordinateur. voilà, je sais pas si tu as compris mais je vais recopier ça dans mon mémoire car ça semble assez bien dit. mail à Yannick Sustrac: > >> Les resultat que j'ai eu sont differents des tiens: > >c'est normal puisque c'est un système différent ;-) > ...tu me prends pour un con voir + loin nan chte pran pa pour un con. c'est simplement que tu comparerais jamais un Mac avec un PC, donc on peut pas comparer "directement" un Intel avec un IBM, sauf pour voir la vitesse de calcul. ce qui m'a mis sur le Q. > >les deux colonnes de droite ont l'air OK. > >mais je suis scié par la colonne du strip count. il reste tout le temps > >à 32 ! > ... et c'est pour ca que je te disais que les resultats etaient differents!! tu parles qu'ils sont différents, ils sont mieux que sur PII 266 ! > >ensuite il redescend après 1MO. tu aurais 1MO de cache ????? > ...croa pô... 512ko c'est déjà bien quand même ! > >il y a un truc que je pige pas. tu as bien marqué le numéro de la ligne > >sur laquelle le signe || s'est arrêté ? > OUI j'ai vu, mais je ne te prends pas pour un con, c'est seulement que ça me scie... > >je sais pas trop comment prendre ce résultat. > ... ma ca correspond a qoui le STRIP (ou SLIP) la taille de la fenêtre de calcul. pour voir sa taille en octets, tu multiplies le STRIP par la largeur du tableau. > >tu disais que tu as un clone IBM (cyrix) ? dans mes archives j'arrive pas à > >trouver où tu m'en parles... la vitesse du bus (tu sais, les jumpers sur la > >carte mère) la taille et le temps de réponse des RAM et de la L2... > V-bus 66Mhz > RAM 98Mo EDO/100ns > L1 32ko (Le pentium MMX n'en a que 16. c'est peut être la la difference) je sais que c'est 16Ko données + 16Ko instructions. je ne sais pas si c'est la même chose pour toi. > L2 512ko 7ns je crois que c'est ce dernier paramètre qui fait toute la différence... > >> C'est tres nul comme saisie des tailles de tableau avec la sourie. > >? qu'est-ce que tu veux dire ? > Faits une saisie avec un saisie clavier pour les tailles tableau. C'est la > merde pour faire correspondre la sourie a un pixel! ah. pourtant en zoom 0 (1:1) les coordonnées sont affichées, et le résultat quand tu cliques est arrondi sur 3 bits (valeur la plus proche) donc c'est assez tolérant. mais je n'ai pas eu le temps d'écrire toute une interface superjolie cette année en assembleur... la fonction est disponible, un peu délicate, mais au moins y'a plus à réassembler le programme à chaque fois :-) enfin, j'essairai d'améliorer c'détail quand je pourrai. > >> >j'en ai encore pour qqs semaines dessus, après je rédige le mémoire. > >> T'as pas commencé ton memoire! mais tu vas ou là? > >nan j'ai plein de trucs partout mais il va falloir passer pas mal de > >temps pour tout organiser... j'en ai pour 100 apges facile ! sans parler > >des annexes ! > ... tu l'auras jamais cette maitrise....au train ou tu vas tu va être > obliger de rempiler. je crois que l'année prochaine sera plus cool. je compte bosser un peu et passer les UE que j'ai court-circuitées cette année. Mais je passerai la soutenance absolument à la rentrée, car le programme prend une bonne tournure. bientôt, on pourra même dessiner les parois à la souris ! > >> j'le sent mal ton projet ;-(( > >je n'ai jamais été si près du but. > >300000 autres trucs à faire pour le diplôme. résultat: 4 ans de recherches, > >400% d'accélération :-) pour t'en convaincre, regarde les chiffres > >au-dessus. > Tu te fout de moi! il y a quatre ans les CPU etait a 133Mhz avec 8ko de > cache, 4 ans de recherche pour s'appercevoir que les PC vont 4 X plus > vite ... bravo! nan. quelle que soit la technologie, la marque ou l'âge, si tu as de la mémoire cache, l'algo que j'ai mis au point permet d'accélérer le calcul des gaz sur réseaux. c'est une histoire de différence d'échelle, pas de valeur absolue. et ça, c'est puissant. Renseignements pris, la CPU est à 200MHz et le bus à 66MHz. mercredi 22: j'ai proposé une architecture pour le F1 du FCPU. mais à 8H15 j'ai surtout réussi à faire fonctionner le générateur de listes de modifications pour la première fois sans un accroc. maintenant, il faut transformer l'algo en Pascal vers l'assembleur, alors j'analyse l'algo sur le papier. SP19, où les particules bougent correctement, est l'exemple des problèmes causés par des parois défectueuses. SP20 est le chantier pour mettre en place les parois automatiquement. d'abord, afficher les parois :-) (copier,coller&snipsnip, c'est fait). ensuite mettre l'ossature. d'abord la fonction qui scanne la liste de segments (ça a déjà été fait mais cette fois-ci on compte moins sur les registres) On compte sur le bit G pour afficher les points avant d'implémenter la génération des listes elles-mêmes. cela permet de détecter les erreurs au plus tôt. Cette fois-ci, on peut se permettre de faire des coordonnées en virgule fixe, afin que les dimensions s'adaptent à l'écran. 0:0 représente le point (x=0,y=1) et 0FFFFh:0FFFFh représente (x=XSIZE,y=YSIZE-2). c'est automatique. par contre, pour faire des objets plus délicat, il faudra tenir compte du ratio XSIZE/YSIZE. jeudi 24, 8h du mat: la base pour les listes de modifications, c.à.d. les boucles pour les lignes, est en place. il faut maintenant fabriquer les cellules individuelles, ce qui est encore plus compliqué et plus difficile à débeuguer car on ne peut pas tracer le code ni examiner les données. Dans toute cette partie-là, je ne préoccupe ni du style ni de la vitesse car j'y passerais trop de temps et j'ai simplement envie que ça fonctionne. De plus, le temps d'exécution de la routine est très bref, donc je m'occuperai des détails plus tard. Heureusement que j'ai déjà débroussaillé l'algorithme en Pascal !!! le source est maintenant SP21.asm vendredi 2h du mat: SP21.asm a du mal à retrouver des cellules déjà créées, ce qui explique qu'un bon millier de paragraphes ne soit pas suffisant. je passe à SP22. j'ai la crêve de la mort mais je suis vivant. l'état importe peu tant que je peux encore taper sur le clavier. bug idiot: j'ai oublié un $ devant base__. résultat, la liste de cellules n'était pas balayée et des cellules étaient rajoutées jusqu'à saturation. bête. j'ai perdu une heure dessus. "tout àa pour un dollar". ça fait pas cher de l'heure. Mon travail se résume finalement à 90% d'intuition et 10% de travail. mais c'est le travail qui est 99% de la difficulté ! tout est relatif. et j'aimerais bien prendre de super vacances. SP22 commence à fonctionner: on peut créer une liste pour chaque cellule, et elles se chevauchent automatiquement. maintenant, il faut arriver à rajouter des éléments à ces listes. je passe à SP23. 6h: j'ai un bug hallucinant. je lance le programme, il bugge au niveau des listes de modifications. je retourne sous l'éditeur de Turbo Pascal, et le clavier envoie des ordres dans tous les sens. je dois alors rebooter. j'ai l'impression qu'il y a des pointeurs balladeurs quelque part. où ? 6h30: cette fois-ci c'est vraiment un beau bug. il affiche une ligne "fantome". en l'écrivant, je crois avoir trouvé la raison, puisque je fais le pushad/popad à l'extérieur de la fonction make_cell... vérification faite, c'est vraiment une histoire de puhasd/popad: la multiplication modifiait eax, qui était réutilisé ensuite pour l'appel suivant. wow... 8h: je passe à SP23. 10H: SP23 donne des premiers resultats satisfaisants pour les cas généraux. Les grandes géométries sont bien traitées mais les détails ne sont pas encore gérés. Samedi: j'ai un nouveau stock d'amoxicilline. j'ai aussi reçu plein de super emails hier. j'ai même saturé la table de relocation, je ne peux plus utiliser $base__, mais je peux utiliser $base1__ :-) Il ne reste plus grand chose à faire sur le programme de génération de listes. j'y travaille aujourd'hui. 21H30: SP23 fonctionne presque parfaitement. SP24 tente d'éliminer des points durs indésirables. Astuce: pour déterminer a priori la taille de la fenêtre par calibration, utiliser un tunnel de même largeur mais de hauteur plus réduite, afin de réduire le temps de calibration. SP24 a corrigé un avant-dernier problème: mauvais registre utilisé comme masque. SP25 va s'attaquer à la dernière difficulté: l'utilisation abusive de cellules consécutives. il faut vérifier l'existence d'"antécédents" identiques. il est déjà 3h du mat et je suis mort. à demain. Dimanche, 3H du mat: j'essaie de revoir l'algorithme sans trop toucher au déjà fait. pas très facile, vu la complexité, mais avec du travail, on y arrivera. SP25 est en bordel, je passe à SP26 Peut-être que si je réussis, je pourrais mettre un "garbage collector" ? 6H40: l'algo fonctionne du feu de dieu, je l'envoie à Sustrac ! Je vais enfin pouvoir me reposer. 8H: quelques mesures. j'ai trouvé que le scrolling vertical fonctionne mal pour des très grandes tailles, zoom=4:1. Il 'sature' à 6xxx alors que le tunnel est haut de 16384... fâcheux. mais 1:1 est OK. Je trouve que le code a pas mal ralenti. le speedup est moins fort pour les grosses tailles. voyons ce qui peut nous freiner. Première raison : il serait temps de se préoccuper de l'associativité des caches ! je crois me souvenir que la L1 du Pentium a des sets de 4KO, ce qui cause quelques paniques pour les lignes très larges. Cela a été 'intégré' dans les mesures précédentes et n'a pas fait de vagues, donc voyons autre chose. Deuxième raison possible: les listes de modification foutent la merde dans la cache. Les listes ne sont pas ordonnées et modifient l'ordre et l'endroit où les données sont cachées, quelles qu'elles soient. essayons avec moins de segments. résultat sur grosse taille (1024*16384): gain entre 2% (strip=1) et 5% (strip=6). 1024*16384, 13 segments: Strip=6 min=18824483 strip=1:41468567 1024*16384, 4 segments: strip=6 min=17930777 strip=1:40559429 Il est donc intéressant de penser à un "garbage collector intelligent" qui permettrait de gagner quelques % en réorganisant les données et en les "compressant" encore plus. On frise le compilateur LISP ici. Autre essai en essayant de "sortir" du bourbier des lignes de cache: 1088*16384, 4 segments, strip=6, min=19386521 ratio: 1.08118688/ 1088/1024=1.0625 1024*16384, 4 segments, strip=6, min=17930777 ratio: 1.063542265/ 1024/960=1.0666/ 960*16384, strip=6, min=16859487 Pas de grosse différence, sauf que 1088/1024 < 1024/960 alors que 19386521/17930777 > 17930777/16859487. Pas de beaucoup mais la tendance est intéressante à signaler. Cela rejoint la remarque que la largeur du tunnel est plus pénalisante que la hauteur. le multiprocessing par division de la largeur du tunnel (bandes verticales) est donc une voie sûre d'amélioration des performances. Mesures brutes: 13 segments, zoom=4:1 min/taille taille strip min strip=1 speedup (cycles par cel.) 256*256 25 92168 147963 1.60536 1.406372 384*384 17 182768 310230 1.69739 1.239474 512*512 12 301497 532305 1.76553 1.150119 768*768 9 646880 1231260 1.90338 1.096733 1024*1024 6 1227273 2736858 2.23004 1.170418 1536*1536 4 2986700 5912358 1.97956 1.265928 2048*2048 3 6036906 10464761 1.73346 1.439310 3072*3072 2 16731691 23226581 1.38817 1.772953 4096*4096 2 35138297 41666952 1.18579 2.094405 le speedup diminue sensiblement, probablement à cause du buffer de trame qui pompe la moitié de la L1. Mais il y a VRAIMENT un problème de cache pour les grosses trames. pourquoi le speedup a-t-il baissé ???? Aucun calcul n'est effectué. Mais pour les petites tailles, on tourne à 1,1 cycles par cellule, ce qui reste raisonnable pour une procédure de déplacement qui n'est pas optimisée. En effectuant le mouvement hexagonalement au lieu d'horizontalement seulement comme au début, la vitesse est réduite de seulement la moitié, tout en permettant d'avoir des parois arbitraires. Mais je crois surtout que la diminution de la vitesse par 2 est dûe au buffer qui prend la moitié de la L1. A 1,096733 cycles par cellule, on tourne à 183Mc/s en crête. le calcul diminuera cette vitesse d'environ 2 ou 3 (pour la crête), ce qui reste dans les premières estimations. Curieusement, la "crête" s'est déplacée de 512*512 (taille de la L2) vers 768*768 (2*L2). explication ??? 2,1 cycles par cellule font tourner à 100Mc/s. encore correct. mais on est toujours "memory bound" pour une raison qui m'échappe. Il faudrait un autre "type" de bécane mais je n'ai pas assez d'argent... La courbe de performance lors des calibrations s'est aussi notablement modifiée, remontant plus haut que la vitesse pour strip=1. Je me pose de sérieuses questions, mais je n'ai aucune idée. Je sais seulement que chaque "amélioration" de l'algorithme va diminuer cette performance au fur et à mesure des ajouts, comme cela se produit depuis le début des mesures. Tout ce que je sais c'est qu'il s'agit d'un problème d'architecture mémoire. Il serait aussi temps de se préoccuper de l'ordre des champs dans les cellules du tunnel. le "discrétiseur" n'a montré aucun défaut, du moins pour "make_cell". la procédure centrale (génération des traits) devra cependant être refaite. plus tard, un jour. mais j'aurais bien besoin de générer un cercle lisse :-) mardi: fait des mesures sur le PII. le comportement est un peu différent des simulations précédentes, mais la L2 dans le même boîtier permet de garder des performances raisonnables, toujours de l'ordre de 1 à 2 cycles par cellule. PII 266 monoproc, SP27.asm, 13 segments taille strip min strip=1 256*256 25 92168 147963 384*384 17 182768 310230 512*512 12 301497 532305 768*768 9 646880 1231260 1024*1024 6 1227273 2736858 1536*1536 4 2986700 5912358 2048*2048 3 6036906 10464761 strip=8:max=11210693 3072*3072 2 16731691 23226581 strip=6:max=25761657 4096*4096 2 35138297 41666952 L'algo ne se comporte plus très bien pour les grandes tailles, largeur et hauteur*largeur. Essais avec grandes tailles et largeur différentes: 2048*8192 3 23471642 40212128 1024*16384 6 18824483 41468567 la largeur pose un gros problème. essai avec 4 segments seulement: 1024*16384 6 17930777 40559429 5% de gagnés 2,2% de gagnés La complexité des structures pose un petit problème. il faudrait fabriquer le "garbage collector" !!! essais avec largeurs légérement différentes: 1088*16384 6 19386521 760*16384 7 16859487 (pas très convaincant) mercredi: je passe à sp28, j'essaie d'adapter le source de Zaleski. Il semble utiliser la même technique d'equations que celle à laquelle j'ai conclu, c'est à dire : XOR à la fin, constitution du masque de XOR au fur et à mesure. c'est une technique assez intelligente qui n'est pas évidente pour un compilo VHDL ou les tables de Karnaugh, d'où la supériorité de l'homme sur la machine :-) Un compilo n'est pas assez intelligent pour détecter la réversibilité de certaines configurations, et le bénéfice immense du XOR. Le fait d'inclure un programme GPLé dans le mien rend le mien aussi GPL de fait. maintenant au moins, j'ai l'étiquette GPL rajoutée dans l'en-tête. C'est officiel, non seulement je suis ouvert mais en plus j'en ai l'étiquette. Je crois que je viens de comprendre le sens de "collision_left" et "collision_right": c'est pour savoir la chiralité de la collision. <confirmation Zaleski> Par contre, il n'utilise pas la même table de collision : la collision triangulaire débouche sur une autre configuration triangulaire. tout le travail est donc à refaire. Au moins, je sais que ma piste n'est pas miteuse. Equation en 0-x-x: ça a l'avantage d'être assez compact. problème: faire un generateur de nombres aléatoires à 3 sorties dont une seule est active à la fois. Je n'arrive pas à réduire la collision à un cas où 2 sorties sont possibles. Relecture des notes de jusssieu, page 3': Cs (vitesse du son) = sqrt (3/7) = 0,654653... unités par pas de temps. Re* = 2,220 Fmax=0,285 (densité optimale) or, 0,285 est très proche de 2/7 (au millième), ce qui veut dire que 2 particules par site sont nécesaires. Je cherchais une fraction simple pour initialiser le fluide mais c'est encore plus évident que je ne l'avais pensé :-))) le truc est donc de mettre deux fois plus de particules que de sites, et ça tournera à plein régime à 2,2 Re par cellule. g=invariance galliléenne, 7(1-2F)/12(1-F), F=2/7-> g=7(1-(4/7))/12(1-(2/7)) = 7(3/7)/12(5/7) = 3/(79/7)=21/79=0.2658... bon, là je suis paumé car je ne sais pas dans quelle dimension s'exprime cette valeur... Lecture complète effectuée, il apparait que c'est le coefficient de transport des turbulences... il faut s'approcher de g=1 pour simuler un fluide réel, mais cela force à ralentir fortement l'efficacité du modèle ! une solution existe avec un modèle à 3 particules au repos et atteint g=1,07 (7% de fiabilité, wow), et même 1 pour d=0,2. mais le Re chute alors terriblement. Samedi matin Je pense qu'on peut fabriquer un modèle FHP3 avec une meilleure invariance en reprenant le tableau et en "favorisant" la création des particules immobiles par rapport à leur "destruction". En clair cela se traduit par le fait que les transitions qui "créent" des particules immobiles soient plus nombreuses que celles qui les détruisent. Le problème est alors de "choisir" (il n'y a jamais de choix) quelles collisions vont les détruire, ou comment (nombre aléatoire ?). Je crois aussi que la table des collisions (6 lignes et 3 colonnes) donne aussi la solution de comment faire les lois pour le modèle à 3 particules immobiles. Mais pour l'instant j'en suis à rechercher les invariants dans le modèle à 1 particule immobile. les collisions 0-x-x sont mieux connues. Elles modifient toutes 4 particules, selon 2 cas de figures: -2 paires opposées dans le cas d'une collision binaire-binaire -3 particules consécutives et une particule immobile dans le cas d'une collision binaire-ternaire. On retrouve ce deuxième cas de figure dans les collisions 1-x-x donc je ne peux pas commencer la programmation. Il m'est aujourd'hui évident que ce deuxième cas "xor ternaire" est typique d'une collision avec échange d'une particule immobile. les xor quaternaires sont des "sauts dans la même case". Reste à comprendre pourquoi cette configuration-là et pas une autre. En tous cas, une chose est maintenant sûre : il y a toujours 4 particules qui changent de direction. ce qui veut dire que trois ne sont pas déviées, quel que soit le cas de figure. C'est important car on peut diminuer à coup sûr le nombre d'opérations SI on considère les particules qui ne sont pas déviées, au lieu des particules déviées. Cela veut dire qu'il faudra inverser le masque de XOR avant de l'appliquer sur les données. Cela rajoute quelques instructions en assembleur à la fin du "calcul" mais j'espère réduire d'un quart sa lourdeur. J'espère ne pas m'être trompé car l'enjeu est immense. J'ai appris cette nuit qu'on pouvait faire des écoulements alternatifs de Von Karman avec une paroi oblique. Cela m'évite la fabrication d'un cercle mais me force quand même à faire une ligne oblique. De toutes façons, la variance galliléenne est trop forte pour faire un écoulement correct de ce type. J'ai décidé de sacrifier une fraction de chiralité pour simplifier les collisions sortantes des 0-3-x. Il faudra aussi une version paire et impaire. Il faut réexaminer toutes les équations sur le papier. Toutes les équations programmées devront être testées en vrai sur l'ordinateur... pour vérifier qu'elles fonctionnent bien comme prévu. J'ai l'impression que le papier à listing n'est plus assez grand, qu'il me faudra des rouleaux de papier peint ou de nappes en papier pour contenir la quantité de choses à écrire... Durant le développement de ce logiciel, j'ai développé aussi une méthode de débogage itérative. La première chose à faire est d'isoler la partie "fautive", sans laquelle aucune erreur se produit. De nombreux essais peuvent être nécessaires mais on est sûr de trouver. La deuxième partie est de trouver le "POURQUOI" après le "QUOI": une analyse du flot d'instructions et des dépendences de données (quelle est la valeur de chaque registre à chaque moment) permet dans la plupart des cas de trouver la partie fautive. Parfois, en "remontant" les instructions, on arrive à trouver la vraie raison, qui fait qu'un code éprouvé ne se comporte pas comme il devrait. Une grande partie des causes de ces bugs sont de vraies erreurs d'inattention (la moitié). samedi soir: Maryvonne Lebrun: >Quelles sont les applications que tu as en vue? (AERONAUTIQUE?). >Sais tu que dans mon domaine on manque de logiciels pour calculer les >souffles d'explosion "a cote" de l'axe. (ca forme theoriquement un coeur). >Cela conduit a penaliser les industriels dans bien des cas. >Si jamais les "explosions" t'interessent je pourrais de donner des >coordonnees de gens qui font ca. C'est vrai qu'on peut simuler des explosions. j'en avais simulé plein dans mes premiers programmes. C'était même assez amusant :-) Pour la collision : à la fin: [A]^= ~xorA [B]^= ~xorB [C]^= ~xorC [D]^= ~xorD [E]^= ~xorE [F]^= ~xorF [G]^= ~xorG en asm MMX: (remplacer ? par la direction ou un numéro de registre) pcmpeqb mm?,mm? ; met mm? tout à 1 pxor mm?, mm?? ou [xor?] ; mm? contient le masque inversé pxor mm?, [?] movq [?],mm? Autre solution : partir d'un masque XOR entièrement à 1 et "enlever" des bit à chaque fois. mais ça ne fait que "repousser" le problème puisqu'il faut mettre les masques à 0. Mais vu qu'il faut aussi le mettre à 0 au début..... Remarque : pour mesurer les collisions, le OR de 3 masques XOR opposés (disons A, C et E) est suffisant (au lieu de 7 OR). COL=xorA | xorC | xorE entrelacé avec la modification des masques : movq mm0,[xorA] ; charge A pcmpeqb mm4,mm4 ; mm4=11111.... movq mm1,[xorC] ; charge C movq mm3,mm0 ; crée le compteur de collisions movq mm2,[xorE] ; charge E pxor mm0,mm4 ; inverse le masque A por mm3,mm1 ; change le compteur de collisions pxor mm0,[A] ; modifie A pxor mm1,mm4 ; inverse le masque C por mm3,mm2 ; change le compteur de collisions pxor mm1,[C] ; modifie C pxor mm2,mm4 ; inverse le masque E movq [A],mm0 ; écriture de A, mm0 est maintenant libre movq mm0,mm4 ; mm0=1111 pour Xor futur avec la mémoire movq [col],mm3 ; sauve le compteur de collisions movq mm3,mm4 ; mm4=1111 pour Xor futur avec la mémoire pxor mm2,[E] ; modifie E ;ici: mm3=mm4=mm0=1111 ; sauver mm1 (C), mm2 (E) ; puis traiter B,D,F et G movq [C],mm1 movq mm1,mm4 pxor mm4,[xorB] ; ici il n'y a plus rien à entrelacer :-( movq [E],mm2 pxor mm0,[xorD] pxor mm4,[B] pxor mm3,[xorF] pxor mm0,[D] movq [B],mm4 pxor mm1,[xorG] pxor mm3,[F] pxor mm1,[G] movq [D],mm0 movq [F],mm3 movq [G],mm1 32 instructions, 5 registres utilisés. La difficulté de ce code est de ne pas mettre plus d'un accès à la mémoire par paire d'instructions afin que le PMMX soit content. Mais à la fin, il n'y a plus d'autre choix, il n'y a plus rien qui n'accède pas à la mémoire pour entrelacer. Les règles de codage importantes ici sont (autant que possible): -1 un seul accès à la mémoire par paire d'instructions (à la fin, il n'y a pas d'autre chose à faire, donc on abandonne) -2 repousser les dépendances de registres au moins à 2 instruction, plus pour les écritures de registres. Il faut voir le code qui viendra avant pour trouver des valeurs existant déjà dans les registres et enlever quelques accès à la mémoire. On peut commencer par les collisions quaternaires 0-2-x-/R et 0-4-x-R qui sont très simples à coder: tout se passe entre paires. la particule immobile ne change pas, donc il faut l'inverser ;-) Il reste donc une paire à modifier (celle qui ne change pas). Elle est choisie au hasard entre deux configurations. Le générateur de nombres aléatoires est finalement assez rustique : un simple registre 64 bits qui est changé à chaque calcul par une simple addition. on peut initialiser le registre par l'horloge si on veut. Le fait d'additionner une cellule (n'importe laquelle, A par exemple) au compteur lui fait très vite donner des valeurs incohérentes, pas besoin d'utliser de congruence ! Un examen papier de 0-x-x conclut pour les collisions quaternaires: NG=!G ; inverse la particule immobile ; 1) paires opposées identiques: PA=A^D PB=B^E PC=C^F SYM=!(PA + PB + PC) ; 1 s'il y a 3 paires Si SYM <> 0 continuer: ; 2: il faut deux paires à 1: PA=(A^NG)& SYM PB=(B^NG)& SYM PC=(C^NG)& SYM SYM= !(PA^PB^PC) & (PA|PB) ;SYM=1 si une collision quaternaire est valide, ;on détecte le cas où deux (seulement) paires sont à 1 ;(cas 0,1,3 ignorés car non quaternaires). ; 3: sortie: l'algorithme est une sélection séquentielle. XORG=SYM ; ne pas modifier la particule immobile... R=RANDOM XORA=XORD= R & A & SYM R^=A XORB=XORE= R & B & SYM R^=B XORC=XORF= R & C & SYM 26 opérations booléennes pour 6 collisions (sans compter tous les mouvements...) ça fait 4 opérations par collision :-/ Je crois que ça suffira pour l'instant. Demain je m'attaque à la réduction des équations ternaires. Avant ça, un test en réel de l'equation quaternaire serait utile... (SP29.asm) Lundi matin: les collisions quaternaires fonctionnent ! il a fallu aménager la règle initiale car elle n'était pas exacte mais ça y est. Le code reste en chantier (je dirais même: le bordel) pour permettre le développement de l'algorithme dans des conditions acceptables, on gagnera des cycles plus tard (au moins la moitié !). Le code: 1) paires opposées identiques: ------------------------------ PA=A^D PB=B^E PC=C^F ;charge les données movq mm0,[edi+A] movq mm1,[temp_B] $base1__ equ $-4 movq mm2,[temp_C2] $base1__ equ $-4 movq mm3,[edi+D] movq mm4,[edi+E] movq mm5,[edi+F] pcmpeqb mm6,mm6 ; mm6=-1 pxor mm3,mm0 ; PA=mm3 pxor mm4,mm1 ; PB=mm4 pxor mm5,mm2 ; PC=mm5 SYM=!(PA + PB + PC) = 1 s'il y a 3 paires identiques por mm3,mm4 por mm3,mm5 pxor mm3,mm6 ; NOT mm3 NG=!G : inverse la particule immobile pxor mm6,[edi+G] ; mm6=1 xor G 2: il faut deux paires à 1: (cas 0,1 et 3 inintéressants) --------------------------- PA=(A^NG)& SYM PB=(B^NG)& SYM PC=(C^NG)& SYM pxor mm0,mm6 pxor mm1,mm6 ; ^NG pxor mm2,mm6 pand mm0,mm3 pand mm1,mm3 ; & SYM pand mm2,mm3 SYM= !(PA^PB^PC) & (PA|PB) movq mm4,mm0 movq mm5,mm0 pxor mm4,mm1 ; mm4=PA^PB por mm5,mm1 ; mm5=PB|PA pxor mm4,mm2 ; mm4=PA^PB^PC pandn mm4,mm5 ; SYM=mm4 Si SYM <> 0 continuer... SYM=1 si une collision quaternaire est valide, on détecte le cas où deux (seulement) paires sont à 1 (cas 0,1,3 ignorés car non quaternaires). ici: mm0=PA mm1=PB mm2=PC mm3= mm4=SYM mm5= mm6=NG (inutilse) mm7= 3: sortie: l'algorithme est une sélection/élimination séquentielle. ---------- XORG=SYM ; ne pas modifier la particule immobile... (????) R=RANDOM SYM=!SYM XORA=XORD= (R & PA) | SYM R^=PA XORB=XORE= (R & PB) | SYM R^=PB XORC=XORF= (R & PC) | SYM pcmpeqb mm7,mm7 ; mm7 = -1 pxor mm4,mm7 movq [xorG],mm7 $base1__ equ $-4 movq mm5,mm0 ;r1=mm5 movq mm3,[RANDOM];mm3=rand $base1__ equ $-4 pand mm5,mm3 ;and r1,rand movq mm6,mm1 ;r2=B pxor mm3,mm0 ;xor rand,A por mm5,mm4 ;or r1,sym movq mm7,mm2 ;r3=C movq [xorA],mm5 ;mov [xorA],r1 $base1__ equ $-4 pand mm6,mm3 ;and r2,rand movq [xorD],mm5 ;mov [xorD],r1 $base1__ equ $-4 por mm6,mm4 ;or r2,sym pxor mm3,mm1 ;xor rand,B movq [xorB],mm6 ;mov [xorB],r2 $base1__ equ $-4 pand mm7,mm3 ;and r3,rand movq [xorE],mm6 $base1__ equ $-4 por mm7,mm4 ;or r3,sym movq [xorC],mm7 $base1__ equ $-4 movq [xorF],mm7 $base1__ equ $-4 J'ai remarqué que des particules "traversent" les parois, je ne comprends pas exactement pourquoi mais elles le font. Il faut placer le déplacement des particules autre part ! SP30 tente de résoudre ce problème. La solution la plus simple et la plus radicale est d'interdire les collisions avec réarrangement (le "calcul") avec un masque (la "somme" des masques de modification). Avec un léger aménagement, le programme fonctionne très bien. Il faudra seulement penser à faire le ménage après, et il y aura du travail ! Ce qui me chagrine c'est que je ne peux pas directement tester les collisions 0-4-x, car la création des configurations de particules est un peu difficile en assembleur... Alors je mets plein de particules G et je regarde ce qui se passe ! L'expérience consiste à saturer le tunnel de particules G et mettre beaucoup de particules mobiles, puisque les collisions 0-4-x ne s'effectuent qu'à haute densité. L'affichage des particules G est désactivé. En quelques temps de circulation, l'écran est complétement noyé dans le brouillard, sans effet de bord visible. J'estime cela suffisant pour continuer. SP31: On passe au reste ! J'ai mis en place un "banc d'essais" des collisions. il faut encore les tester une à une et réassembler à chaque fois mais au moins ça marche, sans empirisme. Les collisions quaternaires (0-2-x <->0-2-x et 0-4-x <-> 0-4-x) sont validées. SP32: ready Mardi: le jour où le Bonto a été réinventé. Les collisions quaternaires étant en place, il faut se soucier des collisions ternaires 0-2-x-R <-> 0-3-x <-> 0-4-x-/R. les états allant vers 0-3-x sont simples, il y a deux solutions et le générateur de nombres aléatoires est adapté. Le problème est que les collisions partant vers 0-2-x et 0-4-x ont chacune 3 possibilités !!!!! Je me suis refusé à baisser les bras devant un problème aussi simple, car l'option de "briser" la symétrie du modèle est désagréable à penser. La première solution pour faire 3 sorties est "statique"; elle utilise les lignes paires et les lignes impaires, dans lesquelles on a: lignes paires: 0-3-1 -> 0-x-1 0-3-2 -> 0-x-2 OU 0-x-3 lignes imppaires: 0-3-1 -> 0-x-2 0-3-2 -> 0-x-1 OU 0-x-3 <arbre binaire> La programmation est simple mais le résultat est assez mauvais, car il ne correspond pas aux cas étudiés dans les équations. J'étais presque parti pour le mettre en place lorsque j'ai repensé à la solution que j'avais déjà mise en place pour l'élimination/sélection progressive pour la sortie des collisions quaternaires. Celà reviendrait en premier lieu à sélectionner/éliminer une première partie des sorties par un masque aléatoire: on obtiendrait A=1 B=0 C=1 ou A=0 B=1 C=0 selon le masque (0 ou 1). Ensuite, avec une autre valeur aléatoire (un deuxième masque) on sélectionnerait une seule sortie s'il en reste plusieurs: Si B=0 alors A=masque et C=/masque. Le problème est que statistiquement, B a une chance sur 2 d'apparaître alors que A et C ont une chance sur quatre d'être tirés. <arbre binaire> Une autre solution est d'inverser le rôle de A et B (ou B et C) selon la parité de la ligne ou un troisième nombre aléatoire. <2 arbres binaires> Dans ce cas, une sortie aura 1 chance sur 4 d'être sélectionnée alors que les deux autres sorties auront 3 chances sur 8. mauvais. <HT width=50> Il faut se rendre à l'évidence, on ne peut pas faire un 3 avec une combinaison de 2. 2<>3, 2^2=4<>3, 2^3=8<>9, donc tout tirage ne peut donner qu'une combinaison de 2. Il faudrait introduire un 3 qui viendrait de quelque part (afin de rendre le tout multiple de 3) mais notre équation ne permet pas de trouver de combinaison de 3 sorties dont une SEULE est à 1, car si cette règle d'exclusion n'est pas respectée on retombe encore dans le cas polinômial ! Une voie possible est de travailler en base 3, mais ce domaine est riqué puisqu'on risque de retomber dans le cas polinômial si on ne fait pas attention. En plus, les équations booléennes permettant de travailler dans cette base où deux bits codent 3 possibilités sont plus compliquées que nécessaire. Comme 3 n'est pas un grand nombre, on peut cependant travailler avec trois variables dont une seule est à 1. Ce sera notre générateur aléatoire. Le problème, maintenant que le nombre est défini, c'est d'avoir une suite aléatoire de nombres. Et c'est là que la situation se dénoue : c'est une suite qu'il nous faut. Les 3 nombres dont un seul est à 1 peuvent être "décalés", subir une rotation, selon la valeur du nombre aléatoire, dont la valeur est 0 ou 1. Si le nombre est 1, alors le nombre A passe dans B, B devient C et C devient A, cycliquement. Un peu comme dans le jeu de "bonto" (merci maman !) <dessin de rotation de bits> sauf que l'ordre de commutation n'est pas important. Ce qui compte c'est que la valeur du "vecteur" A,B,C soit inconnue pour tout pas de temps N>3 (qui est une valeur de "démarrage" très raisonnable). Evidemment, il reste la possibilité de prévoir avec une chance sur deux la valeur du "nombre" en connaissant la valeur précédente, une chance sur deux de connaitre la valeur en connaissant la valeur N-2, mais pour des temps très grands, il est impossible de prévoir cette valeur si le générateur de nombres bianires est imprévisible. Cela vient du fait que la probabilité de chaque valeur ne dépend plus d'un arbre binaire de profondeur limité (correspondant à une fraction), mais la probabilité dépend de la manière dont est fait l'échantillonnage. Si l'échantillonnage est fait après un "long" temps N, alors la fraction se rapprochera, "tendra" vers une approximation de 1/3. On a résolu le problème de statistique en introduisant le temps, qui a priori n'est pas connu (statistique), donc lui aussi imprévisible. D'ailleurs, cette "aventure" pose une question: L'infini est-il divisible par trois ? Quelle que soit la réponse, on a obtenu un algorithme simple et adaptable pour d'autres cas. Ses propriétés sont satisfaisantes a priori pour être utilisé dans le modèle FHP. pseudocode: int A,B,C,RANDOM,A2,B2,C2 A2=A B2=B C2=C A=(B2 & RANDOM) | (A2 & !RANDOM) B=(C2 & RANDOM) | (B2 & !RANDOM) C=(A2 & RANDOM) | (C2 & !RANDOM) c'est simple, non ? c'est comme si on faisait une rotation bit à bit, avec un nombre de rotation de 0 ou 1. Cette rotation peut s'effectuer assez rarement, pour chaque trame par exemple. en asm MMX: 1) charger RANDOM movq mm0,[RANDOM] 2) le recopier dans 3 registres pour faire un PANDN: movq mm1,mm0 movq mm2,mm0 movq mm3,mm0 3) charger les valeurs initiales: movq mm4,[RAND_A] movq mm5,[RAND_B] movq mm6,[RAND_C] 4) les masquer avec PANDN pour avoir (?2 & !RANDOM) pandn mm1,mm4 pandn mm2,mm5 pandn mm3,mm6 5) masquer les originaux avec RANDOM (? & RANDOM) pand mm4,mm0 pand mm5,mm0 pand mm6,mm0 6) résultat: OR et entrelaçage por mm4,mm2 por mm5,mm3 por mm6,mm1 7) écriture du résultat movq [RAND_A],mm4 movq [RAND_B],mm5 movq [RAND_C],mm6 Les dépendances de registres sont fortes. il faut réécrire et réorganiser les instructions. L'allocation des registres semble toutefois correcte. Remarque: les performances du programme diminuent un peu, le STRIP aussi, à cause du plus grand nombre de variables temporaires utilisées, qui prennent de la mémoire. Il faudra trouver une solution à ce problème. Réécriture du code de rotation: il y a un problème : les accès à la mémoire. sur PII on ne peut faire d'écriture qu'une fois par "paquet", toutes les 3 isntructions. sur PMMX on ne peut accéder à la mémoire que toutes les 2 instuctions. Or il y a 3 accès à effectuer successivement par "groupe". J'espère que le code où ce morceau sera mis aura des instructions à entrelacer. D'abord, rechercher le "chemin critique" : c'est la plus longue chaine de dépendances. Ensuite, les autres instructions seront entrelacées dans les parties restantes, où les dépendances de registres laissent de la place. L'analyse papier n'a pas trouvé de problème particulier avec le code ci-dessus, on va donc le garder pour l'instant, avec quelques entrelaçages: ;******************************* ; RANDOM NUMBER GENERATOR UPDATE: ;1) load RANDOM movq mm0,[RANDOM] $base1__ equ $-4 ;2) copy before PANDN: movq mm1,mm0 ;3) load the initial values: movq mm4,[RAND_A] $base1__ equ $-4 movq mm2,mm0 ;4) mask out with PANDN (x1 & !RANDOM) pandn mm1,mm4 movq mm5,[RAND_B] $base1__ equ $-4 ;5) (x2 & RANDOM) pand mm4,mm0 movq mm3,mm0 movq mm6,[RAND_C] $base1__ equ $-4 pandn mm2,mm5 pand mm5,mm0 ;6) result por mm4,mm2 pandn mm3,mm6 pand mm6,mm0 ;7) write the result movq [RAND_A],mm4 $base1__ equ $-4 por mm5,mm3 por mm6,mm1 movq [RAND_B],mm5 $base1__ equ $-4 movq [RAND_C],mm6 $base1__ equ $-4 ;test: movq [dword 0],mm4 randa equ $-4 movq [dword 0],mm5 randb equ $-4 movq [dword 0],mm6 randc equ $-4 La dernière partie affiche les résultats à l'écran. après un certain temps (pour que le premier générateur de nombres sans queue ni tête se mette en marche) le résultat semble satisfaisant. Il faut compter une trentaine de pas (mais pas les pas de temps, car un appel à la procédure de calcul calcule plusieurs pas de temps). Question: dans quelle mesure l'algorithme "bonto" influence la chiralité du modèle ? Je ne pense pas qu'il y ait une influence quelconque puisque si on peut peut-être connaitre la valeur du nombre aléatoire en fonction de sa valeur précédente, il n'y a aucun rapport entre ce générateur et les valeurs qu'il modifie. En effet, le nombre aléatoire n'est pas dépendent des 8 configurations de particules initiales qu'il modifie... Mercredi matin, SP33: Je commence à voir comment les collisions restantes de 0-x-x peuvent être programmées. La tâche est un peu plus compliquée car il y a plus de paramètres à prendre en compte. D'abord il faut reconnaître les 0-2-x-R et 0-4-x-/R: ils ont en fait déjà reconnus presque totalement par une opération effectuée précédemment pour les collisions quaternaires: NG=!G ; inverse la particule immobile ; 1) paires opposées identiques: PA=A^D PB=B^E PC=C^F SYM=!(PA + PB + PC) ; 1 s'il y a 3 paires ici on rajoute: SYM3=A^B & B^C & PA & PB & PC pour détecter 0-3-x ; 2: il faut deux paires à 1: PA=(A^NG)& SYM PB=(B^NG)& SYM PC=(C^NG)& SYM SYM= !(PA^PB^PC) & (PA|PB) Lundi 12 juillet 1999: Au prix d'une astuce, je crois que les collisions 1-x-x peuvent être simplifiées à 3 rotations de 3 cas. L'astuce pas encore trouvée consiste à savoir si au moins 3 particules sur les 6 possibles sont présentes. En fait on "plie" les combinaisons 1-4-x et 1-5-x vers 1-2-x et 1-1-x. On inverse le sens de 000000 >=3 interessant 000001 000010 000011 000100 000101 000110 000111 1 001000 001001 001010 001011 1 1 001100 001101 1 1 001110 1 001111 1 010000 010001 010010 010011 1 1 010100 010101 1 Y 010110 1 1 010111 1 011000 011001 1 1 011010 1 1 011011 1 011100 1 011101 1 1 011110 1 011111 1 1 100000 100001 100010 100011 1 100100 100101 1 1 100110 1 1 100111 1 101000 101001 1 1 101010 1 Y 101011 1 1 101100 1 1 101101 1 101110 1 1 101111 1 1 110000 110001 1 110010 1 1 110011 1 110100 1 1 110101 1 1 110110 1 110111 1 1 111000 1 111001 1 111010 1 1 111011 1 1 111100 1 111101 1 1 111110 1 1 111111 1 le problème est de trouver l'equation donnant 1 au moins pour les cas présentés. Je crois qu'un comptage partiel et judicieux devrait faire l'affaire mais je ne sais pas exactement comment. Je sais aussi qu'il faut reprendre les equations 0-2-x et 0-4-x afin d'intégrer dedans les collisions allant vers 0-3-x. Mardi 18 juillet 5H20 du matin: je viens de "résoudre" l'équation permettant de trouver la "majorité" de 6 nombres. seuls les cas où il y a 4, 5 ou 6 bits à 1 mettent le résultat à 1. L'algo de base est un "population count" à base de "half adder" (AND et XOR). la somme de 6 nombres de 1 bits est effectuée et seul le MSB est gardé. l'arbre de dépendence a montré que deux half adders peuvent être parallélisés dans la limite des registres. on est juste en dessous de la limite des 8 registres mais ça tient bien. il y a cependant "l'amorçage" avec ses 6 chargements de mémoire qui peuvent poser problème mais ce n'est pas une surprise. l'algo a été optimisé de manière "absolue", sans trop tenir compte des particularités de chaque processeur sauf pour la mémoire. je ne pense pas qu'il y ait d'algo meilleur car l'arbre de dépendences a été analysé avec soin et toute dépendence a été repoussé aussi loin que possible. je ne pense même pas qu'un compilo fasse (beaucoup) mieux. Il y a en tout 32 instructions : movq mm2,[A] movq mm1,[B] movq mm6,mm2 movq mm5,[C] movq mm7,mm1 movq mm3,[D] pxor mm2,mm5 pxor mm1,mm3 movq mm0,[E] pand mm5,mm6 movq mm4,[F] pand mm3,mm7 ;Half Add stage 2: movq mm7,mm2 movq mm6,mm0 pxor mm2,mm1 pxor mm0,mm4 pand mm1,mm7 pand mm4,mm6 ;HA 3: movq mm6,mm1 pand mm2,mm0 pxor mm1,mm5 pand mm5,mm6 ;HA 4: movq mm6,mm1 movq mm7,mm4 pand mm1,mm3 pand mm4,mm2 pxor mm2,mm6 pxor mm3,mm7 ;OR: por mm1,mm4 pand mm2,mm3 por mm1,mm5 por mm1,mm2 MM1 contient : Sigma(bits)>3 (4,4,6) mercredi 3H du matin J'ai "unifié" les collisions ternaires et quaternaires de 0-x-x (sans les collisions sortantes des triangulaires). Ce n'est pas évident mais vu que j'y suis arrivé... il suffit d'y aller un pied devant l'autre. mais vu qu'il faut aussi souvent reculer, ça ressemble plus à une danse. Je suis reparti des 0-2-x-/G et 0-4-x-G en logique positive. j'ai "mélangé" les parties communes de 0-2-x-G et 0-4-x-/G, qui ont des caractéristiques similaires, entre autres le XOR des entrées par la particule G pour profiter de la "dualité". finalement, à la fin du calcul il faut utiliser des entrées "inversées" car les deux type de code G et /G les inversent. donc il faut revoir l'équation en considérant les entrées inversées dès le début. Comme pour la première version, on inverse simplement G et le tour est joué. Autre difficulté : le faible nombre de registres. On est obligé d'utiliser quelques variables en mémoire pour faciliter le travail et garder autant que possible les variables importantes en registre. PA, PB et PC sont les meilleurs candidats car leur utilisation est indépendente et successive. Finalement, les deux algorithmes de modification de la fin sont: - pour 02x/G et 04xG, l'algorithme d'élimination/sélection progressive décrit plus tôt. Problème : il faut aussi inverser le résultat. - pour 02xG et 04x/G, le principe est plus simple : une seule paire est 'active' à la fois, il suffit de sélectionner un des éléments de la paire au hasard et de l'envoyer sur le XORx correspondant. Formule de base: G^=1 A^=G B^=G C^=G D^=G E^=G F^=G PA=A&D PB=B&E PC=C&F X1=A^D+B^E+C^F (paires identiques) X2=PA^PB^PC (parité : cas g ou /g) Q=/(B.C).X2.X1 (collisions ternaires: 1 paire à 1) T=/X2.(B+C).X1 (2 paires à 1) XORG=T T1=RAND & T T2=/RAND & T PA: P1=/(PA & RAND) & Q RAND^=PA XORA=T1 & PA XORD=T2 & PA PB: P2=/(PB & RAND) & Q RAND^=PB XORB=T1 & PB XORE=T2 & PB PC: P1=/(PC & RAND) & Q XORC=T1 & PC XORF=T2 & PC Il faut encore fabriquer la fomule pour 0-3-x-x puis l'intégrer dans la formule ci-dessus, avant de construire l'arbre de dépendence qu'il faudra ensuite "plier" pour faire tenir tout ça dans 8 registres... 0-3-x-x: DELTA=(A^D & B^E & C^F) & (A^B & B^C) XORG|=DELTA XORA|=DELTA & RAND1-3 & A XORD|=DELTA & RAND1-3 & /A XORB|=DELTA & RAND2-3 & /A XORE|=DELTA & RAND2-3 & A XORC|=DELTA & RAND3-3 & A XORF|=DELTA & RAND3-3 & /A Mais comme les entrées (A,B,C,... même G) sont inversées, je ne suis pas sûr de la "polarité" de A. J'utilise donc le A d'origine. Cette formule s'insère assez facilement dans la précédente mais la fait grossir assez conséquemment. Elle était déjà compliqué mais elle l'est encore plus maintenant. Si une erreur apparait expérimentalement, elle sera difficile à détecter. G^=1 A^=G B^=G C^=G D^=G E^=G F^=G PA=A&D PB=B&E PC=C&F X1=A^D+B^E+C^F (paires identiques) X2=PA^PB^PC (parité : cas g ou /g) DELTA=(A^D & B^E & C^F) & (A^B & B^C) Q=/(B.C).X2.X1 (collisions ternaires: 1 paire à 1) T=/X2.(B+C).X1 (2 paires à 1) XORG=T | DELTA T1=RAND & T T2=/RAND & T PA: P1=/(PA & RAND) & Q RAND^=PA XORA=(T1 & PA) | (DELTA & RAND1-3 & A) XORD=(T2 & PA) | (DELTA & RAND1-3 & /A) PB: P2=/(PB & RAND) & Q RAND^=PB XORB=(T1 & PB) | (DELTA & RAND2-3 & /A) XORE=(T2 & PB) | (DELTA & RAND2-3 & A) PC: P3=/(PC & RAND) & Q XORC=(T1 & PC) | (DELTA & RAND3-3 & A) XORF=(T2 & PC) | (DELTA & RAND3-3 & /A) En reconsultant mes notes, je me suis rappelé d'un détail: la formule de 0-3-x-G est mauvaise, car il faut XORer A avec G. G^=1 A^=G B^=G C^=G D^=G E^=G F^=G PA=A&D PB=B&E PC=C&F X1=/(A^D+B^E+C^F) (paires identiques) X2=PA^PB^PC (parité : cas g ou /g) DELTA=(A^D & B^E & C^F) & (A^B & B^C) Q=/(B.C).X2.X1 (collisions ternaires: 1 paire à 1) T=/X2.(B+C).X1 (2 paires à 1) XORG=T | DELTA T1=RAND & T T2=/RAND & T DELTA1=DELTA & (A^G) DELTA2=DELTA & /(A^G) PA: P1=/(PA & RAND) & Q RAND^=PA XORA=(T1 & PA) | (DELTA1 & RAND1-3) XORD=(T2 & PA) | (DELTA2 & RAND1-3) PB: P2=/(PB & RAND) & Q RAND^=PB XORB=(T1 & PB) | (DELTA2 & RAND2-3) XORE=(T2 & PB) | (DELTA1 & RAND2-3) PC: P3=/(PC & RAND) & Q XORC=(T1 & PC) | (DELTA1 & RAND3-3) XORF=(T2 & PC) | (DELTA2 & RAND3-3) 68 opérations logiques environ, ce qui fait presque une centaine d'instructions. erreur /X1 corrigée (oubli d'inversion) ainsi que plusieurs fautes de frappe... 17H45: l'arbre de dépendences prend deux pages... il est 3 fois plus complexe que le popcount à 6 entrées. jeudi 2H du matin: j'ai réussi à écrire environ 60 instructions de 0-x-x-x en partant de la fin ("applatissement d'arbre" à la main). je souffre cruellement du manque de registres et du format à 2 opérandes : pour 33 instructions logiques, 29 instructions sont nécessaires pour "bouger" les données, dont 9 pour copier un registre dans un autre. 27 accès à la mémoire sont effectués, avec la quasi certitude de "toucher" la cache L1. Il me reste environ 26 opérations logiques à programmer, soit encore une cinquantaine d'instructions. 5H10 du matin : 108 instructions ! J'ai réussi ce pari. Le code n'est pas 100% parfait, probablement 98% mais le reste c'est du débugging, du chipottage et un peu de renommage de registres pour enlever une ou deux instructions. pcmpeqb mm0,mm0 ; 108 pxor mm0,[G] ;$base1__ equ $-4 movq mm7,[A] ;$base1__ equ $-4 movq mm3,[D] ;$base1__ equ $-4 pxor mm7,mm0 pxor mm3,mm0 movq mm4,mm7 pxor mm7,mm3 pand mm3,mm4 ; 100 movq mm2,[E] ;$base1__ equ $-4 movq mm6,mm7 movq mm1,[B] ;$base1__ equ $-4 pxor mm2,mm0 pxor mm1,mm0 movq [PA],mm3 $base1__ equ $-4 movq mm3,mm2 pxor mm4,mm1 pand mm2,mm1 pxor mm3,mm1 ; 90 movq mm5,[C] ;$base1__ equ $-4 por mm6,mm3 pand mm7,mm3 movq mm3,[F] ;$base1__ equ $-4 pxor mm5,mm0 pxor mm3,mm0 movq [PB],mm2 $base1__ equ $-4 movq mm2,mm3 pxor mm5,mm3 pand mm2,mm3 ; 80 por mm6,mm5 pand mm7,mm5 movq [PC],mm2 $base1__ equ $-4 movq mm5,mm3 movq mm3,mm0 ; some renaming to do since here.... pand mm4,mm7 pxor mm2,[PB] $base1__ equ $-4 movq mm0,mm1 pxor mm3,mm6 movq mm6,mm5 ; 70 pxor mm2,[PA] $base1__ equ $-4 pand mm1,mm5 pxor mm6,mm0 pand mm3,mm2 por mm5,mm0 pandn mm1,mm3 pand mm4,mm6 movq mm7,[RAND] $base1__ equ $-4 pandn mm2,mm5 ; 60 movq [Q],mm1 $base1__ equ $-4 movq mm5,mm7 movq mm6,mm2 movq mm1,[G] ;$base1__ equ $-4 pandn mm7,mm2 por mm6,mm4 pxor mm1,[A] ;$base1__ equ $-4 movq mm3,mm4 pand mm2,mm5 movq [T2],mm7 $base1__ equ $-4 pand mm4,mm1 movq [XORG],mm6 ; 50 $base1__ equ $-4 pandn mm1,mm3 movq mm3,[PA] $base1__ equ $-4 movq mm7,mm5 movq mm0,[RANDA] $base1__ equ $-4 pand mm7,mm3 movq mm6,mm3 pandn mm7,[Q] $base1__ equ $-4 pxor mm5,mm3 movq [P1],mm7 ; 40 $base1__ equ $-4 movq mm7,mm0 pand mm0,mm4 pand mm3,mm2 pand mm7,mm1 pand mm6,[T2] $base1__ equ $-4 por mm3,mm0 movq mm0,[PB] $base1__ equ $-4 por mm6,mm7 movq mm7,mm5 movq [XORA],mm3 ; 30 $base1__ equ $-4 pand mm7,mm0 movq [XORD],mm6 $base1__ equ $-4 pandn mm7,[Q] $base1__ equ $-4 movq mm3,[RANDB] $base1__ equ $-4 movq mm6,mm2 movq [P2],mm7 movq mm7,mm3 pxor mm5,mm0 pand mm6,mm0 pand mm3,mm1 ; 20 pand mm7,mm4 pand mm0,[T2] $base1__ equ $-4 por mm6,mm3 movq mm3,[RANDC] $base1__ equ $-4 por mm7,mm0 movq mm0,[PC] $base1__ equ $-4 pand mm1,mm3 movq [XORB],mm6 $base1__ equ $-4 pand mm3,mm4 movq [XORE],mm7 ; 10 $base1__ equ $-4 pand mm5,mm0 pand mm2,mm0 pand mm0,[T2] $base1__ equ $-4 por mm2,mm3 pandn mm5,[Q] $base1__ equ $-4 por mm0,mm1 movq [XORC],mm2 $base1__ equ $-4 movq [P3],mm5 $base1__ equ $-4 movq [XORF],mm0 ; 1 $base1__ equ $-4 6H30: J'ai repéré quelques fautes donc je doute que la formule fonctionne du premier coup... Sur PMMX, cette fonction devrait durer environ 60 ou 65 cycles, soit 1 cycle par cellule... 38 accès à la mémoire, soit un tous les deux cycles au moins. Pour l'essayer, il faudra mettre au point un cadre spécial pour visualiser les résultats P1, P2, P3, XORx etc PUIS modifier la partie de déplacement. A+ 18H45: SP34 en mise au point, mire binaire en place. j'ai modifié légérement l'utilisation de quelques variables : j'ai "réuni" P1 et PA, P2 et PB, P3 et PC. d'autres variables pourraient encore bénéficier de cette réduction de variable. Q=XORF, T2=XORC l'avantage de ce type de code linéaire c'est que s'il bugge, il a peu de chance de rebooter la machine :-) ce qui accélère le développement. 23H20: j'ai déjà repéré une erreur assez simple (propagation d'une mauvaise valeur). Une deuxième, moins facile à réparer, propage une autre mauvaise valeur, F au lieu de C. l'arbre doit être "touché" pour résoudre le problème. jeudi, 0h30: le problème a été résolu. DELTA et Q sont validés 1H10: T résolu. 3H10: eh bien non, T est inversé avec Q. j'ai repris les équations du départ et je n'avais pas pris en compte l'inversion des entrées. heureusement il n'y a pas beaucoup de choses à changer, mais quand même... cela correspond à une observation précédente mais que j'avais crue erronée. En plus, T et Q se confondent lorsque G est à 1... Ce dernier bug est plus gênant car l'origine n'est pas connue, probablement une mauvaise programmation. formule corrigée: G^=1 A^=G B^=G C^=G D^=G E^=G F^=G PA=A&D PB=B&E PC=C&F X1=/(A^D+B^E+C^F) (paires identiques) X2=PA^PB^PC (parité : cas g ou /g) DELTA=(A^D & B^E & C^F) & (A^B & B^C) T=/(B.C).X2.X1 (collisions ternaires: 1 paire à 1) Q=/X2.(B+C).X1 (2 paires à 1) XORG=T | DELTA T1=RAND & T T2=/RAND & T DELTA1=DELTA & (A^G) DELTA2=DELTA & /(A^G) PA: P1=/(PA & RAND) & Q RAND^=PA XORA=(T1 & PA) | (DELTA1 & RAND1-3) XORD=(T2 & PA) | (DELTA2 & RAND1-3) PB: P2=/(PB & RAND) & Q RAND^=PB XORB=(T1 & PB) | (DELTA2 & RAND2-3) XORE=(T2 & PB) | (DELTA1 & RAND2-3) PC: P3=/(PC & RAND) & Q XORC=(T1 & PC) | (DELTA1 & RAND3-3) XORF=(T2 & PC) | (DELTA2 & RAND3-3) maintenant, je me demande si je n'avais pas fait l'erreur en recopiant la formule l'autre jour... 4H15: comme un con, j'avais simplement oublié de recopier le code dans la deuxième moitié de la section... Gott sei dank, ç'aurait pu ête pire... 4H40: SP35.asm, im00000014.bmp la formule semble fonctionner, du moins pour la détection des conditions. on passe à la suite pour vérifier le fonctionnement plus en détail mais d'abord il faut modifier à fond la procédure de déplacement. 6H15: heureusement que j'ai le générateur de tables car j'en suis à 262 labels ! j'ai été forcé d'utiliser $base2__ car $base__ et $base1__ ont atteint la centaine. en contrepartie, j'ai une belle faute de protection... à première vue, elle n'est pas dûe à une erreur de frappe d'un label. elle se produit dans la partie de calcul, je vais tout simplement isoler le problème. 7H: SP36.asm la GPF semble produite dans le code de déplacement. -la GPF est produite dans la partie des lignes paires (enfin, la deuxième copie). la première partie fait des zolis dessins :-) l'examen du code a révélé un label mal placé (après une instruction qui n'accédait pas à la mémoire), ainsi que quelques oublis de labels et un mauvais registre modifié, ce qui explique les zolies zébrures... Maintenant, les particules semblent ne pas se soucier des parois. je crois que c'est dû à la mauvaise utilisation de WALLMASK. comme il reste un registre disponible, on va s'en occuper... 7H30: les murs ne sont pas complétement résolus. des particules semblent même disparaitre. du boulot en perspective... 8H30: j'ai trouvé une petite erreur de renommage de registre. il a fallu comparer terme à terme deux versions du programme (avant et après renommage) pour trouver l'erreur, principalement d'inattention, dans la section de déplacement. SP37 FONCTIONNE ! je procède aux essais des combinaisons de collisions. 9H: j'ai peur d'avoir à me taper les 128 combinaisons, mais il y a un problème dans le test de tenue de route : il y a des particules créées, ce qui sature le tunnel... :-/ 19H15: la "nuit" a porté conseil et je me suis souvenu que je n'avais pas testé les collisions DELTA->Q. elles sont effectivement en cause, car elles sortent dans le "mauvais sens". il faut inverser DELTA1 et DELTA2, ou tout simplement le A XOR G... mais ça ne dispense pas d'une analyse exhaustive des collisions. 19h40: SP37 est "stable", recopié vers sp38. quelque effets hydrodynamiques apparaissent mais il manque tout 1-x-x-x ! 23H30: j'ai fait fonctionner SP37 plusieurs heures et tout va bien. j'ai ensuite fait SP39: il montre les collisions, en passant par le buffer temporaire 2D. l'effet visuel est assez étonnant, proche d'aurore boréales :-) 13 aout 99, 4H30 : je me suis penché depuis longtemps sur 1-x-x. Cela passe par un additionneur (popcount) 6->3. le résultat peut être utilisé a peu de frais pour l'affichage des densités. Le vieux problème de QUELLE méthode d'"explosion" (passer de 64 bits à 64 octets) resurgit puisque: 1ère méthode : movq mm1,mm0 (mm0=debut) psrlq mm0,1 psrlb mm1,7[varie] (octet = 0 ou 1) padd mm1,[ptr] movq [ptr],mm1 prend 5 instructions, soit 40 pour les 64 octets, ou 20 cycles dans le cas idéal. Problème : il faut "désembrouiller" les octets à l'affichage car on les récupère dans cet ordre : paquet 0: 0 8 16 24 32 40 48 56 paquet 1: 1 9 17 25 33 41 49 57 paquet 2: 2 10 18 26 34 42 50 58 paquet 3: 3 11 19 27 35 43 51 59 paquet 4: 4 12 20 28 36 44 52 60 paquet 5: 5 13 21 29 37 45 53 61 paquet 6: 6 14 22 30 38 46 54 62 paquet 7: 7 15 23 31 39 47 55 63 La méthode d'explosion a l'avantage d'être très rapide mais nécessite un algorithme au moins aussi rapide pour réorganiser les octets lors de l'affichage. La deuxième méthode est plus expéditive : movq mm1,mm0 psrlq mm0,8 punpcklbw mm1,mm1 punpckldw mm1,mm1 punpckldq mm1,mm1 ;ici on a l'octet recopié 8 fois. ; il faut donc trier ici les bits : pand mm1,[=0x8040201008040201] pcmpeqb mm1,0 [reg ou mem] ici, on doit faire un masque 0x0101010101010101 mais on peut s'en passer si on fait uns soustraction car le résultat de pcmpeqb est 0 ou 255, ce qui épargne une instruction. Le problème est que la réorganisation ne se fait qu'une fois lors de l'affichage pour l'algorithme 1, ce qui est "amorti" par un grand nobre de passes de strip mining. En plus, cela utilise 2x moins d'instructions et de registres que la version 2. Quel est le code qui peut changer ces foutus octets le plus VITE possible ???? une solution inacceptable est de réorganiser les bits au niveau du tableau : l'algorithme de déplacement deviendrait infernal. Un algorithme de "base" c'est à dire idiot, manipulerait les octets un à un, ce qui nécessiterait probablement plus de 100 instructions, ce qui est absolument inacceptable. Je crois que comme pour le cas de l'affichage du curseur, les instructions punpkXXX peuvent nous aider beaucoup. les instructions punpckhbw et punpcklbw permettent d'obtenir : paquet 0: 0 1 16 17 32 33 48 49 \ paquet 1: 8 9 24 25 40 41 56 57 / paquet 2: 2 3 18 19 34 35 50 51 \ paquet 3: 10 11 26 27 42 43 58 59 / paquet 4: 4 5 20 21 36 37 52 53 \ paquet 5: 12 13 28 29 44 45 60 61 / paquet 6: 6 7 22 23 38 39 54 55 \ paquet 7: 14 15 30 31 46 47 62 63 / l'opération de base est : mm0=paquet(0+n*2) mm1=paquet(1+n*2) movq mm2, mm0 (copie temporaire) p'0=punpcklbw mm0,mm1 p'1=punpckhbw mm2,mm1 avec 8 isntructions d'opération, et probablement plus de "mouvement" car il n'y a pas assez de registres. ensuite avec punpckhwd et punpcklwd on peut avoir: paquet 0: 0 1 2 3 32 33 34 35 \ paquet 1: 8 9 10 11 40 41 42 43 \ paquet 2: 16 17 18 19 48 49 50 51 / paquet 3: 24 25 26 27 56 57 58 59 / paquet 4: 4 5 6 7 36 37 38 39 \ paquet 5: 12 13 14 15 44 45 46 47 \ paquet 6: 20 21 22 23 52 53 54 55 / paquet 7: 28 29 30 31 60 61 62 63 / et finalement punpckhdq et punpckldq permet d'obtenir le tableau désiré en 24 opérations, mais Dieu sait combien de copies de registres... on a pu passer de O(n) à O(log2(n)) au prix d'une belle prise de tête. Le choix est donc fait, sur le principe que la complexité doit être repoussée loin de la boucle principale. Il faut maintenant analyser l'arbre de dépendences pour déterminer le flot d'instructions. Une contrainte supplémentaire d'ajoute : les paquets doivent être affichés dans l'ordre afin que le processeur puisse constituer des "paquets" pour les "bursts" 256 bits du bus processeur. L'arbre de dépendences ressemble beaucoup à une FFT-radix8 : l'aspect log2 de l'arbre et la dépendence de chaque résultat à chaque entrée fait que le faible nombre de registres ainsi que les instructions à 2 opérandes rendent le travail désespérant, mais enfin on a vu pire... On doit utiliser des variables en mémoire, probablement 4 au moins. Pour commencer, on fait en radix-4 puisque le code est utilisé 2 fois, et ça tient dans les registres. les résultats du premier seront mis en mémoire et réutilisés à la fin. Cela correspond à une sorte de balayage en "zig-zag" des gros arbres. premier résultat pour radix-4 : movq mm0, [0] ** ; || movq mm4, [8] ||* * movq mm2,mm0 ||| |* movq mm1, [16] ||*||* punpcklbw mm0,mm4 |||||| * * movq mm3,mm1 |||| *| | movq mm5, [32] |||**|| | punpckhbw mm2,mm4 |||||||*| * punpcklbw mm1,mm5 |||||||*| punpcklbw mm3,mm5 |||||||* movq mm4,mm0 ||||||* movq mm5,mm2 ||||||* punpcklwd mm0,mm1 |||||| punpcklwd mm2,mm3 |||| punpckhwd mm4,mm1 || punpckhwd mm5,mm3 || movq [t0],mm0 movq [t1],mm2 movq [t2],mm4 movq [t3],mm5 radix 2: movq mm6, [40] movq mm7, radix-4 x 2 : on utilise les 2 registres restants pour assurer l'interim radix 2 commence par: movq mm6, [40] movq mm7, [48] on l'entrelace avec le code radix-4 en essayant de ralonger la "distance" moyenne entre les utilisations de registres tout en essayant de ne pas saturer l'unité de mémoire (1 accès par cycle seulement). la distance minimale est de 3 instructions. dès qu'un registre se libère, on continue le calcul: movq mm0, [0] ** ;cycle mort || movq mm4, [8] ||* * movq mm2,mm0 #|| |* movq mm1, [16] ||*||* punpcklbw mm0,mm4 ##|||| * * ---- long movq mm6, [32] * |||| | | ---- très long movq mm3,mm1 | #||| | * | movq mm5, [24] | |||*|*| | punpckhbw mm2,mm4 | ##|||||*| * --- assez long... movq mm7, [40] |* ||||||| | ---- très long aussi punpcklbw mm1,mm5 || ##|||||* *| movq mm4,mm0 || #|||||*|| ---- déplacé punpcklbw mm3,mm5 || ##||||||** movq mm5,mm2 || #|||||||* punpcklwd mm0,mm1 || * ##|||||| punpckhwd mm4,mm1 || |* ##|||| movq mm1,[48] ||*|| |||| punpcklwd mm2,mm3 |||||* ##|| ---- déplacé punpckhwd mm5,mm3 ||||||* ## movq mm3,mm6 #||||||* movq [t2],mm4 ||||#||| punpcklbw mm6,mm7 ##|| |||* * movq mm4,[56] ||| ||||* * | punpckhbw mm3,mm7 #|| ||#||*| | * movq [t1],mm2 || #| |||| | | movq mm2,mm1 #| | ||||*| | movq [t3],mm5 || # |||||| | movq mm7,mm6 * || #||||| | punpcklbw mm1,mm4 | #| #||||*| movq mm5,mm3 |* | #||||| punpckhbw mm2,mm4 || | ##|||* movq mm4,mm0 || # |||| * punpcklwd mm6,mm1 || | ##||* | punpckhwd mm7,mm1 #| | * * #||| | movq mm1,[t1] |*| | | ||| | punpcklwd mm3,mm2 |||* | * | ##| | punpckhwd mm5,mm2 #||| | | | #| | * punpckldq mm0,mm6 |#| | | | #*| | movq mm2,mm1 # | | |* | ||| | punpckhdq mm4,mm6 | | | || | #|#* | movq mm6,[t2] | |*| || | | | | punpckldq mm1,mm3 # #||*|| | | | | movq [v0],mm0 ||||| | # | | movq mm0,mm6 #||||*| | | movq [v32],mm4 ||||||| # | punpckldq mm6,mm7 ##|||||* | movq mm4,[t3] ||||||* | punpckhdq mm2,mm3 |##|||| * | movq [v8],mm1 # |||| | | punpckhdq mm0,mm7 ##|| |* | movq mm3,mm4 |#* || | punpckldq mm4,mm5 |#|*|| # movq [v16],mm6 # |||| | ; |||| | movq [v24],mm4 |#|| | punpckhdq mm3,mm5 # ||* # movq [v40],mm2 #|| ; || movq [v48],mm0 #| ; | movq [v56],mm3 # Calcul : ma carte video absorbe 28MO/s et j'envoie 64 octets en 20 ns. tiendra-t-elle le coup ? en attendant de la faire exploser, j'essaie avec NASM/DEBUG. -une erreur de recopie : punpcklbw au lieu de punpckhbw -plus sérieux : l'échange de valeurs à la fin : l'accès à la mémoire vidéo se fait encore plus dans le désordre ! :-( essayons de voir ce qui peut être réarrangé: movq mm0, [m0] ; ** ;cycle mort ; || movq mm4, [m8] ; ||* * movq mm2,mm0 ; #|| |* movq mm1, [m16] ; ||*||* punpcklbw mm0,mm4 ; ##|||| * * ---- long movq mm6, [m32] ; * |||| | | ---- très long movq mm3,mm1 ; | #||| | * | movq mm5, [m24] ; | |||*|*| | punpckhbw mm2,mm4 ; | ##|||||*| * --- assez long... movq mm7, [m40] ; |* ||||||| | ---- très long aussi punpcklbw mm1,mm5 ; || ##|||||* *| movq mm4,mm0 ; || #|||||*|| ---- déplacé punpckhbw mm3,mm5 ; || ##||||||** movq mm5,mm2 ; || #|||||||* punpcklwd mm0,mm1 ; || * ##|||||| punpckhwd mm4,mm1 ; || |* ##|||| movq mm1,[m48] ; ||*|| |||| punpcklwd mm2,mm3 ; |||||* ##|| ---- déplacé punpckhwd mm5,mm3 ; ||||||* ## movq mm3,mm6 ; #||||||* movq [t2],mm4 ; ||||#||| punpcklbw mm6,mm7 ; ##|| |||* * movq mm4,[m56] ; ||| ||||* * | punpckhbw mm3,mm7 ; #|| ||#||*| | * movq [t1],mm2 ; || #| |||| | | movq mm2,mm1 ; #| | ||||*| | movq [t3],mm5 ; || # |||||| | movq mm7,mm6 ; * || #||||| | punpcklbw mm1,mm4 ; | #| #||||*| movq mm5,mm3 ; |* | #||||| punpckhbw mm2,mm4 ; || | ##|||* movq mm4,mm0 ; || # |||| * punpcklwd mm6,mm1 ; || | ##||* | punpckhwd mm7,mm1 ; #| | * * #||| | movq mm1,[t1] ; |*| | | ||| | punpcklwd mm3,mm2 ; |||* | * | ##| | punpckhwd mm5,mm2 ; #||| | | | #| | * punpckldq mm0,mm6 ; |#| | | | #*| | movq mm2,mm1 ; # | | |* | ||| | punpckhdq mm4,mm6 ; | | | || | #|#* | movq mm6,[t2] ; | |*| || | | | | punpckldq mm1,mm3 ; # #||*|| | | | | movq [v0],mm0 ; ||||| | # | | movq mm0,mm6 ; #||||*| | | punpckldq mm6,mm7 ; ##|||||* | | movq [v8],mm4 ; |||||| # | punpckhdq mm2,mm3 ; |##||| * | movq mm4,[t3] ; | |||* | | punpckhdq mm0,mm7 ; | ##|| |* | movq [v16],mm6 ; | #| || | movq mm3,mm4 ; | #* || | movq [v24],mm0 ; | || |# | punpckldq mm4,mm5 ; | #|*| # movq [v32],mm1 ; # ||| | punpckhdq mm3,mm5 ; #|| * # movq [v40],mm2 ; |# | ; | | movq [v48],mm4 ; # | ; | movq [v56],mm3 ; # OK: bswap.asm est validé. et en plus, il accède à l'écran dans l'ordre :-))) 58 instructions, temps estimé à 31 cycles P200, soit 16 ns pour 64 octets. ça va saigner. 22h: j'ai chronométré la boucle sous MSDOS et je trouve environ 130 cycles processeur. je ne comprends pas car toutes les données et le code sont en cache et j'ai enlevé les IRQ. Le plus probable problème est un mauvais respect des règles de pairage, probablement que les instruction punpckXXXX ont un quota par cycle... 23H30: la consultation des docs d'Intel a dévoilé une partie du mystère: l'accès à la mémoire ne se fait qu'avec le pipe U, et il n'y a qu'un seul shifter par cycle. l'accès à la mémoire pourrait expliquer le reste : "small load after big store" et vice versa. movq mm0, [m0] ; ** ;cycle mort ; || movq mm4, [m8] ; ||* * movq mm2,mm0 ; #|| |* movq mm1, [m16] ; ||*||* punpcklbw mm0,mm4 ; ##|||| * * ---- long movq mm6, [m32] ; * |||| | | ---- très long movq mm3,mm1 ; | #||| | * | movq mm5, [m24] ; | |||*|*| | punpckhbw mm2,mm4 ; | ##|||||*| * --- assez long... movq mm7, [m40] ; |* ||||||| | ---- très long aussi punpcklbw mm1,mm5 ; || ##|||||* *| movq mm4,mm0 ; || #|||||*|| ---- déplacé punpckhbw mm3,mm5 ; || ##||||||** movq mm5,mm2 ; || #|||||||* punpcklwd mm0,mm1 ; || * ##|||||| punpckhwd mm4,mm1 ; || |* ##|||| ;* movq mm1,[m48] ; ||*|| |||| punpcklwd mm2,mm3 ; |||||* ##|| ---- déplacé punpckhwd mm5,mm3 ; ||||||* ## movq mm3,mm6 ; #||||||* movq [t2],mm4 ; ||||#||| punpcklbw mm6,mm7 ; ##|| |||* * movq mm4,[m56] ; ||| ||||* * | punpckhbw mm3,mm7 ; #|| ||#||*| | * movq [t1],mm2 ; || #| |||| | | movq mm2,mm1 ; #| | ||||*| | movq [t3],mm5 ; || # |||||| | movq mm7,mm6 ; * || #||||| | punpcklbw mm1,mm4 ; | #| #||||*| movq mm5,mm3 ; |* | #||||| punpckhbw mm2,mm4 ; || | ##|||* movq mm4,mm0 ; || # |||| * punpcklwd mm6,mm1 ; || | ##||* | ;* punpckhwd mm7,mm1 ; #| | * * #||| | ;* movq mm1,[t1] ; |*| | | ||| | punpcklwd mm3,mm2 ; |||* | * | ##| | punpckhwd mm5,mm2 ; #||| | | | #| | * ;* punpckldq mm0,mm6 ; |#| | | | #*| | movq mm2,mm1 ; # | | |* | ||| | punpckhdq mm4,mm6 ; | | | || | #|#* | ;* movq mm6,[t2] ; | |*| || | | | | punpckldq mm1,mm3 ; # #||*|| | | | | movq [v0],mm0 ; ||||| | # | | movq mm0,mm6 ; #||||*| | | punpckldq mm6,mm7 ; ##|||||* | | ;* movq [v8],mm4 ; |||||| # | punpckhdq mm2,mm3 ; |##||| * | movq mm4,[t3] ; | |||* | | punpckhdq mm0,mm7 ; | ##|| |* | movq [v16],mm6 ; | #| || | movq mm3,mm4 ; | #* || | movq [v24],mm0 ; | || |# | punpckldq mm4,mm5 ; | #|*| # movq [v32],mm1 ; # ||| | punpckhdq mm3,mm5 ; #|| * # movq [v40],mm2 ; |# | ; | | movq [v48],mm4 ; # | ; | movq [v56],mm3 ; # En tenant compte des règles de pairage, il y a 6 problèmes détectés, six cycles de perdus. Cela n'explique pas la différence entre les 34 cycles théoriques et les 130 mesurés. on m'en vole 100 ! 3/4 de perte. Je préfère arrêter ici car de toutes manières la mémoire vidéo ne peut pas absorber un tel débit. c'est équivalent à envoyer 64 octets à 15MHz, soit 960MO/s alors que le bus PCI est limité en théorie à 133MO/s. Bonne nouvelle cependant : ça laisse du "temps" pour rajouter des traitements statistiques intermédiaires :-/ Le PII en profitera moins cependant. Minuit : Pour afficher, nous avons donc la procédure de "retournement" des octets. Pour voir quelque chose, il faut aussi la palette adaptée. Le projet prévoit un algorithme min/max d'optimisation de la palette au fur et à mesure du calcul. Pour l'instant, on ne peut pas trop se le permettre car le reste est plus important (collisions 1-x-x-x) alors on met une palette fixe. On ne va pas pouvoir afficher les murs par contre pour l'instant. seul le zoom 1:1 sera implémenté bien sûr. Pour la palette, elle commence par le noir et prend des niveaux de gris de plus en plus clairs. il suffit de la programmer. xor al,al mov dx,3C8h out dx,al inc dl loop_palette_: out dx,al out dx,al out dx,al add al,12 jnc loop_palette_ ; boucle 21 fois [rajouté à SP40] Ensuite, pour voir qqc, il faut qqc à voir. On va commencer par les collisions : ça prend un seul bit. Je me suis cassé le bec sur le champ de vélocité. La procédure d'accumulation utilise une méthode légèrement différente pour deux raisons : la première était mauvaise, et elle utilisait deux décalages. je préfère utiliser un registre supplémentaire et éviter quelques dégâts en utilisant un masque. voilà ce que ça donne quand on essaie d'être plus malin qu'on ne l'est. pxor mm1,mm1 pcmpeqb mm2,mm2 movq mm0,[COLL] ; là où la collision est sockée $ psubb mm1,mm2 ; mm1=0x0101010101010101 ; mm2 libre movq mm2,mm0 movq mm3,mm0 pand mm0,mm1 psrlq mm2,1 paddb mm0,[m0] movq mm4,mm2 psrlq mm3,2 pand mm2,mm1 paddb mm2,[m8] psrlq mm4,2 movq [m0],mm0 movq mm5,mm3 movq [m8],mm2 pand mm3,mm1 movq mm6,mm4 pand mm4,mm1 paddb mm3,[m16] psrlq mm5,2 paddb mm4,[m24] movq mm7,mm5 psrlq mm6,2 pand mm5,mm1 movq [m16],mm3 movq mm0,mm6 movq [m24],mm4 psrlq mm7,2 paddb mm5,[m32] pand mm6,mm1 pand mm7,mm1 psrlq mm0,2 movq [m32],mm5 pand mm0,mm1 paddb mm6,[m40] paddb mm7,[m48] paddb mm0,[m56] movq [m40],mm6 movq [m48],mm7 movq [m56],mm0 La conclusion de cette journée est que non seulement FHP est memory bound mais MMX l'est encore plus ! 24 cycles pour faire 8 AND... Je ne sais pas ce que ça aurait donné si j'avais triplé l'arbre au lieu de me contenter de le doubler. ça n'aurait pas changé que Intel c'est merdique. 3H10: comme on pouvait s'en douter, il y a un petit bug. je ne crois pas qu'il s'agisse d'assembleur, on verra ça. Mais la bonne nouvelle c'est que le code tourne en 26 cycles, soit seulement 2 de plus que prévu. 186 cycles "à sec" mais ça boucle à la vitesse escomptée. 3H30: bon, 'spèce d'abruti, le "bug" était que je n'avais pas mis la procédure de désembrouillage à la fin : "explode1.asm" fonctionne très bien. "Il n'y a plus qu'à" mettre tout ça dans SP40.ASM. 4H: l'ajout de la fonction d'accumulation ralentit le code de 15% en zone de performance maximale, ce qui est raisonnable si l'on se souvient qu'il accède à la grosse zone tampon. 4H20: je viens de me rendre compte qu'une variable temporaire est lue mais pas écrite. bizarre ! j'avais simplement remplacé le mode d'adressage, enlevant le "t". je fatigue... Je remplace t1,t2 et t3 par PA, PB et PC qui existent dèjà et ne sont pas utilisés. c'est incroyable ce que cette formule génère comme variables temporaires... Je renomme SP41. Premier essai : il faut vider le plan temporaire et élucider le mystère des bandes verticales. 1- une petite modif de la procédure d'initialisation "étend" la vidange au buffer contigu. mais ce n'est pas suffisant. 2- il faut modifier display_tunnel car il affiche des données erronnées maintenant que le buffer temporaire est utilisé. je me contente de le mettre entre parenthèses pour l'instant. c'est juste une histoire d'interface, elle devra être refondue plus tard de toute façon. 3- les bandes verticales... là, c'est le bouquet. La fonction d'accumulation suivie de l'affichage fonctionne bien mais pas au bon endroit. Cela vient d'une utilisation différente du buffer pour le déplacement et l'accumulation : le pointeur va dans un sens utile au déplacement des particules mais donne un résultat faux pour l'accumulation. Au premier balayage de fenêtre, la première ligne de buffer sert pour accumuler ET déplacer la première ligne du tunnel mais au deuxième balayage, la première ligne du buffer sert pour la deuxème ligne du tunnel. l'accumulation se fait entre des lignes différentes ce qui provoque des effets inattendus. Je pense que le mieux serait de revoir le déplacement, car l'accumulation manipule une plus grosse masse de données (les 4/5è). 7H50: Je me demande aussi s'il est intéressant de rendre le buffer d'accumulation statique afin d'améliorer l'affichage. Cela occupe deux fois plus de mémoire mais ne change pas vraiment le temps d'exécution car la bande passante est la même je crois. La gestion des pointeurs sera plus simple aussi, de même que l'affichage. C'est parti pour NA00.asm. D'abord il faut changer le format des cellules: mettre les accumulateurs au milieu, à côté de WALL. Cela fait des cellules de 128 octets. Il faut aussi changer le format et le calcul d'une tonne de formules et de SMC. ça va être marrant mais au moins l'affichage ne sera pas soumis aux caprices du calcul. Je ne pense pas qu'il soit nécessaire d'augmenter la taille de la fenêtre de strip mining car la pratique a montré que les 32 lignes étaient rarement utilisées. L'algorithme de balayage et de déplacement n'est pas modifié. Dimanche 1h30: hier matin j'ai compilé l'aditionneur 6/7 bits qui va s'occuper à la fois de 1-x-x et de l'affichage des densités. C'est un aditionneur 2*3 bits suivi d'un 2 bits sans entrée de retenue pui d'un incrémenteur pour le 7ème bit, afin d'avoir le résultat de l'adition sur 6 bits avant. C'était assez facile à faire, avec l'habitude. movq mm0,[A] ;* movq mm2,[B] movq mm1,mm0 movq mm3,[D] pand mm0,mm2 ; HA1 movq mm5,[E] movq mm4,mm3 movq mm6,[C] pxor mm2,mm1 ; HA1 movq mm7,[F] pand mm3,mm5 ; HA3 pxor mm5,mm4 ; HA3 movq mm1,mm2 movq mm4,mm7 pand mm2,mm6 ; HA2 pand mm7,mm5 ; HA4 pxor mm6,mm1 ; HA2 pxor mm5,mm4 ; HA4 por mm0,mm2 por mm3,mm7 movq mm4,mm6 movq mm1,mm0 pand mm6,mm5 ; HA5 pand mm0,mm3 ; HA6 pxor mm5,mm4 ; HA5 movq mm7,[G] pxor mm3,mm1 ; HA6 movq mm4,mm5 movq mm1,mm3 pand mm5,mm7 ; HA8 pand mm3,mm6 ; HA7 pxor mm6,mm1 ; HA8 por mm0,mm3 pxor mm7,mm4 ; HA8 movq mm4,mm5 pand mm6,mm5 ; HA9 pxor mm5,mm4 ; HA9 movq [ST],mm0 por mm0,mm6 movq [S0],mm7 ;* movq [S1],mm5 ;* movq [S2],mm0 39 instructions, 20 opérations logiques en oubliant un étage. 42 inst. 22 OP en corrigé :-| C'est raisonnable si on considère que cela effectue 2 opérations à la fois. Maintenant, il faut savoir comment effectuer les collisions 1-x-x-x en essayant de réduire les ubiquités avec 0-x-x-x ! 6H45: j'ai levé un lièvre. je divise XORx en EPSILON1 et EPSILON2. cela permet de traiter 3 directions seulement à la fois, ce qui réduit la pression sur les registres. XORx est alors EPSILON1x AND EPSILON2x. le AND sera réduit au fur et à mesure du calcul ce qui permet de mettre les EPSILON dans les emplacements XORx. Il faudra donc refaire, revoir et repenser les collisions quaternaires. je crois que les anciennes equations tiennent toujours, mais on peut s'aider des nouvelles. Jeudi 19 août 1999, 1h30: j'ai trouvé une formule satisfaisante: t1 = F xor B t2 = /t1 and (A xor B) EA1 = t2 and (B xor G) PA1 = EA1 and A EA2 = NOT ((A &/EA1) OR RAND1/2&(t1 or PA1 or (RAND2/ & t2 & B))) t1 = A xor C t2 = /t1 and (B xor C) EB1 = t2 and (C xor G) PB1 = EB1 and B EB2 = NOT ((B &/EB1) OR RAND1/2&(t1 or PB1 or (RAND2/ & t2 & C))) t1 = B xor D t2 = /t1 and (C xor D) EC1 = t2 and (D xor G) PC1 = EC1 and C EC2 = NOT ((C &/EC1) OR RAND1/2&(t1 or PC1 or (RAND2/ & t2 & D))) t1 = F xor B t2 = /t1 and (D xor E) ED1 = t2 and (E xor G) PA2 = ED1 and D ED2 = NOT ((A &/ED1) OR RAND1/2&(t1 or PA2 or (RAND2/ & t2 & E))) PA = PA1 & PA2 XORA = EA2 & ED1 XORD = EA1 & ED2 XORG = XORA or XORD t1 = F xor B t2 = /t1 and (E xor F) EE1 = t2 and (F xor G) PB2 = EE1 and E EE2 = NOT ((A &/EE1) OR RAND1/2&(t1 or PB2 or (RAND2/ & t2 & F))) PB = PB1 & PB2 XORB = EB2 & EE1 XORE = EB1 & EE2 XORG = XORG or XORB or XORE t1 = F xor B t2 = /t1 and (F xor A) EF1 = t2 and (A xor G) PC2 = EA1 and F EF2 = NOT ((A &/EF1) OR RAND1/2&(t1 or PC2 or (RAND2/ & t2 & E))) PC = PC1 & PC2 XORC = EC2 & EF1 XORF = EC1 & EF2 XORG = XORG or XORC or XORF estimation: 150-200 instructions. PA-PC n'est pas complétement calculé mais le gros du calcul est fait. Le calcul est organisé autour d'une utilisation des registres "à tour de rôle" pour contenir les données des 3 entrées. J'en ai profité pour influencer l'equation afin de n'utiliser qu'une fois une des variables d'entrées afin de disposer d'un maximum de registres... RAND1/2, RAND2/3, G(xor st) sont conservés en mémoire. les EPSILONx sont conservés dans XORx. Je crois même que j'en ai oublié XORG dans l'histoire... <corrigé> Pour PA-PC on verra après. 5h45: j'ai fini le graphe des collisions ternaires. il tient sur six pages. je n'ai pas fait XORG. à cause de sa taille, et ses 200 instructions (à vue de nez) il faudra faire une vérification sérieuse à toutes les étapes du codage, ainsi que des tests sur le terrain pour éviter tous les problèmes. Le code se "pipeline" assez bien, les six étapes se recouvrent entre voisines ce qui rend sa mise en boucle sous-efficace car l'allocation des registres est délicate. j'ai quand même trouvé des registres "fixes" ce qui facilite le codage. maintenant, j'ai les doigts pleins de taches de feutre de couleur... mais mes doigts ne sont pas des graphes ! 23H je viens de me rappeler qu'il faut aussi voir comment on peut "entrelacer" le code d'adition et celui de collision... 0H30: une petite modification dans le code de l'adition est suffisante: ST reste dans MM0 et MM6 est ORé avec mm0 avant d'écrire S2. movq mm0,[A] ; U ;* movq mm2,[B] ; U movq mm1,mm0 movq mm3,[D] ; U pand mm0,mm2 ; HA1 movq mm5,[E] ; U movq mm4,mm3 movq mm6,[C] ; U pxor mm2,mm1 ; HA1 movq mm7,[F] ; U pand mm3,mm5 ; HA3 pxor mm5,mm4 ; HA3 movq mm1,mm2 movq mm4,mm7 ; U pand mm2,mm6 ; HA2 pand mm7,mm5 ; HA4 pxor mm6,mm1 ; HA2 pxor mm5,mm4 ; HA4 por mm0,mm2 ; mm2 free movq mm2,[G] ; mm2=G original por mm3,mm7 movq mm4,mm6 movq mm1,mm0 pand mm6,mm5 ; HA5 pand mm0,mm3 ; HA6 pxor mm5,mm4 ; HA5 movq mm7,mm2 pxor mm3,mm1 ; HA6 movq mm4,mm5 movq mm1,mm3 ; U pand mm5,mm2 ; HA8 pand mm3,mm6 ; HA7 pxor mm6,mm1 ; HA8 por mm0,mm3 ; mm3 free MM0=ST pxor mm7,mm4 ; HA8 movq mm3,[B] ; mm3 = B original movq mm4,mm6 ; erreur de recopie pand mm6,mm5 ; HA9 pxor mm5,mm4 ; HA9 mm4 free movq mm4,[A] ; mm4 = A original por mm6,mm0 ; last computation for S2 movq [S0],mm7 ; mm7 free pxor mm2,mm0 ; mm2=G! movq [S1],mm5 ; mm5 free movq mm5,mm3 ; copy B movq [S2],mm6 ; mm6 free movq mm7,mm3 ; copy B movq mm6,[F] pxor mm3,mm0 ; B! movq [G2],mm2 ; save G! pxor mm5,mm4 ; BxA movq mm1,[RAND2/3] pxor mm7,mm6 ; BxF pxor mm4,mm0 ; A! pxor mm2,mm3 ; GxB pand mm1,mm3 ; RAND2/3 & B movq mm6,mm7 ; copy BxF pandn mm7,mm5 ; BxA &/(BxF) movq mm5,mm4 ; copy A! pand mm1,mm7 ; U pand mm2,mm7 ; EPSILON_A1 movq mm7,mm4 por mm6,mm1 ; pandn mm5,mm2 ; U pand mm7,mm2 ; PA movq [XORD],mm2 ; save EA1 por mm6,mm7 movq mm1,[C] ; ;;; ; ; ; ; pcmpeqb mm2,mm2 pand mm6,[RAND1/2] ; movq [PA],mm7 ; mm7 free por mm6,mm5 ; mm5 free ; pxor mm6,mm2 ; EA2 mm2 free ; ; ; ; movq [XORA],mm6 ; save EA2 mm6 free Ce joli petit code a été chronométré à 51 cycles pour 41 cycles théoriques. je me demande d'où vient ce blocage. Essai de l'aditionneur: S1 ne marche pas. erreur de recopie : copie d'un mauvais registre, ce qui execute l'equivalent de xor m5,m5 ce qui fait que m5 est nul quand il est sauvé dans la mémoire. ST et S0-2 sont validés. PA est faux, par définition de formule. il capte F/AB/G et /FA/BG alors qu'il devrait capter /FA/B/G et F/ABG. il y a donc une bifurcation dans la formule que j'avais mal définie. lundi 30 août, 4h50 du matin : après le fiasco de l'autre fois, j'ai fais plus attention en construisant ma formule. je suis parti du cas général afin de simplifier le "gros" de la troupe (les 5 autres parties) et profiter de l'expérience de l'applatissement précédent où j'arrivais à répéter certaines parties. j'ai d'abord conçu la formule, que j'ai explosée en graphe de dépendences. j'ai brisé une longue branche, ce qui m'oblige à utiliser une variable en mémoire. j'ai éjecté ST des registres car il ne sert pas beaucoup une fois que le calcul est lancé. en faisant le graphe j'ai fait attention à ne pas prendre de décisions à la va-vite, j'ai utilisé des noeuds à plusieurs entrées : la commutativité du OR par exemple permet de générer plusieurs graphes. Pour choisir le meilleur, j'ai analysé le "chemin critique" en numérotant les noeuds en partant par le dernier. j'ai ainsi pu réduire le chemin critique global et garder des branches assez courtes. le chemin critique est d'environ 8 opérations. Ensuite il faut trouver quelles branches deviennent des "faisceaux", des branches correspondant à un registre à la fois. la technique est d'abord de voir ce que les instructions obligent à faire, comme NAND qui 'force' un 'chemin' lors des bifurcations : le registre qui contient le résultat est celui dont l'entrée est inversée. Pour le reste de la constitution des faisceaux, il faut profiter de la commutativité de certains opérateurs et ne pas hésiter à inverser des opérandes si nécessaire. et la dernière méthode utilisée est: constituer des faisceaux les plus courts possibles : en partant du bas, on remonte et lorsqu'une bifurcation est commutative, on choisit la branche qui donne le chemin le plus court, par exemple trouver le chemin le plus court vers un chargement de donnée à partir d'un registre ou de la mémoire. Ensuite, l'algorithme est très simple : on prend la dernière branche (celle de la fin en bas), on remonte le faisceau que l'on alloue à un registre. ensuite, lorsque le faisceau se termine, on choisit un autre faisceau qui se termine juste avant le début du faisceau qu'on vient de remonter, on alloue ce faisceau au registre courant, on remonte le faisceau, etc. arrivé en haut du graphe, on revient en bas et on recommence avec un autre registre pour le faisceau qui finit le plus bas : on le remonte, on choisit un faisceau qui se termine juste avant ce point etc. arrivé là, j'ai eu la chance de voir que 6 registres étaient nécessaires. le graphe était donc assez "étroit" mais il faut une variable en mémoire. 21-23 opérations logiques environ par "tour", 12 opérations de mouvement, environ 14 accès à la mémoire. 300 opérations, ou 150 cycles pour les 6 tours. voyons le code: j'ai attribué 5 registres "fixes" qui délimitent chaque tour, et 3 registres "tournant" mm0, mm1 et mm2 qui contiennent les "entrées" de l'eq. Comme avant, l'entrée qui n'est plus utilisée pour le tour suivant sert de faisceau pour le tour courant. c'est cette valeur qui nécessite une variable en mémoire. les registres tournants sont nommés RA, RB et RC dans le code. RC RB RA tour A F mm0 A mm1 B mm2 tour B A mm1 B mm2 C mm0 tour C B mm2 C mm0 D mm1 tour D C mm0 d mm1 e mm2 tour E d mm1 e mm2 f mm0 tour F e mm2 f mm0 a mm1 ensuite je n'ai qu'à "lire" le graphe, en partant du haut et en suivant chaque "ligne", chaque "étape" du chemin critique, ici en partant de la droite de la ligne. cette amélioration à ma technique de "compilation" permet d'avoir des distances assez égales entre deux opérations interdépendantes :-) (si le graphe s'y prette) ligne1: movq mm5,RB movq mm4,RA movq [RC(scratch)],RC ligne2: movq mm6,RC movq mm3,[G!] pxor mm5,RC pxor mm4,RC ligne3: movq mm7,RB pand mm6,RA pandn mm3,RB pandn mm4,mm5 movq mm5,RC pxor mm5,[G!] ligne4: pandn mm7,mm6 pand mm3,mm4 pand RC,mm4 movq mm6,RB movq mm4,RB pxor mm5,RA ligne5: por mm7,mm3 (pand mm3,[PA]) pand mm6,RC (pand RC,[EPSILONA1]) pandn mm4,mm5 movq mm5,[RC(scratch)] ligne6: por mm6,mm4 pand mm7,[RAND1/3] por mm5,RB ligne7: [ por mm6,mm7 [ por mm5,RA pcmpeqb mm7,mm7 ligne8: pand mm6,[rand1/2] pxor mm7,mm5 ligne 9: por mm7,mm6 ligne10: (pand mm7,[EPSILANA2]) movq [EPSILONA2],mm7 je n'ai pas pris en compte le scheduling des accès à la mémoire ainsi que les problèmes PII/PMMX aux règles différentes, je reste en PMMX et je crois que ça suffira. de plus j'ai lâchement oublié la fabrication d'un des termes tournants à l'entrée de l'équation... movq RA,[B] ; pxor RA,[ST] ; movq mm5,RB movq mm4,RA movq [RC(scratch)],RC ;; movq mm6,RC movq mm3,[G2] pxor mm5,RC pxor mm4,RC ;; movq mm7,RB pand mm6,RA pandn mm3,RB pandn mm4,mm5 movq mm5,RC pxor mm5,[G2] ;; pandn mm7,mm6 pand mm3,mm4 pand RC,mm4 movq mm6,RB movq mm4,RB pxor mm5,RA ;; por mm7,mm3 (pand mm3,[PA]) pand mm6,RC (pand RC,[EPSILONA1]) pandn mm4,mm5 movq mm5,[RC(scratch)] ;; por mm6,mm4 pand mm7,[RAND1/3] por mm5,RB ;; movq [PA],mm3 por mm6,mm7 movq [EPSILONA1],RC por mm5,RA pcmpeqb mm7,mm7 ;; pand mm6,[rand1/2] pxor mm7,mm5 ;; por mm7,mm6 ;; (pand mm7,[EPSILANA2]) movq [EPSILONA2],mm7 ************************************** tentative de "fusion" : (avec contre-vérification au passage) movq RA,[B] ;x pxor RA,[ST] ;x movq mm5,RB movq mm4,RA movq [RC(scratch)],RC movq mm6,RC movq mm3,[G2] pxor mm5,RC pxor mm4,RC ----------------------- movq mm7,RB pand mm6,RA pandn mm3,RB pandn mm4,mm5 movq mm5,RC pxor RC,[G2] pandn mm7,mm6 pand mm3,mm4 pand RC,mm4 movq mm6,RB movq mm4,RB pxor mm5,RA por mm7,mm3 (pand mm3,[PA]) pand mm6,RC (pand RC,[EPSILONA1]) pandn mm4,mm5 movq mm5,[RC(scratch)] por mm6,mm4 pand mm7,[RAND1/3] por mm5,RB movq [PA],mm3 por mm6,mm7 movq [EPSILONA1],RC por mm5,RA ***movq RA,[B] pcmpeqb mm7,mm7 ***pxor RA,[ST] pxor mm7,mm5 pand mm6,[rand1/2] ***movq mm5,RB por mm7,mm6 ***movq mm4,RA (pand mm7,[EPSILONA2]) ***movq mm6,RC ***movq mm3,[G2] ***pxor mm5,RC ***movq [RC(scratch)],RC ***pxor mm4,RC movq [EPSILONA2],mm7 ----------------------- ****************************** tentative de terminaison: ----------------------- movq mm7,RB pand mm6,RA pandn mm3,RB pandn mm4,mm5 movq mm5,RC pxor RC,[G2] pandn mm7,mm6 pand mm3,mm4 pand RC,mm4 movq mm6,RB movq mm4,RB pxor mm5,RA por mm7,mm3 pand mm3,[PA] pand mm6,RC pand RC,[EPSILONA1] pandn mm4,mm5 movq mm5,[RC(scratch)] por mm6,mm4 pand mm7,[RAND1/3] por mm5,RB movq [PA],mm3 por mm6,mm7 movq [EPSILONA1],RC por mm5,RA ; pcmpeqb mm7,mm7 ; pxor mm7,mm5 pand mm6,[rand1/2] ; por mm7,mm6 ; pand mm7,[EPSILONA2] ; ; ; ; ; movq [EPSILONA2],mm7 ----------------------- ********************************** amorçage avec l'adition: movq mm7,[A] ; U ; movq mm3,[B] ; U movq mm4,mm7 movq mm2,[C] ; U pand mm7,mm3 ; HA1 movq mm5,[D] ; U movq mm1,mm2 movq mm0,[E] ; U pxor mm3,mm4 ; HA1 movq mm6,[F] ; U pand mm2,mm5 ; HA3 pxor mm5,mm1 ; HA3 movq mm4,mm3 movq mm1,mm6 ; U pand mm3,mm0 ; HA2 pand mm6,mm5 ; HA4 pxor mm0,mm4 ; HA2 pxor mm5,mm1 ; HA4 por mm7,mm3 ; mm2 free movq mm3,[G] ; mm3=G original (copie pour le XOR ensuite) por mm2,mm6 movq mm1,mm0 movq mm4,mm7 pand mm0,mm5 ; HA5 pand mm7,mm2 ; HA6 pxor mm5,mm1 ; HA5 movq mm6,mm3 ; copie de G pour le calcul pxor mm2,mm4 ; HA6 movq mm1,mm5 movq mm4,mm2 ; U pand mm5,mm3 ; HA8 pand mm2,mm0 ; HA7 pxor mm0,mm4 ; HA8 por mm7,mm2 ; mm2 free MM7=ST pxor mm6,mm1 ; HA8 (G xor MM1->mm6, mm3=G) movq mm2,[B] ; mm2 = B original movq mm1,mm0 ;!!! pand mm0,mm5 ; HA9 pxor mm5,mm1 ; HA9 mm1 free movq mm1,[A] ; mm1 = A original por mm0,mm7 ; last computation for S2 movq [S0],mm6 ; mm6 free pxor mm3,mm7 ; mm3=G! **** movq [S1],mm5 ; mm5 free pxor mm2,mm7 ; B! movq [S2],mm0 ; mm0 free pxor mm1,mm7 ; A! movq mm0,[F] movq mm5,mm1 movq [G2],mm3 ; save G! pxor mm0,mm7 ; F! movq [ST],mm7 movq mm4,mm2 ; copie B! movq mm6,mm0 pxor mm5,mm0 pxor mm4,mm0 ;stall, V movq [RCscratch],mm0 ----------------------- P:mm3:G2 M:mm7:ST O:mm4 R:mm5 K:mm6 RC L RB N RA Q tour A F mm0 A mm1 B mm2 J'ai renommé les registres de la partie d'addition afin d'intégrer celle-ci dans l'amorce du premier tour. le code semble correct mais il faut le tester pour en être sûr. en plus, l'additionneur n'est pas fantastique, j'aurais peut-être dû le refaire avec la nouvelle méthode. midi 20: S0, S1 et S2 sont OK mais ST est dans les choux. j'ai peur que le renommage des registres l'ait perturbé. mais ce n'est pas grave : ce n'est qu'une trentaine d'instructions... 23H : ST était bêtement surécrit par une vieille partie du code dans lequel j'ai greffé l'extrait. faute d'inattention. 23H45: PA et EPSILONA1 sont validés. il faut encore valider EPSILONA2 avec les 4 combinaisons d'entrées pour les variables aléatoires. Minuit: houra ! la formule fonctionne. 2 ennuis tout proches cependant : -1) RANDOM 1/2 à munir d'une version inversée. dans mon empressement, je n'ai pas inclus de version PANDN dans le graphe. -2) les XORx ne seront peut-être pas suffisants, 3 autres variables sont nécessaires car l'ordre de chargement a changé: il n'y a plus de recouvrement avec les registres. Tout de même le grand moment est arrivé : la mise bout à bout de toutes les sections. dès que ça fonctionne, il faut chronométrer tout ça ! 3H30: je me suis trompé pour les XORx, ils se comportent comme prévu auparavant. reste tout de même le problème de RAND1/2 qu'il faut inverser. Finalement, quand c'est bien conçu, le tout est assez "indolore"... 4H: bonne nouvelle : PA, PB et PC fonctionnent. mauvaise nouvelle: ils ne font que la détection, il faudra rajouter encore un peu de code (déjà connu: sélection progressive) pour faire la phase de "décision" en utilisant RANDOM. Pour ce qui est du code, il y a en tout 300 lignes et 750 octets de binaire en mode 16 bits, ce qui donnera environ 1KO en mode 32 bits. Pour ce qui est de savoir si ça va marcher,.... J'ai récupéré SP39 car je ne peux rien faire avec NA00 qui est inachevé. je m'en sers seulement pour tester les collisions. C'est bizarre mais en regardant ces vieux sources, je me rends compte que j'ai oublié de calculer XORG... 3 septembre, 21H: j'ai résolu le problème de XORG dans la partie de translation. je teste SP51 "à vide", sans la partie calcul. la partie de translation a besoin d'un grand coup d'optimisation !!! adaptation du coeur du calcul: ; ; POPCOUNT: ; movq mm7,[edi+A] ; U ; movq mm3,[temp_B] ; U $base3__ equ $-4 movq mm4,mm7 movq mm2,[temp_C2] ; U $base3__ equ $-4 pand mm7,mm3 ; HA1 movq mm5,[edi+D] ; U movq mm1,mm2 movq mm0,[edi+E] ; U pxor mm3,mm4 ; HA1 movq mm6,[edi+F] ; U pand mm2,mm5 ; HA3 pxor mm5,mm1 ; HA3 movq mm4,mm3 movq mm1,mm6 ; U pand mm3,mm0 ; HA2 pand mm6,mm5 ; HA4 pxor mm0,mm4 ; HA2 pxor mm5,mm1 ; HA4 por mm7,mm3 ; mm2 free movq mm3,[edi+G] ; mm3=G original (copie pour le XOR ensuite) por mm2,mm6 movq mm1,mm0 movq mm4,mm7 pand mm0,mm5 ; HA5 pand mm7,mm2 ; HA6 pxor mm5,mm1 ; HA5 movq mm6,mm3 ; copie de G pour le calcul pxor mm2,mm4 ; HA6 movq mm1,mm5 movq mm4,mm2 ; U pand mm5,mm3 ; HA8 pand mm2,mm0 ; HA7 pxor mm0,mm4 ; HA8 por mm7,mm2 ; mm2 free MM7=ST pxor mm6,mm1 ; HA8 (G xor MM1->mm6, mm3=G) movq mm2,[temp_B] ; mm2 = B original $base3__ equ $-4 movq mm1,mm0 ;!!! pand mm0,mm5 ; HA9 pxor mm5,mm1 ; HA9 mm1 free movq mm1,[edi+A] ; mm1 = A original por mm0,mm7 ; last computation for S2 movq [S0],mm6 ; mm6 free $base3__ equ $-4 pxor mm3,mm7 ; mm3=G! **** movq [S1],mm5 ; mm5 free $base3__ equ $-4 pxor mm2,mm7 ; B! movq [S2],mm0 ; mm0 free $base3__ equ $-4 pxor mm1,mm7 ; A! movq mm0,[edi+F] movq mm5,mm1 movq [G2],mm3 ; save G! $base3__ equ $-4 pxor mm0,mm7 ; F! movq [ST],mm7 $base3__ equ $-4 movq mm4,mm2 ; copie B! movq mm6,mm0 pxor mm5,mm0 pxor mm4,mm0 ;stall, V movq [RCscratch],mm0 $base3__ equ $-4 ;--------------- ; A: RC:F:mm0 RB:A:mm1 RA:B:mm2 ;--------------- movq mm7,mm1 pand mm6,mm2 pandn mm3,mm1 pandn mm4,mm5 movq mm5,mm0 pxor mm0,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm0,mm4 movq mm6,mm1 movq mm4,mm1 pxor mm5,mm2 por mm7,mm3 pand mm6,mm0 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND03] $base3__ equ $-4 por mm5,mm1 movq [PA],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORD],mm0 $base3__ equ $-4 por mm5,mm2 movq mm0,[temp_C2] ;*** $base3__ equ $-4 pcmpeqb mm7,mm7 pxor mm0,[ST] ;*** $base3__ equ $-4 pxor mm7,mm5 pand mm6,[RAND02] $base3__ equ $-4 movq mm5,mm2 ;*** por mm7,mm6 movq mm4,mm0 ;*** movq mm6,mm1 ;*** movq mm3,[G2] ;*** $base3__ equ $-4 pxor mm5,mm1 ;*** movq [RCscratch],mm1 ;*** $base3__ equ $-4 pxor mm4,mm1 ;*** movq [XORA],mm7 $base3__ equ $-4 ;--------------- ; B RC:A:mm1 RB:B:mm2 RA:C:mm0 ;--------------- movq mm7,mm2 pand mm6,mm0 pandn mm3,mm2 pandn mm4,mm5 movq mm5,mm1 pxor mm1,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm1,mm4 movq mm6,mm2 movq mm4,mm2 pxor mm5,mm0 por mm7,mm3 pand mm6,mm1 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND03] $base3__ equ $-4 por mm5,mm2 movq [PB],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORE],mm1 $base3__ equ $-4 por mm5,mm0 movq mm1,[edi+D] pcmpeqb mm7,mm7 pxor mm1,[ST] $base3__ equ $-4 pxor mm7,mm5 pand mm6,[RAND12] $base3__ equ $-4 movq mm5,mm0 por mm7,mm6 movq mm4,mm1 movq mm6,mm2 movq mm3,[G2] $base3__ equ $-4 pxor mm5,mm2 $base3__ equ $-4 movq [RCscratch],mm2 pxor mm4,mm2 movq [XORB],mm7 $base3__ equ $-4 ;--------------- ; C ;--------------- movq mm7,mm0 pand mm6,mm1 pandn mm3,mm0 pandn mm4,mm5 movq mm5,mm2 pxor mm2,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm2,mm4 movq mm6,mm0 movq mm4,mm0 pxor mm5,mm1 por mm7,mm3 pand mm6,mm2 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND13] $base3__ equ $-4 por mm5,mm0 movq [PC],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORF],mm2 $base3__ equ $-4 por mm5,mm1 movq mm2,[edi+E] pcmpeqb mm7,mm7 pxor mm2,[ST] $base3__ equ $-4 pxor mm7,mm5 pand mm6,[RAND02] $base3__ equ $-4 movq mm5,mm1 por mm7,mm6 movq mm4,mm2 movq mm6,mm0 movq mm3,[G2] $base3__ equ $-4 pxor mm5,mm0 movq [RCscratch],mm0 $base3__ equ $-4 pxor mm4,mm0 movq [XORE],mm7 $base3__ equ $-4 ;--------------- ; D ;--------------- movq mm7,mm1 pand mm6,mm2 pandn mm3,mm1 pandn mm4,mm5 movq mm5,mm0 pxor mm0,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm0,mm4 movq mm6,mm1 movq mm4,mm1 pxor mm5,mm2 por mm7,mm3 pand mm3,[PA] $base3__ equ $-4 pand mm6,mm0 pand mm0,[XORA] $base3__ equ $-4 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND13] $base3__ equ $-4 por mm5,mm1 movq [PA],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORA],mm0 $base3__ equ $-4 por mm5,mm2 movq mm0,[edi+F] pcmpeqb mm7,mm7 pxor mm0,[ST] $base3__ equ $-4 pxor mm7,mm5 pand mm6,[RAND12] $base3__ equ $-4 movq mm5,mm2 por mm7,mm6 movq mm4,mm0 pand mm7,[XORD] $base3__ equ $-4 movq mm6,mm1 movq mm3,[G2] $base3__ equ $-4 pxor mm5,mm1 movq [RCscratch],mm1 $base3__ equ $-4 pxor mm4,mm1 movq [XORD],mm7 $base3__ equ $-4 ;--------------- ; E ;--------------- movq mm7,mm2 pand mm6,mm0 pandn mm3,mm2 pandn mm4,mm5 movq mm5,mm1 pxor mm1,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm1,mm4 movq mm6,mm2 movq mm4,mm2 pxor mm5,mm0 por mm7,mm3 pand mm3,[PB] $base3__ equ $-4 pand mm6,mm1 pand mm1,[XORB] $base3__ equ $-4 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND23] $base3__ equ $-4 por mm5,mm2 movq [PB],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORB],mm1 $base3__ equ $-4 por mm5,mm0 movq mm1,[edi+A] $base3__ equ $-4 pcmpeqb mm7,mm7 pxor mm1,[ST] $base3__ equ $-4 pxor mm7,mm5 pand mm6,[RAND02] $base3__ equ $-4 movq mm5,mm0 por mm7,mm6 movq mm4,mm1 pand mm7,[XORE] $base3__ equ $-4 movq mm6,mm2 movq mm3,[G2] $base3__ equ $-4 pxor mm5,mm2 movq [RCscratch],mm2 $base3__ equ $-4 pxor mm4,mm2 movq [XORE],mm7 $base3__ equ $-4 ;--------------- ; F ;--------------- movq mm7,mm0 pand mm6,mm1 pandn mm3,mm0 pandn mm4,mm5 movq mm5,mm2 pxor mm2,[G2] $base3__ equ $-4 pandn mm7,mm6 pand mm3,mm4 pand mm2,mm4 movq mm6,mm0 movq mm4,mm0 pxor mm5,mm1 por mm7,mm3 pand mm3,[PC] $base3__ equ $-4 pand mm6,mm2 pand mm2,[XORC] $base3__ equ $-4 pandn mm4,mm5 movq mm5,[RCscratch] $base3__ equ $-4 por mm6,mm4 pand mm7,[RAND23] $base3__ equ $-4 por mm5,mm0 movq [PC],mm3 $base3__ equ $-4 por mm6,mm7 movq [XORC],mm2 $base3__ equ $-4 por mm5,mm1 ; pcmpeqb mm7,mm7 ; pxor mm7,mm5 pand mm6,[RAND12] $base3__ equ $-4 ; por mm7,mm6 ; pand mm7,[XORF] $base3__ equ $-4 ; ; ; ; ; movq [XORF],mm7 $base3__ equ $-4 10H 30: entre deux appels de ma soeur obnubilée par ses taches ménagères, je débugge SP52. il a souffert d'abord d'un trop grand nombre de labels, puis de nombreuses erreurs de translation/copie/inattention. 11H30: il y a vraiment un problème avec l'équation. minuit 30: l'equation fonctionne correctement, sans PA-PC. renommé vers SP54. 6H45: l'algo a été aplati pour PA-PC: PA = PA OR RANDOM R = PA OR RANDOM2 PB = PB OR R R2 = R AND PB PC = PC OR R2 remarque 1 : c'est beaucoup plus léger que la première version (cf plus haut). remarque 2: c'est tout aussi lourd car seulement 3 instructions sur 12 n'accèdent pas à la mémoire. dur dur pour seulement 5 opérations logiques! mais ici, pas de problème d'allocation des registres... movq mmA,[PB] $base4__ equ $-4 movq mmB,[PA] $base4__ equ $-4 movq mmC,[RANDOM] $base4__ equ $-4 movq mmD,mmA por mmC,MMB por mmB,[RANDOM2] $base4__ equ $-4 pand mmD,mmB por mmB,mmA por mmD,[PC] $base4__ equ $-4 movq [PA],mmC $base4__ equ $-4 movq [PB],mmB $base4__ equ $-4 movq [PC],mmD $base4__ equ $-4 7H50 du matin, j'ai oublié de masquer la sortie... il faut donc faire un OR de tous les Pn puis faire un AND de toutes les sorties... j'en profite pour entrelacer ceci avec l'épilogue des collisions... 9H: la formule n'est pas stable. observation. certaines collisions frontales ont apparemment le mauvais XOR, PB et PC semblent inversés. en plus,maintenant, une particule G est ajoutée... jeudi 23 septembre 99: le zu est revenue. a part ça, la formule est "en dérangement". les derniers jours ont vu une certaine agitation de ce côté-là aussi. Avec FHP, plus on cherche, plus on trouve, et plus il faut chercher. A B C G | Pn E1 E2 ------------------ 0 0 0 0 | 1 0 0 0 1 | 1 0 0 1 0 | 1 0 0 1 1 | A 0 1 0 0 | 1 1/3 0 1 0 1 | 1 1/2 0 1 1 0 | 1 0 1 1 1 | 1 1 0 0 0 | 1 1 0 0 1 | /A 1 0 1 0 | 1 1 1 0 1 1 | 1/3 1 1 0 0 | 1 1 1 0 1 | 1 1 1 1 0 | 1 1 1 1 1 | 1 E2 est vraiment différent des premiers essais. il faut noter que les collisions impossibles peuvent avoir une sortie à 1. rappel : "collision impossible" veut dire : nombre de particules >=4 ou G=!G. Ainsi, quasiment toutes les sorties sont à 1, la complexité de l'équation est réduite. Le problème est sur les variables aléatoires, comme lors des derniers essais ! la partie 1/3 ne pose pas de problème. la partie A et /A non plus, mais 1/2 doit avoir la valeur A puis /A lors des six étapes du calcul. On va donc ruser. Il faut que dans le calcul, RANDOM soit chargé de la mémoire, inversé dans un autre registre, et ainsi lors de la phase de "renommage", on peut faire un "movq" qui copie RANDOM ou son inverse. ça fout la pression sur les registres mais je crois qu'on peut s'en tirer. Dans un sens, ça réduit le nombre d'accès à la mémoire, ce qui n'est pas plus mal. 8H30 : bon, on ne va pas "ruser". grâce à une "petite" simplification logique (qu'aucun compilo n'aurait pu détecter) on peut utiliser la bonne vielle méthode de renomamge manuel. Cela veut aussi dire qu'il faut se trimbaler RANDOM et son inverse tout le temps mais ce n'est pas trop grave. La nouvelle "formule" donne : A/B= G &/ B & (A xor C) & (A xor RANDOM1/2) <- c'est la nouvelle astuce Pn=(A xor B) &/ (A xor C) &/ (A xor G) E1=(A xor B) &/ (A xor C) & (A xor G) 1/3=Pn & Random1/3 Pn=Pn & B 1/2=E1 & RANDOM1/2 <- c'est ce random qu'on renomme Là où le problème commence, c'est quand on veut constituer E2. comme indiqué dans le tableau du haut, presque toutes les sorties sont à 1. donc, comme E2 est une somme de produits, on peut inverser la sortie du OR. MAIS (il y en a toujours un) comment faire pour la probabilité de 1/3 ? En toute logique, l'inverse est la probabilité de 2/3. Vérification faite, on peut initialiser le générateur de nombres aléatoires avec n'importe quelle configuration puisque les bits sont juste "décalés" à chaque cycle. On peut donc mettre 2 bits au lieu d'un pour chaque position, ce qui donne bien la probabilité de 2 sur 3. Si on inverse le résultat, on se retrouve avec la probabilité de 1/3. Pour l'occasion, SP55 est renommé en SP56. E2= NOT (A/B or 1/3 or 1/2) Ce NOT est "cher". On peut en éviter une moitié, lorsque E1 and E2 est effectué à la fin : il faut faire "pandn E2,[E1]". dimanche 3 octobre 99 : AXG = A xor G AXC = A xor C -> C disparait AXB = A xor B copy B A/B = /B & AXC ABC = AXB & /AXC load RANDOM2 cp ABC (?) E1 = ABC & AXG Pn = ABC & /AXG A/B = A/B & G AXR = A xor RANDOM2 // E1 = E1 & [E2] A/B = A/B & AXR 1/3 = Pn & [RAND1/3] sauve E1 Pn = Pn & B 1/2 = E1 & (/)RANDOM2 E2= A/B or 1/3 E2 = E2 or 1/2 X1 = 1 // X1 = [E1(x-3)] E2 = E2 xor X1 // E2 = E2 & X1 sauve [E2] reste à faire le travail sur graphe. lundi soir : avant de se lancer dans le travail de graphe, il faut vérifier si la méthode est correcte. Il faut donc transformer bêtement la formule ci-dessus en assembleur et tester son fonctionnement. !movq _R2,[RANDOM] ; load RANDOM2 !movq _G,[G] ; load G movq _RA,[A] movq _AXB,_RB ; AXB = A xor B pxor _AXC,_RA ; AXC = A xor C -> C disparait et AXC le remplace pxor _AXB,_RA movq _A/B,_RB ; A/B = /B & AXC pandn _A/B,_AXC pandn _AXC,_AXB ; ABC = AXB & /AXC AXB:fini, AXC->ABC movq _AXG,_G ; AXG = A xor G (déplacé) pxor _AXG,_RA movq _Pn,_AXC ; cp ABC (?) pand _AXC,_AXG ; E1 = ABC & AXG E1=_AXC pandn _AXG,_Pn ; Pn = ABC & /AXG Pn=_AXG (_Pn fini) pand _A/B,_G ; A/B = A/B & G movq _AXR,_RA ; AXR = A xor RANDOM2 pxor _AXR,_R2 //pand _AXC,[E2] ; // E1 = E1 & [E2] pand _A/B,_AXR ; A/B = A/B & AXR movq _1/3,[RAND13]; 1/3 = Pn & [RAND1/3] pand _1/3,_AXG movq [E1],_AXC ; sauve E1 pand _AXG,_RB ; Pn = Pn & B por _A/B,_1/3 ; E2= A/B or 1/3 movq _1/3,_R2 pand(n) _1/3,_AXC ; 1/2 = E1 & (/)RANDOM2 por _A/B,_1/3 ; E2 = E2 or 1/2 pcmpeqb _X1,_X1 ; X1 = 1 // movq _X1,[E1] ; // X1 = [E1(x-3)] pxor _A/B,_X1 ; E2 = E2 xor X1 // pand _A/B,_X1 ; // E2 = E2 & X1 movq [E2], _A/B ; sauve [E2] Assignation des registres : mm0: _RA mm1: _RB mm2: _RC/_AXC mm3: _AXB/_AXG/_X1 mm4: _A/B mm5: _Pn/_1/3/_AXR mm6: _R2 mm7: _G renommage des registres : !movq mm6,[RANDOM] ; load RANDOM2 !movq mm7,[G] ; load G movq _RA,[A] movq mm3,_RB ; AXB = A xor B pxor _AXC,_RA ; AXC = A xor C -> C disparait et AXC le remplace pxor mm3,_RA movq mm4,_RB ; A/B = /B & AXC pandn mm4,_AXC pandn _AXC,mm3 ; ABC = AXB & /AXC AXB:fini, AXC->ABC movq mm3,mm7 ; AXG = A xor G (déplacé) pxor mm3,_RA movq mm5,_AXC ; cp ABC (?) pand _AXC,mm3 ; E1 = ABC & AXG E1=_AXC pandn mm3,mm5 ; Pn = ABC & /AXG Pn=_AXG (_Pn fini) pand mm4,mm7 ; A/B = A/B & G movq mm5,_RA ; AXR = A xor RANDOM2 pxor mm5,mm6 //pand _AXC,[E2] ; // E1 = E1 & [E2] pand mm4,mm5 ; A/B = A/B & AXR movq mm5,[RAND13] ; 1/3 = Pn & [RAND1/3] pand mm5,mm3 movq [E1],_AXC ; sauve E1 pand mm3,_RB ; Pn = Pn & B por mm4,mm5 ; E2= A/B or 1/3 /// pand mm3,[Pn] ; (oublié) movq mm5,mm6 pand(n) mm5,_AXC ; 1/2 = E1 & (/)RANDOM2 por mm4,mm5 ; E2 = E2 or 1/2 movq [Pn],mm3 ; sauve Pn pcmpeqb mm3,mm3 ; X1 = 1 // movq mm3,[E1] ; // X1 = [E1(x-3)] pxor mm4,mm3 ; E2 = E2 xor X1 // pand mm4,mm3 ; // E2 = E2 & X1 movq [E2], mm4 ; sauve [E2] Première observation : le code semble plus court. il n'est pas encore optimisé mais ça semble prometteur. mercredi : SP57 n'implémente pas encore les Pn. il faut que je retrouve exactement la formule et que je voie ce qu'elle fait. 19H: grand houra, SP57 fonctionne pour les cas étudiés. sauf un. 5è colonne, 3è ligne. je me demande si ce n'est pas un problème d'initialisation des particules. ensuite je chercherai une faute quelconque de recopie. recopie dans SP58.asm. il y a aussi un problème de chiralité. les collisions triangulaires avec particule au repos ont des probabilités inégales. 21H30: le problème de la collision qui n'avait pas lieu a été résolu. il faut encore observer le cas des probabilités fausses. je me demande si ce n'est pas lié à la manière dont le générateur de nombres aléatoires est constitué (y aurait-il du Zaleskisme derrière tout ça ?) solution (?) : essayer de voir ce qui se passe si le générateur ne bouge pas. solution 2 : initialiser une "mer" de particules aléatoires. 22H30: des observations plus poussées laissent entendre qu'il n'y a pas lieu de s'inquiéter. il faut juste faire attention à ce que la "bouillie de bits" soit uniforme, ce qui est vrai si le "fluide" est "lâché", livré à lui-même. Sinon, les caractéristiques géométriques du fluide se retrouvent dans la chiralité. J'ai aussi une pensée pour le travail qui reste à réaliser. même si presque tout est au point, il y a tout de même des milliers de lignes de code à optimiser à tous les niveaux. Maintenant, il faut tester le reste des collisions. le source fait actuellement 172KO et le binaire fait 12KO. il faut ajouter les Pn (collisions frontales) pour que toutes les collisions à deux éléments soient testées. 1H30 : SP59 est prêt pour la compilation. j'ai réactivé les Pn dans le code de mouvement et j'ai refait le ménage dans la sélection progressive. deux colonnes sont ajoutées pour les tests de collisions. remarque 1 : les "vecteurs de test" sont un "peu boggés". remarque 2 : problème apparemment au niveau de la sélection progressive: la sortie des collisions est toujours la même. remarque 3 : il y a toujours le problème de la troisième ligne à régler. remarque 4 : interférence avec XORG. mais ce qui est dingue c'est que ce n'est pas pour toutes les collisions (sortie=AD pour certaines lignes). remarque 5 : collisions frontales à 5 éléments : on n'en parle même pas... En faisant tourner le programme, la population des particules a légèrement augmenté mais pas "explosivement". Cela, plus l'expérience précéndente, prouve qu'il n'est pas suffisant de faire tourner le programme sans anomalies avant de le déclarer valide. Souvenez-vous de Zaleski... 15H30: j'ai trouvé le bug du XORG. le por mm5,[PC] était placé avant le movq mm7,mm5 dans la deuxième instance du code de mouvement. XORG avait donc en plus les bits de PC... Il faut aussi débogger la sélection progressive. SP60.asm Il semble aussi que le cas où il y a plus de 4 particules est mal géré. voyons d'abord la sélection progressive. il y a probablement un problème au niveau des dépendences de données. jeudi, 0h5: un petit tour du côté de chez Debug.exe a montré que l'algo de sélection est boggé. la question reste la même: qu'est-ce qui ne va pas ????? session debug: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ debug sel.com<comm >co type co -g Le programme s'est terminé normalement -d160 2E73:0160 FF 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0170 00 FF 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0180 00 00 FF 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0190 0F 0F 0F 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01A0 FF 0F 0F 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01B0 FF FF F0 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01C0 00 F0 FF 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01D0 DC 8C C0 40 8E C0 83 EF-10 26 01 1D 48 8E C0 EB ...@.....&..H... -q ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ le problème est avec PA, la première colone. Pour le reste, PB et PC sont parfaits. J'ai enfin compris le problème : c'est à cause du OR pour PA2. Résultat, il faut refaire les équations. heureusement elles ne sont pas aussi lourdes que la formule centrale. Nouvelle formule: PA2 = PA or Rand PB2 = PB or /Rand PC2 = PC or (PA & Rand) or (PB & /rand) Cette fois-ci ça devrait aller. PA2 = PA or Rand R1 = PA & Rand PB2 = PB or /Rand R2 = PB & /Rand PC2 = PC or R1 or R2 ... load PA /R = -1 load PB OR3 = PA load PC /R = /R xor Rand OR3 = OR3 or PB R1 = PA OR3 = OR3 or PC R2 = PB PA = PA or Rand PB = PB or /R R1 = R1 & Rand PA = PA & OR3 R2 = R2 & /R PC = PC or R1 PB = PB & OR3 PC = PC or R2 save PA PC = PC & OR3 save PB save PC .... ess_sel1.asm : ; movq PA,[PA] ;;$base4__ equ $-4 ; pcmpeqb /R,/R ; movq PB,[PB] ;;$base4__ equ $-4 ; movq OR3,PA ; movq PC,[PC] ;;$base4__ equ $-4 ; pxor /R,Rand ; por OR3,PB ; movq R1,PA ; por OR3,PC ; movq R2,PB ; por PA,Rand ; por PB,/R ; pand R1,Rand ; pand PA,OR3 ; pand R2,/R ; por PC,R1 ; pand PB,OR3 ; por PC,R2 ; movq [PA],PA ;;$base4__ equ $-4 ; pand PC,OR3 ; movq [PB],PB ;;$base4__ equ $-4 ; movq [PC],PC ;;$base4__ equ $-4 ; assignation des registres : ; PA=mm0 ; PB=mm1 ; PC=mm2 ; /R=mm3 ; OR3=mm4 ; R1=mm5 ; Rand=mm6 ; R2=mm7 movq mm0,[PA] ;$base4__ equ $-4 pcmpeqb mm3,mm3 movq mm1,[PB] ;$base4__ equ $-4 movq mm4,mm0 movq mm2,[PC] ;$base4__ equ $-4 pxor mm3,mm6 por mm4,mm1 movq mm5,mm0 por mm4,mm2 movq mm7,mm1 por mm0,mm6 por mm1,mm3 pand mm5,mm6 pand mm0,mm4 pand mm7,mm3 por mm2,mm5 pand mm1,mm4 por mm2,mm7 movq [PA2],mm0 ;$base4__ equ $-4 pand mm2,mm4 movq [PB2],mm1 ;$base4__ equ $-4 movq [PC2],mm2 ;$base4__ equ $-4 donne le résultat suivant : -g Le programme s'est terminé normalement -d160 2E73:0160 FF 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0170 00 FF 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0180 00 00 FF 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:0190 0F 0F 0F 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01A0 FF 0F 0F 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01B0 F0 FF F0 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01C0 0F F0 FF 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 2E73:01D0 C7 46 FA 00 00 E9 93 00-8A 46 FA D0 E0 8B 56 FC .F.......F....V. -q Ce qui est le résultat désiré ! on revient de loin... 2H30 : collision frontale avec une G : sur la troisième ligne, la collision donne toujours le même résultat. comme ce phénomène a déjà eu lieu, je me doute que c'est un mauvais vecteur de test : la même configuration, une ligne plus bas, donne le bon résultat. 2H45 : problème résolu pour la collision frontale + G. la colonne suivante a un problème assez similaire. le problème avec ce truc c'est qu'on est jamais parfaitement sûr que la collision a bien lieu de la manière désirée, à l'endroit voulu. il suffit qu'une particule soit décentrée et la collision n'a pas lieu correctement. Collisions testées : A+G -> F+B F+B -> A+G A+D -> B+E ou C+F A+C+G -> A+B+D ou B+C+F A+B+D -> A+C+G B+C+F -> A+C+G A+D+G -> A+C+E ou B+D+F A+C+E -> A+D+G ou B+E+G ou C+F+G A+C+E+G -> A+D+B+E ou B+E+C+F ou C+F+A+D SP61.asm : essai de simplification des vecteurs de tests on utilise une petite fonction qui interprète un octet. 4H30 : l'utilisation d'une petite fonction pour faire le "sale travail" allège le programme de 20KO et diminue les "chances" d'erreurs. Mais ABDEG est boggé jusqu'à la moelle... SP62: transition réussie du code. mais le déboggage doit continuer. les 51 contre-exemples sont en place, pas de surprise de ce côté-là. 5H15: rectification : il n'y a que 45 vecteurs de contre-vérification. les "manquants" sont le cas 0 et les cas A-F qui fonctionnent sans problème, heureusement. cela porte le nombre de collisions "impossibles" à 52, soit 76 collisions possibles, ce qui correspond exactement au modèle FHP saturé. Par contre il y a un vrai problème lorsque popcount>3, sur la cinquième ligne. j'avais déjà remarqué cela pour le complémentaire de la collision frontale simple, mais là je me demande ce qui se passe. 12H45: la nuit porte conseil. d'abord, compléter les vecteurs de tests. 13H20 : il y a effectivement un bos grug. le problème c'est que les symptômes sont mélangés. jusqu'à maintenant, j'ai pu discerner les différents cas mais je ne suis plus sûr. Comme le problème apparaît pour popcount>3, il faut probablement voir du côté de l'additionneur. il a déjà été testé avec succès, donc je suis très étonné. Autre symptôme, une collision ABCD donne un ABDG. Il faut peut-être voir si le XOR[ST] est bien effectué. 14H: j'ai trouvé le pot aux roses: j'ai oublié de XORer l'amorce. F et A n'étaient pas xorés, même si G l'était. Xing fingers. 14H15 : le contrôle visuel est : OK ! renommé vers SP64.asm. samedi, 2h40: après ce succès (aber alles ist relativ) il faut songer à l'optimisation, même si le plus gros est déjà fait. tout ? non. après l'optimisation au niveau des instructions (microscopique), l'optimisation au niveau des blocs (macroscopique, et ses gros graphes) il faut songer maintenant à l'optimisation générale. Cela veut dire à l'heure actuelle : mettre tous les blocs ensemble et regarder ce qu'ils font. Je suis sûr que des optimisations importantes restent à faire. Une fois que cela sera fait, on recodera les blocs comme d'habitude, en faisant beaucoup plus attention, cette fois-ci. enfin, on regardera les compteurs de performance pour voir où sont perdus les cycles. Voyons voir : 1) parois il n'y a rien à y faire. c'est entièrement "data dependent" et déjà assez bien codé. On peut cependant tester si WALLMASK est complétement à 1, pour éviter de passer l'étape de calcul. Cela nécessite aussi de faire une version "allégée" du code de déplacement. il faut tester cela apreès le pré-mouvement. 2) pré-mouvement, variables temporaires : Ces quelques instructions peuvent être facilement fusionnées avec le popcount. cela joue seulement avec les variables temporaires de B et C mais réduit légèrement le nobre de registres libres. D'ailleurs je viens de penser à l'instant à un moyen d'éviter la duplication du code pour les lignes paires et impaires, grâce à l'instruction de décalage avec un registre au lieu d'un immédiat. problème : cela rajoute des instructions et oblige à accéder (inutilement) à 2 emplacements mémoire. la version actuelle, bien que plus lourde, tient dans la cache d'instructions et ne pose donc pas de problème. ça fonctionne déja bien. Il faudrait utiliser cette astuce si le code ne tenait plus en L1. vérification faite auprès de Andy Glew, il n'y a pas de problème jusqu'à 16KO. 3) popcount: load A-G, store ST, S0-S2. il faut refaire la coloration des registres, et fusionner avec le prologue de la détection. il faut aussi intégrer le prémouvement. 4) détection * 6 load ST,RandA-C, A-F (progressivement) store XORA-F, Pn Il faut fusionner la sélection (Pn) avec la détection. il faut aussi faire qqc pour le OR des XOR, mais il n'y a pas assez de registres... 5) sélection : dans 4) load/store P, Random penser à sauver le OR des Pn pour afficher les collisions... problème : où effectuer le masquage des Pn ? il faut effectuer un OR de tous les Pn, effectuer la "sélection" puis masquer le tout par le OR des données initiales. si 5 est dans 4, il y aura beaucoup d'accès à la mémoire car il n'y a pas assez de registres. il faudra sauver le OR, et plein d'autres choses. finalement il vaut mieux faire ça séparément, on n'a vraiment pas assez de registres. si le or des XOR était plus sobre, on pourrait peut-être effectuer la sélection dans la partie de déplacement. 6) déplacement load XORs, A-G, store A-G (modifiés) + temporaires il y a le problème des OR qui prennent beaucoup de registres. attention : un seul shift par cycle ! c'est à recoder entièrement. 7) sommation/densité load S0-S2 & densité, store densité. Selon les ordres de l'utilisateur, il faut pouvoir changer de module (visualiser les collisions ou la densité ?) 5H: maintenant que j'y pense, le code de sommation que j'ai créé n'effectue qu'une sommation d'un bit. il est souhaitable de pouvoir effectuer la sommation sur 3 bits pour afficher les particules, et pas seulement les collisions. approx 110 instructions. Comme on commence à applatir un graphe à partir du bas, c'est par cette fonction, la dernière, qu'on va commencer le travail. registre 0-2: valeurs originales registre 3 : masque registre 4 : valeur accumulée registre 5 : valeur temporaire (masquée+shift(!)) il y a donc une petite marge mais pas assez pour effectuer le dédoublement de l'arbre comme auparavant. quoique ? registre 6 : valeur accumulée [2] registre 7 : valeur temporaire (masquée+shift(!)) [2] je ne suis pas sûr. il faudra jouer très serré à cause du décalage de la valeur initiale, qui nécessite de l'adresse. of course le prologue de cette fonction sera entrelacé avec l'épilogue de la partie de déplacement. lundi, 4h du mat. : je repense au code qui en fait est complétement asphyxié par ... les masques. il en faut 3 en fait. il ne reste donc plus que deux registres pour faire notre travail. on a t1,t2,v1,v2,v3,m1,m2,m3 t1=v1 // t2=v2 v1>>=1 // v2>>=1 t1&=m1 // t2&=m2 t1+=t2 t1+=[mem] (optionnel ?) t2=v3 v3>>=1 t2&=m3 t2+=t1 sauve t2 noter que le v?>>=1 peut être déplacé à volonté. mais la troisième partie est difficile car il n'y a pas grand-chose à entrelacer. à part les décalages ? le graphe donne: movq t1,v1 psrlq v1,1 ; sauve t2 ici: (cycle précédent) movq [mem],t2 movq t2,v2 pand t1,m1 pand t2,m2 psrlq v2,1 por t2,t1 movq t1,v3 psrlq v3,1 (padd t2,[mem])/stall pand t1,m3 padd t2,t1 /stall on voit qu'il y a au minimum 1 stall, sinon plus. il faut 7 cycles par tour, après l'initialisation du "pipeline". cette initialisation prend deux ou trois tours: il faut faire la création des masques et aussi shifter quelques valeurs: 0: créer le masque 0101010101010101 cycle 1: masque 1 existe, m2 et m3 sont libres. cela permet de bien s'étaler dans les registres. 2: m2=m1<<1 cycle 2: m1 et m2 existent, m3 permet encore de s'étaler. 3: m3=m2<<1 la suite a été traitée plus haut. il faut donc faire du boulot autour des cycles 1 et 2 : movq v1,[S0] movq v2,[S1] movq v3,[S2] pxor m1,m1 pcmpeqb m2,m2 psubb m1,m2 ; m1=0x0101010101010101, m2 libre movq t1,v1 movq t2,v2 movq t3,v3 pand t1,m1 pand t2,m1 pand t3,m1 psrlq v1,1 psllq t2,1 psllq t3,2 por t2,t1 paddb t3,[mem] paddb t2,t3 ; noter l'"arbre" qui brise les dépendances parasites. movq m2,m1 psllq m2,1 movq [mem],t2 *** movq t1,v1 movq t2,v2 movq t3,v3 pand t1,m1 pand t2,m2 pand t3,m2 psrlq v1,1 psrlq v2,1 psllq t1,1 por t2,t1 paddb t3,[mem] paddb t2,t3 movq m3,m2 psllq m3,1 movq [mem],t2 PostIt : penser à faire un interpréteur de commandes. ça facilitera énormément l'automatisation des expériences ! penser aussi à sauver les résultats des calibrations dans des fichiers au format gnuplot !!! résultats du graphe: movq t1,[S0] ;$ pcmpeqb m1,m1 ; m1=-1 movq t2,[S1] ;$ movq v1,t1 movq t3,[S2] ;$ pxor m3,m3 ; m3=0 movq v2,t2 psubb m3,m1 ; m3=0x0101010101010101 movq v3,t3 pand t3,m1 pand t2,m1 psllq t3,2 pand t1,m1 psllq t2,1 paddb t3,[m0] por t2,t1 movq m1,m3 paddb t2,t3 movq t3,v3 psllq m3,1 movq t2,v2 psrlq v1,1 movq [m0],t2 pand t3,m3 movq t1,v1 psllq t3,1 pand t2,m3 psrlq v1,v1 paddb t1,[m1] por t2,t1 psrlq v2,1 paddb t2,t1 movq m2,m3 ; t3 se transforme en m2 ici psllq m3,1 ; 34 instructions sans stall ! 17 cycles (optimistement) Maintenant je me demande s'il n'est pas mieux de mettre m3 dans une variable globale. ça fait un accès mémoire en plus mais plus d'opportunités pour le parallélisme/ILP. ah. j'adore ce mot. <graphe> en effet, avec un accès à la mémoire: movq t1,v1 psrlq v1,1 movq t2,v2 psrlq v2,1 movq [M],t3 movq t3,v3 pand t1,m1 pand t2,m2 pand t3,[m3] ;$ por t2,t1 paddb t3,[M] psrlq v3,1 paddb t3,t2 //stall 13 instructions et 1 stall. pas grand-chose de changé, sauf que c'est moins tordu... et lorsque le "paddb t3,[M]" est enlevé, il n'y a pas de stall, ce qui réduit la boucle à 6 cycles. Pour enlever le stall et gagner un cycle sur 15, il faut traiter deux occurences de la boucle ensemble, il suffit pour cela de déplacer les accès à la mémoire. ;1 movq t1,v1 psrlq v1,1 movq t2,v2 psrlq v2,1 movq [M],t3 movq t3,v3 pand t1,m1 pand t2,m2 pand t3,[m3] ;$ por t2,t1 paddb t3,[M] psrlq v3,1 paddb t3,t2 movq t1,v1 psrlq v1,1 movq t2,v2 movq [M],t3 psrlq v2,1 movq t3,v3 pand t1,m1 pand t3,[m3] ;$ pand t2,m2 paddb t3,[M] por t2,t1 psrlq v3,1 paddb t3,t2 et voilà ! 13 cycles au lieu de 14 ! mais on a beau faire, on ne peut pas diminuer le nombre d'opérations. le scheduling des instructions doit faire le reste. il a fallu cependant revoir l'organisation des données pour arriver à réduire au maximum le nombre de cycles (PMMX seulement, mais ça marche toujours sur P6). ce processus de développement est itératif, il est fortement dépendent des algorithmes à écrire et il n'y a donc pas de recette miracle, mais une bonne dose d'aprofondissement. pour cela, il faut tout mettre à plat et retourner tout dans tous les sens pour trouver une faille. En tous cas il faut un peu refaire l'amorce, ensuite, vérifier que ça marche ! movq t1,[S0] ;$ pcmpeqb m1,m1 ; m1=-1 movq t2,[S1] ;$ movq v1,t1 movq t3,[S2] ;$ pxor m2,m2 ; m2=0 movq v2,t2 psubb m2,m1 ; m2=0x0101010101010101 movq v3,t3 pand t3,m2 pand t2,m2 psllq t3,2 pand t1,m2 psllq t2,1 paddb t3,[_M0] por t2,t1 movq m1,m2 paddb t2,t3 movq t3,v3 psllq m2,1 movq t1,v1 psrlq v1,1 movq [_M0],t2 ; damnit ! problème de dépendence !!! lock t2. renommage avec t1 pand t3,m2 pand t1,m1 ; oublié. movq t2,v2 psllq t3,1 pand t2,m2 psrlq v1,1 paddb t2,[_M1] por t3,t1 psrlq v2,1 paddb t3,t2 PRLLSUM0.ASM : chronométré à 59 cycles. le comptage manuel augurait 57 cycles mais je ne me plains pas. et comme le code semble fonctionner, je peux enfin respirer :-) PRLLSUM1.ASM: sans l'addition (initialisation du buffer) BOUDIOU de "'{(=}=+ø0 !!!!!! 122 cycles ! j'ai lâché ma hargne sur comp.arch. disons que l'algo fonctionne. il faut maintenant choisir entre 4 codes: -code de début de bloc ou non -code de densité ou de collision. le code de début de bloc ne lit pas la donnée du buffer d'affichage, ce qui évite d'avoir à le remettre à 0. le code de densité est 3x plus lourd que le code de collision. je pense qu'on peut séparer les codes destiné aux collisions et aux densités. il faut donc recopier la boucle centrale et modifier l'interface pour inclure cette option. pour savoir si on est à la première ligne d'un bloc, on va utiliser un flag dans EBX. le bit 16 sera à 1 dans ce cas. à la fin de la ligne, il sera remis à zero. à l'intérieur de la boucle de ligne, avant d'accumuler, on testera le flag et on dirigera vers la procédure adaptée. ROUND 2: le déplacement. ici aussi, prévoir une version "light" au cas où WALLMASK=-1. c'est un peu la jungle, comme on peut le remarquer. jusqu'à maintenant, le code a été pensé pour être lisible et pour fonctionner, pas pour aller vite. c'est aussi un endroit très sensible au niveau des accès à la mémoire. D'abord, le OR des XORs. Il faut penser à garder des points de sortie pour les collisions et XORG. niveau 1: les variable d'entrée movq _ma,XORF movq _mb,XORA movq _mc,XORB movq _ma,XORC movq _mb,XORD movq _mc,XORE movq _ma,XORF movq _mb,XORA niveau 2: les OR de premier niveau movq _ma,[XORF] movq _mb,[XORA] por _ma,_mb movq _mc,[XORB] por _mb,_mc movq _ma,[XORC] por _mc,_ma movq _mb,[XORD] por _ma,_mb movq _mc,[XORE] por _mb,_mc movq _ma,[XORF] por _mc,_ma movq _mb,[XORA] por _ma,_mb niveau 3: les OR de deuxième niveau movq _ma,[XORF] movq _mb,[XORA] por _ma,_mb movq _mc,[XORB] por _ma,_mc por _mb,_mc movq _ma,[XORC] por _mb,_ma por _mc,_ma movq _mb,[XORD] por _mc,_mb por _ma,_mb movq _mc,[XORE] por _ma,_mc por _mb,_mc movq _ma,[XORF] por _mb,_ma por _mc,_ma movq _mb,[XORA] por _mc,_mb niveau 4: XORG + petite simplification de la fin movq _ma,[XORF] movq _mb,[XORA] por _ma,_mb movq _mc,[XORB] por _ma,_mc por _mb,_mc movq _ma,[XORC] por _mb,_ma por _mc,_ma movq _mb,[XORD] por _mc,_mb por _ma,_mb movq [XORG],_mc movq _mc,[XORE] por _ma,_mc por _mb,_mc movq _ma,[XORF] por _mb,_ma por _mc,_ma por _mc,[XORA] por _mc,[XORG] niveau 5: /WALLMASK et Pn movq _ma,[XORF] movq _mb,[XORA] por _ma,_mb movq _mc,[XORB] por _ma,_mc por _mb,_mc por _ma,[PA] pand _ma,_wm xor _ma,[A] movq _ma,[XORC] por _mb,_ma por _mc,_ma por _mb,[PB] pand _mb,_wm xor _mb,[B] movq _mb,[XORD] por _mc,_mb por _ma,_mb movq [XORG],_mc por _mc,[PC] pand _mc,_wm xor _mc,[C] movq _mc,[XORE] por _ma,_mc por _mb,_mc por _ma,[PA] pand _ma,_wm xor _ma,[D] movq _ma,[XORF] por _mb,_ma por _mc,_ma por _mb,[PB] pand _mb,_wm xor _mb,[E] por _mc,[XORA] movq _ma,_mc por _mc,[PC] pand _mc,_wm xor _mc,[F] por _ma,[XORG] movq _mb,[PnOr] pand _ma,_wm por _mb,_ma xor _ma,[G] pand _mb,_wm movq [coll],_mb ; pas utilisé : passe directement à la passe suivante. à ce niveau-là, le scheduling est quasi inexistant. l'allocation des registres se fera à la fin, dans le graphe qui incluera le déplacement. partie paire: ; A: pair et impair movq mm1,[edi+A] pxor mm1,mm7 ; modification movq mm0,mm1 ; A psrlq mm1,1 ; A psllq mm0,63 ; A' movq [edi+A],mm1; A por mm0,[edi+A-CEL] ; A' movq [edi+A-CEL],mm0; A' paddd mm1,[RANDOM] $base1__ equ $-4 movq [RANDOM],mm1 $base1__ equ $-4 ; B: (last part) ; pair movq mm0,[temp_B] $base__ equ $-4 pxor mm0,mm6 movq [esi+72],mm0 ; result-> temp B ;impair: ; B: (last part) movq mm1,[temp_B] $base1__ equ $-4 pxor mm1,mm6 movq mm0,mm1 psrlq mm1,1 psllq mm0,63 movq [esi+72],mm1 ; result-> temp B por mm0,[esi+72-80] movq [esi+72-80],mm0 ; ;C: (last part) ;pair: movq mm2,[temp_C2] $base__ equ $-4 movd mm1,[temp_C] $base__ equ $-4 pxor mm2,mm5 movq mm0,mm2 psllq mm2,1 psrlq mm0,63 por mm2,mm1 movd [temp_C],mm0 $base__ equ $-4 movq [esi+64],mm2 ; result-> temp C ;impair: ;C: (last part) movq mm0,[temp_C2] $base1__ equ $-4 pxor mm0,mm5 movq [esi+64],mm0 ;bug ; result-> temp C ; D: pair et impair movq mm2,[edi+D] movd mm1,[temp_D] $base__ equ $-4 pxor mm2,mm7 movq mm0,mm2 psllq mm2,1 psrlq mm0,63 por mm2,mm1 movd [temp_D],mm0 $base__ equ $-4 movq [edi+D],mm2 ; E: pair movq mm0,[edi+E] movd mm2,[temp_E] $base__ equ $-4 pxor mm0,mm6 movq mm1,mm0 psrlq mm0,63 psllq mm1,1 por mm1,mm2 movd [temp_E],mm0 $base__ equ $-4 movq [edi+1000000],mm1 _LINE_E equ $-4 ;impair: ; E: movq mm0,[edi+E] pxor mm0,mm6 movq [edi+10000000],mm0 _LINE_E2 equ $-4 ; F: ; pair: movq mm0,[edi+F] pxor mm0,mm5 movq [edi+10000000],mm0 _LINE_F equ $-4 ; F: ; impair movq mm0,[edi+F] pxor mm0,mm5 movq mm1,mm0 psrlq mm0,1 psllq mm1,63 movq [edi+10000000],mm0 _LINE_F2 equ $-4 por mm1,[edi+1000000] _LINE_F_CELL equ $-4 movq [edi+1000000],mm1 _LINE_F_CELL2 equ $-4 ; G: pair et impair pxor mm7,[edi+G] movq [edi+G],mm7 il faut donc faire de nombreux graphes légèrement différents: -collision paire -collision impaire -densité paire -densité impaire suivis par un morceau avec accumulation ou non (saut conditionnel). j'abandonne provisoirement l'idée d'un court-circuit sur Wallmask==-1. il est plus simple d'avoir un flag=1 sur le LSB du pointeur de liste des murs. dimanche, 2h du matin: j'ai retrouvé ça dans les archives: Received: from or.mime.univ-paris8.fr (or.mime.univ-paris8.fr [193.54.153.27]) by front3.grolier.fr (8.9.0/MGC-980407-Frontal-No_Relay) with ESMTP id QAA12691 for <whygee@club-internet.fr>; Thu, 10 Dec 1998 16:27:53 +0100 (MET) Received: from dichlore.mime.univ-paris8.fr (dichlore.mime.univ-paris8.fr [193.54.153.26]) by or.mime.univ-paris8.fr (8.9.0/8.8.8) with ESMTP id QAA04638 for <whygee@mime.univ-paris8.fr>; Thu, 10 Dec 1998 16:18:39 +0100 (CET) Received: from mime.univ-paris8.fr (localhost [127.0.0.1]) by dichlore.mime.univ-paris8.fr (8.8.8/8.8.8) with ESMTP id QAA01534; Thu, 10 Dec 1998 16:24:42 +0100 (CET) (envelope-from whygee@mime.univ-paris8.fr) Sender: whygee@dichlore.mime.univ-paris8.fr Message-ID: <366FE7B9.A5DFE1CF@mime.univ-paris8.fr> Date: Thu, 10 Dec 1998 16:24:41 +0100 From: "Whygee (Yann Guidon)" <whygee@mime.univ-paris8.fr> Reply-To: whygee@mime.univ-paris8.fr Organization: The Whygee Corporation X-Mailer: Mozilla 4.05 [en] (X11; I; FreeBSD 3.0-980105-SNAP i386) MIME-Version: 1.0 Newsgroups: comp.arch To: Andy Glew <glew@cs.wisc.edu> Subject: Re: Is it possible to optimize Intel PII instruction flows ? References: <366DEDBD.3267EEED@mime.univ-paris8.fr> <366EC0FC.64B36FE6@cs.wisc.edu> <366F1FF9.426955FE@mime.univ-paris8.fr> <366F2CCD.4948C354@cs.wisc.edu> Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-UIDL: 710a138d99b1c9910744e848d285403a Status: RO X-Mozilla-Status: 8015 Andy Glew wrote: > > It appears that the usual lookup-table oriented method is > > suboptimal and not suitable for the PII (we have one at the university) > > since it needs a lot of masks and ORs, and there are a LOT of > > partial register stalls that did not appear in the previous > > processor generations. > You can almost 100% definitely eliminate all of these partial stalls, > by, as you say, masks and ORs. yes but this requires additional instructions (shifts+OR ) that were not required before. the 7 cycles per table update is much too high. > By the way, the i486 and P5 had partial stalls, just not so bad. yes, but 7 cycles can't remain unnoticed. I have (the numbers speak) to use the boolean approach. > > so the (renamed) register is updated at the next cycle ? > Yes. > > That's why i abandon the PMMX for the PII (i have no PMMX anyway). > Sorry. We wanted to give you 2, or even 4, loads per cycle, but > back in 1991 we ran out of hardware. that's not fair :-( I presume, the PII successors will include more units ? i believe the Intel's ooo achitecture is not yet mature. furthermore, ie for tight loops, there should be a uops cache (as does another chip, i don't remember which). > > Anyway, how much can i count on the out of order execution ? > ??? (sorry) how much can the ooo scheme help me ignore nasty details like in the Pentium ? the 40 renamed registers seem to be enough to delay some instructions during about 5 cycles. so, bothering only of the instruction translation seems enough to ensure a good performance (to a certan point). the code i have to write is about 400 instructions long, has no jump and uses only about ten kinds of instructions, so a reduced number of parameters are to be taken in account (i hope). at least, 95% of the memory accesses hit the L1 cache :-) > If your code is flat-out capable of using 64 bits of data from memory > every clock cycle, out-of-order won't help. even i L1 ? > Where it will help is if > you have a burst in one place, and a burst in another. Or, even more > likely, if you have cache misses that can be overlapped. Since i optimized the algo for Pentium-like processors, i don't see your point... > In the presence of data cache misses a Pentium II beats an Alpha, > at least until the 21264 comes along. If i had an Alpha... Unfortunately, there is no DOS-like OS for ALPHA that allows me to use the yummy possibilities of that chip. We run LINUX on the Alphas we have and this is a multithreading OS, and unfortunately task switches break the strip mining's performance gains. In a word, i prefer to write a little compiler for the PII than an OS for the ALPHA :-/ > > > Similarly, stores require two uops, just like every other P6 instruction > > > that contains a store: store-address and store-data. > > Maybe "i should read the docs" but i'm not sure that i understand it right. > > Are the two uops executed simultaneously ? > No, the second depends on the first. But they can overlap operations from > other instructions in the window. so the overall store throughput is one per cycle... > > So, in the scope of instruction scheduling the question now is: what > > matters most ? > > feeding all the execution units or feeding all the uops translation units ? > > I think that a compound solution is better but all the latencies and the > > other stages make the compiler very complex. > > My recommendation: run a piece of sample code, and profile it with VTUNE, > looking for places where 0, 1, 2, or 3 instructions retire per cycle. > In the vicinity of places with low performance, look for the following > bottlenecks: > > a) more than one taken branch per cycle nope > b) more than 2 ALU ops per cycle decoding cycle ? translation cycle ? > c) more than 1 load per cycle (idem as above) > d) the 411 template handled by the code generator > e) more than 16 bytes of instruction per cycle mmm.... as previously said, instructions are 3 or 7 bytes long. gasp :-( So this is another constraint (i have not seen it in the opt. manual) for the code generator :-( > I usually do this by totalling up the instructions in a loop, > and seeing how many cycles it should take to execute according to each > of the rules above. If one rule is unduly lower than the others > - e.g. if the loop has 6 ALU instructions => 3 ALU cycles, but also > 6 loads => loads are the bottleneck at 6 cycles - I try to optimize that > bottleneck. Since the code will be automatically generated, i probably won't use this technique. i'll have to measure the code anyway, and tune the optimisation techniques this way. wow... it was about fluid simulation at the beginning :-/ WHYGEE ~~~~~~~~~~~~~~~~~~~~ "I'm a poor, a lonesome coder..." SHARCPAGE: http://www.mime.up8.edu/~whygee/sharcpage.html et aujourd'hui, j'ai eu une autre réponse d'Andy concernant le problème du code deux fois plus lent. Je ne m'attendais pas à une réponse aussi rapide alors j'avais commencé à travailler sur le reste. From aglew@students.wisc.edu Sat Oct 16 23: 00:57 1999 Received: from or.mime.univ-paris8.fr (or.mime.univ-paris8.fr [193.54.153.27]) by front5m.grolier.fr (8.9.3/No_Relay+No_Spam_MGC990224) with ESMTP id XAA10508 for <whygee@club-internet.fr>; Sat, 16 Oct 1999 23:00:56 +0200 (MET DST) Received: from mail1.doit.wisc.edu (mail1.doit.wisc.edu [144.92.9.40]) by or.mime.univ-paris8.fr (8.9.3/8.9.3) with ESMTP id WAA16040 for <whygee@mime.univ-paris8.fr>; Sat, 16 Oct 1999 22:54:11 +0200 (CEST) Received: from [204.71.144.26] by mail1.doit.wisc.edu id PAA62522 (8.9.1/50); Sat, 16 Oct 1999 15:07:48 -0500 Message-ID: <00b401bf1812$c8a41e20$1a9047cc@epoxy> From: "Andy Glew" <aglew@students.wisc.edu> To: <whygee@mime.univ-paris8.fr> X-Mailer: Microsoft Outlook Express 5.00.2314.1300 Subject: Re: PMMX memory access: now i'm furious. Date: Sat, 16 Oct 1999 15:12:33 -0500 X-Priority: 3 X-MSMail-Priority: Normal X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300 Status: X-Mmail: \Recent X-M-Uid: 1627.940107657 X-Mozilla-Status: 8013 X-Mozilla-Status2: 00000000 X-UIDL: 1627.940107657 Is this a P55C chip? I.e. a Pentium with MMX? I think they were sold up to 200MHz. *Not* a Pentium II with MMX, right? Not a P6 core? Anyway, if a P55C, the answer is obvious: P55C has a no-write-allocate cache. It doesn't put things in the cache unless they have been read. Thus, when you remove the loads, the writes to the same cache address that follow are guaranteed cache misses. Prefetching for writes by scheduling reads ahead of time is/was a well-known technique for P5 family. It's on all the "how to code well" web pages. P6s, however, have a write-allocate cache, so this prefetch technique is much less necessary - it sometimes helps, since it may start a memory request earlier, but sometimes it hurts. That's one of the problems of reading generic documentation, like "Optimizing for Intel's 32 bit processors" - they are shy about optimizations that are not uniform across all chips. C'est clair : dans le contexte actuel il est préférable de garder la forme générale de la procédure, celle qui lit les données pour accumuler. Il faut vider le plan temporaire après l'affichage. je suis à la fois en train de payer et de profiter de l'optimisation agressive du code. dans un sens j'ai la certitude que mon code fonctionne comme prévu, en le testant bloc par bloc, et dans l'autre je passe beaucoup de temps sur des détails. ces détails sont moins insignifiants cependant lorsque le code est 2 ou 3 fois plus lent que prévu... et c'est là que réside un grand sujet que le mémoire doit traiter. y apporter des réponses est encore mieuxxxxx. dimanche soir, 2h40: comment faire, donc ? la nouvelle situation est qu'on dispose d'un autre plan, immense, où est stockée l'information à afficher. Cela permet donc de faire des scrollings et de sauver la totalité de l'image sans avoir à tout recalculer. Cela veut aussi dire qu'il ne faut vider une ligne que juste avant de commencer le calcul, lorsqu'on déplace la fenêtre, lorsqu'un nouvelle ligne est commencée. Le problème est un problème de mémoire cache. comme Andy le dit, il faut charger la donnée en mémoire avant d'y écrire. 2 solution viennent à l'esprit : avoir une petite procédure qui vide la ligne tout en effectuant un préfetch, ou modifier la procédure d'accumulation pour que le chargement ne soit pas utilisé. mais cette deuxième solution est hors de question car 1- la forme de l'accès ne permet pas cela 2- il faudrait rajouter 8 instructions qui seraient utilisées dans tous les cas, ce qui ralentirait globalement le code. Il faudrait, durant toute la partie du calcul, avoir du code qui précharge les données et qui les efface conditionnellement, avec du code ia32 à prédication. mais même cela: -1 le PMMX ne dispose pas de cela dans les tables "officielles", -2 ça rajoute du code inutile dans la plupart des cas, même si ça sert de bouche-trou dans tous les cas, je ne sais pas comment faire. Qu'est-ce qui est prêt ? A) l'accumulation de densité: (PRLLSUM0.ASM) (PARLLSUM.ASM est un peu boggé, l'allocation des registres a apparemment dévoilé un bug) org 100h ;t1=mm0 ;t2=mm1 ;t3=mm2 ;m1=mm3 ;m2=mm4 ;v1=mm5 ;v2=mm6 ;v3=mm7 cli xor eax,eax xor edx,edx mov ecx,16 wrmsr mov cx,ax align 16 loop_entry: ;;0 movq mm0,[S0] ;$ pcmpeqb mm3,mm3 ; m1=-1 movq mm1,[S1] ;$ movq mm5,mm0 movq mm2,[S2] ;$ pxor mm4,mm4 ; m2=0 movq mm6,mm1 psubb mm4,mm3 ; m2=0x0101010101010101 movq mm7,mm2 pand mm2,mm4 pand mm1,mm4 psllq mm2,2 pand mm0,mm4 psllq mm1,1 paddb mm2,[_M0] por mm1,mm0 movq mm3,mm4 paddb mm1,mm2 movq mm2,mm7 psllq mm4,1 ; 10 cy movq mm0,mm5 psrlq mm5,1 movq [_M0],mm1 ; damnit ! problème de dépendence !!! lock t2. renommage avec t1 pand mm2,mm4 pand mm0,mm3 ; oublié. movq mm1,mm6 psllq mm2,1 pand mm1,mm4 psrlq mm5,1 paddb mm1,[_M1] por mm2,mm0 psrlq mm6,1 paddb mm2,mm1 ;;1 movq mm0,mm5 psrlq mm5,1 movq mm1,mm6 psrlq mm6,1 movq [_M1],mm2 movq mm2,mm7 pand mm0,mm3 pand mm1,mm4 ; 20 cy pand mm2,[m3] ;$ por mm1,mm0 paddb mm2,[_M2] psrlq mm7,1 paddb mm2,mm1 movq mm0,mm5 psrlq mm5,1 movq mm1,mm6 movq [_M2],mm2 psrlq mm6,1 movq mm2,mm7 pand mm0,mm3 pand mm2,[m3] ;$ pand mm1,mm4 paddb mm2,[_M3] por mm1,mm0 psrlq mm7,1 paddb mm2,mm1 ;;2 movq mm0,mm5 psrlq mm5,1 ; 30 cy movq mm1,mm6 psrlq mm6,1 movq [_M3],mm2 movq mm2,mm7 pand mm0,mm3 pand mm1,mm4 pand mm2,[m3] ;$ por mm1,mm0 paddb mm2,[_M4] psrlq mm7,1 paddb mm2,mm1 movq mm0,mm5 psrlq mm5,1 movq mm1,mm6 movq [_M4],mm2 psrlq mm6,1 movq mm2,mm7 pand mm0,mm3 pand mm2,[m3] ;$ pand mm1,mm4 ; 40 cy paddb mm2,[_M5] por mm1,mm0 psrlq mm7,1 paddb mm2,mm1 ;;3 movq mm0,mm5 psrlq mm5,1 movq mm1,mm6 psrlq mm6,1 movq [_M5],mm2 movq mm2,mm7 pand mm0,mm3 pand mm1,mm4 pand mm2,[m3] ;$ por mm1,mm0 paddb mm2,[_M6] psrlq mm7,1 paddb mm2,mm1 movq mm0,mm5 psrlq mm5,1 movq mm1,mm6 ; 50 cy movq [_M6],mm2 psrlq mm6,1 movq mm2,mm7 pand mm0,mm3 pand mm2,[m3] ;$ pand mm1,mm4 paddb mm2,[_M7] por mm1,mm0 psrlq mm7,1 paddb mm2,mm1 ; 55 cy ; stall! dec cx movq [_M7],mm2 jnz near loop_entry rdtsc sti mov [time],eax mov [time+4],edx db 0CCh ret align 16 time dd 0,0,0,0 S0 db 0,-1,0,-1,0,-1,0,-1, 0,0,0,0,0,0,0,0 S1 db 0,0,-1,-1,0,0,-1,-1, 0,0,0,0,0,0,0,0 S2 db 0,0,0,0,-1,-1,-1,-1, 0,0,0,0,0,0,0,0 m3 db 4,4,4,4,4,4,4,4, 0,0,0,0,0,0,0,0 db 0;... align 16 _M0 dd 0,0,0,0 _M1 dd 0,0,0,0 _M2 dd 0,0,0,0 _M3 dd 0,0,0,0 _M4 dd 0,0,0,0 _M5 dd 0,0,0,0 _M6 dd 0,0,0,0 _M7 dd 0,0,0,0 db 0 align 16 B) l'accumulation des collisions: ligne 3280 Il faut aussi penser à inclure une procédure de détection de la souris et de la mémoire. ensuite faire l'interprétation des lignes de commande et des fichiers de commande, la sauvegarde des mesures et des images... optionware... il faut cependant trouver une solution pour notre petit problème de buffer à mettre à zero. le problème étant le préfetch, non ? Lundi 3 am: j'ai lu plein de docs d'Intel. de 1: pour charger une valeur dans un compteur de performance avec WRMSR, il suffit d'utiliser EAX, EDX est ignoré. de 2: l'explication pour les write miss est là, mais assez floue. Intel préfère se concentrer sur le P6. de 3: j'ai fait pas mal de mesures, et il se trouve que je me suis rendu compte que WBINVD vide TOUTES les caches, pas seulement la cache de données. alors comment je vais faire pour vérifier l'effet du code de préfetch ???? de 4: j'ai imprimé les docs qui traitent des compteurs de performance. je pense donc traiter par les essais le problème du préfetch. j'en reviens donc au premier cas où le code d'accumulation est sélectionné. Cette solution est envisageable si l'on considère que le problème à l'origine du blocage est résolu. yo. premiers essais de préfetch: on passe de 122 à 76 cycles. encourageant. maintenant il faut trouver la meilleure configuration et gagner encore une quinzaine de cycles. 4am: les compteurs de performance ont l'air de bien fonctionner. c'est vraiment très utile !!! malheureusement, les vérifier tous prend du temps. J'ai réussi à descendre à 66 cycles, mais en m'apercevant que cette &~"#'{-|`^ç@àø+= de CPU traite les données par paquet de 128 bits, pas 256... il faut effectuer 4 opérations de préfetch au lieu de 2 comme je le pensais. (! voir ligne 6788) Je suis descendu à 58 cycles en enlevant les instructions de préfetch du corps de la boucle, elles effectuent des opérations assez lourdes. je me demande quelle opération accède à la mémoire sans bloquer le pipeline ? en tous cas, ça montre qu'on peut prefetcher à l'extérieur de la boucle. Une autre mesure montre qu'il faut encore gagner 5 cycles par rescheduling, c'est à dire repairage, et on ne pourra pas faire mieux. 5am: je suis à 55 cycles ! lundi soir, 10h: le PII semble assez insensible aux conditions de la memoire. il stagne toujours à qqs pourcents autour de 59 cycles pour la boucle. je ne sais pas a priori comment le booster. je verrai plus tard, je pense. 11h: j'ai essayé d'assembler sp65.asm. resize a planté. pas de compilo pascal sous la main : j'ai récupéré mon DD 1G2 et l'ai placé dans le rack. j'ai isolé le problème qui, si je m'en souviens, m'est déjà arrivé. il a suffit d'enlever uses crt,dos pour en finir : l'adresse de l'erreur pointait vers cette partie-là dans le ficher .MAP :-) v.exe fonctionne mais la souris ne répond pas. pourtant elle fonctionne bien avec le reste des applications et le programme ne signale pas d'erreur. 11h20: il suffisait de brancher la souris sur COM1 et pas COM2... d'où l'intérêt de se faire son propre détecteur de souris. le code n'a même pas réussi à trouver le pot aux roses. 11H50: le BSWAP (BYTEREV?) est chronométré à 38 cycles à vide sur PII. je suis rassuré... 2h15: actuellement, j'ai 4 morceaux de code prêts. A) bswap -> affichage B) accumulation densité \ C) initialisation densité / 55 cycles D) accumulation collisions = 26 cycles B&C sont environ 2 fois plus lents alors qu'ils effectuent 3 fois plus d'operations. cela vient du fait qu'il y a autant d'accès à la mémoire dans tous les cas : load/store de 8 mots de 64 bits. naturellement, il reste à faire l'initialisation des collisions. mardi soir, 1h15 du matin: hier soir avant de me coucher, j'avais mis au point une technique de "software pipelining". elle a la contrainte de nécessiter autant de registres que la boucle a d'opérations. je l'ai adaptée un peu (problème de durée de boucle) pour rescheduler l'"initialisation collisions" et le résultat est pas mal : ça permet pour des boucles au corps simple de se passer de l'analyse des graphes. Comme d'habitude, on commence le scheduling par la fin. Je m'aperçois que l'initialisation à 0x0101010101010101 du masque est commune à toutes les parties de code (collision/densité et accumulation/initialisation). mmH=masque mmG=valeur de la collision. pcmpeqb mmF,mmF pxor mmH,mmH movq mmA,mmG psubb mmH,mmF movq mmB,mmG psrlq mmA,1 pand mmG,mmH movq mmC,mmB psrlq mmB,2 movq [_M0],mmG pand mmA,mmH movq mmD,mmC psrlq mmC,3 movq [_M1],mmA pand mmB,mmH movq mmE,mmD psrlq mmD,4 movq [_M2],mmB pand mmC,mmH movq mmF,mmE psrlq mmE,5 movq [_M3],mmC pand mmD,mmH movq mmG,mmF psrlq mmF,6 movq [_M4],mmD pand mmE,mmH psrlq mmG,7 pand mmF,mmH movq [_M5],mmE pand mmG,mmH ;16 movq [_M6],mmF movq [_M7],mmG 18 cycles théoriques. si on y rajoute le cycle de bouclage, on trouve les 19 cycles mesurés. encore une fois, l'importance du préfetch est cruciale. il faut trouver un endroit dans les instructions précédentes pour caser les 4 instructions de préfetch. 2h10 : j'ai 5 morceaux prêts. il faut maintenant passer à l'étape de déplacement, ce qui commence par une analyse du code déjà écrit : il faut en tirer le graphe et analyser le besoin en registres. Ensuite on entrelace les graphes entre eux, pairs et impairs, on rajoute le code de OR des XORs et on applatit le tout. 5h15: pour chaque cas, il suffit au maximum de 3 registres, il reste donc un registre pour respirer. cela permet d'entrelacer plus intimement les blocs entre eux. vendredi, 19h: j'ai analysé le graphe de dépendence pour les déplacements pairs. 85 instructions, 40 accès à la mémoire, 30 étages de graphe. je n'ai même pas compté dedans les instructions de préfetch. 20h: consultation des docs: l'instruction de prefetch sera un "test al,[mem]" car elle est pairable U et V mais ce n'est pas pairable avec une instruction MMX qui accède à la mémoire. 3h: 47 cycles en comptant les préfetch, soit une demi-douzaine de cycles morts, principalement à cause du grand nombre d'accès à la mémoire (presque la moitié). déplacement pair: en entrée,t4=PA, t3=PB buggé. ;1 movq mA,[XORF] pcmpeqb mM,mM ;2 movq mB,[XORA] ; STALL ;3 movq mC,[XORB] por mA,mB ;4 pxor mM,[WallMask] por mA,mC ;5 movq t1,[A] por mB,mC ;6 movq [PA],t4 ;t4 contenait PA por mA,t4 ;7 movq [PB],t3 ;t3 contenait PB pand mA,mM ;8 test al,[_M0] ; prefetch pxor t1,mA ;9 movq mA,[XORC] por mB,t3 ;10 movq t2,t1 por mB,mA ;11 por mC,mA movq t3,t1 ;12 paddd t2,[RANDOM] pand mB,mM ;13 por mA,t4 psrlq t1,1 ;14 movq t4,[temp_B] psllq t3,63 ;15 movq [RANDOM],t2 pxor t4,mB ;16 movq mB,[XORD] ; STALL ;16' por t3,[A-CEL] ; STALL ;17 movq [A],t1 por mC,mB ;18 movq [A-CEL],t3 por mA,mC ;19 movq [XORD],mC ; STALL ;20 movq [esi+80],t4 ; STALL ;21 por mC,[PC] ; STALL ;22 movq t1,[temp_C2] pand mC,mM ;23 por mB,[PB] pxor t1,mC ;24 movq mC,[XORE] movq t3,t1 ;25 movd t4,[temp_C_32] por mA,mC ;26 movq t2,[edi+D] psrlq t1,63 ;27 pand mA,mM test al,[_M2] ; prefetch ;27' por mB,mC psllq t3,1 ;28 movd [temp_C_32],t1 pxor t2,mA ;29 movq mA,[XORF] por t3,t4 ;30 test al,[_M4] ; prefetch movq t1,t2 ;31 movq [esi+64],t3 ;(C) por mB,mA ;32 movq t3,[edi+E] por mC,mA ;33 movq t4,[temp_D] psrlq t1,63 ;34 test al,[_M6] ; prefetch psllq t2,1 ;35 movq [temp_D],t1 pand mB,mM ;36 movq mA,mC por t2,t4 ;37 por mA,[XORD] pxor t3,mB ;38 por mC,[PC] movq t1,t3 ;39 movq mB,[edi+F] pand mC,mM ;40 pand mM,mA test al,[_M0] ; prefetch ;41 por mA,[PnOr] ; optionnel pxor mB,mC ;42 movq [edi+D],t2 psllq t1,1 ;43 pxor mM,[edi+G] psrlq t3,63 ;44 movq [edi+F-line],mB ; STALL ;45 por t1,[edi-LINE+E] ; ??? ; STALL ;46 movq [temp_E],t3 ; 32b ??? ; STALL ;47 movq [G],mM ; STALL ;48 movq [edi-LINE+E],t1 ; STALL sortie :mA=collisions 50 cycles, 11 stalls, quelques instructions oubliées. la manipulation manuelle de la (presque) centaine (90) d'instructions a eu quelques petites failles mais le bon sens n'est pas complétement endormi. bonne nouvelle, il n'y a pas de grosse modification à faire pour passer au mode sans collisions. 4H30 : MON DIEU QUEL ABRUTI !!! je viens de comprendre pourquoi les lignes de cache semblaient faire 128 bits au lieu de 256... c'est parce que j'alignais les données sur 16 octets pour que ça soit lisible sous DEBUG... 5h20: le code s'exécute en 53 cycles, exécute 88 instructions et en paire 37. j'espère me rattrapper sur la version impaire en mettant à profit les erreurs que j'ai faites pour ce code. je ne peux plus rien faire pour lui, maintenant, il faudra aussi penser à vérifier son parfait fonctionnement. enfin, il faudra l'adapter au reste du code car l'accès aux variables sera changé d'ici peu... la dernière version est dans PRLLSUM2.ASM. samedi 19h20: déplacement impair: 45 cycles, 9 stalls. j'ai été plus soigneux pour le codage mais je crois que la technique utilisée est mauvaise, il faut revenir à la technique de la remontée de graphes. l'allocation des registres est assez contraignante. néanmoins, vu la saturation des L/S, je ne crois pas que beaucoup de cycles pourraient être épargnés, je m'arrête donc là. de plus, avec 50 cycles en moyenne pour le déplacement, on est en-dessous d'un cycle par cellule. entrée: t4=PA, t3=PB ;1 movq mmA,[XORF] pcmpeqb wm,wm ;2 movq mmB,[XORA] ; STALL ;3 movq mmC,[XORB] por mmA,mmB ;4 pxor wm,[WallMask] por mmA,mmC ;5 movq t1,[A] por mmB,mmC ;6 movq [PA],t4 por mmA,t4 ;7 movq [PB],t3 pand mmA,wm ;8 por mmB,t3 pxor t1,mmA ;9 mov mmA,[XORC] movq t2,t1 ;10 por mmB,mmA movq t3,t2 ;11 paddd t2,[RANDOM] por mmC,mmA ;12 por mmA,t4 test al,[_M0] ; prefetch ;13 movq t4,[temp_B] pand mmB,wm ;14 movq [RANDOM],t2 psrlq t1,1 ;15 ; STALL ; STALL préfetch ? ;15' movq [A],t1 pxor t4,mmB ;16 movq mmB,[XORD] movq t2,t4 ;17 movq t1,[temp_C2] por mmC,mmB ;18 psllq t2,63 por mmA,mmB ;19 movq [XORD],mmC psrlq t4,1 ;20 por mmC,[PC] ; STALL ;21 por t2,[ESI-80] ;(B) pand mmC,wm ;22 movq [esi+64],t4 ; @? pxor t1,mmC ;23 movq mmC,[XORE] ; STALL ;24 movq [esi-80],t2 por mmA,mmC ;25 movq [esi+64],t1 ;(C) por mmB,mmC ;26 por mmC,[XORA] pand mmA,wm ;27 por mmB,[PB] pxor t2,mmA ;28 movq mmA,[XORF] movq t1,t2 ;29 movq t3,[edi+E] por mmB,mmA ;30 movq t4,[temp_D] ;32b ? por mmC,mmA ;31 test al,[_M4] ; prefetch movq mmA,mmC ;32 por mmC,[PC] pand mmB,wm ;33 por mmA,[XORD] psrlq t1,63 ;34 pand mmC,wm psllq t2,1 ;35 pxor mmC,[edi+F] pand wm,mmA ;36 por mmA,[PnOr] pxor t3,mmB ;37 pxor wm,[edi+G] movq mmB,mmC ;38 movd [temp_D],t1 psllq mmC,63 ;38' movq [edi+D],t2 psrlq mmB,1 ;39 movq [edi+E],t3 ; STALL ;40 por mmC,[edi+F-line-cell] ; STALL ;41 movq [edi-line+F],mmB ; STALL ;42 movq [edi+G],wm ; STALL ;43 movq [edi+F-line-cell],mmC ; STALL 10 stall, 2 cycles en plus, 45 cycles. je ne suis même pas sûr que ça marche. 22h: le code tourne en 48 cycles, dont 37 pairés sur 82 instructions. je n'ai pas vérifié le fonctionnement. il faudra se référer à PRLLSUM2 pour la version la plus récente du code. Je dispose donc d'une grande partie du code. il faut aussi inclure la sélection progressive (PnOr) dont une partie est comptée dans le code de déplacement (sauvegarde de Pa et Pb). """ Nouvelle formule: PA2 = PA or Rand PB2 = PB or /Rand PC2 = PC or (PA & Rand) or (PB & /rand) ou: PA2 = PA or Rand R1 = PA & Rand PB2 = PB or /Rand R2 = PB & /Rand PC2 = PC or R1 or R2 """ Mais on a oublié de masquer la sortie... donc Pn=PA or PB or PC RAND=RAND & Pn /RAND=/RAND & Pn [PnOr]=Pn PA2 = PA or Rand R1 = PA & Rand PB2 = PB or /Rand R2 = PB & /Rand PC2 = PC or R1 or R2 masquer les RAND évite de masquer les sorties, soit 1 opération d'économisée. entrée: PB dans mmB RAND dans mmC 1 registre inutilisé ;1 movq t1,[PC] movq t3,mmB ;2 movq t4,[PA] por mmB,t1 ;3 por mmB,t4 ; STALL ;4 movq t2,t1 movq mmA,mmC ;5 movq [PnOr],mmB pandn mmA,mmB ;6 pand mmB,mmC movq mmC,t4 ;7 por t1,mmB pand t2,mmB ;8 por t4,mmA pand mmC,mmA ;9 movq [PC],t1 por t3,t2 ; ensuite: por t3,mmC 18 instructions et 1 stall pour 10 cycles. sympa. le code précédent en comptait 22, avec la sauvegarde des variables, on a bien économisé un cycle. Ce code se "branche" tel quel dans les deux codes précédement créés. 0h30: création d'un nouveau fichier qui décrit le coeur de la boucle: c1.asm la forme étrange de l'arbre précédent est dûe à l'utilisation de PA et PB, ainsi que leur sauvegarde dans la partie de déplacement. ainsi, PC doit être sauvé avant la fin de la sélection, et PB se retrouve dans le chemin critique. Il faut donc finir la rotation de registres du calcul de détection sur PB, ce qui veut dire qu'on commence par PC, puis PA,PB,PC,PA,PB. on gagne ainsi un ou deux cycles lors de la transition entre les blocs. 6h: C1.ASM contient un début de boucle sans : pré-déplacement pair, popcount, détection, display_line... j'ai mis en place le "init flag" dans le bit 16 de ebx, il faudra vérifier son comportement. dimanche soir, 3h: j'ai trouvé une stratégie acceptable pour gérer les init/acc: d'abord, init/acc est sélectionné après la partie de déplacement, selon la valeur du bit 16 d'EBX. un seul branchement, suivi d'un appel à la fonction désirée. le retour du branchement contient le prologue de la boucle, dédoublé pour éviter un branchement supplémentaire. la manipulation de la pile perdrait le mécanisme de prédiction des retours (la pile interne), ce qui ralentirait le retour. l'adresse de branchement, par contre, est changée par SMC afin de gérer le cas où on affiche les densités ou les collisions. on évite donc de dédoubler tout le code une fois de plus. Vendredi 30, (sam.31) 5h30 am: chronométrage comparatif des codes FHP: 1000 itérations sur 1024*640 (655M cellules) P200MMX+32MO SDRAM SP65.asm: 15 secondes, strip=6, zoom=4:1 (+/- 43Mc/s) C: compile: gcc -I/usr/X11R6/include -lX11 -L/usr/X11R6/lib -O3 fhp_0.c run : nice --20 time -o perf ./a.out 58.76 user 0.44 system 1:00.87 elapsed 97% CPU (0 avgtext+0avgdata 0maxresident) k0 inputs+0outputs (153major+178minor)page faults 0 swaps -->11.2 Mc/s mon code asm est 4 fois plus rapide sur PMMX qu'un code optimisé en GCC. cette différence devrait diminuer un peu sur PII à cause (grâce) à la mémoire cache plus puissante et à 50% d'instructions décodées chaque cycle en plus. perf max: 512*512, strip=11, zoom=4:1, 6,3 ms pour 542² cellules, 158Hz*512²=41,6 Mc/s. dimanche, 3am: il faut libérer un registre. j'ai choisi pour cela de laisser RANDOM2 au placard, il sera chargé 2 fois par "tour". l'arbre ne peut pas se simplifier sans ce cinquième registre. sinon, G, RA et RB sont toujours en registre. et pour l'instant je n'ai étudié que la première partie de la détection, qui contient déjà 28 instructions dont 8 accès à la mémoire. cela sera moins marrant ensuite. 4am: j'ai réussi à mettre au point la première moitié de la détection. estimations: 14 cycles sur PMMX, 11 cycles sur PII. à 15 cycles en moyenne par tour, cela fait environ 90 cycles sur PMMX soit 1,5 cycle par cellule :-) amorce: movq _RA,[An] ; U ;V pxor _RA,[ST] ; U movq T3,_RB ; V ;U movq T4,RB ; V (ne pas oublier d'initialiser _G, _RB et AXC avec les bonnes valeurs, et xorées de préférence...) on peut charger ST dans un registre au départ, ce qui est fait de toute façon car on sort du popcount. j'avais prévu d'entrelacer les tours un peu plus mais cela n'a pas été nécessaire. de plus, par chance, le nombre d'instructions est pair, ce qui évite de désagréables ajustements pour le PMMX. boucle: pxor AXC,_RA ;U1 pxor T3,_RA ;V2 movq T2,_G ;U3 pandn T4,AXC ;V4 pandn AXC,T3 ;U5 pxor T2,_RA ;V6 movq T1,AXC ;U7 pand AXC,T2 ;V8 movq T3,[RAND_n] ;U9 pandn T2,T1 ;V10 movq T1,[RAND2] ;U11 pand T4,_G ;V12 pxor T1,_RA ;U13 pand T3,T2 ;V14 pand T2,_RB ;U15 pand T1,T4 ;V16 movq [XORn],AXC ;U17 por T3,T1 ;V18 pand AXC,[RAND2] ;U19 pcmpeqb T1,T1 ;V20 movq [Pn],T2 ;U21 por T3,AXC ;V22 * movq AXC,[An] ;U23 pxor T1,T3 ;V24 * pxor AXC,[ST] ;U25 * movq T3,_RB+1 ;V26 movq [XORn+3],T1 ;U27 * movq T4,_RB+1 ;V28 (les * montrent les lignes entrelacées avec le tour suivant/précédent) reste maintenant à faire la deuxième partie (avec l'"accumulation"). dimanche soir, 2am: j'ai réussi à faire le graphe pour la deuxième partie de la détection. 30 instructions dont 11 accès à la mémoire, mais je n'ai pas réussi à éviter un cycle mort (deux bulles dans V). donc 16 cycles en PMMX et 12 en PII. soit au total (sans l'initialisation) 1.07 cy/site en PII et 1.4 en PMMX. je n'ai pas pu "boucler" directement avec la partie précédente, il faudra donc charcuter la code à la jonction. movq _RA,[A] ;U1 movq T3,RB ;V2 movq [XORn+3],T4 ;U3 movq T2,_G ;V4 pxor _RA,[ST] ;U5 movq T4,_RB ;V6 pxor _AXC,_RA ;U7 pxor T3,_RA ;V8 pxor T2,_RA ;U9 pandn T4,_AXC ;V10 pandn _AXC,T3 ;U11 movq T1,T2 ;V12 movq T3,[RAND2] ;U13 pandn T2,_AXC ;V14 pand _AXC,T1 ;U15 pand T4,_G ;V16 pand _AXC,[XORn+3] ;U17 pxor T3,_RA ;V18 movq T1,[RAND_n] ;U19 pand T3,T4 ;V20 movq [XORn+0],_AXC ;U21 pand T1,_RA ;V22 movq T4,[RAND2] ;U23 pand T2,_RB ;V24 pand T2,[Pn] ;U25 por T1,T3 ;V26 pandn T4,_AXC ;U27 ;V28 movq [Pn],T2 ;U29 por T4,T1 ;V30 pandn T4,[XORn+0] ;U31 ;V32 voilà. maintenant, faire la jonction avec la sélection progressive des Pn et le renommage des registres : C3.asm 4am: c'est au tour de la première partie. je me rends compte que je mise TRES gros car je ne peux pas à l'heure actuelle vérifier le fonctionnement correct du code. j'ai maintenant 1700 lignes de code compilable mais probablement extrêmement instable... 5am: C4.asm manque du popcount et du prédéplacement. mais surtout il manque la détection de la souris et l'interprétation des fichiers de scripts... 9h30: SP67 contient le kernel de calcul sans l'affichage moderne. pas de test encore, mais des adaptations et des vérifications visuelles. ça va encore être immonde à dévéroler. Mardi soir, 4h30 am: j'ai fini de vérifier visuellement le code de déplacement, j'ai eu qqs problèmes avec Dpair, il manquait certaines instructions. LABELS2 n'a pas fait de problèmes, il y avait juste une quinzaine de labels5 en trop. WallMask renomé en WALLMASK. mais au premier tour, le code a planté. ESC*3. apparemment une boucle infinie ? 5h : nettoyage dans le code pour ne garder que les mouvements. A,D,E,F fonctionnent. B et C sont effacés (?). pointeur fou ? variable incorrectement initialisée ? au bout de 32 (?) pas de temps, ça plante. SP68.asm mercredi 17h30: apparemment un problème de temp_E ? oui: j'avais oublié un $base5__ sous [temp_E] (45 pair) deuxième truc troublant : on dirait que RANDOM est écrit dans le tunnel, à une adresse indépendante de la taille de ce dernier. je pense à RANDOM car : c'est un mot de 64 bits, ses variations ressemblent à RANDOM en régime "artificiel" (sans bouillie de bits), il varie avec les particules A. MAIS: normalement RANDOM est initialisé avec une certaine valeur... 18h30: j'avais oublié de "déclarer" $base5__ au début du fichier. manquent: -pas de collisions -pas de déplacement en B et C -E et F ne sont pas déviés par les parois. (ce qui est normal puisque ça donne B et C qui ne sont pas encore traités) l'oubli de la déclaration étant découvert, B et C se mettent à fonctionner mais B n'a pas le bon angle. 22H: temp_B était décalé deux fois au lieu d'une. 22H10: les collisions fonctionnent, sauf pour un petit détail: les XORs sont inversés. quasiment rien à modifier... 22H30: problème d'explosion. il faut que j'analyse chaque combinaison. apparemment le renommage s'est mal déroulé. 11h : apparemment le problème est un peu plus complexe, il serait lié au fait qu'on commence la boucle sur C au lieu de A. 5h30: après réflexion, le moyen le plus efficace de trouver la bonne combinaison est d'y aller à coups de %defines dans la partie de calcul. 6h: par chance j'ai trouvé la bonne combinaison au premier essai. mais les petites erreurs sont maintenant apparentes : les collisions ABD "ratent" parfois, et les collisions ACE donnent une mauvaise sortie dans 2 cas sur 3. ici, le nombre des particules est conservé mais l'impulsion ne l'est pas, lorsque c'est détecté. Dimanche 13 novembre, 6h20: je cherche la cause de mes soucis en isolant le randgen. Il faudrait aussi faire un fichier pour DEBUG. lundi 14, 1h du matin: rien de probant. DEBUG a montré un comportement normal de la détection. j'en perds mon latin. les tests ont porté sur les combinaisons qui donnent a priori un mauvais résultat mais sous DEBUG tout semble aller pour le mieux. Le seul changement qui a été effectué porte sur l'accès aux données, les $base ont été enlevés et les entrées de l'équation sont passées en variables globales. il faut donc encore revérifier l'écriture du code principal ... Les faisceaux de preuves s'orientent vers un mauvais fonctionnement de D et E. Pourtant, si la détection elle-même s'effectue correctement, alors où se situe le problème ??? D et E m'ont mis sur la voie, BDF m'a presque confirmé, il est déjà 2h du matin. il s'agirait (à confirmer) d'un problème de renommage des XORx lors du "déphasage" de la détection qui commence en C et pas en A. En gros, il faudrait tout refaire ! je n'arrive pas à trouver une cause suffisament localisée et la réécriture du kernel prendrait encore beaucoup de temps, c'est absolument laborieux et je n'ai pas d'outils d'aide semi-automatique. 3h: quand bien même je referais le kernel, je ne comprends pas pourquoi ça ne fonctionne pas. de plus, même si le déplacement n'est pas parfait, il ne peut pas être beaucoup mieux, de par sa nature memory bound. Et puis, je ne comprends pas pourquoi XORD et XORE sont inversés avec XORA et XORB alors que XORC ne l'est pas avec XORF. c'est là que se noue le problème. j'en suis sûr car je n'arrive pas à l'expliquer, ce qui complique la chose un peu plus. 4h30 je me suis aperçu que le code de test dans DEBUG ne contenait pas l'amorce de B et C avant le POPCOUNT. je vérifie donc les résultats antérieurs ... 1) XORD absent pour BDG-R, ok pour BDG-/R 2) XORE absent pour CEG-R, ok pour CEG-/R le reste est bon (ACG, DFG, EAG, FBG) essai avec BCF & Co: ABD,ACF,BDE,ACD,ADE,BEF,BCF,CEF,ADF: ok BCE, ABE: PB est mis ???? XORx manquant pour BCE et CDF XORD et XORE sont encore en cause ! <enlever XORG et G2 des variables globales...> 6h: vu la tournure des choses, et puisque toute autre éventualité a été envisagée, il est envisageable que ce soit une erreur de renommage de registre. mais il y a 300 lignes à vérifier... une à une... cela expliquerait le PB ? -trouver dans quelle moitié se trouve la bourde. on s'aide avec Pn et XORn intermédiaires. on place un RET en plein milieu. ensuite, on empêche l'écriture des résultats de la première partie et on regarde le résultat. 1) A: 00 FF 00 FF FF 00 B: 00 00 FF 00 FF FF C: FF 00 00 FF 00 FF D: FF FF 00 00 FF 00 E: 00 FF FF 00 00 FF F: FF 00 FF FF 00 00 G: 00 00 00 00 00 00 XORA: FF FF FF FF 00 F0 XORB: F0 FF FF FF FF 00 XORC: 00 00 00 00 FF 00 XORD: 00 00 00 00 00 FF XORE: FF 00 00 00 00 00 XORF: FF FF FF FF F0 FF PnOr: 00 00 00 00 00 00 PA: 00 00 00 00 FF 00 PB: 00 00 00 00 00 FF PC: 00 00 00 FF 00 00 RANDOM: 0F 0F 0F 0F 0F 0F RAND_A: FF FF FF FF FF FF RAND_B: FF FF FF FF FF FF RAND_C: 00 00 00 00 00 00 ST: 00 00 00 00 00 00 S1: FF FF FF FF FF FF S2: FF FF FF FF FF FF S3: 00 00 00 00 00 00 2) 2E73:04C0 00 00 FF 00 00 00 2E73:04D0 00 00 00 FF 00 00 2E73:04E0 FF 0F FF FF FF FF 2E73:04F0 FF FF 00 FF 00 00 2E73:0500 00 FF FF 00 FF 00 2E73:0510 00 FF 00 00 00 00 PnOr: FF FF FF 00 00 00 PA: 00 FF 00 00 00 00 PB: FF FF FF FF FF FF PC: FF 0F 0F 00 00 00 samedi 4 décembre , 7h40: ça fait 2 semaines que je retourne ça dans ma tête sans trouver le repos. cette fois-ci je ne sais pas si j'aurai le courage de débugger. j'ai déjà perdu deux semaines dessus. A la relecture des logs, je vois que seule la détection est fautive, je peux donc recommencer le codage en tenant compte des erreurs trouvées. je peux aussi changer l'infrastructure du programme (passage aux noeuds de 128 octets). c'est peut-être la meilleure chose à faire pour me remettre dans le bain et avancer quand même. SP73.asm premier problème, celui des coordonnées avec les coordonnées à l'écran. il est temps de poser les équations. mais pour simplifier le code je passe outre le zoom car il n'y a qu'une seule procédure d'affichage de prête. vendredi 28 janvier : de retour de Boston j'installe le nouveau moniteur SONY trinitron : même si la carte vidéo S3 trio 3D ne l'utilise qu'à 60Hz au lieu des 85Hz que le moniteur supporte, l'apparistion du rouge rend les blancs plus... blancs ! J'ai rencontré Norman Margolus. sur un P3 à 550MHz de son lago, j'ai pu monter à 150Mc/s les doigts dans le nez ! je n'ai pas pu monter plus loin pour des raisons idiotes mais je pense que j'aurais pu atteindre 200Mc/s. le programme est cependant descendu à 3.6 cycles CPU par octet. Le plus important est que j'ai pu atteindre l'équivalent d'une CAM8, même si le P3 et la CAM8 ont de nombreuses années d'écart. J'ai cependant pu montrer qu'il est possible d'atteindre des performances "similaires" avec une machine de bureau (même si c'est la toute dernière) : l'avantage économique est capital et prouve l'intérêt de mon programme. passons en revue les avantages et inconvénients de nos deux bébêtes : (PC 550 contre CAM8/25/64MO) 1) la CAM8 est "limitée" à des dimensions qui sont des puissances de deux alors que le programme n'a qu'une granularité de 64 sites en horizontal : -> meilleure utilisation de la mémoire lorsque des ratios précis doivent être utilisés (1 point pour moi) 2) la CAM8 peut effectuer des simulations jusqu'à dimension 32 : i point pour Norman, mais la CAM8 n'a jamais étée jusque là : on se limite à dimensions 4 ou 5 dans les cas les plus extrêmes. 3) la CAM8 a des "cellules" codées sur 16 bits (les LUT aussi), qui peuvent être cascadées (mais pas pipelinées...) pour atteindre 32, 48 bits si nécessaire. Ainsi, à 25 MHz par carte, on peut faire fonctionner FHP3 à 2 cellules par cycle, ou faire un dérivé à 2 bits par direction : 1 point pour Norman ! il peut faire du FHP3 simple à 2*25*8=400 Mc/s soit deux fois le record possible sur PC. 4) la CAM8 peut définir n'importe quel voisinage de cellule, alors que d'un autre côté mon truc permet une fonction similaire, mais que sur une ligne. Un demi point pour Norman : sa machine est générale. 5) sa machine s'interface avec un système d'exploitation de haut niveau (Solaris). Je n'en connais pas les détails ni l'occupation de la CPU. Une interface en mode texte et un affichage graphique sont fonctionnels. De mon côté j'ai une GUI minimale et un projet de langage script. Même si Harris Gilliam travaille dessus et que le projet en aie les capacités, elles ne sont pas développées ou arrivées à maturité. Mon critère est que l'interactivité n'est pas au centre du projet, ce sont des outils qui sont rajoutés au système (exception faite du moniteur VGA qui dispose de sa propre électronique synchrone aux cartes). Je pense qu'on peut battre la CAM8 actuelle (celle que j'ai vu fonctionner) avec un bi-P3 à 600MHz environ. Mais de son côté, Norman nous prépare une super bécane à partir de 128 puces full-custom à 200MHz... Je ne mets pas en doute sa puissance et sa capacité à fonctionner mais son approche brute (même si elle est intelligente et reflète son expérience) me choque un peu, moi qui n'ai que mon ordinateur et une machine biprocesseur à la fac pour développer son projet. Norman, lui, peut compter sur la DARPA pour obtenir le million (de francs ou de dollars) que nécessite son projet ambitieux. On en revient donc toujours aux problèmes d'argent, et je n'en ai pas. Les applications les plus récentes de sa machine sont la cryptographie par AC et les AC quantiques, afin de simuler un ordinateur quantique (on en revient encore à la crypto). Je pense pouvoir mettre en doute la puissance de la crypto par FHP, après relecture de mon bouquin chéri. Actuellement je ne me sens plus d'attaque pour refaire le programme de fond en comble. J'ai acheté le livre de Zaleski et Rothman (j'aurais peut-être dû rencontrer ce dernier lorsque j'étais à Boston !) donc je ne risque pas de m'ennuyer, il contient de nombreuses remarques très intéressantes qui pourraient trouver leur place dans le mémoire. Mais "Laisse les vieux donner des leçons" (dixit PG). Quelques additions au programme : - changer le butterfly d'affichage par réordonnancement des masques d'accumulation - mode script "interactif" (ligne de commande) sur appui de "tab" ou "caps lock"... {-> fontes de textes qu'on peut récupérer dans la table de fontes VGA...} -> trouver addresse et format des fontes VGA (bible PC ?)