## Thursday, August 30, 2012

### Comparing dates with OpenCL - And a mktime function that scales

I recently came across a simple problem in C++, but a difficult one in OpenCL: comparing dates. Since I needed this functionality in one of my algorithms, I decided to implement my own OpenCL diffDays function and here is what it looks like the one bellow.

### OpenCL

// Globals
__constant int DaysInMonths = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// Macros
#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)

typedef struct
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
} TimeStructure;

void computeYearDay( TimeStructure* t )
{
int nbDays = 0;
for( int i=0; i<(*t).tm_mon-1; ++i )
{
nbDays += DaysInMonths[i];
}
nbDays += (*t).tm_mday;
nbDays += isleap((*t).tm_year ) ? 1 : 0; // leap years
(*t).tm_yday = nbDays;
}

int diffDays(TimeStructure fromDate, TimeStructure toDate)
{
int fndays        = 0;
float fndaysFrom = 0.f;
float fndaysTo   = 0.f;
int ndays = 0;

computeYearDay(&fromDate);
computeYearDay(&toDate);

int toYear   = 1900 + toDate.tm_year;
int fromYear = 1900 + fromDate.tm_year;

fndaysFrom =  (float)(fromDate.tm_mday);
fndaysFrom += (float)(fromDate.tm_hour)/24.f;
fndaysFrom += (float)(fromDate.tm_min)/1440.f;
fndaysFrom += (float)(fromDate.tm_sec)/86400.f;
fndaysTo  = (float)(toDate.tm_mday);
fndaysTo += (float)(toDate.tm_hour)/24.f;
fndaysTo += (float)(toDate.tm_min)/1440.f;
fndaysTo += (float)(toDate.tm_sec)/86400.f;

for(int i=fromYear; i < toYear; i++) {
ndays += isleap(i) ? 366 : 365;
}
fndays = ndays + (fndaysTo - fndaysFrom);
return -fndays;
}

The funny thing about it that when I tried to compare the results between CPU execution (using OpenMP) and the OpenCL implementation, I realized that my C++ code could not scale properly. Even worse, it was slowing down as I was increasing the number of cores. After a good profiling session using Intel Parallel Studio, I realized that the slowing bit was in the mktime function provided with Microsoft Visual C++ 2010. So I ported the OpenCL function back to C++ and that resulted in an amazing x20 increase in performance on 8 CPUs.

And I now have a mktime that scales on multicore architectures !!

### C/C++

#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
const int DaysInMonths = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

void computeYearDay( tm& t )
{
int nbDays = 0;
for( int i=0; i<t.tm_mon-1; ++i )
{
nbDays += DaysInMonths[i];
}
nbDays += t.tm_mday;
nbDays += isleap(t.tm_year ) ? 1 : 0;
t.tm_yday = nbDays;
}

int diffDays(tm b, tm a)
{
int fndays       = 0;
float fndaysFrom = 0.f;
float fndaysTo   = 0.f;
int ndays = 0;

computeYearDay(b);
computeYearDay(a);

int toYear   = 1900 + a.tm_year;
int fromYear = 1900 + b.tm_year;

fndaysFrom  = static_cast<float>(b.tm_mday);
fndaysFrom += static_cast<float>(b.tm_hour)/24.f;
fndaysFrom += static_cast<float>(b.tm_min)/1440.f;
fndaysFrom += static_cast<float>(b.tm_sec)/86400.f;

fndaysTo  = static_cast<float>(a.tm_mday);
fndaysTo += static_cast<float>(a.tm_hour)/24.f;
fndaysTo += static_cast<float>(a.tm_min)/1440.f;
fndaysTo += static_cast<float>(a.tm_sec)/86400.f;

for(int i=fromYear; i < toYear; i++) {
ndays += isleap(i) ? 366 : 365;
}
fndays = ndays + (fndaysTo - fndaysFrom);
return -fndays;
}