samedi 26 mai 2012

Code optimisation (I)

Voici un article qui concerne une optimisation toute simple qui consiste à regrouper plusieurs R_AllocMem consécutives (ou presque consécutives) en une seule.

Rappel pour les débutants en programmation : AmigaOS étant multitâche, la mémoire (fast ou chip) est donc gérée par le Kickstart de façon à ce que tous les programmes utilisent leur propre portion bien définie et non celle d'un autre. Chaque portion mémoire nécessaire au fonctionnement de tels ou tels jeux ou utilitaires doit donc être demandé poliment à une fonction de l'exec.library qui s'appelle R_AllocMem.

Ici, nous allons prendre comme exemple l'initialisation de la fblit.library. Son auteur, Stephen Brookes, est un très bon codeur et tout ce qui suit est une étourderie de sa part, il a codé cette init rapidos, très certainement. Notez qu'en désassemblant divers programmes, je rencontre ce genre de programmation assez souvent, d'où cet article qui sera utile à bon nombre de programmeurs puisqu'elle s'applique à tous les langages, du C/C++ à l'assembleur et sur tout type d'ordinateurs assez récents...

Tout d'abord, le code ici entouré qui dirige vers un guru rouge (_LVOAlert) est inutile puisque la layers.library est présente en Rom : ces sept instructions peuvent être tout simplement ôtées :

Pareil ici pour la graphics.library, elle est aussi en Rom et l'Amiga ne peut pas fonctionner sans, elle est donc forcement disponible et fonctionnelle :

Donc, hop, byebye aussi...

Voici donc arriver le vrai sujet de cet article, les R_AllocMem :

Quatre R_AllocMem presque consécutives avec le même type de mémoire (MEM_FAST|MEMF_CLEAR) demandé pour donc quatre zones de fastram :
  1. BEC_SIZEOF,
  2. LASTCHANCE_SIZEOF,
  3. SS_SIZE,
  4. SS_SIZE.

L'Amiga je le répète étant multitâche, les portions de mémoire seront peut-être consécutives comme sur le croquis du haut, mais peut-être pas... C'est R_AllocMem qui gère les allocations toute seule en fonction de la disponibilité, le coder n'a pas son mot à dire la dessus !
  
L'idée est donc de faire UNE SEULE R_AllocMem pour déjà avoir une mémoire moins fragmentée et de plus un très net gain de vitesse : en effet, R_AllocMem prends un très grand nombre de cycles à s'exécuter.

Il suffit tout simplement d'additionner les portions pour n'en former qu'une seule :

Faire ensuite l'unique R_AllocMem qui donnera un bloc des quatre zones pour ensuite redistribuer à la main avec une simple addition chacune des portions :
  
L'adresse mémoire rendue par R_AllocMem étant dans a2, voilà alors le résultat :
 
Nous obtenons donc l'adresse du second bloc (LASTCHANCE_SIZEOF) avec l'adresse de départ de la portion unique par une simple addition du premier (BEC_SIZEOF) et ainsi de suite pour tous les suivants...

Et avec cette astuce, cerise sur le gâteau, d'autres instructions deviennent inutiles (comme par exemple les 'beq' avant les _LVOInitSemaphore). Regardez tout ce que nous avons économisé :

86 octets sauvés et une seule allocation mémoire pour exactement le même résultat que le code original ! Whaou, super !!

Pour finir, il suffit d'un seul R_FreeMem pour libérer cette zone mémoire au lieu de quatre auparavant !
   

8 commentaires:

  1. Réponses
    1. Héhé, une vieille technique que j'utilise depuis longtemps !

      Supprimer
  2. Je suis d'habitude ébahi par les articles du blog mais là j'avoue rester sur ma faim. Grouper des données et ne faire qu'une allocation mémoire au lieu de trois ... c'est toujours ça mais bon, je ne pense pas que ça va se sentir et rendre la lib plus véloce. Et pour le gain de 86 octets, dans ce cas, je ne crois pas que ça vaille la peine.

    RépondreSupprimer
    Réponses
    1. C'est toujours ça de gagner !
      Penses bien que sur Classic, nous n'avons que 1 Mo de Kickstart, donc chaque octet gratté est important...
      De plus, la mémoire est moins fragmentée, ce qui est un plus intéressant avec cette technique !

      Supprimer
  3. ... et pour organiser la mémoire alloué comme une structure C, on peut faire des équivalent de structure C avec des macros assembleurs, (avec un include sys/types.i pour avoir les macros) ... ce qui semble être le cas ici, puisque les tailles s'appellent machin_SIZEOF , ce qui est la convention des includes.

    un détail: certains coders ont souvent supprimés les test.l d0 au retour d'un jsr (en faisant juste un beq après) ce qui fonctionne sur amiga classic parce que le bit de carry est souvent cohérent, parce que la dernière instruction de l'appel est à tout les coup un move.l ...,d0. Problème: certains émulateurs vont planter, et potentiellement une mise à jour de kickstart pourrait faire planter ça aussi: donc on optimise pas les test de retour .

    RépondreSupprimer
  4. Pourquoi dis-tu que les émulateurs vont planter si la dernière instruction est bien sur d0 ?

    RépondreSupprimer
  5. ... parce que, par exemple, le JIT de morphos va passer l'appel système du jsr vers du code natif pcc, lequel va renvoyer la bonne valeur dans le d0 émulé, mais ne va pas rendre le bit de carry cohérent avec d0 (contrairement au 68000 ou c'est le cas au retour de la plupart des appels systèmes.)
    L'optimisation des tst.l (pratqieu courante au début des années 90) est une des causes qui empêche les vieux programmes 68k de tourner sur ému.

    RépondreSupprimer
  6. Pourquoi donc le JIT ne rends pas le bon SR avec le d0 ? Si c'est vraiment le cas, l'émulation est loin d'être parfaite alors...

    RépondreSupprimer

Posté vos remarques :