Je crois que je tiens un truc...
On a vu que la routine RunObjects coûte environ 3.6% du temps (
https://www.cjoint.com/doc/22_11/LKnnyt ... .html#75D4).
Le profil montre qu'on fait 13095 tours parmi lesquels seulement 3791 fois on tombe sur un objet actif. Donc 71% du temps on ne fait aucun appel de code à executer. La boucle exécutée est alors réduite à
Code:
75D9 E6C4 4 LDB ,U
75DB 2710 3 BEQ $75ED
75ED 33C840 5 LEAU $40,U
75F0 11836FC7 5 CMPU #$6FC7
75F4 26E3 3 BNE $75D9
Ca a l'air court et rapide (20 cycles) mais ne rien faire rapidement est un truc à optimiser.
J'ai essayé d'être (trop) malin en essayant de réduire la taille de l'intervalle des objets à balayer. Pour cela il a fallu que je traque dans le code source les accès en écriture à id,U. J'en ai trouvé un peu partout (par des STA) ainsi que des STD ,U. Ca me plaisait moyen car ca me faisait toucher plein de sources, et puis ca foirait parfois (sans doute que j'ai oublié un endroit où on modifie le flag d'un objet).
Ensuite j'ai regardé le code plus haut. Un truc évident (pour moi) est que le CMPU coûte un max de cycle (25 du temps de la boucle), et qu'il ne sera vrai qu'une seule fois. On peut l'éliminer dans quasi tous les tours de boucles en ajoutant une sentinelle non nul en Object_RAM_End. Tant qu'on lit un B nul on sait qu'on est toujours dans le tableau. Plus besoin de vérifier U à chaque tour, mais uniquement dans les 30% des cas où on tombe sur un objet actif (B non nul). C'est super cool. Par contre cela fait ajouter un octet dans les déclarations de tableaux:
Fichier(s) joint(s):
Commentaire: Il faudrait mettre cette sentinelle dans toutes les déclarations de Object_RAM_End à travers les différents tableaux.
image_2022-11-13_181224436.png [ 57.59 Kio | Vu 2929 fois ]
C'est le léger prix à payer.
Ensuite on voit un LEAU.. C'est bien, c'est rapide, mais si on pouvait utiliser X à la place on ferait un ABX en 3 cycles seulement. Mais zut est utilisé. Bon je ré-écris la boucle comme suit:
Code:
RunObjects *RunObjects:
; SAM's optimized version: use X to make the empty loop as fast as possible
* tst.b (Teleport_flag).w
* bne.s RunObjects_End ; rts
ldu #Object_RAM-next_object * lea (Object_RAM).w,a0 ; a0=object
bra @a
@b ldx #Obj_Index_Page
abx
lda ,x
_SetCartPageA
aslb
ldx #Obj_Index_Address
abx
jsr [,x]
@a leax ,u * we'll use x 'cause it's faster thanks to abx
ldb #next_object
@c abx * quickly loop over unactivated objects
lda id,x
beq @c
leau ,x ; copy back x to u
ldb id,u ; 2 cycles faster than tfr
cmpu #Object_RAM_End
bne @b
rts
La boucle est alors réduite à ABX + LDA + BEQ soit 10 cycles seulement au lieu de 20 avant. Alors certes il y a des opérations en plus comme le passage temporaire de U à X et l'init de B pour les ABX, mais cela se passe hors de la boucle. Donc rarement (30% du temps en moyenne sur tout la carte de jeu).
Au final j'obtiens le profil suivant (
https://www.cjoint.com/doc/22_11/LKnqLt ... .html#75D5) dans lequel la routine tombe à 1.5% du temps seulement.
On fait toujours rien, mais bien plus vite qu'avant. Ca c'est de l'optim rigolote

(bon la vraie optim serait d'utiliser une liste chainée mais le moteur original de sonic n'est pas prévu pour ca et parcourt le tableau d'objets de la même façon).
Pour info sur la position de départ de Sonic je passe ainsi de 87ms/image à 77ms/image. Dis moi si tu observes autant d'augmentation de ton coté (te faut il les fichiers dans un ZIP ou ca ira à partir des infos d'ici).