Blog do projektu Open Source JavaHotel

wtorek, 22 grudnia 2009

Go 64 bit

I decided recently to migrate my test C++ application (based on database flat files and Python interface) to 64 bit world keeping backward 32 bit compatibility and with minimal changes to code avoiding as much as possible branching the code with statements like that:

#ifdef 64BIT
.. something 64 bit specific
#else
.. something 32 bit specific
#endif

I achieved this goal but had to resolve the following problem.

Problem 1 : Code did not compile

#include <algorithm>
using namespace std;

unsigned int w;
.....
  max(w,sizeof(..));

The reason is quite simple, the sizeof operator is now 64 bit (unsigned long) so max template does not compile. In 32 bit version unsigned int and unsigned long are the same types. The solution is to use explicit conversion of one of the compared values.

Another solution could be to create custom MAX template:

template < class T1, class T2 >
T1 MAX( T1 x, T2 y ) {
  if ( x < y ) return y;
  return x;
}

But this solution could cause side effect if y>x - implicit conversion from T2 to T1. So I think that the better is explicit conversion.


Problem 2: Hard test that unsigned long is 4 bytes long.


In one piece was the test that unsigned long is 4 bytes long.

Something like:
config.h
...
#define SIZEOF_LONG 4

and somewhere in the code :

if (sizeof(usigned long) != SIZEOF_LONG) {
   throw error ....
}

The solution was to use (with heavy heart) conditional ifdef directive:

#ifdef BIT64
#define SIZEOF_LONG 8
#else
#define SIZEOF_LONG 4
#endif


 Problem 3: 32/64 file locking.

It took me some time to resolve another issue.

I needed simple mutex mechanism to allow only one process to do some administering task (like indexing common resources first time after installing). There are many synchronization methods available but to keep it as simple as possible I utilized lock mechanism on files.

Code snippet:

bool mutexOp(const std::string &semname,bool lock) {

   // use simple hash function for mapping string to number
   unsigned long no = 0l;
   for (int i=0; semname[i]; i++) no = (17*no) + (semname[i]-32);

   int lockfd;
   // get lockfd of file being used as semaphore

    struct flock fs;
    fs.l_type =  lock ? F_WRLCK : F_UNLCK;
    fs.l_whence = SEEK_SET;
    fs.l_start = no;
    fs.l_len = 1; // lock one byte only

    int res = fcntl( lockfd, F_SETLK, & fs );
    // ...........
    // analize res
}


According to Posix specification it is possible to lock bytes beyond the end of file so it works even having the "semaphore" file of 0 bytes long. But although off_t (type of l_start field) is 64 bit long the fcntl function does not work as expected for number grater than 2 ^ 32. On 32 bit machine unsigned long is 32 bit so hash function is automatically truncated to 32 bit, but on 64 bit it could be greater.
The solution is very simply, just do it yourself.

   unsigned long no = 0l;
   for (int i=0; semname[i]; i++) no = (17*no) + (semname[i]-32);
   no &= 0xffffffff;

Problem 4: Last but not least.

Application uses embedded indexed-sequential (ISAM) data base. One of the data types supported is 'unsigned long' keeping numbers 4 bytes long. 64 bit version should also support this 4 bytes unsigned longs to handle data bases created by 32 bit version. It was the problem I feared the most, it looked for me like an endless chain of fixing, patching, refactoring ... But there is also good news  that solution was very simple.

Application was originally designed that data base field types (int, long, double) could be differ than its counterpart in C++ program. So there is a common point of conversion between stream of bytes read from data base to C++ data types and vice versa and fix was very simple.

void convertStreamOfBytes(int fType, const void *sou, void *dest, bool todb)
{
  switch (fType) {
    case ...

    case ulongfield:
#ifdef BIT64
            if (todb) {
                *reinterpret_cast (dest) = *reinterpret_cast (sou);
            } else {
                *reinterpret_cast (dest) = *reinterpret_cast (sou);
            }
#else
            *reinterpret_cast (dest) = *reinterpret_cast (sou);
#endif
            break;
      case ...
   }
}

And it was all ! The design decision taken a long time ago pays off now !


Conclusion

So finally the goal was achieved. Application is 64 enabled, backward compatibility is kept and only few branchings inside the code.

But it is hard to say what real advantage it gives. The binaries are 10-15% greater. I don't think that it runs faster. Of course, I can address 64-bit space but this application is not memory thirsty. The only real advantage seems that it can be installed and run in pure 64 environment without necessity to install any 32 bit system components (e.g. 32 bit Python).

Welcome 64 bit world.

Brak komentarzy:

Publikowanie komentarza