[psp] Optimisations avec le cache

Voir le sujet précédent Voir le sujet suivant Aller en bas

[psp] Optimisations avec le cache

Message par Akabane87 le Sam 31 Juil - 23:55

Ce tutoriel se focalise exclusivement sur les optimisations de vitesse (et non de code ou d'espace) et donc inévitablement cible un processeur particulier qui sera ici celui de la PSP ;D.


Pour cela, une explication préliminaire sur le cache s'impose car les optimisations dont je vais parler sont exclusivement basées là dessus Very Happy.
Ne vous affolez pas je vais expliquer ça très simplement pour que tout le monde comprenne et je fournirai des lien vers des docs un peu plus techniques pour les plus curieux Wink.


Tout d'abord le cache, c'est quoi ?

Le cache est une mémoire volatile tout comme la ram mais avec des temps d'accès beaucoup plus rapides. Elle est contenue dans le CPU (processeur) sous forme de lignes de cache de taille donnée et permet d'effectuer les jeux d'instructions du CPU beaucoup plus vite que si les données étaient prises directement dans la ram.
Pour ce qui est de la PSP, elle possède bien évidemment elle aussi des lignes de cache dans son CPU (sinon je serais pas là à vous en parler Razz : 32ko de cache avec des lignes de 64o. Soit 512 lignes de 64o chacune.


pour en savoir plus :
http://www.goop.org/psp/cache-howto.html
et
http://oslib.playeradvance.org/doku.php?id=documentation


Comment ça marche ?

Le cache est géré par le CPU de manière totalement invisible et quasi incontrôlable pour l'utilisateur Surprised. Pour expliquer simplement, lorsque le CPU va vouloir accéder à des données (par exemple "val=tab[5];" ), il va d'abord regarder dans son cache si ces données n'existent pas déjà Surprised. Si c'est le cas alors l'instruction aura pris beaucoup moins de temps que si les données étaient directement lues en ram Cool ; si ce n'est pas le cas alors le CPU va copier les données dans le cache avant d'exécuter l'instruction (donc perte de temps :-\).

Alors pourquoi cela fonctionne me demanderez-vous, si l'on met plus de temps lorsque les données ne sont pas dans le cache ??? ?

Et bien si l'on se place d'un point de vue statistique, il s'avère tout simplement que la probabilité d'utiliser la données stockées et ses données voisines est, au cour des instructions suivantes, très élevée. Par conséquent on a tout intérêt à mettre dans le cache la donnée (et ses voisines) lorsqu'elle n'y est pas, plutôt que l'utiliser directement depuis la ram Razz.

Concrètement le fonctionnement est simple : à chaque lecture de donnée, le CPU va vérifier d'abord si la donnée n'est pas dans le cache. Si elle y est, on effectue l'instruction depuis le cache ; si elle n'y est pas on remplit le cache avec la donnée en remplissant le cache par ligne complète. Donc même si l'on ne veut qu'un seul octet, toute la ligne sera remplie (soit les 64o dont le 1er sera notre octet de données).


Bon, maintenant que vous avez compris le principe de fonctionnement du cache, revenons au sujet principal : l'optimisation :Smile !!!

On va se baser sur l'exemple le plus courant et le plus flagrant où cette optimisation rentre en compte : la copie de buffer Very Happy.

Voici le code le plus banal de copie d'un buffer d'affichage contenant 272*512 pixel au format RGBA 32bits (u32 (soit 4o par pixel)) :

Code:

u32 src[272*512];// notre image source
u32 display_buffer[272*512];// notre buffer d'affichage

for(int y=0;y<272;y++)
  for(int x=0;x<480;x++)
      display_buffer[x+y*480]=src[x+y*480];

Bon, déjà il faut voir que l'on a échappé au pire en écrivant la boucle des x dans celle des y LOL , car l'inverse aurait été ca-ta-stro-phique Surprised (aucun accès consécutif aux données du tableau : on saute de 512 en 512 pixels...).

Analysons maintenant ce qu'il se passe au niveau du cache :

Pour chaque itération, on va potentiellement remplir une seule ligne de cache avec la donnée src[x+y*480] (selon l'alignement de la table src : au pire seulement la donnée src[x+y*480] dans la ligne de cache si non aligné avec le reste, au mieux src[x+y*480] à src[x+16+y*480] si totalement aligné avec le reste).
Donc on aura au final des mises en cache un peu n'importe quand avec des mises simultanées de 1 à 16 pixels totalement aléatoires selon l'alignement des données dans le buffer (plus la ram sera fragmentée (à cause d'allocations et de désallocations successives), moins l'alignement sera bon) :'( .
Au final l'utilisation du cache est bien loin de l'optimisation que l'on pourrait en faire...

Maintenant voici le code qui permet d'optimiser la mise en cache :
Code:

u32 __attribute__((aligned(64))) src[272*512];// notre image source alignée par blocs de 64o mini
u32 __attribute__((aligned(64))) display_buffer[272*512];// notre buffer d'affichage aligné par blocs de 64o mini

for(int y=0;y<272;y++)
{
  u32 *row_src = src[y*512];
  u32 *row_dst = display_buffer[y*512];
  for(int x=0;x<480;x++)
  {
      *row_dst=*row_src;
      row_src++;
      row_dst++;
  }
}

Explications :

Tout d'abord, l'attribut __attribute__((aligned(64))) nous assure que nos tables vont être alignées sur 64 octets ce qui implique que notre ligne de cache sera utilisée entièrement avec des données "utiles" Very Happy . Soit 16 pixels mis simultanément toutes les 16 itérations. Ensuite (mais là j'en suis moins sûr) il se peut que le cache se remplisse de plus d'une ligne ce qui économiserait encore des copies vers le cache si les données sont par hasard toujours alignées :Smile.
Enfin le fait de déclarer un pointeur sur le début de la ligne nous économise 479 x 2 calculs d'offsets (x+y*480) dans les tables src et display_buffer (petite optimisation non négligeable).

Ce qu'il faut retenir

L'important (que j'ai voulu mettre en avant dans ce tuto) est le fait de bien être conscient de ce qu'il se passe au niveau du CPU quand vous faites des calculs tout bêtes. L'importance de l'alignement des octets dans la ram est primordiale si l'on sait que ces octets seront lus à un moment ou un autre dans un ordre consécutif lors de la mise dans le cache.
C'est pour cela que vous verrez bien souvent dans des codes se voulant optimisés des choses comme : __attribute__((aligned(16))), __attribute__((aligned(32))), ... ou memalign(16, ...) et d'autres encore.

Je ne saurais trop vous engager à faire des mesures de temps sur une simple copie de buffer comme celle ci-dessus avec différentes valeurs d'alignement et sans, afin de bien vous rendre compte des gains de performance considérables que l'alignement entraine.

L'exemple de copie de buffer n'est bien évidemment pas le seul exemple où l'alignement est important. Il en va de même pour tous les calculs mettant en jeu des tables ou listes et bien évidemment les vertices en 3d si vous avez besoin de faire des calculs dessus par le CPU (pour ce qui est de l'affichage tout est géré par le gpu).

Enfin pour finir ce tutoriel, une petite remarque extrêmement importante pour ceux qui font de la 3D ou utilisent exclusivement le gpu : le cache est à l'origine de bien des problèmes avec les textures :-X. En effet il est la cause de disparitions ou corruptions de données dans les textures car le gpu n'a pas d'accès au cache cpu sur la PSP. Donc si par malheur, pour une raison quelconque, une partie de la texture est restée dans le cache, le gpu n'aura pas accès à ces données donc ne pourra faire un rendu de la partie restée dans le cache. C'est pour cela que l'on utilise couramment les fonctions :
Code:
sceKernelDcacheWritebackAll();
sceKernelDcacheWritebackInvalidateAll();
qui auront pour effet de vider le cache et donc réécrire ces données dans la ram (ou vram) Very Happy. Mais attention tout de même ne balancez pas ces fonctions inconsciemment dans votre code sans savoir ce que vous faites car non seulement c'est lent lors de l'appel même mais également après si le CPU a besoin d'accéder à ces données à nouveau et donc qu'il doit les remettre dans le cache :-[.



N'hésitez pas à me faire remarquer si des choses dans ce tuto ne sont pas claires, incomplètes ou même fausses :-\ (normalement non Razz).
avatar
Akabane87
Admin

Messages : 45
Date d'inscription : 30/07/2010

Voir le profil de l'utilisateur http://dreamraiser.forumactif.com

Revenir en haut Aller en bas

Voir le sujet précédent Voir le sujet suivant Revenir en haut

- Sujets similaires

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum