JDK-4137835 : java.io.RandomAccessFile.writeInt(),readInt() methods results in four kernel cal
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.1.5,1.1.6,1.2.0,5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic,solaris_2.5.1,solaris_2.6
  • CPU: generic,sparc
  • Submitted: 1998-05-13
  • Updated: 2013-11-01
  • Resolved: 1998-09-01
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
Problem
-------
Synopsis: java.io.*.writeInt() methods results in four Solaris write() calls.

Summary:

       The Java.io.*.writeInt() methods results in four Solaris write() system 
       call invocations for a single 32 bit value. The equivalent optimzed
       version of the code using the Java write(byte[], int, int) method can
       show significant improvements in the amount of time required to run and
       a related reduction in the number of write(2) Solaris system calls made.

Code Example
------------

The following Java code reveals the underlying performance of the Solaris
implementation of java.io.*.writeInt():

import java.io.RandomAccessFile;
import java.io.IOException;

class IntWrite
{
    public static void main(String args[])
        throws IOException
    {
        RandomAccessFile raf = new RandomAccessFile("testfile.dat", "rw");

        int lim = Integer.parseInt(args[0]);

        for (int i=0; i<lim; i++) {
            raf.writeInt(i);
        }
        raf.close();
    }
}

-------

Contrasting this with:

import java.io.RandomAccessFile;
import java.io.IOException;
import java.lang.Integer;

class ByteArrayWrite
{
    public static void main(String args[])
        throws IOException
    {
        RandomAccessFile raf = new RandomAccessFile("testfile.dat", "rw");

        byte [] a;

        int lim = Integer.parseInt(args[0]);

        for (int i = 0; i < lim; i++) {
                a = IntToByteArray.getByteA(i);
                raf.write(a,0,4);
        }

        raf.close();
    }
}

----- Code follows for: IntToByteArray.getByteA():

//
// Take an Int, convert it to a byte[].

public class IntToByteArray {

        public static void main ( String args[] ) {
                byte[] b;
                b = IntToByteArray.getByteA(10);
                b = IntToByteArray.getByteA(-1);
        }

        public static byte[] getByteA(int startInt) {

                //int startInt = i;
                //int endInt = 0;
                byte[]  a = new  byte[4];

                // Make a byte array
                a[3] = (byte) startInt;
                a[2] = (byte) ( startInt >>>  8 );
                a[1] = (byte) ( startInt >>> 16 );
                a[0] = (byte) ( startInt >>> 24 );

                //System.out.println ( " Bytes " + a[0] + " " +  a[1] + " " + a
 [2] + " " + a[3] );

                //endInt = ((a[0]&0xff) << 24 | (a[1]&0xff) << 16 | (a[2]&0xff)
 << 8 | a[3]);

                //System.out.println ( "Ending: " + endInt );
                return a;
        }

}

To Reproduce
------------

In my testing I took:

     Solaris 2.6 3/98 + patches (see attachment "showrev-p")
     Ultra 30 UltraSPARC-II 296 MHz 
     128Mb memory

     Disk Vendor: SEAGATE 
         Product: ST34371W SUN4.2G
        Revision: 7462

With the Solaris Java JDK 1.1.5 installed, specifically:

     java full version "Solaris_JDK_1.1.5_02"

although other versions of Java can be seen to exhibit the behaviour
detailed in this bug report.

The relative performance of each can be summarized by the timed statistics
generated by the following test harnesses, firstly for the test "IntWrite":

   #!/bin/ksh -x
   #rm -f testfile.dat
   #truss -o output.tr -t,write java IntWrite $1
   #grep write output.tr| wc -l > writes
   rm -f testfile.dat
   /bin/time -p java IntWrite $1 >time.out.$1 2>&1

and via, the ByteArrayWrite harness:

   #!/bin/ksh -x
   #rm -f testfile.dat
   #truss -o output.tr -t,write java ByteArrayWrite $1
   #grep write output.tr| wc -l > writes
   rm -f testfile.dat
   /bin/time -p java ByteArrayWrite $1 >time.out.$1 2>&1

When driven over a range of values, governing the number of integers
to be written, the timings can be summarized as follows:

Times for JDK 1.1.5

   kuta%  grep real byteW/time.out.*  
   byteW/time.out.10:real 0.19
   byteW/time.out.100:real 0.20
   byteW/time.out.1000:real 0.22
   byteW/time.out.10000:real 0.43
   byteW/time.out.100000:real 2.82
   byteW/time.out.1000000:real 27.48

   kuta% grep real intW/time.out.*  
   intW/time.out.10:real 0.20
   intW/time.out.100:real 0.21
   intW/time.out.1000:real 0.28
   intW/time.out.10000:real 0.96
   intW/time.out.100000:real 8.78
   intW/time.out.1000000:real 89.26

Times for JDK 1.1.6

   kuta% grep real byteW/time.out.*  
   byteW/time.out.10:real 0.37
   byteW/time.out.100:real 0.33
   byteW/time.out.1000:real 0.36
   byteW/time.out.10000:real 0.54
   byteW/time.out.100000:real 2.24
   byteW/time.out.1000000:real 18.65

   kuta% grep real intW/time.out.*  
   intW/time.out.10:real 0.37
   intW/time.out.100:real 0.34
   intW/time.out.1000:real 0.39
   intW/time.out.10000:real 0.88
   intW/time.out.100000:real 5.45
   intW/time.out.1000000:real 52.07


From the above we can highlight the performance of JDK 1.1.6:

   intW/time.out.1000000:real 52.07

as the issue at the core of this bug, contrast this with the time taken to
write the equivalent interation recorded when the ByteArrayWrite class is
used:

   byteW/time.out.1000000:real 18.65

Looking more deeply at what the:

   IntWrite.RandomAccessFile.raf.writeInt(i);

becomes in terms of a system call to Solaris we can see via truss
that exactly four write(2) system calls are made for each 
RandomAccessFile.writeInt() method invocation. Looking at a typical
truss record we see:

   kuta% cat output.tr 
   write(8, "\0", 1)                               = 1
   write(8, "\0", 1)                               = 1
   write(8, "\0", 1)                               = 1
   write(8, "\0", 1)                               = 1

For a call to:

  truss -o output.tr -t,write java IntWrite 1

When optimized via the ByteArrayWrite class and:

  ByteArrayWrite.RandomAccessFile.raf.write(a,0,4);

we can imediately cut the number of calls to the Solaris system call,
giving an improvment in run time, illustrated in the timings shown
above.


Comments
EVALUATION This is an important problem that we should consider addressing for 1.2fcs. While it affects DataOutputStream as well as RandomAccessFile, the latter case is much more serious since it's impossible to use a BufferedOutputStream behind a RandomAccessFile. It also affects the read{Int,Long,Float,Double} methods as well as the corresponding write methods. We could either adopt the solution given in the description or convert these methods into native methods; which is best will have to be determined by experiment. -- mr@eng 5/14/1998 We converted the methods involved into native methods. This yielded some good speedups, but unfortunately broke compatibility with existing subclasses of RandomAccessFile (see 4170047). The optimizations have therefore been backed out. Creating temporary byte arrays is an interesting idea but it can lead to vastly increased object-allocation rates that would serve no purpose in subclasses of RandomAccessFile that do their own buffering. I'm therefore closing this bug as "will not fix." -- mr@eng 8/31/1998
31-08-1998