Pages

Monday, September 5, 2016

Structured Mels

Welcome » NERWous C » Mel
  1. Mel Structures
  2. Mel Structure Buffers
  3. Mel Structures With Multiple Items
  4. Mel Structure Value Property
  5. Mel Structure Direct Itemized Access
  6. Mel Structures In Exclusive Zones
  7. Mel Structures Inside Local Structures


Mel Structures

In the previous section on Mel Arrays, we have the statement:
<mel> int stores[NUMITEMS];
where each element of stores is a mel element by itself and can be accessed independently. In this section, we are going to access stores as a single, monolithic mel element. Under NERWous C, this is done by using the mel struct construct:
#define NUMITEMS 3
struct s { int contents[NUMITEMS]; };
main () {
    <mel> struct s store;
    <!>Producer (store);
    <!>Consumer (store);
}
/* VERSION 1 */
void Producer (<mel> struct s store) {
    int c;
    struct s localstore;
 
    for (int i=0; i<NUMITEMS; ++i) {
       c = produce();
       if ( c == 0 ) {
          <close>store;
          return;
       }
       localstore.contents[i] = c;
     }
     
     <?>store = localstore;
}
/* VERSION 1 */
void Consumer (<mel> struct s store) {
    struct s localstore;
    try {
       localstore = <?>store;
       for (int i=0; i<NUMITEMS; ++i) {
            consume (localstore.contents[i];
       }
    }
    catch ( store<CLOSED> ) {
        printf ("Exit because the Store is closed");
        return;
    }
}
The C language built-in struct construct wraps around the contents array and allows the whole array to be accessed as a single mel element. Declaring a mel struct variable is a two-step process. First we define a regular C language struct template. Then we append the <mel> construct in order to declare the struct entity as a mel variable.

The Producer task uses a mel write to update the structured mel store. First it builds a local copy of store by running a serial for loop to populate the array contents with NUMITEMS items. If it detects a badly produce'd item, it prematurely closes the mel store. If all the products are good, it sends the whole local copy to the remote mel store, via the statement <?>store = localstore.

The Consumer task does a mel wait on the structured mel store. If during this wait it gets a CLOSED exception due to the Producer's premature closure, it jumps to the catch code block. When it sees that Producer has updated the mel store, it retrieves that value into its local copy via the mel read operation: localstore = <?>store. It then consumes that local copy with a serial for loop.


Mel Structure Buffers

The above Producer and Consumer process one structured mel and then close. Let's modify them so that they run continuously via an infinite while loop:
#define NUMITEMS 3
struct s { int contents[NUMITEMS]; };
main () {
    <mel buffer> struct s store;
    <!>Producer (store);
    <!>Consumer (store);
}
/* VERSION 2 */
void Producer (<mel> struct s store) {
   int c;
   struct s localstore;
   while ( 1 ) {
      for (int i=0; i<NUMITEMS; ++i) {
          c = produce();
          if ( c == 0 ) {
             <close>store;
             return;
          }
          localstore.contents[i] = c;
      }  
      <?>store = localstore;
   }
}
/* VERSION 2 */
void Consumer (<mel> struct s store) {
   struct s localstore;
   while ( 1 ) {
      try {
         localstore = <?>store;
         for (int i=0; i<NUMITEMS; ++i) {
            consume (localstore.contents[i];
         }
      }
      catch ( store<CLOSED> ) {
        printf ("Exit because the Store is closed");
        return;
      }
    }
}
We also add a buffer attribute to the structured mel declaration. The goal is to optimize the throughput of the program. If the Producer is faster than the Consumer, it can deposit a finished structured mel product into the writer's side of the buffer while the Consumer can take its time removing deposited structured mel values from the reader's side.

A buffer attribute without any argument means that the above example uses the default number of buffer slots as provisioned by the CHAOS run-time environment. We can specify a different number of slots in the buffer:
/* Increase the default buffer size by 5 more from default */
<mel buffer+5> struct s store;
/* Or specify a fixed number for buffer storage */
<mel buffer=25> struct s store;
Each slot in a structured mel buffer must contain the full structured mel. Naturally, to buffer or not to buffer a structured mel, and to pick the proper buffer size, depends on the need of the NERWous C program: a buffer can increase the performance of the program at the expense of increased resource cost.


Mel Structures With Multiple Items

A C structure can contain heterogeneous items. Likewise, a NERWous C structured mel can contain heterogeneous items, and these items go together from a concurrent access point of view.

Let's now enhance the previous example to introduce a numrounds item to the mel struct stores. Every time the Producer finishes one round of updating contents, it will increase numrounds by one. The Consumer displays the round number before consume'ing the contents:
#define NUMITEMS 3
struct s { 
   int contents[NUMITEMS]; 
   long numrounds;
};
main () {
   <mel buffer> struct s store;
   <!>Producer (store);
   <!>Consumer (store);
}
/* VERSION 3 */
void Producer (<mel> struct s store) {
   int c;
   struct s localstore;
   localstore.numrounds = 0;
   while ( 1 ) {
      for (int i=0; i<NUMITEMS; ++i) {
          c = produce();
          if ( c == 0 ) {
             <close>store;
             return;
          }
          localstore.contents[i] = c;
      }
      localstore.numrounds++;   
      <?>store = localstore;
   }
} 
/* VERSION 3 */
void Consumer (<mel> struct s store) {
    struct s localstore;
    while ( 1 ) {
       try {
          localstore = <?>store;
          for (int i=0; i<NUMITEMS; ++i) {
            consume (localstore.contents[i];
          }
          printf ("Consumed from round [%ld]", localstore.numrounds);
       }
       catch ( store<CLOSED> ) {
          printf ("Exit because the Store is closed");
          return;
       }
    }
}
For both the Producer and Consumer, the numrounds value is accessed locally, either before the localstore is uploaded to the structured mel store, or after the structured mel is downloaded back to localstore.


Mel Structure Value Property

In the Consumer example above, we use a local variable, localstore, to capture the read value of the structured mel store. As we know about mel result properties, when a task does a read to a mel (be it a simple, array or structured entity as in this chapter), the read value of the mel is cached locally in the value property.

Let's replace localstore with the value property:
/* VERSION 4 */
void Consumer (<mel> struct s store) {
    while ( 1 ) {
       try {
          <?>store;
          for (int i=0; i<NUMITEMS; ++i) {
            consume (store<value>.contents[i]);
          }
          printf ("Consumed from round [%ld]", store<value>.numrounds);
       }
       catch ( store<CLOSED> ) {
          printf ("Exit because the Store is closed");
          return;
       }
    }
}
Once the value is cached after the mel read, <?>store;, that value can be accessed via the value property, store<value>, like any other local variable. Since this is a structured mel, we use the dot (.) notation to get to the inner items (contents and numrounds).


Mel Structure Direct Itemized Access

The title of this section is a mouthful. What it means is that can we access an item of a mel structure directly over the NERW network, instead of downloading the whole mel structure and accessing that item locally, like we did using the localstore variable or the value property? The answer is no, but it may look like we can.

Let's re-write the Consumer task:
/* VERSION 5 */
void Consumer (<mel> struct s store) {
    while ( 1 ) {
       try {
           for (int i=0; i<NUMITEMS; ++i) {
             consume (<?>store.contents[i]);
          }
          printf ("Consumed from round [%ld]", <?>store.numrounds);
       }
       catch ( store<CLOSED> ) {
          printf ("Exit because the Store is closed");
          return;
       }
    }
}
The above snippet is syntactically correct. However, under NERWOUS, the statement:
consume (<?>store.contents[i])
means:
<?>store; consume (store<value>.contents[i]);
In other words, the full structured mel is downloaded behind-the-scene, and then the requested element is presented from the local entity. For the for loop above, each iteration means a repeat of the wait on the mel structure and a full download, instead of a single wait and download for all iterations as in previous versions. Thus even though VERSION 5 code looks more compact, its run-time performance is much worse.

In addition, the integrity of the data consume'd is doubtful. If the Producer has generated another round of products between the Consumer for loop iterations, contents[i] and contents[i+1] may come from different rounds of the mel structure store.

Structured mel direct itemized access does have its use as a compact way to access a single item of a mel structure when we don't need to access other items. This method is bad here for our Consumer task because we also need to access other items of the mel structure. For this, the download-and-local-access method is the better approach.


Mel Structures In Exclusive Zones

Like any mel, structured mels can be accessed via mel zones: reader zones and writer zones.

Mel Structures In Reader Zones

Once a task is in a reader zone for a structured mel, it blocks all read access to that mel. If the mel is hosted remotely, it copies the contents of the structured mel into a local eponymous structured variable. It then works with this local variable. On the checkout off the reader zone, it can either removes the structured mel it is holding (causing the next reader task to pick up the next structured mel value), updates the remote structured mel without removing it (allowing the next reader to pick the same structured mel but with the updated value), or skips doing neither a removal nor update (letting the next reader to pick the same structured mel with the existing value).

In all cases, the task updates the local sequence property of the structured mel to be the same as the sequence number of the structured mel on entry of the reader zone. In the update and skip cases, this update prevents the task from processing a stale structured mel again. Upon encountering a stale mel, the task just waits for a new structured mel value to be deposited by a writer task.

We will now modify our Producer/Consumer example, so that the Consumer task will make use of the reader zone. In fact, we will have two Consumer tasks. The DO_EVEN one only processes the structured mel if the product's round count (numrounds) is even. Likewise the DO_ODD one processes only on odd numrounds:
#define NUMITEMS 3
#define DO_EVEN 0
#define DO_ODD 1
struct s { 
   int contents[NUMITEMS]; 
   long numrounds;
};
main () {
   <mel buffer> struct s store;
   <!>Producer (store);
   <!>Consumer (store, DO_EVEN);
   <!>Consumer (store, DO_ODD);
}
/* VERSION 3 */
void Producer (<mel> struct s store) {
   int c;
   struct s localstore;
   localstore.numrounds = 0;
   while ( 1 ) {
      for (int i=0; i<NUMITEMS; ++i) {
          c = produce();
          if ( c == 0 ) {
             <close>store;
             return;
          }
          localstore.contents[i] = c;
      }
      localstore.numrounds++;  
      <?>store = localstore;
   }
} 
/* VERSION 6 */
void Consumer (<mel> struct s store, int evenodd ) {
    while ( 1 ) {
       try {
          <?>store {
             if (  ((evenodd == DO_EVEN) && (store.numrounds % 2) == 0))
                || ((evenodd == DO_ODD) && (store.numrounds % 2) != 0)) ) {

                for (int i=0; i<NUMITEMS; ++i) {
                   consume (store.contents[i]);
         
                printf ("Consumed from round [%ld]", store.numrounds);
                <checkout>;   /* optional here */
             }
             else {
                printf ("Skipped round [%ld]", store.numrounds);
                <checkout skip>;    /* logically required */
             }
          }

          showme (store<value>.contents);
      }
      catch ( store<CLOSED> ) {
          printf ("Exit because the Store is closed");
          return;
       }
    }
}

void showme (int contents[]) {
    printf ("Contents array:\n");
    for (int i=0; i<NUMITEMS; ++i) printf ("%d - ", contents[i]);
}
Within the exclusive zone, the task makes use the eponymous local variable, store, and thus does not need the wait operator ? as when it accesses the remote mel variable store.

When the DO_EVEN Consumer task sees an even numrounds, it will do the consumption of contents and invokes the <checktout> statement to get out of the exclusive zone. Although that statement is not required (since this is the default behavior when the code falls off the exclusive zone code block), it is added explicitly here to balance with the use of <checkout skip> in the else code block. If the numrounds is odd, the DO_EVEN Consumer task does not want to consume the contents; instead it invokes <checkout skip> to get out of the exclusive zone without reading out the structured mel. Then the DO_ODD Consumer enters the reader zone, it will pick up this value and consumes it.

The showme function is introduced as an artifact to discuss the value property of a mel variable on checkout of a reader zone. In both the <checkout> and <checkout skip> cases, the value property reflects the value of the eponymous local variable at the time of the reader zone checkout. For example, for the DO_EVEN task, if the numrounds is even, showme will show the contents that are copied from the structured mel to the eponymous structured variable, consume'd, and then copied from the eponymous structured variable to the value property; if the numrounds is odd, showme will show the contents that are are copied from the structured mel to the eponymous structured variable, and then copied from the eponymous structured variable to the value property, without being consume'd.

Mel Structures In Writer Zones

To illustrate the use of structured mels with a writer zone, we now change the Producer task:
/* VERSION 4 */
void Producer (<mel> struct s store) {
   int counter = 0;
   while ( 1 ) {
      <?>store = {
          for (int i=0; i<NUMITEMS; ++i) {
              store.contents[i] = produce();
              if ( store.contents[i] == 0 ) 
                  <checkout skip>;
          }
          store.numrounds = ++counter;
      } /* implicit <checkout> */
   }
} 
The new Producer waits until it is at the top of the mel store writer queue and an empty slot opens up. The Producer then puts a hold on that slot, and enters the writer zone. Inside the zone, the local eponymous variable store is declared by default and available for Producer to use. It then starts to fill the store.contents with produce results.

If a result of a produce is 0, the Producer gets out of the writer zone with a <checkout skip> statement: it releases the hold of the empty slot of the mel buffer without writing anything to it. The next writer task can make use of that slot. In our example here, there is no other writer task besides Producer. Since Producer runs an infinite while loop, it will iterate and gets into the writer zone again. Hopefully this time the issue that causes produce to return 0 has been resolved, and Producer can fill all the NUMITEMS of the local store.contents. It then updates the local store.numrounds, then gets out of the writer zone by doing an implicit <checkout> statement at the closing } of the zone code block.

When a task invokes an explicit or implicit <checkout> operation, the eponymous variable that the task has been building inside the writer zone, is written into the empty slot on hold. The task then frees the hold to allow a reader task to access the new structured mel value.

The big difference between VERSION 3 and VERSION 4 of Producer is the holding of the empty slot to receive the write. In VERSION 3, the Producer builds the items of the structured mel store locally. Once everything is all built, it waits for the empty slot from the writer side of the mel buffer, and gets hold of it long enough to copy the items over. The VERSION 4 of Producer does the reverse. It makes sure that it has an empty slot to hold on to before it starts to do the produce'ing and updating the numrounds value. The latter method keeps the hold on the empty slot much longer, potentially making other writers to wait on accessing the mel. On the other hand, if what it produces is precious, it is willing to trade concurrency performance for the assurance that it can deposit its product.


Mel Structures Inside Local Structures

In the previous sections, we include new items into a mel structure to make it a multi-item mel structure. We will now do the reverse: we will include a mel entity inside a local structure. Inside this local structured, whatever declared local can only be accessed by the declaring task, and whatever declared as a mel is accessible by other tasks.

In the following example, we define struct sn to include a "serial number" identification that is local to Producer which uses it to stamp an identification into each of its products. The produced items are still stored in the mel structure store which is shared between the Producer and the Consumer. However the Consumer does not have access to the "serial number" information used solely by the Producer.
#define NUMITEMS 3
struct s { 
     int contents[NUMITEMS]; 
     long numrounds;
};
struct sn {
 int snid;
 <mel> struct s store;
}
main () {
     struct sn localsn;
     localsn.snid = 8475;     /* starting serial number */
     localsn.<?>store = {1, 2, 3}, 0L); /* seed the store */

     <!>Producer (localsn);
     <!>Consumer (localsn.store);
}
/* VERSION 5 */
void Producer (struct sn localsn) {
   try {
      struct s localstore;
      localstore.numrounds = 0;
      while (1) {
         for (int i=0; i<NUMITEMS; ++i) {
            int c = produce(localsn.snid++);
            if ( c == 0 ) {
                <close>localsn.store;
                break;
             }
             localstore.contents[i] = c;
         }
         localstore.numrounds++;
         localsn.<?>store = localstore;
      }
   }
   catch ( localsn.store<NOREADERS> ) {
        printf ("Exit because no more Consumer");
        return;
   }
} 
/* VERSION 4 */
void Consumer (<mel> struct s store) {
    while ( 1 ) {
       try {
          <?>store;
          for (int i=0; i<NUMITEMS; ++i) {
            consume (store<value>.contents[i]);
          }
          printf ("Consumed from round [%ld]", store<value>.numrounds);
       }
       catch ( store<CLOSED> ) {
          printf ("Exit because the Store is closed");
          return;
       }
    }
}
We re-use VERSION 4 of the Consumer code. For the Producer, its argument is changed to the local struct localsn which contains the structured mel store. Producer accesses store via the construct:
localsn.<?>store
if outside a mel exclusive zone (like in Producer above), and via:
localsn.store
if inside an exclusive zone (as in the Consumer below).

What if we also pass localsn to Consumer? Let's look at this code:
/* VERSION 7 */
void Consumer (struct sn localsn) {
    printf ("First serial number [%d]", localsn.snid);
    try localsn.<?>store {
     printf ("Updated serial number [%d]", localsn.snid);
     printf ("Consuming from round [%ld]", localsn.store.numrounds);
        for (int i=0; i<NUMITEMS; ++i) {
            if (! consume (localsn.store.contents[i]) ) 
                return;      /* triggering the NOREADERS exception at Producer */
         }
         <resume>;     /* back to the mel zone wait for more consumption */
    }
    catch ( localsn.store<CLOSED> ) {
        printf ("Exit because the Store is closed");
        return;
    }
    catch ( localsn.store<NOWRITERS> ) {
        printf ("Exit because there is no more Producer");
        return;
    }
}
The "First" printf statement will show the value 8475 initialized by the task main at the time that it creates the task Consumer via the pel <!> statement. In each round going through its read zone, Consumer also does an "Updated" printf statement. This statement still shows the value for localsn.snid to be 8475, not whatever value Producer has at that moment. This is because locansn.snid is local to each task, and the above Consumer has not changed the value of this variable since accepting it from main. On the other hand, the localsn.store.contents and localsn.store.numrounds will reflect the new values from Producer since they are items of a structured mel.


Previous Next Top