by José Barros

Java Tip 65: Measure data transfer speeds via Sun’s ORB in JDK 1.2 beta 4

how-to
Jan 1, 19998 mins

Determine effective data transfer speeds between client and server using CORBA

This Java Tip offers a simple example of how to determine effective data transfer speeds between a client and a server via CORBA. The results obtained by the reader may influence decisions about the transport layer to use for data transfer — for example, CORBA, RMI, or sockets.

Different network layers provide different speeds, and sockets are the fastest. But sockets are no help when it comes to marshalling or unmarshalling user data from the plain byte buffers. Using CORBA, the marshall/unmarshall aspect of the data is taken care of outside the user application, which is very attractive.

An additional benefit of using CORBA or RMI versus sockets is the availability of a Naming Service that can be used by client applications to look up server applications. When running this example, the reader must first start the Naming Service application.

The initial step is to create an idl file with definitions for the arrays and methods for sending the arrays to the server:

typedef sequence <octet> ByteSeq;
typedef sequence <long> IntSeq;
typedef sequence <float> FloatSeq;
typedef sequence <double> DoubleSeq;
interface View {
        void send_byte   (in long long time, in long n, in ByteSeq
array);
        void send_int    (in long long time, in long n, in IntSeq
array);
        void send_float  (in long long time, in long n, in FloatSeq
array);
        void send_double (in long long time, in long n, in DoubleSeq
array);
};

Note that the first argument in the send_xxx() methods is the system time just before the method is invoked. When running the client and the server on different systems, the clocks must be synchronized to get accurate speed measurements.

The client gets the object reference to the server from the Naming Service and creates an instance of the ViewClient2 class which draws the menubar.

import java.awt.*;
import java.awt.event.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
public class ViewClient2 extends Frame implements ActionListener {
int i1, i2, i3;
private static View viewRef; // must be 'static'
    public static void main(String args[])
    {
        try{
            // create and initialize the ORB
            ORB orb = ORB.init(args, null);
            // get the root naming context
            org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
            NamingContext ncRef = NamingContextHelper.narrow(objRef);
            System.out.println("client: 1");
            // resolve the Object Reference in Naming
            NameComponent nc = new NameComponent("ViewServer",
"server");
            NameComponent path[] = {nc};
            System.out.println("client: 2");
            viewRef = ViewHelper.narrow(ncRef.resolve(path));
            System.out.println("client: 3");
            if (viewRef==null) System.out.println("viewRef is null");
            new ViewClient2 ("ViewClient2");
        }
        catch (Exception e) {
            System.out.println("ERROR : " + e) ;
            e.printStackTrace(System.out);
        }
    }

Next, we add the constructor to build a simple user interface. There is a simple menubar with a Choices pulldown menu. The pulldown menu “m1” is built with four menu items, with one per data type — byte, integer, float, and double.

   public ViewClient2(String title) {
        super (title);
        MenuBar mb = new MenuBar();
        Menu m1 = new Menu("Choices");
        m1.add ("send byte");
        m1.add ("send integer");
        m1.add ("send float");
        m1.add ("send double");
        m1.addActionListener(this);
        mb.add(m1);
        setMenuBar (mb); // Set the menu bar on the frame
        setSize (350,100);
        show();
   }

Finally, in the callback for the menubar, we allocate an array to hold the data that’s to be transferred to the server and capture the current time. The client invokes the method on the server and repeats this step with different array sizes.

