Sophie

Sophie

distrib > * > 2010.0 > * > by-pkgid > a412ceb851151854794ced2a242192bb > files > 2729

howto-html-fr-20080722-1mdv2010.0.noarch.rpm

<html><head><META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>4.&nbsp;SIMD Within A Register&nbsp;: SWAR (Ex&nbsp;: utilisation de MMX)</title><link href="style.css" rel="stylesheet" type="text/css"><meta content="DocBook XSL Stylesheets V1.68.1" name="generator"><link rel="start" href="index.html" title="Le traitement en parall&egrave;le sous Linux"><link rel="up" href="index.html" title="Le traitement en parall&egrave;le sous Linux"><link rel="prev" href="ar01s03.html" title="3.&nbsp;Clusters de syst&egrave;mes Linux"><link rel="next" href="ar01s05.html" title="5.&nbsp;Processeurs auxiliaires des machines Linux"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table summary="Navigation header" width="100%"><tr><th align="center" colspan="3">4.&nbsp;SIMD <span class="foreignphrase"><em class="foreignphrase">Within A Register</em></span>&nbsp;: SWAR (Ex&nbsp;: utilisation de MMX)</th></tr><tr><td align="left" width="20%"><a accesskey="p" href="ar01s03.html">Pr&eacute;c&eacute;dent</a>&nbsp;</td><th align="center" width="60%">&nbsp;</th><td align="right" width="20%">&nbsp;<a accesskey="n" href="ar01s05.html">Suivant</a></td></tr></table><hr></div><div class="sect1" lang="fr"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="N1119A"></a>4.&nbsp;SIMD <span class="foreignphrase"><em class="foreignphrase">Within A Register</em></span>&nbsp;: SWAR (Ex&nbsp;: utilisation de MMX)</h2></div></div></div><p>

Le SIMD (&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">Single Instruction stream, Multiple Data 
stream</em></span></span>&nbsp;&raquo; ou &laquo;&nbsp;<span class="quote">Un seul flux d'instruction, 
plusieurs flux de donn&eacute;es</span>&nbsp;&raquo;) &agrave; l'Int&eacute;rieur d'Un Registre (ou 
&laquo;&nbsp;<span class="quote">SIMD <span class="foreignphrase"><em class="foreignphrase">Within A 
Register</em></span></span>&nbsp;&raquo;&nbsp;: SWAR) n'est pas un concept 
r&eacute;cent. En consid&eacute;rant une machine dot&eacute;e de registres, bus de donn&eacute;es et 
unit&eacute;s de fonctions de <span class="emphasis"><em>k</em></span> bits, il est connu depuis 
longtemps que les op&eacute;rations sur les registres ordinaires peuvent se 
comporter comme des op&eacute;rations parall&egrave;les SIMD sur 
<span class="emphasis"><em>n</em></span> champs de 
<span class="emphasis"><em>k</em></span>/<span class="emphasis"><em>n</em></span> bits chacun. Ce n'est en 
revanche qu'avec les efforts r&eacute;cents en mati&egrave;re de multim&eacute;dia que 
l'acc&eacute;l&eacute;ration d'un facteur deux &agrave; huit apport&eacute;e par les techniques SWAR 
a commenc&eacute; &agrave; concerner l'informatique g&eacute;n&eacute;rale. Les versions de 1997 de 
la plupart des microprocesseurs incorporent une prise en charge 
mat&eacute;rielle du SWAR<sup>[<a href="#ftn.N111BA" name="N111BA">23</a>]</sup>&nbsp;:

</p><p>

