Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
En pratique : Adaptation des données pour accélérer le cache PowerPC
(Article écrit par Victorien Ferry et extrait de GuruMed.net - décembre 2002)
|
|
De la vitesse bon sang !
Ah ah ! Sur les Amiga classiques avec des extensions PowerPC, vous avez un processeur puissant
sur le papier, mais vous avez moins de puissance que vous ne le désiriez avec vos routines.
Pourquoi ? Parce que le processeur n'est pas tout. Il y a un bus entre lui et la mémoire, mais
il y a également des "mémoires cache" à l'intérieur du processeur : quand vous accédez à la mémoire
(en écriture ou en lecture), les données prennent le bus et celui-ci est lent. Heureusement, le
cache garde ce qui a été récemment lu ou écrit et évite des accès inutiles à la RAM. Je veux dire
si vous écrivez ces lignes de C :
void DoTwoAssignment ( int *somewhereInMemory )
{
int a = *somewhereInMemory ;
int c = *somewhereInMemory ;
}
|
...la deuxième assignation sera exécutée bien plus rapidement que la première parce que la valeur a été
chargée par le bus pendant le premier et les a mis à la fois dans "a" et dans le cache. Pendant la
deuxième assignation, la valeur a déjà été trouvée dans le cache, donc aucun besoin de la charger
encore par le bus lent. Si nous avions :
void Do_A_Write_And_A_Read ( int *somewhereInMemory )
{
*somewhereInMemory =4;
int c = *somewhereInMemory ;
}
|
...la première assignation serait lente, parce que cette fois une valeur est écrite dans la mémoire.
En fait, le cache va lire cette mémoire et effectuer l'écriture dans le cache. Cette écriture assure
donc que cette mémoire est présente pour la prochaine instruction qui devrait être exécutée rapidement.
La vraie écriture en mémoire sera réalisée n'importe quand dans le futur (dans la plupart des cas).
Notez que le comportement du cache n'est jamais visible (d'où son nom) : on ne peut pas savoir ce qu'il
garde, et vous pouvez parfaitement l'oublier quand vous codez.
Certains s'exclameront que tout le monde est au courant... Oui, mais sur Amiga classiques avec des
extensions PowerPC 603 et 604, le processeur est rapide, mais les bus sont incroyablement lents :
(il n'y a pas de cache de niveau 2, alors que les Pegasos et les AmigaOne en ont). Ainsi, la différence
de vitesse entre un source optimisé pour un cache PowerPC et un qui ne l'est pas, est énorme.
Mais d'abord, jetons un oeil aux descriptions de ces processeurs. Voici la taille des caches sur les
processeurs des Amiga.
|
Taille du cache de données |
Taille du cache d'instructions |
Taille de la ligne de cache |
PowerPC G3 |
256 ko |
... |
... |
PowerPC 604 |
32 ko |
32 ko |
32 octets |
PowerPC 603 |
16 ko |
16 ko |
32 octets |
68060 |
8 ko |
8 ko |
16 octets |
68030 |
256 octets |
256 octets |
16 octets |
Que cela signifie-t-il ? Que le cache du PowerPC 603 se "souviendra" des 16 derniers kilooctets lu par
votre programme. Quand un cache est complètement rempli, il efface les valeurs les moins utilisées
par des neuves. Le cache de données (data cache) est le cache utilisé pour la mémoire commune.
Le cache d'instructions (instruction cache) garde seulement le code exécuté : ainsi, elle accélère
les boucles et les fonctions réutilisées.
Et maintenant la chose importante : qu'est-ce qu'un cacheline (ligne de cache) ? C'est le morceau
de données vraiment lu ou écrit lors d'un accès. Le fait est, quand vous lisez un simple octet
avec un PowerPC, vous lisez en fait les 32 octets autour de lui, en commençant par l'adresse alignée
sur 32 la plus proche (adresse multiple de 32), et à partir de ce moment, ces 32 octets sont dans le cache.
Comment utiliser ce savoir pour un effet visuel ?
Prenez un effet de démo classique comme le rotozoom (aussi connu sous le nom de "rotator") et faites-le
tourner et zoomer sur l'écran : c'est un effet 2D qui fonctionne comme une simple application de texture.
Une image est déformée. Pour cet effet, deux zones mémoires sont employées :
- La mémoire de l'écran qui est écrite.
- Et la mémoire de l'image qui est lue.
L'image originale sans zoom ni rotation
Le rotozoom en action avec rotation et zoom
Voici une présentation rapide de ce code : une boucle parcours l'écran verticalement, et à l'intérieur
de cette boucle, une autre boucle parcours chaque ligne horizontalement. Dans cette boucle horizontale,
une équation de rotation lit un pixel sur une image source et l'écrit sur l'écran visible. De cette façon,
tous les pixels de l'écran sont traités et affiche une déformation de l'image.
Les pixels de l'écran sont écrit sur des lignes horizontales
L'image source est lue sur des vecteurs qui bougent
Avec un écran de 320x240 en 15 bits et une image 256x256 pixels, ça prendra autour de 100 hertz pour
dessiner l'image avec le seul PowerPC et une rotation de 0 degré ou de 180 degrés.
C'est parce que l'effet se comporte alors comme une simple copie : quand un pixel est lu, le cache garde
les 15 prochains pixels et n'a pas à les relire par le bus pour le pixel suivant. Le fait est que nos
pixels font deux octets et la taille de la ligne de cache du PowerPC est de 32 octets.
Quand l'image est lue verticalement, avec un degré de rotation de 90 ou -90, la mémoire est lue de façon
non continue parce que le prochain pixel à écrire est sur d'autres lignes de l'image. L'effet est ralenti
à environ 5 hertz ! Soit environ 20 fois plus lent ! Énorme différence ! En fait, le cache ralenti
le code dans ce cas de figure.
Voilà un premier truc pour empêcher cela
Précalculez une deuxième texture : la même de 256x256 pixels, mais avec une rotation de 90 degrés !
Avant le dessin de votre effet, vérifiez le vecteur de lecture sur l'image qui correspond à l'écriture
horizontale sur l'écran : si abs(Y)>abs(X), échangez tous les X et Y et utilisez la deuxième image source.
Alors, quand votre rotozoom changera sa rotation, l'image source utilisée changera. De cette façon, les
pixels seront lus dans le même ordre que leurs dispositions en mémoire, et le cache sera efficace.
Mais vous noterez que les angles 45°, -45° sont alors toujours plus lent que 0°, 180° et 90° et -90°.
Mais il existe un truc bien plus puissant !
Oooouh ! Honte sur moi ! Ce truc semble être périmé ! J'ai été averti par mes amis programmeurs
Peskanov/Capsule et Scorpion/Silicon qu'un autre truc de cache texture est plus rapide et prend
moins de mémoire : il faut simplement changer l'ordre des pixels de l'image, de sorte que le
cache ait la même chance de lire les prochains pixels quelle que soit la valeur de la rotation !
Utilisez seulement une image de 256x256 pixels comme source : habituellement, on met les pixels d'une
image dans la mémoire ligne par ligne, de la gauche vers la droite, et les lignes de haut en bas. Dès
lors, on adresse ses pixels avec :
De cette manière, dans les 65 536 pixels, le Y est codé par les 8 bits de poids fort et
les X par les 8 bits de poids faible. Eh bien, nous allons entrelacer l'ordre de ces 16 bits de cette façon :
Ordre classique (avant entrelacement) :
6 bits de poids fort de Y | 2 bits de poids faible de Y | 6 bits de poids fort de X | 2 bits de poids faible de X
Ordre entrelacé :
6 bits de poids fort de Y | 6 bits de poids fort de X | 2 bits de poids faible de Y | 2 bits de poids faible de X
Puis adaptez votre code de lecture/écriture des pixels à cet entrelacement, avec des instructions de décalage
de bits.
Image source avec alignement classique
Image source avec entrelacement
Sur ce schéma, vous pouvez noter les pixels chargés par une ligne de cache avec les frontières noires.
En bleu, il y a les vecteurs de lecture des pixels quand la rotation est autour de 0° ou 180°, en rouge
quand la rotation est 90° ou -90°. Les points représentent les pixels lus. Ainsi, vous pouvez comprendre
que les vecteurs bleus sont rapidement exécutés, mais sans l'entrelacement le vecteur rouge charge 16
fois trop de mémoire, ce qui explique le ralentissement déjà décrit.
Ainsi, cette manière de programmer votre image est meilleure pour tous les effets de déformation.
En fait, les cartes graphiques entrelacement leurs textures d'une manière voisine dans leurs mémoires !
Une autre manière de changer l'ordre des pixels des images est le "mipmapping" ("mip" vient du latin "multim
im parvo" signifiant "plusieurs choses dans peu de place" :-)). Il s'agit de réduire et filtrer votre image
vers d'autres images de tailles 128x128, 64x64, 32x32, 16x16, 8x8... jusqu'à 1x1, et d'utiliser la plus
adaptée à votre facteur de zoom. Dans le domaine du rendu matériel, cette technique améliore le rendu,
mais pour des déformations d'image logicielle (rotozoom, placage de texture...), il l'accélère également,
parce que pour un petit facteur de zoom, la lecture d'une grande image donnerait de grands sauts en mémoire.
Extension du domaine...
Et voilà ! Vous avez appris comment employer votre connaissance de l'architecture des caches pour
accélérer un code graphique donné. A l'avenir, essayez de voir si cela peut s'appliquer à d'autres algorithmes :
par exemple, si vous avez beaucoup de petites structures en mémoire lues non linéairement, vous pourriez
gagner de la vitesse en les alignant sur des adresses multiples de 32 et prêter attention à ce que ces
structures ne dépassent pas 32, 64 ou 96 octets. Une taille de 34 octets ferait lire le double de données pour
rien ! N'oubliez jamais que les meilleures implémentations prennent moins de mémoire, et c'est ça l'esprit Amiga !
|