Arquitectura de Computadores AP-1/1 Apéndice-1. Vectorización de programas utilizando MMX Domingo Benítez Universidad de Las Palmas de Gran Canaria, 2008 1. DESCRIPCIÓN DEL ALGORITMO “SELECCIÓN CONDICIONAL”.........................................................1 2. IMPLEMENTACIÓN C++..................................................................................................................2 3. EJECUCIÓN Y ANÁLISIS DE PRESTACIONES .....................................................................................6 4. PRUÉBELO USTED MISMO ...............................................................................................................7 BIBLIOGRAFÍA ...................................................................................................................................7 AGRADECIMIENTOS ...........................................................................................................................7 En este Apéndice se describe la implementación del algoritmo de Selección Condicional utilizando dos versiones de código: una denominada “Convencional” y otra denominada “Vectorial”. En primer lugar se describe el algoritmo, en un segundo apartado se describe el programa principal en el que se incluyen dos procedimientos que utilizan selectivamente instrucciones vectoriales MMX, y por último se muestra los resultados de la ejecución del programa. 1. Descripción del algoritmo “Selección Condicional” El objetivo del algoritmo de Selección Condicional consiste en procesar tres imágenes denominadas “croma”, “fondo” y “monigote”, para superponer la figura que se encuentra dentro de la imagen “monigote” sobre la imagen “fondo” superponiendo el personaje (ver Figura 1). El color que se muestra como fondo de la imagen “monigote” desaparece en la superposición. El algoritmo se divide en cuatro etapas o pasos (ver Figura 2). En un primer paso se comparan píxel-á-píxel a través de una función XNOR los píxel de la imagen “monigote” con los de la imagen “croma” y se obtiene una nueva imagen donde los píxeles del personaje se inicializan a 0. En un segundo paso se realiza la función Y-lógica píxel-á-píxel de los píxeles de la imagen “fondo” con los píxel de la imagen resultado del Paso 1. En un tercer paso algorítmico, se invierten los píxeles de la imagen resultado del Paso 1 y la imagen “monigote” original. Y por último, en el Paso 4 se realiza la función O-lógica entre los resultados de los Pasos 2 y 3, para dar el resultado final. Figura 1. Objetivo del algoritmo de Selección Condicional. Arquitectura de Computadores AP-1/2 PASO 3 PASO 1 Color de fondo Personaje COMPARACIÓN Personaje Y-lógica PASO 4 PASO 2 Escenario O-lógica Y-lógica Figura 2. Representación del algoritmo de Selección Condicional. 2. Implementación C++ Para la implementación del algoritmo se utiliza el lenguaje C++ a través del entorno Visual Studio 2005. Adicionalmente, la parte del programa que se sectoriza con el repertorio MMX utiliza las “funciones intrínsecas MMX” que se describen a continuación en la Tabla 1. Tabla 1. Funciones intrínsecas MMX utilizadas en la implementación del algoritmo Selección Condicional. Función Descripción Se comparan los bytes de m1 con los respectivos bytes _m_pcmpeqb de m2, y se inicializa los bytes de la variable __m64 con los resultados de las comparaciones. Ejemplo __m64 = _m_pcmpeqb (__m64 m1 , __m64 m2); _m_pand __m64 = _m_pand Realiza una Y-lógica de los 64 bits de los dos operandos (__m64 m1 , __m64 de la función. m2); _m_pandn Realiza una inversión lógica del valor de 64 bits de m1, __m64 = _m_pandn y utiliza el resultado para realizar una Y-lógica con el (__m64 m1 , __m64 valor de m2. m2); _m_por __m64 = _m_por Realiza una O-lógica de los 64 bits del valor de m1 con (__m64 m1 , __m64 el valor de 64 de m2. m2); A continuación se muestra el código fuente en C++ que corresponde a las dos implementaciones del algoritmo de Selección Condicional. La versión convencional de la implementación se realiza a través del procedimiento “SeleccionCondicional”, y la versión vectorial utilizando MMX se realiza con el procedimiento “SeleccionCondicional_MMX”. Todo el código se divide principalmente en tres partes. Arquitectura de Computadores AP-1/3 Parte 1. Procedimiento vectorial “SeleccionCondicional_MMX”. Este procedimiento implementa los cuatro pasos del algoritmo utilizando las funciones intrínsecas MMX que se muestran en la Tabla 1. La primera de las funciones MMX llamadas (_m_pcmpeqb) aplica de forma vectorial la instrucción MMX pcmpeqb sobre datos de 8 bits dentro de un registro MMX de 64 bits. Cada byte de este registro corresponde a un píxel distinto de la imagen. Por ello, cada operación pcmpeqb realiza 8 operaciones de comparación en paralelo utilizando una única instrucción MMX sobre 8 píxeles de las correspondientes imágenes. Las otras funciones intrínsecas MMX que se utilizan y se indican en la Tabla 1 son de tipo lógica y se aplican de forma independiente a cada bit de los registros MMX. Este procedimiento consta básicamente de un bucle que itera una vez cada 8 píxeles de las imágenes ya que obtiene 8 píxeles resultados después de aplicarles las cuatro funciones intrínsecas MMX que se indican en la Tabla 1. Parte 2. Procedimiento convencional “SeleccionCondicional”. Este procedimiento aplica los cuatro pasos de algoritmo pero no utiliza ninguna función especial, sino que se codifica a través de las estructuras convencionales de C++. Consta de un bucle cuyo número de iteraciones coincide con el número de píxeles de las imágenes. Parte 3. Programa principal “_tmain”. En esta parte se inicializan las estructuras de datos y las ventana donde se van a mostrar las imágenes de entrada al algoritmo y los resultados del mismo. Además se realizan dos bucles de 10000 iteraciones; el primero de ellos llama ese número de veces al procedimiento vectorial “SeleccionCondicional_MMX”, y el segundo llama el mismo número de veces al procedimiento convencional “SeleccionCondicional”. Por último, en esta parte es donde se monitoriza el tiempo de ejecución a través de las funciones clock(). #include "stdafx.h" #include <iostream> #include <sstream> #include <stdio.h> #include <time.h> #include <iomanip> #include <cmath> #include <cv.h> #include <cxcore.h> #include <highgui.h> #include <emmintrin.h> //MMX instructions #define MAINFRAME "MainFrame" using namespace std; static long double dif_mmx, dif; // Procedimiento que implementa el algoritmo Selección condicional utilizando // las funciones intrínsicas MMX void SeleccionCondicional_MMX( BYTE* pMask, BYTE* pMonigote, BYTE* pFondo, BYTE* pResult, int nNumberOfPixels) { // 8 pixeles procesados en paralelo, 3 canales por cada pixel int nLoop = (nNumberOfPixels/8)*3; __m64* __m64* __m64* __m64* __m64 __m64 __m64 __m64 mMask mMonigote mFondo mResult = = = = (__m64*) (__m64*) (__m64*) (__m64*) tmp_compare_mask; tmp_and_fondo; tmp_andn_monigote; tmp_or; pMask; pMonigote; pFondo; pResult; // // // // // // // // croma key monigote fondo result variable variable variable variable temporal temporal temporal temporal Arquitectura de Computadores AP-1/4 _mm_empty();// es neceario para inicializar los registros MMX for ( int i = 0; i < nLoop; i++ ) { // PASO 1 del algoritmo Selección Condicional tmp_compare_mask = _m_pcmpeqb (*mMask , *mMonigote); // PASO 2 del algoritmo Selección Condicional tmp_and_fondo = _m_pand (*mFondo, tmp_compare_mask); // PASO 3 del algoritmo Selección Condicional tmp_andn_monigote = _m_pandn (tmp_compare_mask, *mMonigote); // PASO 4 del algoritmo Selección Condicional tmp_or = _m_por (tmp_and_fondo, tmp_andn_monigote); // 8 píxeles resultados del algoritmo Selección Condicional *mResult = tmp_or; mMask++; mMonigote++; mFondo++; mResult++; } } _mm_empty(); // se apunta a los siguientes 8 píxeles // es neceario para inicializar la pila de punto flotante // Procedimiento que implementa el algoritmo Selección condicional utilizando // las funciones convencionales de C++ void SeleccionCondicional( BYTE* pMask, BYTE* pMonigote, BYTE* pFondo, BYTE* pResult, int nNumberOfPixels) { // 1 pixel es procesado en cada iteración, 3 canales por cada pixel int nLoop = (nNumberOfPixels)*3; BYTE* BYTE* BYTE* BYTE* BYTE BYTE BYTE BYTE mMask = pMask; mMonigote = pMonigote; mFondo = pFondo; mResult = pResult; tmp_compare_mask; tmp_and_fondo; tmp_andn_monigote; tmp_or; // // // // croma key monigote fondo result // // // // variable variable variable variable temporal temporal temporal temporal for ( int i = 0; i < nLoop; i++ ){ // PASO 1 del algoritmo Selección Condicional tmp_compare_mask = (*mMask == *mMonigote)? 0xFF:0x00; // PASO 2 del algoritmo Selección Condicional tmp_and_fondo = (*mFondo & tmp_compare_mask); // PASO 3 del algoritmo Selección Condicional tmp_andn_monigote = (~tmp_compare_mask & *mMonigote); // PASO 4 del algoritmo Selección Condicional tmp_or = (tmp_and_fondo ^ tmp_andn_monigote); // Resultados del algoritmo Selección Condicional *mResult = tmp_or; } } mMask++; // siguiente byte mMonigote++; mFondo++; mResult++; int _tmain(int argc, _TCHAR* argv[]) { Arquitectura de Computadores AP-1/5 cout << "SELECCION CONDICIONAL \n" << "Arquitectura de Computadores 2008/09 \n" << "Facultad de Informatica \n" << "University of Las Palmas G.C. \n" << "Antonio Jose Sanchez Lopez \n"; IplImage IplImage IplImage IplImage IplImage *img_fondo = 0; *img_monigote = 0; *img_croma = 0; *img_result = 0; *img_result2 = 0; // se inicializan los punteros de memoria // donde se alojan las imágenes tanto de // entrada como de salida img_fondo = cvLoadImage("./fondo.jpg"); // se leen las imágenes *.jpg img_monigote = cvLoadImage("./monigote.jpg"); img_croma = cvLoadImage("./cromakey.jpg"); img_result = cvCreateImage(cvSize(img_fondo->width,img_fondo->height), IPL_DEPTH_8U, 3); img_result2 = cvCreateImage(cvSize(img_fondo->width,img_fondo->height), IPL_DEPTH_8U, 3); cvNamedWindow("Fondo"); cvNamedWindow("Monigote"); cvNamedWindow("Croma"); int cx, cy; cx = img_fondo->width; cy = img_fondo->height; cvShowImage( "Fondo", img_fondo ); cvShowImage( "Monigote", img_monigote ); cvShowImage( "Croma", img_croma ); cvMoveWindow("Fondo", 0, 0); cvMoveWindow("Monigote", cx+50, 0); cvMoveWindow("Croma", 2*cx+100, 0); cvWaitKey(1); clock_t start; clock_t end; start = clock(); // contadores de intervalos temporales // se comienza a contar el tiempo de ejecución int index = 0; for (int i = 0; i<10001; i++) { // bucle con la version vectorial SeleccionCondicional_MMX((BYTE*)img_croma>imageData,(BYTE*)img_monigote->imageData,(BYTE*)img_fondo>imageData,(BYTE*)img_result->imageData, cx*cy); if (index>=1000) { std::stringstream sindex; sindex << "Vectorial MMX, iter " << i; cvNamedWindow(sindex.str().c_str()); cvShowImage( sindex.str().c_str(), img_result ); cvMoveWindow(sindex.str().c_str(), ((i/index)-1)*0.5*cx, cy+50); index=0; cvWaitKey(1); } index++; } end = clock(); // se termina de contar el tiempo de ejecución dif_mmx = end-start; cout << "Seleccion Condicional con MMX tarda: " << dif_mmx << " ticks\n"; index = 0; start = clock(); // se comienza a contar otra vez el tiempo de ejecución Arquitectura de Computadores AP-1/6 for (int i = 0; i<10001; i++) { // bucle con la versión convencional SeleccionCondicional((BYTE*)img_croma->imageData, (BYTE*)img_monigote->imageData,(BYTE*)img_fondo->imageData,(BYTE*)img_result2>imageData, cx*cy); if (index>=1000) { std::stringstream sindex; sindex << "Secuencial, iter " << i; cvNamedWindow(sindex.str().c_str()); cvShowImage( sindex.str().c_str(), img_result2 ); cvMoveWindow(sindex.str().c_str(), ((i/index)-1)*0.5*cx, cy*2+100); index=0; cvWaitKey(1); } index++; } end = clock(); // se termina de contar el tiempo de ejecución dif = end-start; cout << "Seleccion Condicional Secuencial tarda: " << dif << " ticks\n"; cout << "SpeedUp = "; cout << fixed << setprecision(2)<< dif/(long double)dif_mmx; cout << "X\n"; } cvWaitKey(0); 3. Ejecución y análisis de prestaciones Una vez compilado el código anterior utilizando los ficheros *.h necesarios y la librería OpenCV que se utiliza para la visualización de imágenes, se ejecuta el código generado cuyos resultados se muestran en la Figura 3. En ella se pueden observar las tres imágenes de partida en la parte superior: fondo, monigote, y croma. En una segunda fila de ventanas se muestran las imágenes resultado de las iteraciones número 1000, 2000, …, 10000 utilizando el procedimiento vectorial MMX “SeleccionCondicional_MMX”. En una tercera fila aparece los mismos resultados de la segunda fila pero esta vez utilizando el procedimiento que no incluye funciones MMX “SeleccionCondicional”. Cuando se está ejecutando el programa, se observa que los resultados del procedimiento vectorial MMX se muestran más rápidamente que cuando se muestran los resultados del procedimiento convencional y que no utiliza la arquitectura MMX. En la ventana de comandos de la parte superior de la Figura 3 se observan los resultados de prestaciones donde se destaca que cuando se utiliza MMX, el algoritmo se ha ejecutado en 1281 ticks (intervalos temporales de monitorización), y cuando se utiliza la versión convencional, el resultado es 11313 intervalos temporales. Esto hace que el Speed-Up cuando se utiliza MMX es de 8.83X. Este valor puede depender del ordenador y de su estado en el momento que se ejecuta. Arquitectura de Computadores AP-1/7 Figura 3. Ejecuciones serie y MMX del algoritmo de Selección Condicional. 4. Pruébelo usted mismo A este apéndice se adjunta un programa ejecutable denominado “SCmmx.exe” que realiza el algoritmo de Selección Condicional sobre las imágenes que se muestran en la Figura 3. Pruebe a ejecutar este programa en su ordenador y verifique unos resultados parecidos a los que se muestrean en la Figura 3. Bibliografía Gary Bradski, Adrian Kaehler; Learning OpenCV: Computer Vision with the OpenCV Library. O'Reilly Media, Inc.; 1st edition (October 3, 2008) MMX Technology. Visual C++ Language Reference. Microsoft Visual Studio 2005/.NET Framework 2.0 (http://msdn.microsoft.com/en-us/library/698bxz2w(VS.80).aspx) Agradecimientos Antonio José Sánchez López desarrolló en Octubre de 2008 el código C++ que aparece en este apéndice.