<div class="itemizedlist"><ul type="disc"><li><p>
<a href="http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/20726.pdf" target="_top">AMD K6 MMX (&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">MultiMedia eXtensions</em></span></span>&nbsp;&raquo;)</a>

</p></li><li><p>
<a href="http://www.sun.com/sparc/vis/index.html" target="_top">Sun SPARC V9 VIS (&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">Visual Instruction Set</em></span></span>&nbsp;&raquo;)</a>
</p></li></ul></div>

</p><p>
Il existe quelques lacunes dans la prise en charge mat&eacute;rielle
apport&eacute;e par les nouveaux microprocesseurs, des caprices comme
par exemple la prise en charge d'un nombre limit&eacute; d'instructions
pour certaines tailles de champ. Il est toutefois important de
garder &agrave; l'esprit que bon nombre d'op&eacute;rations SWAR peuvent se
passer d'acc&eacute;l&eacute;ration mat&eacute;rielle. Par exemple, les op&eacute;rations
au niveau du bit sont insensibles au partitionnement logique d'un
registre.
</p><div class="sect2" lang="fr"><div class="titlepage"><div><div><h3 class="title"><a name="N111DA"></a>4.1.&nbsp;Quels usages pour le SWAR&nbsp;?</h3></div></div></div><p>
Bien que <span class="emphasis"><em>tout</em></span> processeur moderne soit capable
d'ex&eacute;cuter un programme en effectuant un minimum de parall&eacute;lisme
SWAR, il est de fait que m&ecirc;me le plus optimis&eacute; des jeux d'instructions
SWAR ne peut g&eacute;rer un parall&eacute;lisme d'int&eacute;r&ecirc;t vraiment g&eacute;n&eacute;ral. A dire
vrai, de nombreuses personnes ont remarqu&eacute; que les diff&eacute;rences de
performance entre un Pentium ordinaire et un Pentium &laquo;&nbsp;<span class="quote">avec technologie MMX</span>&nbsp;&raquo;
&eacute;taient surtout d&ucirc;es &agrave; certains d&eacute;tails, comme le fait que l'augmentation
de la taille du cache L1 co&iuml;ncide avec l'apparition du MMX. Alors,
concr&egrave;tement, &agrave; quoi le SWAR (ou le MMX) est-il utile&nbsp;?
</p><div class="itemizedlist"><ul type="disc"><li><p>

Dans le traitement des entiers, les plus courts &eacute;tant les meilleurs. 
Deux valeurs 32 bits tiennent dans un registre MMX 64 bits, mais forment 
aussi une cha&icirc;ne de huit caract&egrave;res, ou encore permettent de repr&eacute;senter un
&eacute;chiquier complet, &agrave; raison d'un bit par case. Note&nbsp;: il 
<span class="emphasis"><em>existera une version &laquo;&nbsp;<span class="quote">virgule flottante</span>&nbsp;&raquo; du 
MMX</em></span>, bien que tr&egrave;s peu de choses aient &eacute;t&eacute; dites &agrave; ce sujet. 
Cyrix a d&eacute;pos&eacute; une pr&eacute;sentation, <a href="ftp://ftp.cyrix.com/developr/mpf97rm.pdf" target="_top">ftp://ftp.cyrix.com/developr/mpf97rm.pdf</a>, qui contient quelques 
commentaires concernant <span class="emphasis"><em>MMFP</em></span>. Apparemment, MMFP 
pourra prendre en charge le chargement de nombres 32 bits en virgule 
flottante dans des registres MMX 64 bits. Ceci combin&eacute; &agrave; deux pipelines 
MMFP fournirait quatre FLOP <sup>[<a href="#ftn.N111F5" name="N111F5">24</a>]</sup> en simple pr&eacute;cision par cycle d'horloge.

</p></li><li><p>

Pour le SIMD, ou parall&eacute;lisme vectoris&eacute;. La m&ecirc;me op&eacute;ration s'applique &agrave; 
tous les champs, simultan&eacute;ment. Il existe des moyens d'annihiler ses 
effets sur des champs s&eacute;lectionn&eacute;s (l'&eacute;quivalent du 
&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">SIMD enable masking</em></span></span>&nbsp;&raquo;, ou 
&laquo;&nbsp;<span class="quote">activation du masquage</span>&nbsp;&raquo;), mais ils sont compliqu&eacute;s &agrave; 
mettre en &#339;uvre et gr&egrave;vent les performances.

</p></li><li><p>

Pour les cas o&ugrave; la r&eacute;f&eacute;rence &agrave; la m&eacute;moire se fait de mani&egrave;re localis&eacute;e 
et r&eacute;guli&egrave;re (et de pr&eacute;f&eacute;rence regroup&eacute;e). Le SWAR en g&eacute;n&eacute;ral, et le MMX 
en particulier se r&eacute;v&egrave;lent d&eacute;sastreux sur les acc&egrave;s al&eacute;atoires. 
Rassembler le contenu d'un vecteur <code class="literal">x[y]</code> (o&ugrave; 
<code class="literal">y</code> est l'index d'un tableau) est extr&ecirc;mement co&ucirc;teux.

</p></li></ul></div><p>
Il existe donc d'importantes limitations, mais ce type de parall&eacute;lisme
intervient dans un certain nombre d'algorithmes, et pas seulement dans les
applications multim&eacute;dia. Lorsque l'algorithme est adapt&eacute;, le SWAR est plus
efficace que le SMP ou le parall&eacute;lisme en <span class="foreignphrase"><em class="foreignphrase">clusters</em></span>&hellip; et son utilisation
ne co&ucirc;te rien&nbsp;!
</p></div><div class="sect2" lang="fr"><div class="titlepage"><div><div><h3 class="title"><a name="N1121A"></a>4.2.&nbsp;Introduction &agrave; la programmation SWAR</h3></div></div></div><p>
Le concept de base du SWAR (&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">SIMD Within A Register</em></span></span>&nbsp;&raquo;,
ou &laquo;&nbsp;<span class="quote">SIMD &agrave; l'int&eacute;rieur d'un registre</span>&nbsp;&raquo;) r&eacute;side dans le fait que l'on peut utiliser des op&eacute;rations sur
des registres de la taille d'un mot pour acc&eacute;l&eacute;rer les calculs en effectuant des
op&eacute;rations parall&egrave;les SIMD sur <span class="emphasis"><em>n</em></span> champs
de <span class="emphasis"><em>k</em></span>/<span class="emphasis"><em>n</em></span> bits. En revanche,
employer les techniques du SWAR peut parfois s'av&eacute;rer maladroit, et certaines de leurs
op&eacute;rations peuvent au final &ecirc;tre plus co&ucirc;teuses que leurs homologues en s&eacute;rie
car elles n&eacute;cessitent des instructions suppl&eacute;mentaires pour forcer le partitionnement
des champs.
</p><p>
Pour illustrer ce point, prenons l'exemple d'un m&eacute;canisme SWAR
grandement simplifi&eacute; g&eacute;rant quatre champs de 8 bits dans chaque
registre de 32 bits. Les valeurs de ces deux registres pourraient
&ecirc;tre repr&eacute;sent&eacute;es comme suit&nbsp;:
</p><p>

<pre class="programlisting">
         PE3     PE2     PE1     PE0
      +-------+-------+-------+-------+
Reg0  | D 7:0 | C 7:0 | B 7:0 | A 7:0 |
      +-------+-------+-------+-------+
Reg1  | H 7:0 | G 7:0 | F 7:0 | E 7:0 |
      +-------+-------+-------+-------+
</pre>

</p><p>

Ceci indique simplement que chaque registre est vu essentiellement comme 
un vecteur de quatre valeurs enti&egrave;res 8 bits ind&eacute;pendantes. 
Alternativement, les valeurs <code class="literal">A</code> et 
<code class="literal">E</code> peuvent &ecirc;tre vues comme les valeurs, dans Reg0 et 
Reg1, de l'&eacute;l&eacute;ment de traitement 0 (ou &laquo;&nbsp;<span class="quote">PE0</span>&nbsp;&raquo; pour 
&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">Processing Element 0</em></span></span>&nbsp;&raquo;), 
<code class="literal">B</code> et <code class="literal">F</code> comme celles de l'&eacute;l&eacute;ment 
1, et ainsi de suite.

</p><p>
Le reste de ce document passe rapidement en revue les classes de
base des op&eacute;rations parall&egrave;les SIMD sur ces vecteurs d'entiers et
la fa&ccedil;on dont ces fonctions peuvent &ecirc;tre impl&eacute;ment&eacute;es.
</p><div class="sect3" lang="fr"><div class="titlepage"><div><div><h4 class="title"><a name="N11252"></a>4.2.1.&nbsp;Op&eacute;rations polymorphiques</h4></div></div></div><p>
Certaines op&eacute;rations SWAR peuvent &ecirc;tre effectu&eacute;es de fa&ccedil;on triviale
en utilisant des op&eacute;rations ordinaires sur des entiers 32 bits, sans
avoir &agrave; se demander si l'op&eacute;ration est r&eacute;ellement faite pour agir en
parall&egrave;le et ind&eacute;pendemment sur ces champs de 8 bits. On qualifie ce
genre d'op&eacute;ration SWAR de <span class="emphasis"><em>polymorphique</em></span>, parce que
la fonction est insensible aux types des champs (et &agrave; leur taille).
</p><p>
Tester si un champ quelconque est non-nul est une op&eacute;ration polymorphique,
tout comme les op&eacute;rations logiques au niveau du bit. Par exemple, un &laquo;&nbsp;<span class="quote">ET</span>&nbsp;&raquo;
logique bit-&agrave;-bit ordinaire (l'op&eacute;rateur &laquo;&nbsp;<span class="quote"><code class="literal">&amp;</code></span>&nbsp;&raquo;
du registre C) effectue son calcul bit-&agrave;-bit, quelque soit la taille des champs.
Un &laquo;&nbsp;<span class="quote">ET</span>&nbsp;&raquo; logique simple des registres ci-dessus donnerait&nbsp;:
</p><p>

<pre class="programlisting">
          PE3       PE2       PE1       PE0
      +---------+---------+---------+---------+
Reg2  | D&amp;H 7:0 | C&amp;G 7:0 | B&amp;F 7:0 | A&amp;E 7:0 |
      +---------+---------+---------+---------+
</pre>

</p><p>
Puisque le bit de r&eacute;sultat <span class="emphasis"><em>k</em></span> de l'op&eacute;ration &laquo;&nbsp;<span class="quote">ET</span>&nbsp;&raquo; logique
n'est affect&eacute; que par les valeurs des bits d'op&eacute;rande <span class="emphasis"><em>k</em></span>,
toutes les tailles de champs peuvent &ecirc;tre prises en charge par une m&ecirc;me instruction.
</p></div><div class="sect3" lang="fr"><div class="titlepage"><div><div><h4 class="title"><a name="N11278"></a>4.2.2.&nbsp;Op&eacute;rations partitionn&eacute;es</h4></div></div></div><p>
Malheureusement, de nombreuses et importantes op&eacute;rations SWAR ne sont
pas polymorphiques. Les op&eacute;rations arithm&eacute;tiques comme l'addition, la
soustraction, la multiplication et la division sont sujettes aux
interactions de la &laquo;&nbsp;<span class="quote">retenue</span>&nbsp;&raquo; entre les champs. Ces op&eacute;rations sont
dites <span class="emphasis"><em>partitionn&eacute;es</em></span> car chacune d'elles doit
cloisonner effectivement les op&eacute;randes et le r&eacute;sultat pour &eacute;viter les
interactions entre champs. Il existe toutefois trois m&eacute;thodes diff&eacute;rentes
pouvant &ecirc;tre employ&eacute;es pour parvenir &agrave; ces fins&nbsp;:
</p><div class="sect4" lang="fr"><div class="titlepage"><div><div><h5 class="title"><a name="N11283"></a>4.2.2.1.&nbsp;Instructions partitionn&eacute;es</h5></div></div></div><p>
L'approche la plus &eacute;vidente pour impl&eacute;menter les op&eacute;rations partitionn&eacute;es
consiste peut-&ecirc;tre &agrave; proposer une prise en charge mat&eacute;rielle des
&laquo;&nbsp;<span class="quote">instructions parall&egrave;les partitionn&eacute;es</span>&nbsp;&raquo; coupant le syst&egrave;me de retenue
entre les champs. Cette approche peut offrir les performances les plus
&eacute;lev&eacute;es, mais elle n&eacute;cessite la modification du jeu d'instruction du
processeur et implique en g&eacute;n&eacute;ral des limitations sur la taille des
champs (Ex&nbsp;: ces instructions pourraient prendre en charge des champs larges
de 8 bits, mais pas de 12).
</p><p>
Le MMX des processeurs AMD, Cyrix et Intel, Le MAX de Digital, celui d'HP,
et le VIS de Sun impl&eacute;mentent tous des versions restreintes des instructions
partitionn&eacute;es. Malheureusement, ces diff&eacute;rents jeux d'instructions posent
des restrictions assez diff&eacute;rentes entre elles, ce qui rend les algorithmes
difficilement portables. &Eacute;tudions &agrave; titre d'exemple l'&eacute;chantillon d'op&eacute;rations
partitionn&eacute;es suivant&nbsp;:
</p><p>

<pre class="programlisting">
  Instruction           AMD/Cyrix/Intel MMX   DEC MAX   HP MAX   Sun VIS
+---------------------+---------------------+---------+--------+---------+
| Diff&eacute;rence Absolue  |                     |       8 |        |       8 |
+---------------------+---------------------+---------+--------+---------+
| Maximum             |                     |   8, 16 |        |         |
+---------------------+---------------------+---------+--------+---------+
| Comparaison         |           8, 16, 32 |         |        |  16, 32 |
+---------------------+---------------------+---------+--------+---------+
| Multiplication      |                  16 |         |        |    8x16 |
+---------------------+---------------------+---------+--------+---------+
| Addition            |           8, 16, 32 |         |     16 |  16, 32 |
+---------------------+---------------------+---------+--------+---------+
</pre>

</p><p>
Dans cette table, les chiffres indiquent les tailles de champ reconnues
par chaque op&eacute;ration. M&ecirc;me si la table exclut un certain nombre d'instructions
dont les plus exotiques, il est clair qu'il y a des diff&eacute;rences. Cela a pour effet
direct de rendre inefficaces les mod&egrave;les de programmation en langages de haut niveau,
et de s&eacute;v&egrave;rement restreindre la portabilit&eacute;.

</p></div><div class="sect4" lang="fr"><div class="titlepage"><div><div><h5 class="title"><a name="N11295"></a>4.2.2.2.&nbsp;Op&eacute;rations non partitionn&eacute;es avec code de correction</h5></div></div></div><p>
Impl&eacute;menter des op&eacute;rations partitionn&eacute;es en utilisant des instructions
partitionn&eacute;es est certainement tr&egrave;s efficace, mais comment faire lorsque
l'op&eacute;ration dont vous avez besoin n'est pas prise en charge par le mat&eacute;riel&nbsp;?
R&eacute;ponse&nbsp;: utiliser une s&eacute;rie d'instructions ordinaires pour effectuer l'op&eacute;ration
malgr&eacute; les effets de la retenue entre les champs, puis effectuer la correction
de ces effets ind&eacute;sir&eacute;s.
</p><p>
C'est une approche purement logicielle, et les corrections apportent
bien entendu un surco&ucirc;t en temps, mais elle fonctionne pleinement avec
le partitionnement en g&eacute;n&eacute;ral. Cette approche est &eacute;galement &laquo;&nbsp;<span class="quote">g&eacute;n&eacute;rale</span>&nbsp;&raquo;
dans le sens o&ugrave; elle peut &ecirc;tre utilis&eacute;e soit pour combler les lacunes du
mat&eacute;riel dans le domaine des instructions partitionn&eacute;es, soit pour apporter
un soutien logiciel complet aux machines qui ne sont pas du tout dot&eacute;es
d'une prise en charge mat&eacute;rielle. &Agrave; dire vrai, en exprimant ces s&eacute;quences
de code dans un langage comme le C, on permet au SWAR d'&ecirc;tre totalement
portable.
</p><p>
Ceci soul&egrave;ve imm&eacute;diatement une question&nbsp;: quelle est pr&eacute;cis&eacute;ment l'inefficacit&eacute;
des op&eacute;rations SWAR partitionn&eacute;es simul&eacute;es &agrave; l'aide d'op&eacute;rations non partitionn&eacute;es&nbsp;?
Eh bien c'est tr&egrave;s certainement la question &agrave; 65536 dollars, mais certaines op&eacute;rations
ne sont pas aussi difficiles &agrave; mettre en &#339;uvre que l'on pourrait le croire.
</p><p>
Prenons en exemple le cas de l'impl&eacute;mentation de l'addition d'un vecteur de quatre
&eacute;l&eacute;ments 8 bits contenant des valeurs enti&egrave;res, soit <code class="literal">x</code>+<code class="literal">y</code>,
en utilisant des op&eacute;rations 32 bits ordinaires.
</p><p>
Une addition 32 bits ordinaire pourrait en fait rendre un r&eacute;sultat correct,
mais pas si la retenue d'un champ de 8 bits se reporte sur le champ suivant.
Aussi, notre but consiste simplement &agrave; faire en sorte qu'un tel report ne se produise
pas. Comme l'on est s&ucirc;r qu'additionner deux champs de <span class="emphasis"><em>k</em></span> bits
ne peut g&eacute;n&eacute;rer qu'un r&eacute;sultat large d'au plus <span class="emphasis"><em>k</em></span>+1 bits,
on peut garantir qu'aucun report ne va se produire en &laquo;&nbsp;<span class="quote">masquant</span>&nbsp;&raquo; simplement le
bit de poids fort de chaque champs. On fait cela en appliquant un &laquo;&nbsp;<span class="quote">ET</span>&nbsp;&raquo; logique
&agrave; chaque op&eacute;rande avec la valeur <code class="literal">0x7f7f7f7f</code>, puis
en effectuant un addition 32 bits habituelle.
</p><p>

<pre class="programlisting">
t = ((x &amp; 0x7f7f7f7f) + (y &amp; 0x7f7f7f7f));
</pre>

</p><p>
Ce r&eacute;sultat est correct&hellip; sauf pour le bit de poids fort de chacun
des champs. Il n'est question, pour calculer la valeur correcte, que
d'effectuer deux additions 1-bit partitionn&eacute;es, depuis <code class="literal">x</code>
et <code class="literal">y</code> vers le r&eacute;sultat &agrave; 7 bits calcul&eacute;
pour <code class="literal">t</code>. Heureusement, une addition
partitionn&eacute;e sur 1 bit peut &ecirc;tre impl&eacute;ment&eacute;e &agrave; l'aide d'une
op&eacute;ration &laquo;&nbsp;<span class="quote">OU Exclusif</span>&nbsp;&raquo; logique ordinaire. Ainsi, le r&eacute;sultat est
tout simplement&nbsp;:
</p><p>

<pre class="programlisting">
(t ^ ((x ^ y) &amp; 0x80808080))
</pre>

</p><p>
D'accord. Peut-&ecirc;tre n'est-ce pas si simple, en fin de compte. Apr&egrave;s tout, cela fait six
op&eacute;rations pour seulement quatre additions. En revanche, on remarquera
que ce nombre d'op&eacute;rations n'est pas fonction du nombre de champs. Donc,
avec plus de champs, on gagne en rapidit&eacute;. &Agrave; dire vrai, il se peut que
l'on gagne quand m&ecirc;me en vitesse simplement parce que les champs sont
charg&eacute;s et red&eacute;pos&eacute;s en une seule op&eacute;ration (vectoris&eacute;e sur des entiers),
que la disponibilit&eacute; des registres est optimis&eacute;e, et parce qu'il y a
moins de code dynamique engendrant des d&eacute;pendances (parce que l'on &eacute;vite
les r&eacute;f&eacute;rences &agrave; des mots incomplets).
</p></div><div class="sect4" lang="fr"><div class="titlepage"><div><div><h5 class="title"><a name="N112DC"></a>4.2.2.3.&nbsp;Contr&ocirc;ler les valeurs des champs</h5></div></div></div><p>
Alors que les deux autres approches de l'impl&eacute;mentation des op&eacute;rations
partitionn&eacute;es se centrent sur l'exploitation du maximum d'espace possible
dans les registres, il peut &ecirc;tre, au niveau du calcul, plus efficace de
contr&ocirc;ler les valeurs des champs de fa&ccedil;on &agrave; ce que l'interaction
de la retenue entre ceux-ci ne se produise jamais. Par exemple, si l'on sait que
toutes les valeurs de champs additionn&eacute;es sont telles qu'aucun d&eacute;passement
ne peut avoir lieu, une addition partitionn&eacute;e peut &ecirc;tre impl&eacute;ment&eacute;e &agrave;
l'aide d'une instruction ordinaire. Cette contrainte pos&eacute;e, une addition
ordinaire pourrait en fin de compte appara&icirc;tre polymorphique,
et &ecirc;tre utilisable avec n'importe quelle taille de champ sans programme de
correction. Ce qui nous am&egrave;ne ainsi &agrave; la question suivante&nbsp;: Comment s'assurer
que les valeurs des champs ne vont pas provoquer d'&eacute;v&eacute;nements dus &agrave; la
retenue&nbsp;?
</p><p>
Une mani&egrave;re de faire cela consiste &agrave; impl&eacute;menter des instructions partitionn&eacute;es
capables de restreindre la port&eacute;e des valeurs des champs. Les instructions
vectoris&eacute;es de maximum et de minimum du MAX de Digital peuvent &ecirc;tre
assimil&eacute;es &agrave; une prise en charge mat&eacute;rielle de l'&eacute;cr&ecirc;tage des valeurs des
champs pour &eacute;viter les interactions de la retenue entre les champs.
</p><p>
En revanche, si l'on ne dispose pas d'instructions partitionn&eacute;es &agrave; m&ecirc;me
de restreindre efficacement la port&eacute;e des valeurs des champs, existe-t-il
une condition qui puisse &ecirc;tre impos&eacute;e &agrave; un co&ucirc;t raisonnable et qui soit
suffisante pour garantir le fait que les effets de la retenue n'iront pas
perturber les champs adjacents&nbsp;? La r&eacute;ponse se trouve dans l'analyse des
propri&eacute;t&eacute;s arithm&eacute;tiques. Additionner deux nombres de <span class="emphasis"><em>k</em></span>
bits renvoie un r&eacute;sultat large d'au plus <span class="emphasis"><em>k</em></span>+1 bits.
Ainsi, un champ de <span class="emphasis"><em>k</em></span>+1 bits peut recevoir en toute
s&eacute;curit&eacute; le r&eacute;sultat d'une telle op&eacute;ration, m&ecirc;me s'il est produit par
une instruction ordinaire.
</p><p>
Supposons donc que les champs de 8 bits de nos pr&eacute;c&eacute;dents exemples
soient d&eacute;sormais des champs de 7 bits avec &laquo;&nbsp;<span class="quote">espaces de s&eacute;paration
pour retenue</span>&nbsp;&raquo; d'un bit chacun&nbsp;:
</p><p>

<pre class="programlisting">
              PE3          PE2          PE1          PE0
      +----+-------+----+-------+----+-------+----+-------+
Reg0  | D' | D 6:0 | C' | C 6:0 | B' | B 6:0 | A' | A 6:0 |
      +----+-------+----+-------+----+-------+----+-------+
</pre>

</p><p>
Mettre en place un vecteur d'additions 7 bits se fait de la mani&egrave;re
suivante&nbsp;: On part du principe qu'avant l'ex&eacute;cution de toute instruction
partitionn&eacute;e, les bits des s&eacute;parateurs de retenue
(<code class="literal">A'</code>, <code class="literal">B'</code>,
<code class="literal">C'</code>, et <code class="literal">D'</code>)
sont nuls. En effectuant simplement une addition ordinaire, tous les
champs re&ccedil;oivent une valeur correcte sur 7 bits. En revanche, certains
bits de s&eacute;paration peuvent passer &agrave; 1. On peut rem&eacute;dier &agrave; cela en
appliquant une seule op&eacute;ration suppl&eacute;mentaire conventionnelle et masquant
les bits de s&eacute;paration. Notre vecteur d'additions enti&egrave;res sur 7 bits,
<code class="literal">x</code>+<code class="literal">y</code>, devient
ainsi&nbsp;:
</p><p>

<pre class="programlisting">
((x + y) &amp; 0x7f7f7f7f)
</pre>

</p><p>
Ceci n&eacute;cessite seulement deux op&eacute;rations pour effectuer quatre additions.
Le gain en vitesse est &eacute;vident.
</p><p>
Les lecteurs avertis auront remarqu&eacute; que forcer les bits de s&eacute;paration
&agrave; z&eacute;ro ne convient pas pour les soustractions. La solution est toutefois
remarquablement simple&nbsp;: Pour calculer <code class="literal">x</code>-<code class="literal">y</code>,
on garantira simplement la condition initiale, qui veut que tous les bits
de s&eacute;paration de <code class="literal">x</code> valent 1 et que tous ceux
de <code class="literal">y</code> soient nuls. Dans le pire des cas, nous
obtiendrons&nbsp;:
</p><p>

<pre class="programlisting">
(((x | 0x80808080) - y) &amp; 0x7f7f7f7f)
</pre>

</p><p>
On peut toutefois souvent se passer du &laquo;&nbsp;<span class="quote">OU</span>&nbsp;&raquo; logique en s'assurant
que l'op&eacute;ration qui g&eacute;n&egrave;re la valeur de <code class="literal">x</code>
utilise <code class="literal">| 0x80808080</code> plut&ocirc;t que
<code class="literal">&amp; 0x7f7f7f7f</code> en derni&egrave;re &eacute;tape.
</p><p>
Laquelle de ces m&eacute;thodes doit-on appliquer aux op&eacute;rations partitionn&eacute;es du
SWAR&nbsp;? La r&eacute;ponse est simple&nbsp;: &laquo;&nbsp;<span class="quote">celle qui offre le gain en rapidit&eacute; le
plus important</span>&nbsp;&raquo;. Ce qui est int&eacute;ressant, c'est que la m&eacute;thode id&eacute;ale peut
&ecirc;tre diff&eacute;rente selon chaque taille de champ, et ce &agrave; l'int&eacute;rieur d'un
m&ecirc;me programme, s'ex&eacute;cutant sur une m&ecirc;me machine.
</p></div></div><div class="sect3" lang="fr"><div class="titlepage"><div><div><h4 class="title"><a name="N11349"></a>4.2.3.&nbsp;Op&eacute;rations de communication et de conversion de type</h4></div></div></div><p>
Bien que certains calculs en parall&egrave;le, incluant les op&eacute;rations sur les
pixels d'une image, ont pour propri&eacute;t&eacute; le fait que la <span class="emphasis"><em>n</em></span>i&egrave;me
valeur d'un vecteur soit une fonction des valeurs se trouvant &agrave; la <span class="emphasis"><em>n</em></span>i&egrave;me
position des vecteurs des op&eacute;randes, ce n'est g&eacute;n&eacute;ralement pas le cas. Par exemple,
m&ecirc;me les op&eacute;rations sur les pixels telles que l'adoucissement ou le flou r&eacute;clament
en op&eacute;rande les valeurs des pixels adjacents, et les transformations comme la transform&eacute;e
de Fourrier (FFT) n&eacute;cessitent des sch&eacute;mas de communications plus complexes (et moins
localis&eacute;s).
</p><p>
Il est relativement facile de mettre efficacement en place un syst&egrave;me de communication
&agrave; une dimension entre les valeurs du voisinage imm&eacute;diat pour effectuer du SWAR, en utilisant
des op&eacute;rations de d&eacute;calage non partitionn&eacute;es. Par exemple, pour d&eacute;placer une valeur depuis
<code class="literal">PE</code> vers <code class="literal">PE</code>(<span class="emphasis"><em>n</em></span>+1),
un simple d&eacute;calage logique suffit. Si les champs sont larges de 8 bits, on utilisera&nbsp;:
</p><p>

<pre class="programlisting">
(x &lt;&lt; 8)
</pre>

</p><p>
Pourtant, ce n'est pas toujours aussi simple. Par exemple, pour d&eacute;placer une valeur
depuis <code class="literal">PE</code><span class="emphasis"><em>n</em></span> vers
<code class="literal">PE</code>(<span class="emphasis"><em>n</em></span>-1), un simple d&eacute;calage vers la
droite devrait suffire&hellip; mais le langage C ne pr&eacute;cise pas si le d&eacute;calage vers la
droite conserve le bit de signe, et certaines machines ne proposent, vers la droite,
qu'un d&eacute;calage sign&eacute;. Aussi, d'une mani&egrave;re g&eacute;n&eacute;rale, devons-nous mettre explicitement &agrave;
z&eacute;ro les bits de signe pouvant &ecirc;tre r&eacute;pliqu&eacute;s.
</p><p>

<pre class="programlisting">
((x &gt;&gt; 8) &amp; 0x00ffffff)
</pre>

</p><p>

L'ajout de connexions &laquo;&nbsp;<span class="quote">&agrave; enroulement</span>&nbsp;&raquo; 
(&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">wrap-around</em></span></span>&nbsp;&raquo;) &agrave; l'aide de 
d&eacute;calages non partitionn&eacute;s <sup>[<a href="#ftn.N11385" name="N11385">25</a>]</sup> est aussi raisonnablement efficace. Par exemple, pour 
d&eacute;placer une valeur depuis <code class="literal">PE</code><span class="emphasis"><em>i</em></span> 
vers <code class="literal">PE</code>(<span class="emphasis"><em>i</em></span>+1) avec 
enroulement&nbsp;:

</p><pre class="programlisting">
((x &lt;&lt; 8) | ((x &gt;&gt; 24) &amp; 0x000000ff))
</pre><p>

Les probl&egrave;mes s&eacute;rieux apparaissent lorsque des sch&eacute;mas de communications 
plus g&eacute;n&eacute;raux doivent &ecirc;tre mis en &#339;uvre. Seul le jeu 
d'instructions du MAX de HP permet le r&eacute;arrangement arbitraire des 
champs en une seule instruction, nomm&eacute;e <code class="literal">Permute</code>. 
Cette instruction <code class="literal">Permute</code> porte vraiment mal son 
nom&nbsp;: Non seulement elle effectue une permutation arbitraire des 
champs, mais elle autorise &eacute;galement les r&eacute;p&eacute;titions. En bref, elle met 
en place une op&eacute;ration arbitraire de <code class="literal">x[y]</code>.

</p><p>

Il reste malheureusement tr&egrave;s difficile d'impl&eacute;menter 
<code class="literal">x[y]</code> sans le concours d'une telle instruction. La 
s&eacute;quence de code est g&eacute;n&eacute;ralement &agrave; la fois longue et inefficace, parce 
qu'en fait, il s'agit d'un programme s&eacute;quentiel. Ceci est tr&egrave;s d&eacute;cevant. 
La vitesse relativement &eacute;lev&eacute;e des op&eacute;rations de type 
<code class="literal">x[y]</code> sur les les supercalculateurs SIMD MasPar 
MP1/MP2 et Thinking Machines CM1/CM2/CM200 &eacute;tait une des cl&eacute;s majeures 
de leur succ&egrave;s. Toutefois, un <code class="literal">x[y]</code> reste plus lent 
qu'une communication de proximit&eacute;, m&ecirc;me sur ces supercalculateurs. 
Beaucoup d'algorithmes ont donc &eacute;t&eacute; con&ccedil;us pour r&eacute;duire ces besoins en 
op&eacute;rations de type <code class="literal">x[y]</code>. Pour simplifier, disons que 
sans soutien mat&eacute;riel, le meilleur est encore tr&egrave;s certainement de 
d&eacute;velopper des algorithmes SWAR en consid&eacute;rant <code class="literal">x[y]</code> 
comme &eacute;tant interdit, ou au moins tr&egrave;s co&ucirc;teux.

</p></div><div class="sect3" lang="fr"><div class="titlepage"><div><div><h4 class="title"><a name="N113BD"></a>4.2.4.&nbsp;Op&eacute;rations r&eacute;currentes (r&eacute;ductions, balayages, et c&aelig;tera)</h4></div></div></div><p>
Une r&eacute;currence est un calcul dans lequel il existe une relation
s&eacute;quentielle apparente entre les valeurs &agrave; traiter. Si toutefois des
op&eacute;rations associatives sont impliqu&eacute;es dans ces r&eacute;currences, il peut
&ecirc;tre possible de recoder ces calculs en utilisant un algorithme
parall&egrave;le &agrave; structure organis&eacute;e en arbre.
</p><p>
Le type de r&eacute;currence parall&eacute;lisable le plus courant est probablement
la classe connue sous le nom de r&eacute;duction associative. Par exemple,
pour calculer la somme des valeurs d'un vecteur, on &eacute;crit du code C
purement s&eacute;quentiel, comme&nbsp;:
</p><p>

<pre class="programlisting">
t = 0;
for (i=0; i&lt;MAX; ++i) t += x[i];
</pre>

</p><p>

Cependant, l'ordre des additions est rarement important. Les op&eacute;rations
en virgule flottante et les saturations peuvent rendre diff&eacute;rents r&eacute;sultats
si l'ordre des additions est modifi&eacute;, mais les additions ordinaires sur les
entiers (et qui reviennent au d&eacute;but apr&egrave;s avoir atteint la valeur maximum)
renverront exactement les m&ecirc;mes r&eacute;sultats, ind&eacute;pendemment de l'ordre dans
lequel elles sont effectu&eacute;es. Nous pouvons ainsi r&eacute;&eacute;crire cette s&eacute;quence
sous la forme d'une somme parall&egrave;le structur&eacute;e en arbre, dans laquelle nous
additionnons d'abord des paires de valeurs, puis des paires de ces sous-totaux,
et ainsi de suite jusqu'&agrave; l'unique somme finale. Pour un vecteur de quatre
valeurs 8 bits, seulement deux additions sont n&eacute;cessaires. La premi&egrave;re effectue
deux additions de 8 bits et retourne en r&eacute;sultat deux champs de 16 bits,
contenant chacun un r&eacute;sultat sur 9 bits&nbsp;:

</p><p>

<pre class="programlisting">
t = ((x &amp; 0x00ff00ff) + ((x &gt;&gt; 8) &amp; 0x00ff00ff));
</pre>

</p><p>
La deuxi&egrave;me fait la somme de ces deux valeurs de 9 bits dans
un champs de 16 bits, et renvoie un r&eacute;sultat sur 10 bits&nbsp;:
</p><p>

<pre class="programlisting">
((t + (t &gt;&gt; 16)) &amp; 0x000003ff)
</pre>

</p><p>

En r&eacute;alit&eacute;, la seconde op&eacute;ration effectue l'addition de deux champs de 
16 bits&hellip; mais les 16 bits de poids fort n'ont pas de 
signification. C'est pourquoi le r&eacute;sultat est masqu&eacute; en une valeur de 10 
bits.

</p><p>

Les balayages (&laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">scans</em></span></span>&nbsp;&raquo;), 
aussi connus sous le nom d'op&eacute;rations &laquo;&nbsp;<span class="quote">&agrave; pr&eacute;fixe parall&egrave;le</span>&nbsp;&raquo; 
sont un peu plus difficile &agrave; mettre en &#339;uvre efficacement. Ceci 
est d&ucirc; au fait que contrairement aux r&eacute;ductions, les balayages peuvent 
produire des r&eacute;sultats partitionn&eacute;es.

</p></div></div><div class="sect2" lang="fr"><div class="titlepage"><div><div><h3 class="title"><a name="N113E5"></a>4.3.&nbsp;SWAR MMX sous Linux</h3></div></div></div><p>

Pour Linux, nous nous soucierons principalement des processeurs IA32. La 
bonne nouvelle, c'est qu'AMD, Cyrix et Intel impl&eacute;mentent tous le m&ecirc;me 
jeu d'instructions MMX. En revanche, les performances de ces diff&eacute;rents 
MMX sont variables. Le K6, par exemple, n'est dot&eacute; que d'un seul 
<span class="foreignphrase"><em class="foreignphrase">pipeline</em></span> l&agrave; o&ugrave; le Pentium MMX en a deux. 
La seule nouvelle vraiment mauvaise, c'est qu'Intel diffuse toujours ces 
stupides films publicitaires sur le MMX&hellip; ;-)

</p><p>

Il existe trois approches s&eacute;rieuses du SWAR par le MMX&nbsp;:

</p><div class="orderedlist"><ol type="1"><li><p>

L'utilisation des fonctions d'une biblioth&egrave;que d&eacute;di&eacute;e au MMX. Intel, en 
particulier, a d&eacute;velopp&eacute; plusieurs &laquo;&nbsp;<span class="quote">biblioth&egrave;ques de 
performances</span>&nbsp;&raquo;, offrant toute une gamme de fonctions optimis&eacute;es &agrave; 
la main et destin&eacute;es aux t&acirc;ches multim&eacute;dia courantes. Avec un petit 
effort, bon nombre d'algorithmes non-multim&eacute;dia peuvent &ecirc;tre 
retravaill&eacute;s pour permettre &agrave; quelques unes des zones de calcul intensif 
de s'appuyer sur une ou plusieurs de ces biblioth&egrave;ques. Ces 
biblioth&egrave;ques ne sont pas disponibles sous Linux, mais pourraient &ecirc;tre 
adapt&eacute;es pour le devenir.

</p></li><li><p>

L'utilisation directe des instructions MMX. C'est assez compliqu&eacute;, pour
deux raisons&nbsp;: D'abord, le MMX peut ne pas &ecirc;tre disponible sur le processeur
concern&eacute;, ce qui oblige &agrave; fournir une alternative. Ensuite, l'assembleur IA32
utilis&eacute; en g&eacute;n&eacute;ral sous Linux ne reconna&icirc;t actuellement pas les instructions
MMX.

</p></li><li><p>

L'utilisation d'un langage de haut niveau ou d'un module du 
compilateur qui puisse directement g&eacute;n&eacute;rer des instructions MMX 
appropri&eacute;es. Quelques outils de ce type sont actuellement en cours de 
d&eacute;veloppement, mais aucun n'est encore pleinement fonctionnel sous 
Linux. Par exemple, &agrave; l'Universit&eacute; de Purdue (<a href="http://dynamo.ecn.purdue.edu/~hankd/SWAR/" target="_top">http://dynamo.ecn.purdue.edu/~hankd/SWAR/</a>), nous d&eacute;veloppons 
actuellement un compilateur qui admettra des fonctions &eacute;crites dans un 
&laquo;&nbsp;<span class="quote">dialecte</span>&nbsp;&raquo; explicite parall&egrave;le au langage C et qui g&eacute;n&eacute;rera 
des modules SWAR accessibles comme des fonctions C ordinaires, et qui 
feront usage de tous les supports SWAR disponibles, y compris le MMX. 
Les premiers prototypes de compilateurs &agrave; modules ont &eacute;t&eacute; g&eacute;n&eacute;r&eacute;s &agrave; 
Fall, en 1996. Amener cette technologie vers un &eacute;tat r&eacute;ellement 
utilisable prend cependant beaucoup plus de temps que pr&eacute;vu 
initialement.

</p></li></ol></div><p>
En r&eacute;sum&eacute;, le SWAR par MMX est toujours d'un usage malais&eacute;. Toutefois, avec
quelques efforts suppl&eacute;mentaires, la seconde approche d&eacute;crite ci-dessus peut
&ecirc;tre utilis&eacute;e d&egrave;s &agrave; pr&eacute;sent. En voici les bases&nbsp;:
</p><p>

<div class="orderedlist"><ol type="1"><li><p>
Vous ne pourrez pas utiliser le MMX si votre processeur ne le prend pas
en charge. Le code GCC suivant vous permettra de savoir si votre processeur
est &eacute;quip&eacute; de l'extension MMX. Si c'est le cas, la valeur renvoy&eacute;e sera
diff&eacute;rente de z&eacute;ro, sinon nulle.

<pre class="programlisting">
inline extern
int mmx_init(void)
{
	int mmx_disponible;

	__asm__ __volatile__ (
		/* R&eacute;cup&egrave;re la version du CPU */
		"movl $1, %%eax\n\t"
		"cpuid\n\t"
		"andl $0x800000, %%edx\n\t"
		"movl %%edx, %0"
		: "=q" (mmx_disponible)
		: /* pas d'entr&eacute;e */
	);
	return mmx_disponible;
}
</pre>

</p></li><li><p>
Un registre MMX contient essentiellement ce que GCC appellerait un
<code class="literal">unsigned long long</code>. Ainsi, ce sont des variables
de ce type et r&eacute;sidant en m&eacute;moire qui vont former le m&eacute;canisme de communication
entre les modules MMX et le programme C qui les appelle. Vous pouvez aussi d&eacute;clarer
vos donn&eacute;es MMX comme &eacute;tant des structures de donn&eacute;es align&eacute;es sur des adresses
multiples de 64 bits (il convient de garantir l'alignement sur 64 bits en d&eacute;clarant
votre type de donn&eacute;es comme &eacute;tant membre d'une <code class="literal">union</code>
comportant un champ <code class="literal">unsigned long long</code>).
</p></li><li><p>
Si le MMX est disponible, vous pouvez &eacute;crire votre code MMX en utilisant
la directive assemble <code class="literal">.byte</code> pour coder chaque
instruction. C'est un travail p&eacute;nible s'il est abattu &agrave; la main, mais pour un
compilateur, les g&eacute;n&eacute;rer n'est pas tr&egrave;s difficile. Par exemple, l'instruction MMX
<code class="literal">PADDB MM0,MM1</code> pourrait &ecirc;tre encod&eacute;e par la ligne
d'assembleur in-line GCC suivante&nbsp;:


<pre class="programlisting">
__asm__ __volatile__ (".byte 0x0f, 0xfc, 0xc1\n\t");
</pre>


Souvenez-vous que le MMX utilise une partie du mat&eacute;riel destin&eacute; aux op&eacute;rations
en virgule flottante, et donc que le code ordinaire m&eacute;lang&eacute; au MMX ne doit pas
invoquer ces derni&egrave;res. La pile en virgule flottante doit &eacute;galement &ecirc;tre vide
avant l'ex&eacute;cution de tout code MMX. Cette pile est normalement vide &agrave; l'entr&eacute;e
d'une fonction C ne faisant pas usage de la virgule flottante.


</p></li><li><p>
Cl&ocirc;turez votre code MMX par l'ex&eacute;cution de l'instruction <code class="literal">EMMS</code>,
qui peut &ecirc;tre encod&eacute;e par&nbsp;:


<pre class="programlisting">
__asm__ __volatile__ (".byte 0x0f, 0x77\n\t");
</pre>

</p></li></ol></div>

</p><p>
Tout ce qui pr&eacute;c&egrave;de para&icirc;t r&eacute;barbatif, et l'est. Ceci dit, le MMX
est encore assez jeune&hellip; de futures versions de ce document proposeront
de meilleures techniques pour programmer le SWAR MMX.
</p></div><div class="footnotes"><br><hr align="left" width="100"><div class="footnote"><p><sup>[<a href="#N111BA" name="ftn.N111BA">23</a>] </sup>

N.D.T.&nbsp;: initialement, huit liens &eacute;taient propos&eacute;s &agrave; cet endroit, 
devenus pour la plupart obsol&egrave;tes.

</p></div><div class="footnote"><p><sup>[<a href="#N111F5" name="ftn.N111F5">24</a>] </sup>

N.D.T.&nbsp;: FLOP = &laquo;&nbsp;<span class="quote"><span class="foreignphrase"><em class="foreignphrase">FLoating point 
OPeration</em></span></span>&nbsp;&raquo;, ou &laquo;&nbsp;<span class="quote">OP&eacute;ration en Virgule 
Flottante</span>&nbsp;&raquo;.

</p></div><div class="footnote"><p><sup>[<a href="#N11385" name="ftn.N11385">25</a>] </sup>

N.D.T.&nbsp;: il s'agit en fait de simuler la r&eacute;-entr&eacute;e par le cot&eacute; 
oppos&eacute; des donn&eacute;es &eacute;ject&eacute;es par le d&eacute;calage. L'exemple qui suit permet 
d'impl&eacute;menter ce cas en langage C, mais la plupart des microprocesseurs 
sont &eacute;quip&eacute;s d'instructions de rotation effectuant cela en une seule 
op&eacute;ration.

</p></div></div></div><div class="navfooter"><hr><table summary="Navigation footer" width="100%"><tr><td align="left" width="40%"><a accesskey="p" href="ar01s03.html">Pr&eacute;c&eacute;dent</a>&nbsp;</td><td align="center" width="20%">&nbsp;</td><td align="right" width="40%">&nbsp;<a accesskey="n" href="ar01s05.html">Suivant</a></td></tr><tr><td valign="top" align="left" width="40%">3.&nbsp;<span class="foreignphrase"><em class="foreignphrase">Clusters</em></span> de syst&egrave;mes Linux&nbsp;</td><td align="center" width="20%"><a accesskey="h" href="index.html">Sommaire</a></td><td valign="top" align="right" width="40%">&nbsp;5.&nbsp;Processeurs auxiliaires des machines Linux</td></tr></table></div></body></html>