Global Pointer Example Program

The following walkthrough will illustrate a simple example of global pointers. There are actually two seperate source code files involved, since, in this example, we are going to be executing on two differnet contexts. In practice you can use the same source file for different contexts if you like. Our goal here is to illustrate one way of using global pointers to share information among contexts.

Our first source file is where the "main" source is. This code actually does the context creation and calls the invoke functions to do the work. Here is the first section of code :

class sparse_array{
 public:
   int *index1;
   int *index2;
   double *vals;
   int size;
   sparse_array(){
      size = 0;
      index1 = index2 = NULL;
      vals = NULL;
      }
   sparse_array(int n){
      index1 = new int[n];
      index2 = new int[n];
      vals = new double[n];
      size = n;
      }
   sparse_array(sparse_array &a){
      size = a.size;
      index1 = a.index1;
      index2 = a.index2;
      vals = a.vals;
     }     
   sparse_array &operator=(sparse_array &a){
      size = a.size;
      index1 = a.index1;
      index2 = a.index2;
      vals = a.vals;
     return *this;
    }
   void init(){
      for(int i = 0; i < size; i++){
          index1[i] = i; 
          index2[i] = 10*i; 
          vals[i] = 10*i+3.14; 
         } 
      } 
}; 


// in addition to the class sparse array, we need to write
// the functions which tell how to pack and unpack an array
// of sparse arrays.

void hpcxx_pack(HPCxx_Buffer &b, sparse_array *spa, int size){
     for(int i = 0; i < size; i++){
         hpcxx_pack(b, &spa->size, 1);
         hpcxx_pack(b, spa->vals, spa->size);
         hpcxx_pack(b, spa->index1, spa->size);
         hpcxx_pack(b, spa->index2, spa->size);
     }
}

void hpcxx_unpack(HPCxx_Buffer &b, sparse_array *spa, int size){
     for(int i = 0; i lt; size; i++){
         hpcxx_unpack(b, &spa->size, 1);
         spa->index1 = new int[spa->size];
         spa->index2 = new int[spa->size];
         spa->vals = new double[spa->size];
         hpcxx_unpack(b, spa->vals, spa->size);
         hpcxx_unpack(b, spa->index1, spa->size);
         hpcxx_unpack(b, spa->index2, spa->size);
     }
}
    

First we define a class, a simple Sparse Array class, that we can toy with. We then define the pack and unpack functions so that we can copy the data in the class through Global Pointers. You may recall in the example shown on the tutorial page that the pack and unpack functions were declared as friend functions. We haven't done it in this case - all of the members of the sparse_array class are public, so we don't need to worry about data hiding. All of the information is freely available for us to tinker with.

Next we must write stubs for all of the functions that we wish to run globally on other contexts so that we can register them.

// the following are the "stub" versions of the functions that
// are called on the hpcxx_testb.C as remote calls.
// we need the stubs so that the functions can be registered
// with the correct type signatures.

// function stores the sparse array.
int deposit(sparse_array sa){
  // stub version
     return 1;
}
int checksa(int n){
  // stub version
  return 0;
}

// function to kill a remote object

int kill_me(int i){
  // stub version
   return 0;
}

// function to send a contextId to a remote object
int setoutid(HPCxx_ContextID id){
   // stub version
   return 0;
}

// function to tell caledonia to send a sparse array to wild.
int sendToWild(int i){
 // stub version
  return 0;
}
    

Now we get to the initialization of our contexts. We create two constant strings which describe the context and then get the ball rolling. We register all of the functions, and then create the contexts on which to run them.

#define PATH_sgi32 "/u/btemko/hpc++/gp_sgi32/hptb"
#define PATH_sgi64 "/tmp_mnt/nfs/olympus/home/user1/btemko/hpc++/gp_sgi64/hptb"
       
