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[12] = { 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[12] = { 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;
}