Subject: De la vitesse du Saturn (ca pète les titres à l'an From: hpfool@bigfoot.com (Clément PILLIAS (HpFool)) Newsgroups: hpcalc.dev Organization: unavailable Date: Feb 08 2001 01:40:45 User-Agent: Microsoft Outlook Express 4.72.3110.5 Bon je vous avait promis (bien que personne ne l'ai réclamé celui-là ;) ) un message sur la vitesse du Saturn, le voici : Tout d'abord, on peut se demander pourquoi il m'est venu à l'idée de chercher par moi-même la vitesse du Saturn alors que tout le monde dit qu'il tourne à un peu moins de 4MHz et que HP lui même donne les temps de cycles des instructions du Saturn (dans la doc de SASM) ? Pour plusieurs raisons : - Par simple curiosité ;) - Tout d'abord j'ai toujours trouvé ça bizarre qu'un C=DAT0.16 soit plus rapide qu'un C=DAT0.W. C'est illogique. - Ensuite parce que un jour j'ai fait un programme, et j'avais calculé le temps qu'il devrait mettre pour s'exécuter à l'aide des cycles, et j'avais trouvé (avec une certaine marge) que cela aurait du tenir dans une VBL. Or en pratique ce n'était pas le cas ! J'ai d'abord cru que c'était à cause de l'écran : En effet c'est bien connu l'affichage de l'écran nécessite des accès permanents à la mémoire ce qui occupe le bus et donc ralentit la HP. Il aurait été logique que les docs d'HP ne tiennent pas compte de cela et donnent des temps théoriques. J'ai donc réessayé en éteignant l'écran, et ça ne suffisait pas ! La HP était donc plus lente que prévue, et assez considérablement. Ensuite je me suis dit que d'autres avaient du faire la même chose avant moi, et je m'étonnait qu'ils n'aient rien trouvé d'anormal. Il m'est alors revenu a l'esprit deux anecdotes : - Dans ma jeunesse, alors que je venait d'avoir ma HP j'était allé acheter Voyage chez Gibert avec un copain qui en avait une aussi, et la-bas on avait rencontré un type qui en avait une aussi et qui venait pour des raisons semblable, sauf qu'il venait de se mettre à l'ASM. On est allé en discuter dans un bar voisin et il nous a alors annoncé qu'il venait de faire son premier programme, qui permettait de mesurer le temps d'exécution des instructions de la HP. Et il nous le montre ! Et évidement ça ne donne pas les résultats attendus... Sur le coup on a tous supposé qu'il avait commis une erreur parce qu'il était encore débutant, mais après tout, peut être pas ? Et si tous ceux qui avaient essayé avaient remis en cause leurs capacités plutôt que les données officielles ? - Quelques jours auparavant, JYA annonçait sur RTEL qu'en réalité le Saturn tournait à 8MHz, en précisant que HP n'avait jamais dit que la valeur affichée lors des tests ON-D (SPD machin) était la vitesse en Hz du Saturn. Je ne sais plus si a l'époque il bossait déjà pour HP ou pas... Mais voila un fait intéressant non ? ;) Bon passons aux choses sérieuses : Comment mesurer le temps d'une instruction ? L'idée la plus évidente consiste à effectuer ladite instruction un certain nombre de fois et à regarde en combien de temps elle l'a fait (grâce à l'horloge). Le problème est que l'horloge a un pas d'1/8192eme de seconde (avec une précision assez mauvaise), alors qu'une instruction s'effectue (en théorie) en environ 1/400000eme de seconde. Il faut donc faire un sacré paquet d'instructions pour pouvoir mesurer le temps mis ! Et la précision reste mauvaise : Si mon instruction est censée prendre 10 cycles et que ma HP est censée avoir une vitesse d'environ 4MHz, alors en 1/8192eme de seconde je peut faire une cinquantaine d'instructions. Si je fait N instructions et que je mesure le temps mis, j'aurais aussi bien pu faire 50 instructions de plus en trouvant la même valeur pour le temps. D'ou une précision de 50/N environ. Bon ca reste tolérable si on prends N très grand, mais c'est incompatible avec la rigueur scientifique qui dirige cette enquête ! ;) Donc voici une autre méthode, à priori plus fiable : Au lieu de compter le temps T mis pour faire N fois la même instructions on mesure le nombre N de fois ou l'on peut faire la même instruction pendant la durée T. Autrement dit, on surveille l'horloge à l'aide d'une boucle durant laquelle on effectue l'instruction et on incremente un compteur. On sort de la boucle lorsque l'horloge a atteint une certaine valeur et la valeur du compteur donne le nombre de fois ou l'instruction a été exécutée. Cette fois-ci la précision est de l'ordre du temps nécessaire pour effectuer la boucle, qui est plus petit (normalement) qu'un tick d'horloge. Voici donc le programme qu'on va utiliser : %% VITEST (admirez le jeu de mots !) %% Prends en entrée un code compilé du programme dont le temps doit être mesuré. %% Aucune vérification faite sur les arguments, on suppose que l'utilisateur sait l'utiliser ;) %% Bug connu : dérègle l'horloge ;) SAVE INTOFF2 % on interdit les inters pour pas que ca gène dans la mesure du temps C=DAT1.A D1=C D1+5 C=DAT1.A C-5.A % récupération de la taille du code D0=822B2 % ou 822B3 si on veut faire le test en adresse impaire (je met l'adresse en chiffres justement % pour être sur de la parité de l'adresse, mais ce n'est pas bien en temps normal ! Il vaut mieux utiliser % des constantes... D1+5 % on pointe sur le code % on va recopier le code à une adresse paire (ou impaire) : { C-1.A EXITC % le code peut être vide, d'où nécessité du test en début de boucle A=DAT1.1 DAT0=A.1 D0+1 D1+1 % oui je sait c'est bourin mais on va pas se faire chier ! UP } % On va rajouter une instruction RTN en fin de code : LC 10 DAT0=C.B C=0.A R0=C.A % On initialise le compteur d'instructions effectuées et on le fout dans R0 (que le code ne % doit alors pas modifier !) D0=00100 % Si on veut faire la mesure écran allumé, commenter la ligne suivante : LC 0 DAT0=C.1 % (c'est pas très propre comme méthode pour éteindre l'écran ;) ) D0=38 % on pointe sur l'horloge LC (8)2001 DAT0=C.8 % on initialise l'horloge à un peu plus d'une seconde. { A=DAT0.B ?A#0.B UP } % on attends le moment précis ou ca passe à 1 seconde % Et c'est partit pour la boucle !!! { GOSBVL 822B2 % ou 822B3 si on veut faire le test en adresse impaire... C=R0.A C+1.A R0=C.A % on incrémente le compteur D0=00138 % le code peut avoir modifié D0 ! C=DAT0.A ?C#0.A UP % on s'arrête quand l'horloge est passée à 0. } % on rallume l'écran (ça peut être utile ! ;)) D0=00 LC 8 DAT0=C.1 % toujours aussi crade comme façon de faire ;) % fin LOAD A=R0.A A-1.A % en fait on a fait une itération de la boucle en trop ! GOVLNG 0357C % PUSH Aa (SB) et exit : il doit exister un mnémonique dans la extable pour ca... @ Voilà le programme qu'on va compiler en deux versions : une pour l'adresse paire (vt0.cod) et une pour l'adresse impaire (vt1.cod). Ensuite on va utiliser un petit programme en RPL pour servir d'interface : TEST0.RPL : (prend en argument une séquence d'instructions MASD dont le temps doit être mesuré en secondes. Attention au retour chariot) << DUP " @" + ASM VT0.COD SWAP DROP SB~B B->R INV >> De même on fait un TEST1.RPL pour l'adresse impaire. (Notez qu'il suffit d'enlever le INV pour savoir combien de fois la boucle a été faite en 1 seconde) On peut maintenant essayer notre programme pour savoir en combien de temps s'exécute la boucle vide : "" TEST0.RPL Cela permettra de savoir le temps mis par l'instruction avec une simple soustraction. Chez moi ca renvoie : 3.90792918832E-5 On peut alors constater que si l'on recommence l'opération on trouve une valeur légèrement différente (avec une variation inférieure à 0.03% !): Cela est du d'une part à la précision de l'horloge et d'autre part à de légères variations de vitesse du Saturn liées aux conditions extérieures (température par exemple), à son utilisation, etc... Il faut donc faire une moyenne. Pour cela j'utilise le programme suivant qui fait une moyenne sur 10 valeurs : MTEST0.RPL (on fera le même pour les adresses impaires) << 1 10 START TEST0.RPL SWAP NEXT DROP 10 ->LIST SORT DUP HEAD OVER ELIST 10 / ROT 10 GET >> Notez que ELIST est en fait la fonction 'somme"LIST... Ce programme donne 3 valeurs : la plus petite trouvée (niv3), la valeur moyenne (niv 2) et la plus grande (niv 1). Bon maintenant on a un bon arsenal de programmes, on va pouvoir commencer. Tout d'abord attaquons-nous à un cas simple : "P=0" Avant-tout je recalcule le temps moyen mis par ma boucle à vide << "" MTEST0.RPL DROP SWAP DROP >> que je stokes dans 'DFLT0' puisqu'on va s'en servir souvent. Idem en adresse impaire. J'obtient chez moi : DFLT0 = 3.90838742445E-5 et DFLT1 = 3.90847912158E-5 Ce qui est assez proche vu que l'écart entre les deux est très largement inférieur aux variations observées. D'ailleurs c'est parfaitement normal puisque la seule différence entre les deux codes exécutées consiste en la parité de l'adresse d'un RTN (qui s'exécute à la même vitesse quelque soit l'adresse !). Maintenant calculons MTEST0 et MTEST1 pour "P=0" : MTEST0 => 4.02289838688E-5 MTEST1 => 4.02299548681E-5 Je ne donne ici que les valeurs moyennes, les autres on s'en fout, on vérifie juste que la variation est inférieure aux 0.03% déjà observés... Encore une fois c'est très proche et c'est normal ! Maintenant si l'on soustrait DFLT0 et DFLT1 à ces valeurs on obtient le temps moyen mis par l'instruction : en adresse paire : 1.1451096243E-6 secondes en adresse impaire:1.1521901162E-6 secondes Vous remarquerez que c'est plus sensible aux variations, mais ça reste raisonnable (les deux premiers chiffres sont identiques !) Bon maintenant, que nous dis HP ? que P=0 s'exécute en 3 cycles ! Avec une horloge à 3.5MHz et donc avec un cycle= 1/3500000 secondes, on devrait avoir un temps de : 0.857142857143E-6 C'est du même ordre de grandeur, mais la valeur expérimentale est quand même 34% plus grande ! Autrement dit la HP tourne en pratique aux 3/4 de la vitesse annoncée (si l'on peut extrapoler aux autres instructions ce résultat, ce qu'on va vérifier maintenant), soit environ 2,6MHz ! Et encore j'ai pris une fréquence de 3.5MHz, si j'avais pris 4MHz comme on le fait souvent, on aurait trouvé un rapport d'environ 2/3 ! Tient au fait au passage j'oublie de dire que là je fait les tests sur une 49 mais la première fois je les avait fait sur une 48Gx avec les mêmes résultats... Bon je vous laisse le soin de faire des tests pour d'autres instructions et vous apercevoir que c'est pareil. Attention cependant, ne vous faites pas avoir par les lectures en mémoire, surtout sur D0 ! Ca donne des résultats bizarres parce que D0 pointe sur la RAM I/O et que les lectures en RAM I/O sont plus rapides à cause de l'octet tampon entre le bus 8 bits et le Saturn qui n'a pas besoin d'être utilisé ici ! Attention aussi à la chose suivante : Si l'instruction testée est de taille impaire, ça change la parité du RTN et il faut donc utiliser DFTL1 à la place de DFTL0 et réciproquement (mais bon c'est pas bien grave vu que les deux valeurs sont censées être égales !) Maintenant regardons de plus près les résultats obtenus (je les donnes pour ma machine dans un tableau ou le dernier champ est l'instruction, le premier la vitesse mesurée en adresse paire, le second le rapport entre la vitesse officielle et celle mesurée pour l'adresse paire, les troisièmes et quatrièmes étant similaires pour l'adresse impaire) : 1.1451096243E-6 0.7485 1.1521901162E-6 0.7439 P=0 1.4059347791E-6 0.8129 2.0277852447E-6 0.7045 LC 0 2.2828839953E-6 0.7509 2.2888223318E-6 0.7490 LC 01 2.5422531355E-6 0.7867 3.1761950168E-6 0.7196 LC 012 3.4302411670E-6 0.7496 3.4343627548E-6 0.7487 LC 0123 9.4157852805E-6 0.7586 10.0506225373E-6 0.7391 LC 0123456789ABCDE 10.3007956657E-6 0.7489 10.3130010316E-6 0.7480 LC 0123456789ABCDEF On remarque sur ces exemples deux choses : - Pour les instructions dont la vitesse ne dépends pas de l'adresse, on obtient un rapport de vitesse à peu près constant autour de 0.7490, ce qui laisse supposer qu'on puisse étendre ça à toutes les instructions et que la fréquence de 4MHz est éronnée. - Pour les instructions dont la vitesse dépends de l'adresse, on obtient deux rapports de vitesse centrés autour de 0.75 (au dessus en adresse paire et en dessous en adresse impaire), ce qui laisse à penser que l'explication "officielle" qui consiste à dire qu'en adresse impaire un cycle est ajouté est simplificatrice, puisque si elle avait été exacte on aurait obtenu 0.75 en permanence comme dans les autres cas. Le problème est que si cette hypothèse est vraie, alors c'est que la différence de vitesse entre l'exécution en adresse paire et celle en adresse impaire implique plusieurs cycles d'écarts, avec des cycles plus petits (et donc une fréquence plus grande), et cela parce que par définition un cycle d'horloge n'est pas divisible !!! En fait si l'on s'intéresse de plus près au problème, la cause de cet écart est qu'il existe un buffer d'un octet entre le bus 8bits et le Saturn 4bits. Le Saturn ne peut accéder directement qu'au premier quartet de ce buffer, aussi quand il doit accéder à un quartet en adresse impaire, celui ci se trouve dans la partie droite du buffer et il doit alors décaler le buffer d'un quartet vers la gauche pour pouvoir lire le quartet. Et pour des instructions de longueur impaire (qui sont lues en mémoire) le nombre de fois ou cela arrive dépends de la parité de l'adresse de l'instruction : Cela arrive une fois de plus lorsque l'adresse est impaire. Il en résulte que le temps de traitement de ce décalage est EXACTEMENT la différence entre le temps d'exécution d'une instruction de taille impaire en adresse paire et le temps d'exécution de la même instruction en adresse impaire. Si l'on s'intéresse à ces différences on trouve une valeur à peu près constante d'environ 0.6340E-6 secondes !!! Et cela corresponds forcément à un certain nombre de cycles (le nombre de cycles nécessaires pour faire un décalage à priori). Maintenant on peut décomposer le temps d'exécution d'une instruction en trois composantes : - temps de transfert des quartets de l'instruction entre la mémoire et le Saturn = (taille de l'instruction en quartets) * (temps de transfert d'un quartet entre la mémoire et le Saturn, noté Tt) - temps du aux décalages des quartets droits du buffer = (nombre de décalages à faire) * (temps nécessaire à un décalage, noté Td) = (taille de l'instruction en quartets / 2, arrondit en fonction de la parité de l'adresse) * 0.6340E-6 secondes - temps de traitement de l'instruction en elle même (noté Ti). Tous ces temps sont nécessairement des multiples de la durée d'un cycle (notée Tc) : Tt = Kt * Tc, Td = Kd * Tc et Ti = Ki * Tc. Donc le temps d'exécution d'une instruction est : T = Tc * (n*Kt + (n/2)*Kd + Ki), où n est la taille en quartet de l'instruction (et n/2 doit être arrondit en fonction de la parité). Pour le cas de P=0 on a donc T = Tc * (2Kt + Kd + Ki(P=0)) Pour le cas de LC 1 on a donc T = Tc * (3Kt + Kd + Ki(LC 1)) en adresse paire et Tc * (3Kt + 2Kd + Ki(LC 1)) en adresse impaire. Maintenant, comme on connaît Kd*Tc = Td = 0.6340E-6 secondes, on en déduit que : (2Kt+Ki(P=0))*Tc = 0.5111E-6 secondes environ = environ (4/5)*Td. On en déduit que probablement 5*(2Kt+Ki(P=0)) = 4*Kd. et de la même façon avec "LC 0", on trouve : (3Kt+Ki(LC 0))*Tc = 0.719E-6 environ = environ (6/5)*Td, et on en déduit que probablement 5*(3Kt+Ki(LC 0)) = 6Kd Si ces deux hypothèses sont vraies, cela implique que Ki(LC 0)-Ki(P=0) = 2Kd Cela est certes très intéressant mais ça ne permet malheureusement pas de calculer Kd ou les différents Ki ou Kt, et donc pas Tc !!! Heureusement on a un dernier atout dans notre poche : Les instructions de lecture en mémoire !!!! En effet pour celles-ci le temps d'exécution a une composante supplémentaire, qui est le temps nécessaire pour transférer les données lues de la mémoire vers le Saturn. Or ce temps s'exprime aussi en fonction du nombre de quartets lus et de Tt et Td !!! Pour simplifier on va s'intéresser aux instructions de lecture en RAM I/O, puisque la RAM I/O est intégrée au Saturn, elle ne nécessitent pas de passer par le BUS et donc il n'y a pas de temps passé à décaler le buffer (c'est là une jolie découverte, au passage ;) ). Dans ce cas le temps nécessaire à la lecture des données est : m*Tt où m est le nombre de quartets à lire. Dans notre programme, D0 pointe sur 00138, on peut donc lire jusqu'à 8 quartets en RAM I/O simplement en utilisant D0. C'est ce qu'on fait ci-dessous (le tableau ne donne que les temps pour le cas où les instructions sont en adresse paire, puisqu'ils sont indépendants de l'adresse pour ces instructions, la seconde colonne donne les différences ligne à ligne) : C=DAT0.1 7.1240712834E-6 0.2552752549E-6 C=DAT0.2 7.3793465383E-6 0.2541813503E-6 C=DAT0.3 7.6335278886E-6 0.2549932367E-6 C=DAT0.4 7.8885211253E-6 0.2560072717E-6 C=DAT0.5 8.1445283970E-6 0.2497944110E-6 C=DAT0.6 8.3943228080E-6 0.2538175292E-6 C=DAT0.7 8.6481403372E-6 0.8990226430E-6 C=DAT0.8 9.5471629802E-6 X On constate qu'on obtient des écarts relativement constants, avec une assez bonne précision. On peut donc considérer que Tt = 0.2540E-6 secondes, ce qui fait une mémoire à un peu moins de 4MHz. Tient !!! Ca ne vous rappelles rien ? Et si c'était à cause de ça qu'on a tendance à dire que la HP est fréquencée à un peu moins de 4MHz ? Bon vous remarquerez que le cas C=DAT0.8 pose problème, puisqu'il est beaucoup plus long. C'est peut être lié au fait qu'il s'agit du dernier quartet de la RAM I/O ? Pour le vérifier, comparons les temps de "D0=00120" et de "D0=00120 C=DAT0.8" D0=00120 : 3.6914577295E-6 D0=00120 C=DAT0.8 : 12.6034251903E-6 différence (donc le temps de C=DAT0.8 seul à l'adresse 00120) : 8.9119674608E-6 ce qui ce coup-ci est cohérent avec les autres valeurs (si on y enlève 0.2540E-6 on obtient presque la durée de C=DAT0.7). Voilà le mystère résolu ! (enfin ça reste assez mystérieux quand même !) Bon revenons à nos moutons ! On a donc : Tt = Kt * Tc = 0.2540E-6 secondes Td = Kd * Tc = 0.6340E-6 secondes Autrement dit, Tt = (2/5)*Td !!!! Donc Kt = (2/5) * Kd. Si je pose Kt = 2, j'obtient Kd = 5, et Tc = 0.1270E-6 secondes. (en fait toutes valeurs multiples de ces valeurs marchent aussi, et donnent un temps de cycle divisé d'autant, mais il semble logique de prendre le temps de cycle le plus long (et donc la fréquence la plus basse) parce que le Saturn n'est pas vraiment un Pentium...) Ce qui nous donne une fréquence d'horloge de : (accrochez vous bien !) 8MHz (enfin presque, 7.87 environ pour être (trop) précis). Ce n'est pas ce qu'avait annoncé JYA par hasard ? Mais si !!! (Alors pourquoi a-t-il changé d'avis par la suite ? a-t-il reçu des ordres de HP qui n'est pas vraiment fier des performances de son processeur (et il y aurait de quoi avec de telles valeurs !) ? Qui sait à part lui ?) Bon pour conclure le temps mis par une instruction peut être connu à partir de ce temps de cycle (Tc = 0.1270E-6 secondes) et de la formule suivante : T = Tc * (2n + 5*(n/2) + Ki), où : n est le nombre de quartets de l'instruction. (n/2) doit être arrondit à l'entier inférieur si l'instruction est en adresse paire et à l'entier supérieur si elle est en adresse impaire. Ki est le nombre de cycles nécessaire au traitement de l'instruction, il peut être mesurer grâce à cette formule et au programme de mesure donné plus haut. A titre d'exemple voici les valeurs trouvées pour les instructions déjà vues : Ki(P=n) = 0 (incroyable non ?!) Ki(LC(n)xxxx) = 0 (incroyable, non ?!) (mais en fait c'est logique pour des instructions de chargement !) Ki(C=DAT0.n en RAM I/O) = 2n Ki(C=DAT0.n ailleurs) = 2n + 5*(n/2) Je vous laisse faire la suite pour les autres instructions (ça serait très utile d'en avoir la liste !!! Mais à cette heure ci j'ai plus le courage...). remarquez tout de même que pour une lecture en RAM (normale) de 16 quartets, on obtient Ki = 72 cycles, ce qui donne en tout (en comptant la lecture de l'instruction et les décalages du buffer) : 144 cycles !!! à comparer aux 44 officiels (qui étaient déjà énormes !) Notez aussi que le décalage du buffer d'un pauvre petit quartet prends 5 cycles !!! Je sais que le Saturn est vieux, mais quand même, on faisait vraiment les choses aussi mal il y a 20 ans ? Bon j'espère que ça a été instructif (ça je pense que oui) et pas trop compliqué (ça c'est moins sur), ça demande quand même de bien comprendre le fonctionnement d'un processeur... J'ai pas optimisé mes programmes, j'ai trouvé que c'était pas la peine, et volontairement j'ai mis le moins possible de valeurs numériques, parce que c'est chiant à lire et surtout à écrire ;) Ah dernier détail : N'oubliez pas que tout ceci est fait ECRAN ETEINT !!! Donc dans la réalité c'est ENCORE PLUS LENT !!!! A+