   public void actionPerformed (ActionEvent evt) {
        String what = evt.getActionCommand();
        System.out.println ("what = " + what);
        long time_start;
        int n, i, size[] = {100, 500, 1000, 5000,
                        10000, 50000, 100000, 500000,
                        1000000, 5000000};
        if ("send byte".equals(what)) {
           byte[] array;
           for (i=0; i<10; i++) {
                n = size[i];
                System.out.println("Allocating array: n=" + n);
                try { array = new byte[n]; }
                catch (java.lang.OutOfMemoryError ex) {
                        System.out.println("Ouch!  Could not allocate:
" + n);
                        break;
                }
                time_start = System.currentTimeMillis();
                viewRef.send_byte(time_start, n, array);
           }
        }
        if ("send integer".equals(what)) {
           int[] array;
           for (i=0; i<10; i++) {
                n = size[i];
                System.out.println("Allocating array: n=" + n);
                try { array = new int[n]; }
                catch (java.lang.OutOfMemoryError ex) {
                        System.out.println("Ouch!  Could not allocate:
" + n);
                        break;
                }
                time_start = System.currentTimeMillis();
                viewRef.send_int(time_start, n, array);
           }
        }
        if ("send double".equals(what)) {
           double[] array;
           for (i=0; i<10; i++) {
                n = size[i];
                System.out.println("Allocating array: n=" + n);
                try { array = new double[n]; }
                catch (java.lang.OutOfMemoryError ex) {
                        System.out.println("Ouch!  Could not allocate:
" + n);
                        break;
                }
                time_start = System.currentTimeMillis();
                viewRef.send_double(time_start, n, array);
           }
        }
        if ("send float".equals(what)) {
           float[] array;
           for (i=0; i<10; i++) {
                n = size[i];
                System.out.println("Allocating array: n=" + n);
                try { array = new float[n]; }
                catch (java.lang.OutOfMemoryError ex) {
                        System.out.println("Ouch!  Could not allocate:
" + n);
                        break;
                }
                time_start = System.currentTimeMillis();
                viewRef.send_float(time_start, n, array);
           }
        }
   }
}

The server consists of two java files — one with the main() method and the other with the class that implements the methods defined in the idl file.

The main() method creates a ViewServant object, registers it with the Naming Service, and allocates one frame.

import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
public class ViewServer extends Applet {
   boolean appletFlag = true;
   /**
    * Sets the "applet" flag which is used to determine
    * whether or not this program is running as an applet.
    */
   public void setAppletFlag(boolean flag) {
      appletFlag = flag;
   }
    /**
     * Calls the various methods necessary to initialize the
     * program.
     */
    public void init() {
        // Set the layout manager
        setLayout(new BorderLayout());
   }
    /**
     *  Inner class used to "kill" the window when running as
     *  an application.
     */
    static class killAdapter extends WindowAdapter {
       public void windowClosing(WindowEvent event) {
          System.exit(0);
       }
    }
    /**
     *  Used when running as an application.
     */
    public static void main(String[] args) {
       ViewServer drag = new ViewServer();
       drag.setAppletFlag(false);
       drag.init();
       Frame frame = new Frame();
       frame.setSize(400, 400);
       frame.add("Center", drag);
       frame.addWindowListener(new killAdapter());
       frame.setVisible(true);
        System.out.println ("ViewServer: contacting the ORB");
      try{
            // create and initialize the ORB
            ORB orb = ORB.init(args, null);
            // create servant and register it with the ORB
            ViewServant viewRef = new ViewServant();
            orb.connect(viewRef);
            // get the root naming context
            org.omg.CORBA.Object objRef =
                orb.resolve_initial_references("NameService");
            NamingContext ncRef = NamingContextHelper.narrow(objRef);
            // bind the Object Reference in Naming
            NameComponent nc = new NameComponent("ViewServer",
"server");
            NameComponent path[] = {nc};
            ncRef.rebind(path, viewRef);
            // wait for invocations from clients
            System.out.println ("ViewServer: waiting for clients.");
            java.lang.Object sync = new java.lang.Object();
            synchronized (sync) {
                sync.wait();
            }
        } catch (Exception e) {
            System.err.println("ERROR: " + e);
            e.printStackTrace(System.out);
        }
    }
}

The ViewServant class implements the methods declared in the idl file. Each method in this class captures the current time and subtracts the timestamp sent by the client from the current time.

The difference represents the elapsed time since the client submitted the array to the server until the server acknowledges it. The difference is then submitted to the standard output.

class ViewServant extends _ViewImplBase {
/*---------------------------------------------------------*/
    ViewServant () {
        System.out.println("ViewServant() exiting.");
    }
/*---------------------------------------------------------*/
   public void send_byte (long time_start, int n, byte[] array) {
        System.out.println ("send_byte: entered.");
        long time_diff, time_0;
        time_0 = System.currentTimeMillis();
        time_diff = time_0 - time_start;
        System.out.println ("Time to xfer " + n + "BYTEs: " + time_diff + "
(ms)");
        System.out.println ("send_byte: exiting.");
    }
/*---------------------------------------------------------*/
   public void send_int (long time_start, int n, int[] array) {
        System.out.println ("send_int: entered.");
        long time_diff, time_0;
        time_0 = System.currentTimeMillis();
        time_diff = time_0 - time_start;
        System.out.println ("Time to xfer " + n + "INTs: " + time_diff + "
(ms)");
        System.out.println ("send_int: exiting.");
    }
/*---------------------------------------------------------*/
   public void send_double (long time_start, int n, double[] array) {
        System.out.println ("send_double: entered.");
        long time_diff, time_0;
        time_0 = System.currentTimeMillis();
        time_diff = time_0 - time_start;
        System.out.println ("Time to xfer " + n + "DOUBLEs: " + time_diff + "
(ms)");
        System.out.println ("send_double: exiting.");
    }
/*---------------------------------------------------------*/
   public void send_float (long time_start, int n, float[] array) {
        System.out.println ("send_float: entered.");
        long time_diff, time_0;
        time_0 = System.currentTimeMillis();
        time_diff = time_0 - time_start;
        System.out.println ("Time to xfer " + n + "FLOATs: " + time_diff + "
(ms)");
        System.out.println ("send_float: exiting.");
    }
}

Conclusion

The decision to use CORBA should depend on the data transfer speeds between the client and the server running on the desirable platforms. The reader can use the Java IDL implementation or other CORBA implementations and compare results. Hopefully, this Java Tip has given you something to build on when benchmarking application-specific data types and data sizes.

José Barros is a senior consultant at Shell Services International, where he works on systems integration, computational methods for seismic processing and seismic interpretation, and prototyping with Java in his spare time. He has a PhD in Computer Science from the University of Texas at Dallas.