Check out the new USENIX Web site. next up previous
Next: System Components and Implementation Up: System Architecture Previous: System Flexibility

Performance Considerations

At first glance, it would appear that our flexible building block structure impairs the performance of the JVM. A researcher who was not concerned with flexibility could simply hard-code the ObjectSource to call the nodeAlloc function directly. In contrast, our system appears to have two efficiency problems:

For a researcher interested in performance, it would be tempting to bypass the module structure entirely, thereby degrading the extensibility of the system.

However, careful exploitation of compiler optimizations allows Jupiter to achieve the performance of the less flexible scheme, without sacrificing flexibility. The reason that this is possible is that each node-specific MemorySource is associated with a particular node for the duration of its lifetime, making the node number for each MemorySource immutable. Immutable data can be freely duplicated without concern for consistency among the multiple copies, since the data never changes. As a result, immutable data that is normally passed by reference can instead be passed by value, with no change to the system's behaviour. This removes the need to dereference pointers, and also eliminates the alias analysis difficulties that make pointer-based code hard to optimize. The system can continue to use the usual abstract, high-level interfaces, and the compiler can produce highly efficient code.

To illustrate how this is achieved in our example of object allocation, we begin with the standard Jupiter MemorySource interface declarations:

  typedef struct ms_struct *MemorySource;
  void *ms_getMemory(MemorySource this, 
                     int size);

Because the node number for each MemorySource is immutable, it can be passed by value. This can be implemented by replacing the standard declarations with the following:

  typedef int MemorySource;
  
  static inline void 
  *ms_getMemory(MemorySource this, 
                int size){
    if(this == MS_MUX)
      return nodeAlloc(/* The appropriate 
                          node */, size);
    else
      return nodeAlloc(this, size);
  }

In this version, a MemorySource is no longer a pointer to a traditional ``heavyweight'' object; instead, it is simply an integer representing the node number itself. The MuxMemorySource is represented by the special non-existent node number MS_MUX. To allocate memory, this code first checks whether the MuxMemorySource is being used. If so, it uses the desired locality heuristic to choose a node; otherwise, if a particular node-specific MemorySource is used, then memory is allocated from the corresponding node.

With these definitions in place, the existing abstract high-level function calls can be transformed by the compiler into efficient code. Beginning with this:

  void *ptr = ms_getMemory(
                 obs_memorySource(), size);

The compiler can perform a succession of function inlining optimizations to produce this:

  void *ptr = nodeAlloc(/* The appropriate 
                           node */, size);

Hence, there is no longer any performance penalty for using Jupiter's MemorySource interface. This example demonstrates how careful design and implementation allows Jupiter to achieve good performance without any cost to flexibility.


next up previous
Next: System Components and Implementation Up: System Architecture Previous: System Flexibility
Tarek S. Abdelrahman
2002-05-27