|
|
<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äßt sich dann z.B. so formulieren:</P>
|
|
|
|
|
|
<UL>
|
|
|
<LI>Eine Bitmap mit 24-Bit Farbtiefe (8 Bit Rot, 8 Bit Grü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ührt werden.</LI>
|
|
|
</UL>
|
|
|
|
|
|
<P>Die Realisierung durch eine MMX-Funktion soll dabei mit verschiedenen Funktionen
|
|
|
(Assembler und C, ohne MMX) bezüglich der Laufzeit verglichen werden. So kann
|
|
|
festgestellt werden, ob die MMX-Routine tatsä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önnen,
|
|
|
wird eine Methode zur Zeitmessung benötigt. Dabei fiel hier die Wahl auf
|
|
|
den sogenannten Time Stamp Counter der PENTIUM-CPU. Dieser Counter ist 64-Bit
|
|
|
breit und zählt die Taktzyklen (!) die seit dem letzten Reset vergangen sind.</P>
|
|
|
|
|
|
<P>Auf diese Weise ist eine genaue Messung des Zeitverhaltens mö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, übergibt
|
|
|
niederwertigen 32-Bit Anteil im Register EAX und höherwertigen 32-Bit Anteil
|
|
|
im Register EDX) und speichert die so ermittelten Werte in globalen Variablen.
|
|
|
Zusätzlich werden ü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ß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ö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ät besitzen
|
|
|
(Addition eines Wertes auf einen bestimmten Speicherbereich). Die einzelnen Funktionen
|
|
|
wurden jedoch unterschiedlich realisiert.</P>
|
|
|
|
|
|
<P>Die "Bitmap" 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 ü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 übergeben:</P>
|
|
|
|
|
|
<PRE>
|
|
|
char* buffer - Zeiger auf den zu bearbeiteten Speicherbereich
|
|
|
int xsize - Breite des Buffers
|
|
|
int ysize - Hö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<ysize; j++)
|
|
|
{
|
|
|
for (i=0; i<xsize; i++)
|
|
|
{
|
|
|
*(buffer+ j*xsize+ i)+= add;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</PRE></FONT>
|
|
|
|
|
|
<P>Die Funktion <I>c_version1</I> addiert den übergebenen Wert auf alle
|
|
|
Bytes des übergebenen Speicherbereichs, indem f<>r jeden Index i und j erneut
|
|
|
der Offset im Speicherbereich berechnet wird und anschließend der zu addierende
|
|
|
Wert addiert wird. Die ständige Neuberechnung des Offsets kostet unö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<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ö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<xsize*ysize / 4; i++)
|
|
|
{
|
|
|
*( (unsigned int*) buffer )+= add;
|
|
|
buffer+= 4;
|
|
|
}
|
|
|
}
|
|
|
</PRE></FONT>
|
|
|
|
|
|
<P>Dabei sollte beachtet werden, daß diese Vorgehensweise hier nur mö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ür, daß der in ECX übergebene
|
|
|
Additionswert (32-Bit) auf alle gepackten Bytedaten aufgeteilt wird. Dazu wird
|
|
|
zunächst der 32-Bit Wert in ECX nach MM1 geladen. Anschließ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ächstes muß die Anzahl 8-Byte Blöcke ermittelt
|
|
|
werden, die bearbeitet werden sollen. Dazu wird die Anzahl einzelner Bytes ermittelt,
|
|
|
indem Breite und Hö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ßend das Ergebnis in den Speicher
|
|
|
zurü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ü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ß bei diesem Anwendungsfall (Addition ohne Saturation)
|
|
|
die MMX-Routine nur unwesentlich schneller arbeitet als eine ähnliche Funktion
|
|
|
ohne MMX !</P>
|
|
|
|
|
|
<P><B>Anmerkung:</B> Bei Auswertung der obigen Tabelle ist zu beachten, daß die
|
|
|
absoluten Werte von mehreren Faktoren abhängig sind (z.B. der Taktrate des Prozessors)
|
|
|
und auf anderen Computersystemen zu anderen Ergebnissen führen können. Die
|
|
|
relativen Unterschiede zwischen den Funktionen kö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ä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ür
|
|
|
die Anzeige vorausgesetzt). Da die Routinen ohne Saturation funktionieren, können so
|
|
|
gut Fehler erkannt werden, die durch Überlä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ührt wird und damit Fehler beim
|
|
|
Aufhellen der Bitmap verhindert werden. F<>r die MMX-Version heißt das, daß jetzt PADDUSB
|
|
|
verwendet werden muß. Die C-Versionen 1 und 2, sowie die Assembler-Version 1
|
|
|
müssen jetzt für jedes zu bearbeitende Byte überprüfen, ob nach der
|
|
|
Addition das Byte den Wertebereich verlassen hat ( > 255 ). Falls ja, so muß das
|
|
|
Byte auf den Wert 255 beschränkt werden.</P>
|
|
|
|
|
|
<P>Da bei der konventionellen SIMD-Technik (also ohne MMX) keine einfache
|
|
|
Überprüfung eines solchen Überlaufs möglich ist, muß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
|
|
|
"Aufhellung" 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ü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äge zu dieser Webseite, bitte per email an:
|
|
|
<A HREF="mailto:hohmuth@t-online.de">
|
|
|
<B>hohmuth@t-online.de</B>
|
|
|
</A>
|
|
|
<BR>
|
|
|
Letzte Änderungen am: 02.01.1999, Jens Hohmuth.
|
|
|
</ADDRESS>
|
|
|
</BODY>
|
|
|
</HTML>
|