CSC523 – MPI
Parallel Programming
Instructor: Scott Spetka, Ph.D.
Menu:
- MPI Greeting By Rank
- Matrix Multiply (MPI Version)
- Matrix Multiply (PTHREADS Version)
- Parallel Sort
- VTK/MPI Parallel Cone Visualization
- Project Documentation (Paraview Parallel Rendering Lab)
MPI Greeting By Rank:
/*
Program: hello.c
By: Ronny L. Bull
Class: CSC523 - Parrallel Computing
Instructor: Scott Spetka
Date: 01-26-2011
Purpose: This program sends a message to each node on a
cluster or processor in rank order and shows the
name of the node or processor.
*/
/*
References:
Stanford Linear Accelerator Center:
http://www.slac.stanford.edu/comp/unix/farm/mpi.html
Dartmouth.edu:
http://www.dartmouth.edu/~rc/classes/intro_mpi/hello_world_ex.html
*/
/*
Dictionary of Variables:
num, integer - number of processors or nodes used by the program
rank, integer - processor/node rank id
name, ingeger - size of processor/node name
proc_name, char - character array of individual processor/node name
*/
//Includes
#include <stdio.h>
#include <mpi.h> //MPI Library
//Main takes arguments
int main(int argc, char *argv[]) {
//Variables
int num, rank, namesize;
char proc_name[MPI_MAX_PROCESSOR_NAME];
//Initialize MPI using the arguments passed to it
MPI_Init(&argc, &argv);
//Get number of nodes/processors
MPI_Comm_size(MPI_COMM_WORLD, &num);
//Get node/processor rank id
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
//Get the processor or node name
MPI_Get_processor_name(proc_name, &namesize);
//Output
printf("Greetings from: Process %d on %s out of %d\n", rank, proc_name, num);
//Close MPI
MPI_Finalize();
}
Matrix Multiply (MPI Version):
/*
Program: matmult.c
By: Ronny L. Bull
Class: CSC523 - Parrallel Computing
Instructor: Scott Spetka
Date: 02-12-2011
Purpose: This program performs matrix multiplication using mpi
*/
/*
References:
Serial Matrix Multiplication:
http://www.edcc.edu/faculty/paul.bladek/Cmpsc142/matmult.htm
PPMPIC Example - mat x vect:
ppmpic_c/chap05 - parallel_mat_vect.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#define MAX 100 //Max row/col size
typedef int LOCAL_MATRIX_T[MAX][MAX];
int main(int argc, char *argv[]) {
//Variables
int num, rank; //MPI Processor info
int m, n, o, p, local_m, local_n, local_o, local_p; //Matrix Variables
LOCAL_MATRIX_T local_A; //Matrix A
LOCAL_MATRIX_T local_B; //Matrix B
//Function Prototypes
void readMat1(char* prompt, LOCAL_MATRIX_T local_A, int local_m, int n, int rank, int num);
void readMat2(char* prompt, LOCAL_MATRIX_T local_B, int local_o, int p, int rank, int num);
void printMat1(char* title, LOCAL_MATRIX_T local_A, int local_m, int n, int rank, int num);
void printMat2(char* title, LOCAL_MATRIX_T local_B, int local_o, int p, int rank, int num);
void matMult(LOCAL_MATRIX_T local_A, int m, int n, int local_m, int local_n, LOCAL_MATRIX_T local_B, int o, int p, int local_o, int local_p, int rank);
//Initialize MPI using the arguments passed to it
MPI_Init(&argc, &argv);
//Get number of nodes/processors
MPI_Comm_size(MPI_COMM_WORLD, &num);
//Get node/processor rank id
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
//Check for master node and prompt for input
if(rank == 0) {
printf("Enter the size of matrix 1 (rows x cols) \n");
scanf("%d %d", &m, &n);
printf("You chose a %d x %d matrix \n", m, n);
}
//Broadcast matrix 1 order to workers
MPI_Bcast(&m, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
//Check to see if number or rows matches number of procs or is a multiple of the number of procs
if(m == num || m%num == 0) {
local_m = m/num;
local_n = n/num;
}
else {
//If not set num to m
num = m;
local_m = m/num;
local_n = n/num;
}
//Read in and print user data input of matrix 1
readMat1("Enter the integer values for matrix 1", local_A, local_m, n, rank, num);
printMat1("We read", local_A, local_m, n, rank, num);
//Check for master node and prompt for input
if(rank == 0) {
printf("Enter the size of matrix 2 (rows x cols) \n");
scanf("%d %d", &o, &p);
//Check to see if Matrix 1 cols match Matrix 2 rows
while(p != m) {
//Don't Match - Try Again
printf("Rows in Matrix 1 do not match Cols in Matrix 2, cannot proceed with matrix multiplication \n");
printf("Enter the size of matrix 2 (rows x cols) \n");
scanf("%d %d", &o, &p);
}
//Yippie! They match! Move along...
printf("You chose a %d x %d matrix \n", o, p);
printf("Rows in Matrix 1 match Cols in Matrix 2, We may proceed with matrix multiplication \n");
}
//Broadcast matrix 2 order to workers
MPI_Bcast(&o, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(&p, 1, MPI_INT, 0, MPI_COMM_WORLD);
//Check to see if number or rows matches number of procs or is a multiple of the number of procs
if(o == num || o%num == 0) {
local_o = o/num;
local_p = p/num;
}
else {
//if not set num to o
num = o;
local_o = o/num;
local_p = p/num;
}
//Read in and print user data input of matrix 2
readMat2("Enter the integer values for matrix 2", local_B, local_o, p, rank, num);
printMat2("We read", local_B, local_o, p, rank, num);
//multiply matricies
matMult(local_A, m, n, local_m, local_n, local_B, o, p, local_o, local_p, rank);
//Close MPI
MPI_Finalize();
}
//Read Matrix 1
void readMat1(char* prompt, LOCAL_MATRIX_T local_A, int local_m, int n, int rank, int num) {
//Variables
int i,j; //counters
LOCAL_MATRIX_T temp; //matrix
//Fill matrix with 0's
for(i=0; i < num*local_m; i++)
for(j=n; j<MAX; j++)
temp[i][j] = 0;
//Read in values
if(rank == 0) {
printf("%s\n", prompt);
for(i=0; i < num*local_m; i++)
for(j=0; j<n; j++)
scanf("%d",&temp[i][j]);
}
//Spread values to procs
MPI_Scatter(temp, local_m*MAX, MPI_INT, local_A, local_m*MAX, MPI_INT, 0, MPI_COMM_WORLD);
}
//Read Matrix 2
void readMat2(char* prompt, LOCAL_MATRIX_T local_B, int local_o, int p, int rank, int num) {
//Variables
int i,j; //counters
LOCAL_MATRIX_T temp; //matrix
//Fill matrix with 0's
for(i=0; i < num*local_o; i++)
for(j=p; j<MAX; j++)
temp[i][j] = 0;
//Read in values
if(rank == 0) {
printf("%s\n", prompt);
for(i=0; i < num*local_o; i++)
for(j=0; j<p; j++)
scanf("%d",&temp[i][j]);
}
//Spread values to procs
MPI_Scatter(temp, local_o*MAX, MPI_INT, local_B, local_o*MAX, MPI_INT, 0, MPI_COMM_WORLD);
}
//Print Matrix 1
void printMat1(char* title, LOCAL_MATRIX_T local_A, int local_m, int n, int rank, int num) {
//Variables
int i, j; //counters
int temp[MAX][MAX]; //matrix
//Gather up values from procs
MPI_Gather(local_A, local_m*MAX, MPI_INT, temp, local_m*MAX, MPI_INT, 0, MPI_COMM_WORLD);
//Print matrix
if(rank == 0) {
printf("%s\n", title);
for(i=0; i < num*local_m; i++) {
for(j=0;j<n; j++)
printf("%d ", temp[i][j]);
printf("\n");
}
}
}
//Print Matrix 2
void printMat2(char* title, LOCAL_MATRIX_T local_B, int local_o, int p, int rank, int num) {
//Variables
int i, j; //counters
int temp[MAX][MAX]; //matrix
//Gather up values from procs
MPI_Gather(local_B, local_o*MAX, MPI_INT, temp, local_o*MAX, MPI_INT, 0, MPI_COMM_WORLD);
//Print matrix
if(rank == 0) {
printf("%s\n", title);
for(i=0; i < num*local_o; i++) {
for(j=0;j<p; j++)
printf("%d ", temp[i][j]);
printf("\n");
}
}
}
//Multiply
void matMult(LOCAL_MATRIX_T local_A, int m, int n, int local_m, int local_n, LOCAL_MATRIX_T local_B, int o, int p, int local_o, int local_p, int rank) {
//Variables
int i, j, k;
int tempA[MAX][MAX]; //matrix 1
int tempB[MAX][MAX]; //matrix 2
int tempC[MAX][MAX]; //result
//Gather up values from procs
MPI_Gather(local_A, local_m*MAX, MPI_INT, tempA, local_m*MAX, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Gather(local_B, local_o*MAX, MPI_INT, tempB, local_o*MAX, MPI_INT, 0, MPI_COMM_WORLD);
//Perform matrix multiplication
for(i=0; i<m; i++)
for(j=0; j<p; j++)
for(k=0; k<o; k++)
tempC[i][j] += tempA[i][k] * tempB[k][j];
//Print results
if(rank == 0) {
printf("Results: \n");
for(i=0; i<m; i++) {
for(j=0; j<p; j++)
printf("%d ", tempC[i][j]);
printf("\n");
}
}
}
Matrix Multiply (PTHREADS Version):
/*
Program: pthread_mat_mult.c
By: Ronny L. Bull
Class: CSC523 - Parrallel Computing
Instructor: Scott Spetka
Date: 04-05-2011
Purpose: This program performs matrix multiplication using pthreads
*/
/*
References:
1. POSIX Threads Programming:
https://computing.llnl.gov/tutorials/pthreads/
2. Matrix Multiplication in C using PThreads on Linux:
http://macboypro.com/blog/2009/06/29/matrix-multiplication-in-c-using-pthreads-on-linux/
3. Matrix Multiplication Tutorial (Finite Math):
http://people.hofstra.edu/Stefan_Waner/realWorld/tutorialsf1/frames3_2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX 100 //max row/col size
typedef int MATRIX_T[MAX][MAX];
//Data to pass to each thread
struct d {
int r; //row
int c; //col
};
//Globals
MATRIX_T result; //Result Matrix
MATRIX_T A; //1st Matrix
MATRIX_T B; //2nd Matrix
int x; //loop counter
//Function Prototypes
void *matMult(void *param);
//Main
int main(int argc, char *argv[]) {
//Variables
int i,j; //counters
int m,o; //Rows
int n,p; //Cols
int count = 0; //thread counter
//Function Prototypes
void readMat1(char* prompt, MATRIX_T A, int m, int n);
void readMat2(char* prompt, MATRIX_T B, int o, int p);
//Prompt for matrix 1 size
printf("Enter the size of the first matrix (rows x cols)\n");
scanf("%d %d", &m, &n);
printf("You chose a %d x %d matrix\n\n", m, n);
//Enter Matrix 1
readMat1("Enter the integer values for matrix 1", A, m, n);
//Prompt for matrix 2 size
printf("Enter the size of the second matrix (rows x cols)\n");
scanf("%d %d", &o, &p);
printf("You chose a %d x %d matrix\n\n", o, p);
//Check to see if Matrix 1 rows match Matrix 2 cols
while(p != m) {
//Don't Match - Try Again
printf("Rows in Matrix 1 do not match Cols in Matrix 2, cannot proceed with matrix multiplication \n");
printf("Enter the size of matrix 2 (rows x cols) \n");
scanf("%d %d", &o, &p);
printf("You chose a %d x %d matrix\n\n", o, p);
}
//Yippie! They match! Move along...
printf("Rows in Matrix 1 match Cols in Matrix 2, We may proceed with matrix multiplication!\n\n");
//Set x equal to the cols in Matrix A
x = n;
//Enter Matrix 2
readMat2("Enter the integer values for matrix 2", B, o, p);
//Create threads
for(i=0; i<m; i++) {
for(j=0; j<p; j++) {
//Print thread id
printf("Creating thread %d:\n", count);
//Assign a row from matrix A to each thread (Adapted from Reference 1)
struct d *data = (struct d *) malloc(sizeof(struct d));
data->r = i;
data->c = j;
//Create thread and pass it the data as a parameter
pthread_t tid; //Thread ID
pthread_attr_t attr; //Thread attributes
pthread_attr_init(&attr); //Get defaults
pthread_create(&tid,&attr,matMult,data);
//Wait for all threads to complete
pthread_join(tid, NULL);
count++;
}
}
//Print resulting matrix
printf("Results:\n");
for(i=0; i<m; i++) {
for(j=0; j<p; j++)
printf("%d ", result[i][j]);
printf("\n");
}
//Exit Program
return(0);
}
//Read Matrix 1
void readMat1(char* prompt, MATRIX_T A, int m, int n) {
//Variables
int i,j; //counters
//Fill matrix with 0's
for(i=0; i<m; i++)
for(j=n; j<MAX; j++)
A[i][j] = 0;
//Read in values
printf("%s\n", prompt);
for(i=0; i<m; i++)
for(j=0; j<n; j++)
scanf("%d", &A[i][j]);
//Print Matrix
printf("You entered\n");
for(i=0; i<m; i++) {
for(j=0; j<n; j++)
printf("%d ", A[i][j]);
printf("\n");
}
printf("\n");
}
//Read Matrix 2
void readMat2(char* prompt, MATRIX_T B, int o, int p) {
//Variables
int i,j; //counters
//Fill matrix with 0's
for(i=0; i<o; i++)
for(j=p; j<MAX; j++)
B[i][j] = 0;
//Read in values
printf("%s\n", prompt);
for(i=0; i<o; i++)
for(j=0; j<p; j++)
scanf("%d", &B[i][j]);
//Print Matrix
printf("You entered\n");
for(i=0; i<o; i++) {
for(j=0; j<p; j++)
printf("%d ", B[i][j]);
printf("\n");
}
printf("\n");
}
//MatMult (Adapted from reference 1)
void *matMult(void *param) {
//Variables
struct d *data = param; //The data
int i; //Counter
int sum = 0; //Sum
//Row multiplied by column
for(i=0; i<x; i++) {
printf("Processing %d * %d\n", A[data->r][i], B[i][data->c]);
sum += A[data->r][i] * B[i][data->c];
printf("Sum is %d \n", sum);
}
printf("\n");
//Assign data to results matrix
result[data->r][data->c] = sum;
//Exit thread
pthread_exit(0);
}
Parallel Sort
/*
Program: sort.c
By: Ronny L. Bull
Class: CSC523 - Parrallel Computing
Instructor: Scott Spetka
Date: 05-03-2011
Purpose: Sorts an array of integers in order from lowest to highest
*/
#define MAX 100 //Max Array Size
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
typedef int LOCAL_ARRAY_T[MAX];
int main(int argc, char *argv[]) {
//Variables
int p, rank; //Processor Info
int size, local_size; //Array Size
LOCAL_ARRAY_T local_A; //Array A
int done = 1;
//Function Prototypes
void getData(char* prompt, LOCAL_ARRAY_T local_A, int local_size, int ra
nk, int p);
void printArray(char* title, LOCAL_ARRAY_T local_A, int local_size, int
rank, int p);
void sortData(char* title, LOCAL_ARRAY_T local_A, int local_size, int ra
nk, int p);
//Initialize MPI
MPI_Init(&argc, &argv);
//Get number of processors
MPI_Comm_size(MPI_COMM_WORLD, &p);
//Get processor rank id
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
//Get Data
while(done == 1){
if(rank == 0) {
//Prompt for array size
printf("Please enter the number of integers you would li
ke to sort: \n");
scanf("%d", &size);
printf("You chose a %d integer array. \n\n", size);
}
//Broadcast array size
MPI_Bcast(&size, 1, MPI_INT, 0, MPI_COMM_WORLD);
//Check num/p
if(size == p || size%p == 0){
local_size = size/p;
done = 0;
}
else {
if(rank == 0) {
printf("Error: Cannot perform integer sort! Your
chosen array size is not proportional to the processor count. \n");
printf("Please try again with an array size that
is either equal to or evenly divisible by p. \n\n");
}
done = 1;
}
}
//Get the data
getData("Please enter the integers to sort: ", local_A, local_size, rank
, p);
printArray("You entered: ", local_A, local_size, rank, p);
sortData("Sorting: ", local_A, local_size, rank, p);
//Close MPI
MPI_Finalize();
return(0);
}
//Functions
//Collect data, and store in array
void getData(char* prompt, LOCAL_ARRAY_T local_A, int local_size, int rank, int
p) {
//Variables
int i,j;
LOCAL_ARRAY_T temp; //temp array
//Fill array with 0's
for(i=0; i<p*local_size; i++){
temp[i] = 0;
}
//Read in values
if(rank == 0) {
printf("%s\n", prompt);
for(i=0; i<p*local_size; i++)
scanf("%d",&temp[i]);
}
//Spread the values
MPI_Scatter(temp, local_size*p, MPI_INT, local_A, local_size*p, MPI_INT,
0, MPI_COMM_WORLD);
}
//Print Array
void printArray(char* title, LOCAL_ARRAY_T local_A, int local_size, int rank, in
t p) {
//Variables
int i,j;
LOCAL_ARRAY_T temp; //temp array
//Gather the data
MPI_Gather(local_A, local_size*p, MPI_INT, temp, local_size*p, MPI_INT,
0, MPI_COMM_WORLD);
//Print the array
if(rank == 0) {
printf("%s\n", title);
for(i=0; i<p*local_size; i++)
printf("%d ",temp[i]);
printf("\n");
}
}
//Sort Array
void sortData(char* title, LOCAL_ARRAY_T local_A, int local_size, int rank, int
p) {
//Variables
int i,j;
int x, sub;
LOCAL_ARRAY_T temp; //temp array
//Gather the data
MPI_Gather(local_A, local_size*p, MPI_INT, temp, local_size*p, MPI_INT,
0, MPI_COMM_WORLD);
//Sort the array
if(rank == 0) {
printf("%s\n", title);
//compare values and swap
for(sub=0; sub<(p*local_size)-1; sub++) {
for(i=0;i<p*local_size; i++) {
if(temp[i] < temp[i-1]) {
x = temp[i-1];
temp[i-1] = temp[i];
temp[i] = x;
}
}
}
//print sorted array
for(i=0;i<p*local_size; i++)
printf("%d ", temp[i]);
printf("\n");
//print the smallest integer
printf("The smallest integer is: %d \n", temp[0]);
printf("The second smallest integer is: %d \n", temp[1]);
printf("The second largest integer is: %d \n", temp[(p*local_siz
e)-2]);
printf("The largest integer is: %d \n", temp[(p*local_size)-1]);
}
}
VTK/MPI Parallel Cone Visualization:
C++ File
/*
Program: Cone.cpp
By: Ronny L. Bull
Class: CSC523 - Parrallel Computing
Instructor: Scott Spetka
Date: 04-10-2011
Purpose: This program combines VTK and MPI to produce a rendering window with a cone in it for each processor
*/
#include "vtkConeSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderWindow.h"
#include "vtkCamera.h"
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkMPIController.h"
void process(vtkMultiProcessController* controller, void* vtkNotUsed(arg)) {
int rank = controller->GetLocalProcessId();
//Setup Cone Object
vtkConeSource *cone = vtkConeSource::New();
cone->SetHeight( 3.0 );
cone->SetRadius( 1.0 );
cone->SetResolution( 10 );
vtkPolyDataMapper *coneMapper = vtkPolyDataMapper::New();
coneMapper->SetInputConnection( cone->GetOutputPort() );
vtkActor *coneActor = vtkActor::New();
coneActor->SetMapper( coneMapper );
//Setup renderer actors and background
vtkRenderer *ren1= vtkRenderer::New();
ren1->AddActor( coneActor );
ren1->SetBackground( 0.1, 0.2, 0.4 );
//Setup renderer actors and background
vtkRenderer *ren2= vtkRenderer::New();
ren2->AddActor( coneActor );
ren2->SetBackground( 0.2, 0.3, 0.5 );
//Setup window
vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->AddRenderer( ren1 );
renWin->SetSize( 300, 300 );
//Setup window
vtkRenderWindow *renWin2 = vtkRenderWindow::New();
renWin2->AddRenderer( ren2 );
renWin2->SetSize( 300, 300 );
if(rank==0) {
int i;
for(i=0; i < 600; ++i) {
//Render the image
renWin->Render();
//Rotate the camera by 1 degree
ren1->GetActiveCamera()->Azimuth( 1 );
}
} else {
int j;
for(j=0; j < 600; ++j) {
//Render the image
renWin2->Render();
//Rotate the camera by -1 degree
ren2->GetActiveCamera()->Azimuth( -1 );
}
}
//Cleanup VTK
cone->Delete();
coneMapper->Delete();
coneActor->Delete();
ren1->Delete();
ren2->Delete();
renWin->Delete();
renWin2->Delete();
}
int main( int argc, char *argv[]) {
//Initialize MPI
vtkMPIController* controller = vtkMPIController::New();
controller->Initialize(&argc, &argv);
controller->SetSingleMethod(process, 0);
controller->SingleMethodExecute();
//Cleanup MPI
controller->Finalize();
controller->Delete();
return 0;
}
Makefile
##################################################################################################
## ##
## This is a makefile for a simple VTK program ##
## ##
## Usage: ##
## "make" -> Compiles an optimized version of the ray tracer, using g++. The ##
## executable created is defined by EXENAME, below. ##
## ##
## "make clean" -> Removes all temporary files. ##
## ##
## ##
## Chris Wyman (January 2, 2007) ##
##################################################################################################
##################################################################################################
## ##
## To change the name of the executable, modify the following line. ##
## ##
##################################################################################################
EXENAME = VTKCone
##################################################################################################
## ##
## Include all .c, .cpp, .C, .cxx, .cc files that you would like to compile into your ##
## executable on the line below. ##
## ##
##################################################################################################
SOURCE = Cone.cpp
##################################################################################################
## ##
## The following lines should specify the locations of the VTK headers and library (.a) files. ##
## On the lab machines in MacLean Hall 301, you should not need to change these. ##
## ##
##################################################################################################
VTKLIBS = /usr/lib/vtk-5.6/
VTKINCLUDE = /usr/include/vtk-5.6/
##################################################################################################
## ##
## You shouldn't need to change anything below, unless you want to change where compiled files ##
## are stored or if you need to change the compilation flags or libraries. ##
## ##
##################################################################################################
# The C++ compiler
#CC = g++
CC = mpic++
# Directories to search for includes and libraries
INCLUDES = -I. -I /usr/X11R6/include/ -I $(VTKINCLUDE)
# Set the Flags passed to the compiler & linker.
# In the first case, we use optimizations. If debugging is on,
# we do not use optimizations, and we enable the profiler.
LDFLAGS = -L $(VTKLIBS) -L /usr/X11R6/lib64/ -L /usr/X11R6/lib/
CCFLAGS = -Wno-deprecated -O3
EXE = $(EXENAME)
# The linked libraries
LIBS = -lvtkWidgets -lvtkRendering -lvtkImaging -lvtkGraphics -lvtkFiltering \
-lvtkGenericFiltering -lvtkHybrid -lvtkIO -lvtkftgl -lvtkVolumeRendering -lvtkNetCDF \
-lvtkDICOMParser -lvtkCommon -lvtkexoIIc \
-lvtksys -lGL -lGLU -lX11 -lXmu -lXi -lm -lpthread
# A list of objects (replace .cpp with .o)
OBJS = $(SOURCE:.cpp=.o)
# A list of all the objects w/o their relative directory names
BASEOBJ = $(notdir $(OBJS))
# A list of the .o files, which are stored in obj/
FINALOBJ = $(addprefix obj/,$(BASEOBJ))
# The default make rule
default: $(SOURCE) $(EXE)
$(EXE): obj/ $(FINALOBJ)
$(CC) $(LDFLAGS) $(FINALOBJ) $(LIBS) -o $@
obj/%.o: %.cpp
$(CC) -c $(CCFLAGS) $(INCLUDES) $< -o $@
obj/:
mkdir obj
clean:
rm -f $(addprefix obj/,$(BASEOBJ)) $(EXE)
