You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
15 KiB
HTML

<HTML>
<HEAD>
<TITLE>MMX Erweiterung -- Anwendungsbeispiele</TITLE>
<SCRIPT LANGUAGE="Javascript">
<!-- Test Browser Version
// Javascript written 1998 by Jens Hohmuth (Script-Version: 3.0)
// (c) Copyright 1998 by Jens Hohmuth@fh-zwickau.de. All rights reserved.
var browserok= 0;
var version= parseInt( navigator.appVersion );
if( version >= 3 ) browserok= 1;
if( document.images ) browserok= 1;
if( browserok )
{
var imgdata_touched = new Array();
var imgdata_released= new Array();
}
// load imagedata
function precache( name, pic1, pic2 )
{
if( browserok )
{
imgdata_touched [ name ]= new Image();
imgdata_touched [ name ].src= pic2;
imgdata_released[ name ]= new Image();
imgdata_released[ name ].src= pic1;
}
}
// "Release", called if mouse left button
function release( name )
{
if( browserok )
{
document.images[ name ].src= imgdata_released[ name ].src;
}
}
// "Touch", called if mouse over link-button
function touch( name )
{
if( browserok )
{
document.images[ name ].src= imgdata_touched[ name ].src;
}
}
// load (precache) Buttons
if( browserok )
{
precache( "left", "../images/arrowl.gif", "../images/arrowlp.gif" );
precache( "right", "../images/arrowr.gif", "../images/arrowrp.gif" );
precache( "index", "../images/index.gif", "../images/indexp.gif" );
}
//-->
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#C0C0C0" BACKGROUND="../images/stone.jpg">
<H1><FONT COLOR="#800000">5 Anwendungsbeispiele</FONT></H1>
<H2><FONT COLOR="#000080">Aufhellen einer Bitmap</FONT></H2>
<P>Im folgenden soll die MMX-Erweiterung an einem konkreten Beispiel eingesetzt
werden. Dabei soll hier das Aufhellen einer Bitmap, wie bereits im Abschnitt 1
kurz angesprochen wurde, betrachtet werden. Die Aufgabe, die das Beispielprogramm
zu erf<72>llen hat, l&auml;&szlig;t sich dann z.B. so formulieren:</P>
<UL>
<LI>Eine Bitmap mit 24-Bit Farbtiefe (8 Bit Rot, 8 Bit Gr&uuml;n und 8 Bit Blau) soll
aufgehellt werden, indem zu jedem Farbanteil ein konstanter Wert addiert wird.</LI>
<LI>Diese Addition soll einmal mit unsigned Saturation erfolgen und zum Vergleich
einmal ohne Saturation durchgef&uuml;hrt werden.</LI>
</UL>
<P>Die Realisierung durch eine MMX-Funktion soll dabei mit verschiedenen Funktionen
(Assembler und C, ohne MMX) bez&uuml;glich der Laufzeit verglichen werden. So kann
festgestellt werden, ob die MMX-Routine tats&auml;chlich effektiver arbeitet als
konventionelle Funktionen.</P>
<H2><FONT COLOR="#000080">Der Time Stamp Counter (TSC)</FONT></H2>
<P>Um das Laufzeitverhalten der einzelnen Routinen vergleichen zu k&ouml;nnen,
wird eine Methode zur Zeitmessung ben&ouml;tigt. Dabei fiel hier die Wahl auf
den sogenannten Time Stamp Counter der PENTIUM-CPU. Dieser Counter ist 64-Bit
breit und z&auml;hlt die Taktzyklen (!) die seit dem letzten Reset vergangen sind.</P>
<P>Auf diese Weise ist eine genaue Messung des Zeitverhaltens m&ouml;glich. Die in der
Datei <A HREF="../code/timing.h">timing.h</A> und <A HREF="../code/timing.c">timing.c</A>
bereitgestellten Funktionen dienen dem Zugriff auf diesen Counter. Im einzelnen stehen
die folgenden Funktionen bereit:</P>
<FONT COLOR="#0000FF"><PRE> void tstart (void);</PRE></FONT>
<P>Liest den aktuellen Inhalt des TSC aus (Assemblerbefehl RDTSC, &uuml;bergibt
niederwertigen 32-Bit Anteil im Register EAX und h&ouml;herwertigen 32-Bit Anteil
im Register EDX) und speichert die so ermittelten Werte in globalen Variablen.
Zus&auml;tzlich werden &uuml;ber den Assemblerbefehl CLI die Interrupts verboten,
um noch genauere Me<4D>werte zu erhalten.</P>
<FONT COLOR="0000FF"><PRE> void tstop (void);</PRE></FONT>
<P>Liest wiederum den aktuellen Inhalt des TSC aus und ermittelt die Differenz zur
ersten Me&szlig;ung (tstart). Die Interrupts werden durch diese Funktion wieder
zugelassen.</P>
<FONT COLOR="0000FF"><PRE> unsigned int tgetlow (void);</PRE></FONT>
<P>Diese Funktion ermittelt die niederwertigen 32-Bit der durch tstop berechneten
TSC-Differenz.</P>
<FONT COLOR="0000FF"><PRE> unsigned int tgethigh (void);</PRE></FONT>
<P>Diese Funktion ermittelt die h&ouml;herwertigen 32-Bit der durch tstop berechneten
TSC-Differenz.</P>
<H2><FONT COLOR="#000080">Beispiel 1: Addition ohne Saturation</FONT></H2>
<P>Das Beispielprogramm <A HREF="../code/test1.c">test1.c</A> stellt insgesamt 6
Funktionen bereit, die alle die gleiche Funktionalit&auml;t besitzen
(Addition eines Wertes auf einen bestimmten Speicherbereich). Die einzelnen Funktionen
wurden jedoch unterschiedlich realisiert.</P>
<P>Die &quot;Bitmap&quot; entspricht im Beispielprogramm <I>test1.c</I>
einfach einem allokierten Speicherbereich. Das Programm <I>test1.c</I> ruft jetzt
nacheinander die einzelnen Funktionen auf und ermittelt &uuml;ber die obengenannten
Funktionen aus <A HREF="../code/timing.c">timing.c</A> die Laufzeiten der Funktionen.</P>
<P>Den in <I>test1.c</I> definierten Funktionen werden dabei die folgenden
Parameter &uuml;bergeben:</P>
<PRE>
char* buffer - Zeiger auf den zu bearbeiteten Speicherbereich
int xsize - Breite des Buffers
int ysize - H&ouml;he des Buffers
char add - der Wert, der zu den Bufferbytes addiert werden soll
</PRE>
<P>Die folgenden Funktionen sind in <I>test1.c</I> definiert:</P>
<FONT COLOR="0000FF"><PRE>
// C Version Nr. 1
void c_version1 ( char *buffer, int xsize, int ysize, char add )
{
int i, j;
for (j=0; j&lt;ysize; j++)
{
for (i=0; i&lt;xsize; i++)
{
*(buffer+ j*xsize+ i)+= add;
}
}
}
</PRE></FONT>
<P>Die Funktion <I>c_version1</I> addiert den &uuml;bergebenen Wert auf alle
Bytes des &uuml;bergebenen Speicherbereichs, indem f<>r jeden Index i und j erneut
der Offset im Speicherbereich berechnet wird und anschlie&szlig;end der zu addierende
Wert addiert wird. Die st&auml;ndige Neuberechnung des Offsets kostet un&ouml;tig Zeit
und kann z.B. durch Zeigerarithmetik verhindert werden.</P>
<P>Dies wird in der zweiten C-Version verwendet.</P>
<FONT COLOR="0000FF"><PRE>
// C Version Nr. 2
void c_version2 ( char *buffer, int xsize, int ysize, char add )
{
register int i;
for (i=0; i&lt;xsize*ysize; i++)
{
*( buffer )+= add;
buffer++;
}
}
</PRE></FONT>
<P>Aber auch diese Funktion kann noch optimiert werden, denn auch ohne MMX kann die
SIMD-Methode angewendet werden. Anstatt immer nur ein Byte zu addieren, k&ouml;nnen
auch gleichzeitig 4 Byte (also 32-Bit = Integer) addiert werden.</P>
<P>Funktion <I>c_version3</I> implementiert diese Vorgehensweise.</P>
<FONT COLOR="0000FF"><PRE>
// C Version Nr. 3
void c_version3 ( char *buffer, int xsize, int ysize, unsigned int add )
{
register int i;
for (i=0; i&lt;xsize*ysize / 4; i++)
{
*( (unsigned int*) buffer )+= add;
buffer+= 4;
}
}
</PRE></FONT>
<P>Dabei sollte beachtet werden, da&szlig; diese Vorgehensweise hier nur m&ouml;glich ist,
weil ohne Saturation gearbeitet wird.</P>
<P>Zum Vergleich wurden die C-Versionen 2 und 3 in Assembler umgewandelt:</P>
<FONT COLOR="0000FF"><PRE>
// ASM Version Nr. 1
// (besitzt gleiche Funktionalit<69>t wie C Version Nr. 2)
void asm_version1 ( char *buffer, int xsize, int ysize, int add );
#pragma aux asm_version1= \
" imul ecx,eax "\
" "\
"inc_loop: "\
" add byte ptr [edi],bl "\
" inc edi "\
" dec ecx "\
" jnz inc_loop "\
parm [edi] [ecx] [eax] [ebx] modify [edi ecx eax ebx];
</PRE></FONT>
<FONT COLOR="0000FF"><PRE>
// ASM Version Nr. 2
// (besitzt gleiche Funktionalit<69>t wie C Version Nr. 3)
void asm_version2 ( char *buffer, int xsize, int ysize, int add );
#pragma aux asm_version2= \
" imul ecx,eax "\
" shr ecx,2 "\
" "\
"inc_loop: "\
" add dword ptr [edi],ebx "\
" add edi,4 "\
" dec ecx "\
" jnz inc_loop "\
parm [edi] [ecx] [eax] [ebx] modify [edi ecx eax ebx];
</PRE></FONT>
<P>Im folgenden soll die eigentliche MMX-Routinen vorgestellt werden.</P>
<FONT COLOR="0000FF"><PRE>
// MMX Version Nr. 1 (im Modul <A HREF="../code/mmx1.asm">mmx1.asm</A> definiert)
MOVD MM1,ecx ; Additionswert nach MM1
MOVQ MM0,MM1 ; alle gepackten Bytes nach MM1
PSLLQ MM1,32
PADDD MM1,MM0 ; alle gepackten Bytes in MM1
; besitzen Wert aus ecx
imul eax,ebx
xchg ecx,eax
shr ecx,3 ; wir bearbeiten jeweils 8 Byte !
mbadd_loop:
MOVQ MM0,[esi] ; 64-Bit nach MM0
PADDB MM0,MM1 ; 64-Bit gepackte Daten mit
; Unsigned Saturation addieren
MOVQ [esi],MM0 ; Ergebnis zur<75>ck
add esi,8 ; 64-Bit (= 8 Byte) weiter
dec ecx
jnz mbadd_loop
</PRE></FONT>
<P>Der erste Teil der Funktion sorgt daf&uuml;r, da&szlig; der in ECX &uuml;bergebene
Additionswert (32-Bit) auf alle gepackten Bytedaten aufgeteilt wird. Dazu wird
zun&auml;chst der 32-Bit Wert in ECX nach MM1 geladen. Anschlie&szlig;end wird der Wert
nach MM0 kopiert und 32-Bit nach links verschoben. Nach der Addition von MM1 und MM0
befindet sich der korrekte Additionswert als 8 gepackte Byte im Register MM1.</P>
<P>Als n&auml;chstes mu&szlig; die Anzahl 8-Byte Bl&ouml;cke ermittelt
werden, die bearbeitet werden sollen. Dazu wird die Anzahl einzelner Bytes ermittelt,
indem Breite und H&ouml;he des zu bearbeitenden Bereiches multipliziert werden und der so
ermittelte Wert durch 8 geteilt wird.</P>
<P>In der Hauptschleife werden jetzt jeweils 64-Bit nach MM0 geladen, mit dem Befehl
PADDB 8 Byte gepackte Daten addiert und anschlie&szlig;end das Ergebnis in den Speicher
zur&uuml;ckgeschrieben.</P>
<P>Das Programm <A HREF="../code/test1.c">test1.c</A> ruft nun alle Routinen auf und gibt
die ermittelten Laufzeiten auf dem Bildschirm aus. Auf einem PENTIUM 166 Mhz ergaben sich
z.B. die folgenden Werte:</P>
<P>Werte f&uuml;r 921600 bearbeitete Bytes:</P>
<PRE>
Funktion Counter-High Counter-Low
----------------------------------------------------
C Version 1, Taktzyklen= 0 24210951
C Version 2, Taktzyklen= 0 23084943
C Version 3, Taktzyklen= 0 8177420
ASM Version 1, Taktzyklen= 0 5440261
ASM Version 2, Taktzyklen= 0 3087126
MMX Version 1, Taktzyklen= 0 3055348
</PRE>
<P>Es zeigt sich also, da&szlig; bei diesem Anwendungsfall (Addition ohne Saturation)
die MMX-Routine nur unwesentlich schneller arbeitet als eine &auml;hnliche Funktion
ohne MMX !</P>
<P><B>Anmerkung:</B> Bei Auswertung der obigen Tabelle ist zu beachten, da&szlig; die
absoluten Werte von mehreren Faktoren abh&auml;ngig sind (z.B. der Taktrate des Prozessors)
und auf anderen Computersystemen zu anderen Ergebnissen f&uuml;hren k&ouml;nnen. Die
relativen Unterschiede zwischen den Funktionen k&ouml;nnen jedoch (auf dem gleichen
Computersystem !) zur Laufzeitanalyse herangezogen werden.<P>
<P>Beispielprogramm <A HREF="../code/test1vis.c">test1vis.c</A> funktioniert nach dem
gleichen Prinzip. Es l&auml;dt jedoch vor der Bearbeitung ein 24-Bit Bild im TGA-Format.
Die oben beschriebenen Routinen arbeiten dann immer mit diesem Bild. Das Ergebnis der
jeweiligen Routine wird dann auf dem Bildschirm angezeigt (Achtung: VESA 2.0 wird f&uuml;r
die Anzeige vorausgesetzt). Da die Routinen ohne Saturation funktionieren, k&ouml;nnen so
gut Fehler erkannt werden, die durch &Uuml;berl&auml;ufe der einzelnen 8-Bit breiten
Farbkomponenten entstehen.</P>
<H2><FONT COLOR="#000080">Beispiel 2: Addition mit unsigned Saturation</FONT></H2>
<P>Beispielprogramm <A HREF="../code/test2.c">test2.c</A> arbeitet nach dem gleichen
Prinzip wie <A HREF="../code/test1.c">test1.c</A>. Der einzige Unterschied besteht in
der Addition, die mit unsigned Saturation durchgef&uuml;hrt wird und damit Fehler beim
Aufhellen der Bitmap verhindert werden. F<>r die MMX-Version hei&szlig;t das, da&szlig; jetzt PADDUSB
verwendet werden mu&szlig;. Die C-Versionen 1 und 2, sowie die Assembler-Version 1
m&uuml;ssen jetzt f&uuml;r jedes zu bearbeitende Byte &uuml;berpr&uuml;fen, ob nach der
Addition das Byte den Wertebereich verlassen hat ( &gt; 255 ). Falls ja, so mu&szlig; das
Byte auf den Wert 255 beschr&auml;nkt werden.</P>
<P>Da bei der konventionellen SIMD-Technik (also ohne MMX) keine einfache
&Uuml;berpr&uuml;fung eines solchen &Uuml;berlaufs m&ouml;glich ist, mu&szlig;ten diese
Routinen (C-Version 3 und Assembler-Version 2) im Beispielprogramm <I>test2.c</I> entfallen.</P>
<P>Das Beispielprogramm <I>test2.c</I> liefert auf dem Testrechner die folgenden Werte:</P>
<PRE>
Werte f<>r 921600 bearbeitete Bytes:
Funktion Counter-High Counter-Low
----------------------------------------------------
C Version 1, Taktzyklen= 0 40265287
C Version 2, Taktzyklen= 0 27548391
ASM Version 1, Taktzyklen= 0 7133678
MMX Version 1, Taktzyklen= 0 3055361
</PRE>
<P>Dieses Beispiel zeigt die Verbesserung, die durch die MMX-Routine erreicht wurde
(etwa 2.3 mal schneller als die entsprechende Assembler-Version).</P>
<P>Das Beispielprogramm <A HREF="../code/test2vis.c">test2vis.c</A> zeigt die aufgehellten
Ergebnisse am Beispiel einer Bitmap (Vgl. <A HREF="../code/test1vis.c">test1vis.c</A>).
Da dieses Mal mit Saturation gearbeitet wurde, entspricht das Ergebnis wohl eher einer
&quot;Aufhellung&quot; als das Ergebnis von Beispielprogramm
<A HREF="../code/test1vis.c">test1vis.c</A>.</P>
<P>Alle hier vorgestellten Beispielprogramme stehen als selbstentpackendes RAR-Archiv
<A HREF="../download/_bitmap.exe">_bitmap.exe</A> zum Download zur Verf&uuml;gung.</P>
<!-- wie gehts weiter ------------------------------------------------------>
<P><HR SIZE=2></P>
<A HREF="mmx_11.htm" onMouseOver="touch( 'left' );" onMouseOut="release( 'left' );">
<IMG SRC="../images/arrowl.gif" NAME="left" ALT="zurueck" BORDER=0 ALIGN=LEFT></A>
<A HREF="mmx_13.htm" onMouseOver="touch( 'right' );" onMouseOut="release( 'right' );">
<IMG SRC="../images/arrowr.gif" NAME="right" ALT="weiter" BORDER=0 ALIGN=RIGHT></A>
<CENTER>
<A HREF="index.htm#Anwendung" onMouseOver="touch( 'index' );" onMouseOut="release( 'index' );">
<IMG SRC = "../images/index.gif" NAME="index" ALT="Zum Index" BORDER=0></A>
</CENTER>
<BR>
<!-- Adresse --------------------------------------------------------------->
<HR SIZE=2>
<ADDRESS>
Probleme oder Vorschl&auml;ge zu dieser Webseite, bitte per email an:
<A HREF="mailto:hohmuth@t-online.de">
<B>hohmuth@t-online.de</B>
</A>
<BR>
Letzte &Auml;nderungen am: 02.01.1999, Jens Hohmuth.
</ADDRESS>
</BODY>
</HTML>