Showing posts with label Tutorials. Show all posts
Showing posts with label Tutorials. Show all posts

Friday, February 18, 2011

Using SIMD for Hardware Acceleration

Most modern processors come with a feature of operating the same instruction on multiple instances of data. The acronym SIMD stands for "Single Instruction Multiple Data" describes just that.

Intel first introduced the MMX instructions that could operate on multiple data in the Pentium CPU's way back in 1994. Intel supported this by coming out with an extended instruction set henceforth called Streaming SIMD Extensions or SSE. The SSE instruction set which first came with the Pentium III processor uses 128 bit registers that can be used to pack 4 integers or 4 floating point data types. The advantage of doing this becomes clear if one tries to consecutively add four different sets of operands. Using the basic instruction set one needs to:
  1. Move the two operands to registers.
  2. Add the operands.
  3. Move result to memory.
  4. Repeat the above steps for three more sets of operands.
But with just three MMX instructions all these can be done at once for the four sets of operands hence rapidly improving performance.

Before I show you how to use these SSE instructions it is important to know how to access data in a SIMD register. As you know a SIMD register is 128 bit which means it can hold 4 x 32 it data or in other 4 ints or 4 floats. This data in order is referred as [x, y, z, w]. The following SSE instruction: addps adds the four ints or floats in the 128 bit XMM0 register and the 128 bit XMM1 register and stores the results back in the XMM0 register.

addps xmm0 xmm1;

The four floats that are loaded into the SSE register can be moved from memory individually but such operations are slow. Moreover moving data between the FPU (Floating Point Processing Unit) registers and the CPU registers is particularly slow because the CPU has to wait for the FPU to complete the present operation at hand. Hence it is a good practice to leave the data in the SSE registers unless and until space has to be cleared.

Let us now see how we can leverage these SIMD instructions from C/C++. Many compilers provide different data types for SIMD operations. Here I will discuss only the Microsoft Visual Studio compiler. The MVCC provides a predefined datatype __m128, which can be used to declare a variable which holds data in a MMX register. A __m128 type variable is stored directly in a MMX register without ever being put in the memory or the CPU registers. It is the programmer's responsibility to align the data to 16 byte address once you load its contents directly into memory.

Here's a sample program which demonstrates the usage of SIMD to perform addition on four floating point data.

__m128 addMMX(__m128 a, __m128 b)
{
__m128 result;
/* inline assembly */
__asm
{
movaps xmm0, xmmword ptr [a]
movaps xmm1, xmmword ptr [b]
addps xmm0, xmm1
movaps xmmword ptr [result], xmm0
}
return result;
}

This is however a bad approach because the code is not portable and one has to embed inline assembly into high level code. A better way to do the same thing is to use intrinsics. Intrinsics are special commands that look and behave like C functions but are internally expanded to inline assembly code by the compiler. In order to use intrinsics be sure to include the xmmintrin.h file into your code.


#include <xmmintrin.h>

__m128 addSIMDwithIntrinsics(__m128 a, __m128 b)
{
/* use intrisics */
__m128 result = _mm_add_ps(a,b);
return result;
}

To load 4 floats into the MMX register simply use the load intrinsic.

/* be sure to 16 byte align your arrays to
reduce the number of fetch cycles required to load */

__declspec(align(16)) float A[] = {1.0f, 2.0f, 3.0f, 4.0f};
__declspec(align(16)) float B[] = {4.0f, 3.0f, 2.0f, 1.0f};
__declspec(align(16)) float C[] = {0.0f, 0.0f, 0.0f, 0.0f};

int main(int args, char* argv[])
{
/* load a and b from the arrays above */
__m128 a = _mm_load_ps(&A[0]);
__m128 b = _mm_load_ps(&B[0]);
__m128 c;

/* call addSIMDwithIntrinsics() function from above */
c = addSIMDwithIntrinsics(a, b);

/* write the result back to array */
_mm_store_ps(&C, c);
}

The next time you set out to write FFT functions or just about any repetitive math operations be sure to utilize this feature.

Wednesday, June 10, 2009

Function-Plot: Sample OpenGL program

Before we sit down to writing code we have to set up our environment. You can download the glut library from here. Alternatively you can use the Dev C++ IDE's Package Manager to download it. For those who use Dev C++ you can go to Tools>Check for Updates/Packages and download glut from there.
After you successfully finish downloading you can start a new glut project by clicking File>New>Project and selecting the Multimedia tab and selecting glut as your project type.
You will instantly see a sample glut program. Just to get a taste of what you can accomplish, try running it. Select Execute>Compile and Run or press F9.