int main(int argc, char **argv)
{
  timeval start_time, stop_time;
  float work_time, speed, sar_size;

  HPCxx_Group *group;
  //hpcxx_id_t fetch_id = hpcxx_register(fetch_int, 22);
  hpcxx_id_t kill_id = hpcxx_register(kill_me,23);
  hpcxx_id_t deposit_id = hpcxx_register(deposit, 25);
  hpcxx_id_t checksa_id = hpcxx_register(checksa, 26);
  hpcxx_id_t setoutid_id = hpcxx_register(setoutid, 27);
  hpcxx_id_t sendToWild_id = hpcxx_register(sendToWild, 28);

  hpcxx_init(argc, argv, group);

  // create the remote objects.
  // the makeContext function returns the contextIDs needed to do the
  // remote function calls.

  HPCxx_ContextID *p_sgi64 = hpcxx_makeContext("caledonia.cs", PATH_sgi64);
  HPCxx_ContextID *p_wild  = hpcxx_makeContext("wildspitze.extreme", PATH_sgi32);
    

The real functions, the ones which actually do something, are located in theother source file. Let's take a look at them one by one so we know what they do.

sparse_array spa(0);
sparse_array spa2(100000);

int deposit(sparse_array sa){
     spa = sa;
     return 1;
}
    
The deposit functions takes a sparse_array and points a local pointer to it. The definitions of the local pointers are listed above the deposit function.
int checksa(int n){
   int count = 0;
   spa2.init();
   for(int i = 0; i < spa.size; i++){
      if(spa.index1[i] != spa2.index1[i]) count++; 
      if(spa.index2[i] != spa2.index2[i]) count++; 
      if(spa.vals[i] != spa2.vals[i]) count++; 
   } 
  printf("error count is %d\n", count); 
  return count; 
}
    
The checksa function compares two sparse arrays to see if they are identical member-wise.

HPCxx_Sync<int> x;

int kill_me(int i){
   printf("[b] i am dead\n");
   x = i;
   return 0;
}
    
The kill_me function stores a value into the HPCxx_Sync variable, defined above, releasing whatever is blocked on it. As you will see later, the main program will block on this variable just prior to termination. When it is written to, the program terminates.

HPCxx_GlobalPtr<int> fetch_int(int i){
     printf("[b] starting fetch_int\n");
     int *p = new int[i];
     for(int j = 0; j < i; j++) p[j] = 100+j;
     HPCxx_GlobalPtr<int> gp(p);
     printf("[b] fetch_int called\n");
     return gp;
}

    
The fetch_int function returns a global pointer to an integer array. It allocates and fils it based on its integer parameter.
HPCxx_ContextID out_id;

int setoutid(HPCxx_ContextID id){
   out_id = id;
   return 0;
}
    
The setoutid function sets a local HPCxx_ContextID (which points to a remote context) to the ContextID passed to it.
hpcxx_id_t deposit_id;
int sendToRemote(int i){
  timeval start_time, stop_time;
  float work_time, speed, sar_size;
  sar_size = 100000*(2.0*sizeof(int)+sizeof(double) + 32);
  int z = 0;
  gettimeofday(&start_time, NULL);
     hpcxx_invoke(out_id, z, deposit_id, spa);
  gettimeofday(&stop_time, NULL);
  work_time = ElapseTime(start_time, stop_time);
  speed = 8*sar_size/(work_time*1.0e6);  // 8 for bits per sec.
  printf("speed for array of size %f MB is %f Mbps\n", sar_size/1.0e6, speed);

  return z;
}

    
The sendToRemote function executes an hpcxx_invoke, using a local context and timing itself in the process.

This represents the meat of the working code. The main function of the program which will be executed on the remote contexts is extremely simple.

int main(int argc, char **argv)
{

  HPCxx_Group *group;
  hpcxx_id_t kill_id  = hpcxx_register(kill_me, 23);
  hpcxx_id_t fetch_id = hpcxx_register(fetch_int, 22);
             deposit_id = hpcxx_register(deposit, 25);
  hpcxx_id_t checksa_id = hpcxx_register(checksa, 26);
  hpcxx_id_t setoutid_id = hpcxx_register(setoutid, 27);
  hpcxx_id_t sendToWild_id = hpcxx_register(sendToWild, 28);

  hpcxx_initAgent(argc, argv);
  printf("agent b initialized\n");
  // now wait to be killed
  int i;
  i = x;
  printf("b will exit now\n");
  nexus_abort();
  _exit(0);
  return 0;

As you can see, all this program does is register its functions, and then it simply attempts to read from an HPCxx_Sync variable, blocking the completion of the program until a value is placed in the variable.

Let's go back to our primary code, and see what we're getting ourselves into. Here's the breakdown of the rest of the main function.


int ret_val;
hpcxx_invoke(*p_sgi64, ret_val, setoutid_id, *p_wild); 

// create a sparse array.

int n = 100000;
sparse_array sar(n);
sar.init();
sar_size = n*(2.0*sizeof(int)+sizeof(double) + 32);

// ship the array to caledonia.  time it too.
int z;
gettimeofday(&start_time, NULL);
hpcxx_invoke(*p_sgi64, z, deposit_id, sar);
gettimeofday(&stop_time, NULL);
    

The first thing we do is invoke the function setoutid() on the context p_sgi64, in this case a 64 bit SGI machine. We pass it the context p_wild, in this case a 32 bit SGI machine. The effect of this is that now on p_sgi64 there is a local context which points to p_wild. We then create a sparse_array and pass it as a parameter to the next invoke, which executes deposit() on p_sgi64, effectively passing the sparse_array to it. Remember that we had to define pack and unpack functions so that the class could be moved among contexts.


work_time = ElapseTime(start_time, stop_time);
speed = 8*sar_size/(work_time*1.0e6);  // 8 for bits per sec.
printf("speed for array of size %f MB is %f Mbps\n", sar_size/1.0e6, speed);

// ask caledonia if the array is correct.

hpcxx_invoke(*p_sgi64, z, checksa_id, n);
printf("[a] errors from caledonia = %d\n", z);

// now tell caledonia to send the array to its remote context.
hpcxx_invoke(*p_sgi64, z, sendToRemote_id, n);

// ask remote if it got there o.k.

hpcxx_invoke(*p_wild, z, checksa_id, n);
printf("[a] errors from wild = %d\n", z);
  
int i = 0;
hpcxx_invoke(*p_sgi64, z, kill_id, i);
hpcxx_invoke(*p_wild, z, kill_id, i);
//hpcxx_exit(group);
nexus_abort();
_exit(0);
}

hpc++@extreme.indiana.edu

Last modified: Thu Apr 22 02:30:47 EST 1999