Now lets jump straight to out own glut project. Our objective is to plot a simple two dimensional sine curve on the screen. This will help to familiarize you to the opengl programming. Try running this program. I will elaborately explain the code in the following lines.


#include
#include<stdlib.h>
#include<math.h>


void Init(void)
{
glClearColor(0.0f,0.0f,0.0f,0.0f); // choose a color to clear the screen with
glColor3f(1.0f,1.0f,1.0f); // choose a fill color
glPointSize(1.0f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,640.0,0.0,480.0); // set up the co-ordinate system
}


void funcPlot(void)
{
glClear(GL_COLOR_BUFFER_BIT);
GLdouble x,y;
glBegin(GL_LINES); // Start drawing lines
glVertex2i(0,240); // Draw a vertex( a point,well almost) at (0,240) world co-ordinates
glVertex2i(640,240);
glEnd(); // end drawing

glBegin(GL_POINTS); // begin drawing points
for(x=0.0;x<8.0;x+=0.005)>
y = (480/2)+ (10 * sin(x));
glEnd(); // end drawing
}

int main(int args,char** argv)
{
glutInit(&args,argv);
glutInitWindowPosition(0,0);
glutInitWindowSize(640,480);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutCreateWindow("Function Plot Example");
glutDisplayFunc(funcPlot);
glutMainLoop();
return(0);
}

To take you through this I prefer to start with the main() function. The function glutInit() is called to start a new new thread and create a new window instance to run your program. The function calls that follow, set the various properties of your rendering context and your window instance. The beauty of glut is that you need'nt worry about creating your window handles and managing your contexts, glut does it all for you. The internal implementation of glut is such that these tasks are performed for you depending on your native window system. Hence the platform independancy. The glutInitDisplayMode() call defines your rendering context. The parameters passed to it ie. GLUT_SINGLE | GLUT_RGB, denote that you need a single display buffer and your color mode is RGB or Red, Green and Blue. It will be clear further what they excatly mean but for now think of the display buffer as a contigious memory block where you're rendered scene will be temporarily stored before it is pushed into the display. The color mode needs further explaination. As you know white light comprises of all colors. But a colored light is characterized by the absence of certain components, coherently it can also be said that colored light is rather a combination of various color components in specific proportions. Thus RGB is color mode is a method in which every color is denoted as a combination of Red, Green and Blue. The next three lines are self explainatory. glutInitWindowSize() specifies the size or resolution of your window in (pixel width) x (pixel height). glutInitWindowPosition() is used to postion your window relative to the screen co-ordinate system. The screen co-ordinate system is defined in two dimensions. The top left corner of your screen is considered the origin (0,0) and x and y co-ordinates increase as we move towards the right and downward respectively. Finally glutCreateWindow() creates your window with the title specified as the parameter. The next section is where you register your call back functions. Call back functions in simple terms are functions that are called when a specific event occurs. For instance, a screen resize operation gives rise to a resize event(I will introduce this later). To handle this event we implement a function that is to be called every time a resize event occurs. Then to let glut know that this is the function to be called for such an event, we register it. In the next line we see that the the display call back is registered. The init() function that is called next is where you setup the canvas for you to draw on. The glClearColor() specifies the color that you want to clear your screen with, everytime you redraw the scene. The first three values are the Red, Green and Blue components of your color and the fourth value is alpha(leave this for now). The glColor3f() function chooses your drawing color. The next few lines are better understood later so for now think of this operation as setting up your world co-ordinate system. The display call back that we registered in main is implemented as shown. I believe most of you will have no difficulty understanding it and for now the comments in the code should suffice.

Saturday, June 6, 2009

OpenGL - Introduction

OpenGL is widely considered as hard to tame. It is often intimidating to amateur and overenthusiastic programmers who's approach is to take a plunge into it at first sight. Though it may be encouraging to see, it often leaves them terribly confused.

Before GLUT, ( GL utility toolkit) which conviniently keeps us away from the complexities of window system related code, OpenGL programming was a daunting task. Despite this benifit, which GLUT gives us, it helps to be guided by a detailed tutorial or walk-through.

The soul aim of this tutorial is to eliminate the hardships faced by wannabe "GLUTtons". This tutorial is tailored in the way in which I taught myself this skill. I will try my best to ensure that I leave no doubts in the reader's mind.