ObjectIn/OutputStream improvements

Martin Buchholz martinrb at google.com
Wed Feb 5 19:39:40 UTC 2014


High-level thoughts:

Although we all love garbage collection, performance sensitive code should
still avoid unnecessary allocation.  Escape analysis has not fulfilled its
promise, and seems unlikely to in the future.  If and when value
types/tuples come to Java, we can create such objects with hopefully more
confidence that they will be stack allocated in the common case.  Memory
management continues to be the biggest concern of people managing large
Java applications.  It continues to be true that Java applications need
much more memory than "equivalent" C++ applications, and much more "memory
tuning".  OTOH, CPU usage parity is much less of an issue.

---

I have done some (incomplete) work on making faster UTF-8 implementations,
both inside and outside the JDK, some of which could be brought into the
JDK proper and built upon.  Increasingly, UTF-8 is the only important
charset worth optimizing for.

http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Utf8.java
http://code.google.com/p/protobuf/source/browse/trunk/java/src/main/java/com/google/protobuf/Utf8.java



On Fri, Jan 31, 2014 at 10:46 AM, Chris Hegarty <chris.hegarty at oracle.com>wrote:

> Forwarding to correct core-libs-dev list.
>
> -Chris.
>
> On 31 Jan 2014, at 14:22, Chris Hegarty <chris.hegarty at oracle.com> wrote:
>
> > Hi Robert,
> >
> > I think your patch can be split into two logical, independent, parts.
> The first is the use of unsafe to access the String UTF length. The seconds
> is to reuse, where possible, the existing StringBuilder instances, skipping
> of primitive/object reading/writing where applicable, and general cleanup.
> >
> > Since this is a very sensitive area I would like to consider these
> separately. To that end, I have taken the changes that are applicable to
> the latter, removed any subjective stylistic changes, and made some
> additional cleanup improvements.
> >
> > http://cr.openjdk.java.net/~chegar/serial_stupp.00/webrev/
> >
> > Specifically,
> > * I think for clarify and readability SerialCallbackContext
> >   checkAndSetUsed() should be invoked directly. It makes it very
> >   clear what the intent is.
> > * Skip primitive/object reading/writing if no primitives/objects in
> >   the stream/class. ( I personally don't see any benefit of rounding
> >   up the size of the array, it just seems to add unnecessary
> >   complexity )
> > * Move primitive types into getPrimitiveSignature for better reuse
> >   of code. This retains your change to not create the additional
> >   StringBuilder when it is not necessary.
> >
> > I am currently running tests on this change.
> >
> > -Chris.
> >
> > On 07/01/14 13:03, Robert Stupp wrote:
> >> Hi,
> >> I've attached the diff to the original email - it has been stripped.
> >> Here's a second try (inline).
> >> I've already signed the OCA and it has been approved :)
> >> Robert
> >> # HG changeset patch
> >> # User snazy
> >> # Date 1387101091 -3600
> >> #      Sun Dec 15 10:51:31 2013 +0100
> >> # Node ID 6d46d99212453017c88417678d08dc8f10da9606
> >> # Parent  9e1be800420265e5dcf264a7ed4abb6f230dd19d
> >> Removed some unnecessary variable assignments.
> >> ObjectInputStream:
> >> - skip primitive/object reading if no primitive/objects in class
> >> - use shared StringBuilder for string reading (prevent superfluous
> >> object allocations)
> >> ObjectOutputStream:
> >> - skip primitive/object writing if no primitive/objects in class
> >> - use unsafe access to calculate UTF-length
> >> - use unsafe access in readBytes() and writeChars() methods to access
> >> String value field
> >> - removed cbuf field
> >> ObjectStreamClass/ObjectStreamField:
> >> - minor improvement in getClassSignature ; share method code with
> >> ObjectStreamField
> >> diff --git a/src/share/classes/java/io/ObjectInputStream.java
> >> b/src/share/classes/java/io/ObjectInputStream.java
> >> --- a/src/share/classes/java/io/ObjectInputStream.java
> >> +++ b/src/share/classes/java/io/ObjectInputStream.java
> >> @@ -39,8 +39,8 @@
> >>  import java.util.HashMap;
> >>  import java.util.concurrent.ConcurrentHashMap;
> >>  import java.util.concurrent.ConcurrentMap;
> >> -import java.util.concurrent.atomic.AtomicBoolean;
> >>  import static java.io.ObjectStreamClass.processQueue;
> >> +
> >>  import sun.reflect.misc.ReflectUtil;
> >>
> >>  /**
> >> @@ -534,7 +534,7 @@
> >>          if (ctx == null) {
> >>              throw new NotActiveException("not in call to readObject");
> >>          }
> >> -        Object curObj = ctx.getObj();
> >> +        ctx.getObj();
> >>          ObjectStreamClass curDesc = ctx.getDesc();
> >>          bin.setBlockDataMode(false);
> >>          GetFieldImpl getField = new GetFieldImpl(curDesc);
> >> @@ -1597,7 +1597,7 @@
> >>          int descHandle = handles.assign(unshared ? unsharedMarker :
> desc);
> >>          passHandle = NULL_HANDLE;
> >>
> >> -        ObjectStreamClass readDesc = null;
> >> +        ObjectStreamClass readDesc;
> >>          try {
> >>              readDesc = readClassDescriptor();
> >>          } catch (ClassNotFoundException ex) {
> >> @@ -1976,29 +1976,34 @@
> >>          }
> >>
> >>          int primDataSize = desc.getPrimDataSize();
> >> -        if (primVals == null || primVals.length < primDataSize) {
> >> -            primVals = new byte[primDataSize];
> >> -        }
> >> -        bin.readFully(primVals, 0, primDataSize, false);
> >> -        if (obj != null) {
> >> -            desc.setPrimFieldValues(obj, primVals);
> >> -        }
> >> -
> >> -        int objHandle = passHandle;
> >> -        ObjectStreamField[] fields = desc.getFields(false);
> >> -        Object[] objVals = new Object[desc.getNumObjFields()];
> >> -        int numPrimFields = fields.length - objVals.length;
> >> -        for (int i = 0; i < objVals.length; i++) {
> >> -            ObjectStreamField f = fields[numPrimFields + i];
> >> -            objVals[i] = readObject0(f.isUnshared());
> >> -            if (f.getField() != null) {
> >> -                handles.markDependency(objHandle, passHandle);
> >> +        if (primDataSize > 0) {
> >> +            if (primVals == null || primVals.length < primDataSize) {
> >> +                primVals = new byte[ ((primDataSize>>4)+1)<<4 ];
> >> +            }
> >> +            bin.readFully(primVals, 0, primDataSize, false);
> >> +            if (obj != null) {
> >> +                desc.setPrimFieldValues(obj, primVals);
> >>              }
> >>          }
> >> -        if (obj != null) {
> >> -            desc.setObjFieldValues(obj, objVals);
> >> +
> >> +        int numObjFields = desc.getNumObjFields();
> >> +        if (numObjFields > 0) {
> >> +            int objHandle = passHandle;
> >> +            ObjectStreamField[] fields = desc.getFields(false);
> >> +            Object[] objVals = new Object[numObjFields];
> >> +            int numPrimFields = fields.length - objVals.length;
> >> +            for (int i = 0; i < objVals.length; i++) {
> >> +                ObjectStreamField f = fields[numPrimFields + i];
> >> +                objVals[i] = readObject0(f.isUnshared());
> >> +                if (f.getField() != null) {
> >> +                    handles.markDependency(objHandle, passHandle);
> >> +                }
> >> +            }
> >> +            if (obj != null) {
> >> +                desc.setObjFieldValues(obj, objVals);
> >> +            }
> >> +            passHandle = objHandle;
> >>          }
> >> -        passHandle = objHandle;
> >>      }
> >>
> >>      /**
> >> @@ -2377,8 +2382,10 @@
> >>          private final byte[] buf = new byte[MAX_BLOCK_SIZE];
> >>          /** buffer for reading block data headers */
> >>          private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
> >> -        /** char buffer for fast string reads */
> >> +        /** char buffer for fast string reads - used by {@link
> >> #readUTFSpan(long)} */
> >>          private final char[] cbuf = new char[CHAR_BUF_SIZE];
> >> +        /** shared string builder for less object allocations - used by
> >> {@link #readUTFBody(long)}, {@link #readUTFChar(long)} and {@link
> >> #readUTFSpan(long)} */
> >> +        private final StringBuilder sbuf = new
> >> StringBuilder(CHAR_BUF_SIZE);
> >>
> >>          /** block data mode */
> >>          private boolean blkmode = false;
> >> @@ -3044,19 +3051,19 @@
> >>           * utflen bytes.
> >>           */
> >>          private String readUTFBody(long utflen) throws IOException {
> >> -            StringBuilder sbuf = new StringBuilder();
> >>              if (!blkmode) {
> >>                  end = pos = 0;
> >>              }
> >>
> >> +            sbuf.setLength(0);
> >>              while (utflen > 0) {
> >>                  int avail = end - pos;
> >>                  if (avail >= 3 || (long) avail == utflen) {
> >> -                    utflen -= readUTFSpan(sbuf, utflen);
> >> +                    utflen -= readUTFSpan(utflen);
> >>                  } else {
> >>                      if (blkmode) {
> >>                          // near block boundary, read one byte at a time
> >> -                        utflen -= readUTFChar(sbuf, utflen);
> >> +                        utflen -= readUTFChar(utflen);
> >>                      } else {
> >>                          // shift and refill buffer manually
> >>                          if (avail > 0) {
> >> @@ -3076,10 +3083,10 @@
> >>           * Reads span of UTF-encoded characters out of internal buffer
> >>           * (starting at offset pos and ending at or before offset end),
> >>           * consuming no more than utflen bytes.  Appends read
> >> characters to
> >> -         * sbuf.  Returns the number of bytes consumed.
> >> +         * {@link #sbuf}.  Returns the number of bytes consumed.
> >>           */
> >> -        private long readUTFSpan(StringBuilder sbuf, long utflen)
> >> -            throws IOException
> >> +        private long readUTFSpan(long utflen)
> >> +                throws IOException
> >>          {
> >>              int cpos = 0;
> >>              int start = pos;
> >> @@ -3111,19 +3118,19 @@
> >>                                  throw new UTFDataFormatException();
> >>                              }
> >>                              cbuf[cpos++] = (char) (((b1 & 0x1F) << 6) |
> >> -                                                   ((b2 & 0x3F) << 0));
> >> +                                    (b2 & 0x3F));
> >>                              break;
> >>
> >>                          case 14:  // 3 byte format: 1110xxxx 10xxxxxx
> >> 10xxxxxx
> >>                              b3 = buf[pos + 1];
> >> -                            b2 = buf[pos + 0];
> >> +                            b2 = buf[pos    ];
> >>                              pos += 2;
> >>                              if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) !=
> >> 0x80) {
> >>                                  throw new UTFDataFormatException();
> >>                              }
> >>                              cbuf[cpos++] = (char) (((b1 & 0x0F) << 12)
> |
> >> -                                                   ((b2 & 0x3F) << 6) |
> >> -                                                   ((b3 & 0x3F) << 0));
> >> +                                    ((b2 & 0x3F) << 6) |
> >> +                                    (b3 & 0x3F));
> >>                              break;
> >>
> >>                          default:  // 10xx xxxx, 1111 xxxx
> >> @@ -3150,12 +3157,12 @@
> >>
> >>          /**
> >>           * Reads in single UTF-encoded character one byte at a time,
> >> appends
> >> -         * the character to sbuf, and returns the number of bytes
> consumed.
> >> +         * the character to {@link #sbuf}, and returns the number of
> >> bytes consumed.
> >>           * This method is used when reading in UTF strings written in
> >> block
> >>           * data mode to handle UTF-encoded characters which
> (potentially)
> >>           * straddle block-data boundaries.
> >>           */
> >> -        private int readUTFChar(StringBuilder sbuf, long utflen)
> >> +        private int readUTFChar(long utflen)
> >>              throws IOException
> >>          {
> >>              int b1, b2, b3;
> >> @@ -3181,8 +3188,7 @@
> >>                      if ((b2 & 0xC0) != 0x80) {
> >>                          throw new UTFDataFormatException();
> >>                      }
> >> -                    sbuf.append((char) (((b1 & 0x1F) << 6) |
> >> -                                        ((b2 & 0x3F) << 0)));
> >> +                    sbuf.append((char) (((b1 & 0x1F) << 6) | (b2 &
> 0x3F)));
> >>                      return 2;
> >>
> >>                  case 14:    // 3 byte format: 1110xxxx 10xxxxxx
> 10xxxxxx
> >> @@ -3198,8 +3204,8 @@
> >>                          throw new UTFDataFormatException();
> >>                      }
> >>                      sbuf.append((char) (((b1 & 0x0F) << 12) |
> >> -                                        ((b2 & 0x3F) << 6) |
> >> -                                        ((b3 & 0x3F) << 0)));
> >> +                            ((b2 & 0x3F) << 6) |
> >> +                            (b3 & 0x3F)));
> >>                      return 3;
> >>
> >>                  default:   // 10xx xxxx, 1111 xxxx
> >> diff --git a/src/share/classes/java/io/ObjectOutputStream.java
> >> b/src/share/classes/java/io/ObjectOutputStream.java
> >> --- a/src/share/classes/java/io/ObjectOutputStream.java
> >> +++ b/src/share/classes/java/io/ObjectOutputStream.java
> >> @@ -35,7 +35,8 @@
> >>  import java.util.concurrent.ConcurrentHashMap;
> >>  import java.util.concurrent.ConcurrentMap;
> >>  import static java.io.ObjectStreamClass.processQueue;
> >> -import java.io.SerialCallbackContext;
> >> +
> >> +import sun.misc.Unsafe;
> >>  import sun.reflect.misc.ReflectUtil;
> >>
> >>  /**
> >> @@ -458,7 +459,7 @@
> >>              if (ctx == null) {
> >>                  throw new NotActiveException("not in call to
> >> writeObject");
> >>              }
> >> -            Object curObj = ctx.getObj();
> >> +            ctx.getObj();
> >>              ObjectStreamClass curDesc = ctx.getDesc();
> >>              curPut = new PutFieldImpl(curDesc);
> >>          }
> >> @@ -1300,7 +1301,7 @@
> >>       */
> >>      private void writeString(String str, boolean unshared) throws
> >> IOException {
> >>          handles.assign(unshared ? null : str);
> >> -        long utflen = bout.getUTFLength(str);
> >> +        long utflen = BlockDataOutputStream.getUTFLength(str);
> >>          if (utflen <= 0xFFFF) {
> >>              bout.writeByte(TC_STRING);
> >>              bout.writeUTF(str, utflen);
> >> @@ -1527,29 +1528,34 @@
> >>          desc.checkDefaultSerialize();
> >>
> >>          int primDataSize = desc.getPrimDataSize();
> >> -        if (primVals == null || primVals.length < primDataSize) {
> >> -            primVals = new byte[primDataSize];
> >> +        if (primDataSize > 0) {
> >> +            if (primVals == null || primVals.length < primDataSize) {
> >> +                primVals = new byte[ ((primDataSize>>4)+1)<<4 ];
> >> +            }
> >> +            desc.getPrimFieldValues(obj, primVals);
> >> +            bout.write(primVals, 0, primDataSize, false);
> >>          }
> >> -        desc.getPrimFieldValues(obj, primVals);
> >> -        bout.write(primVals, 0, primDataSize, false);
> >>
> >> -        ObjectStreamField[] fields = desc.getFields(false);
> >> -        Object[] objVals = new Object[desc.getNumObjFields()];
> >> -        int numPrimFields = fields.length - objVals.length;
> >> -        desc.getObjFieldValues(obj, objVals);
> >> -        for (int i = 0; i < objVals.length; i++) {
> >> -            if (extendedDebugInfo) {
> >> -                debugInfoStack.push(
> >> -                    "field (class \"" + desc.getName() + "\", name:
> \"" +
> >> -                    fields[numPrimFields + i].getName() + "\", type:
> \"" +
> >> -                    fields[numPrimFields + i].getType() + "\")");
> >> -            }
> >> -            try {
> >> -                writeObject0(objVals[i],
> >> -                             fields[numPrimFields + i].isUnshared());
> >> -            } finally {
> >> +        int numObjFields = desc.getNumObjFields();
> >> +        if (numObjFields > 0) {
> >> +            ObjectStreamField[] fields = desc.getFields(false);
> >> +            Object[] objVals = new Object[numObjFields];
> >> +            int numPrimFields = fields.length - objVals.length;
> >> +            desc.getObjFieldValues(obj, objVals);
> >> +            for (int i = 0; i < objVals.length; i++) {
> >>                  if (extendedDebugInfo) {
> >> -                    debugInfoStack.pop();
> >> +                    debugInfoStack.push(
> >> +                        "field (class \"" + desc.getName() + "\", name:
> >> \"" +
> >> +                        fields[numPrimFields + i].getName() + "\",
> >> type: \"" +
> >> +                        fields[numPrimFields + i].getType() + "\")");
> >> +                }
> >> +                try {
> >> +                    writeObject0(objVals[i],
> >> +                                 fields[numPrimFields +
> i].isUnshared());
> >> +                } finally {
> >> +                    if (extendedDebugInfo) {
> >> +                        debugInfoStack.pop();
> >> +                    }
> >>                  }
> >>              }
> >>          }
> >> @@ -1743,15 +1749,11 @@
> >>          private static final int MAX_BLOCK_SIZE = 1024;
> >>          /** maximum data block header length */
> >>          private static final int MAX_HEADER_SIZE = 5;
> >> -        /** (tunable) length of char buffer (for writing strings) */
> >> -        private static final int CHAR_BUF_SIZE = 256;
> >>
> >>          /** buffer for writing general/block data */
> >>          private final byte[] buf = new byte[MAX_BLOCK_SIZE];
> >>          /** buffer for writing block data headers */
> >>          private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
> >> -        /** char buffer for fast string writes */
> >> -        private final char[] cbuf = new char[CHAR_BUF_SIZE];
> >>
> >>          /** block data mode */
> >>          private boolean blkmode = false;
> >> @@ -1763,6 +1765,18 @@
> >>          /** loopback stream (for data writes that span data blocks) */
> >>          private final DataOutputStream dout;
> >>
> >> +        /** use unsafe to directly access value field in
> >> java.lang.String */
> >> +        private static final Unsafe unsafe = Unsafe.getUnsafe();
> >> +        /** use field offset to directly access value field in
> >> java.lang.String */
> >> +        private static final long stringValueOffset;
> >> +        static {
> >> +            try {
> >> +                stringValueOffset =
> >> unsafe.objectFieldOffset(String.class.getDeclaredField("value"));
> >> +            } catch (NoSuchFieldException e) {
> >> +                throw new InternalError(e);
> >> +            }
> >> +        }
> >> +
> >>          /**
> >>           * Creates new BlockDataOutputStream on top of given
> >> underlying stream.
> >>           * Block data mode is turned off by default.
> >> @@ -1972,35 +1986,23 @@
> >>          }
> >>
> >>          public void writeBytes(String s) throws IOException {
> >> -            int endoff = s.length();
> >> -            int cpos = 0;
> >> -            int csize = 0;
> >> +            char[] sChars = (char[])unsafe.getObject(s,
> stringValueOffset);
> >> +            int endoff = sChars.length;
> >>              for (int off = 0; off < endoff; ) {
> >> -                if (cpos >= csize) {
> >> -                    cpos = 0;
> >> -                    csize = Math.min(endoff - off, CHAR_BUF_SIZE);
> >> -                    s.getChars(off, off + csize, cbuf, 0);
> >> -                }
> >>                  if (pos >= MAX_BLOCK_SIZE) {
> >>                      drain();
> >>                  }
> >> -                int n = Math.min(csize - cpos, MAX_BLOCK_SIZE - pos);
> >> +                int n = Math.min(endoff - off, MAX_BLOCK_SIZE - pos);
> >>                  int stop = pos + n;
> >>                  while (pos < stop) {
> >> -                    buf[pos++] = (byte) cbuf[cpos++];
> >> +                    buf[pos++] = (byte) sChars[off++];
> >>                  }
> >> -                off += n;
> >>              }
> >>          }
> >>
> >>          public void writeChars(String s) throws IOException {
> >> -            int endoff = s.length();
> >> -            for (int off = 0; off < endoff; ) {
> >> -                int csize = Math.min(endoff - off, CHAR_BUF_SIZE);
> >> -                s.getChars(off, off + csize, cbuf, 0);
> >> -                writeChars(cbuf, 0, csize);
> >> -                off += csize;
> >> -            }
> >> +            char[] sChars = (char[])unsafe.getObject(s,
> stringValueOffset);
> >> +            writeChars(sChars, 0, sChars.length);
> >>          }
> >>
> >>          public void writeUTF(String s) throws IOException {
> >> @@ -2130,25 +2132,21 @@
> >>          }
> >>
> >>          /**
> >> -         * Returns the length in bytes of the UTF encoding of the given
> >> string.
> >> +         * Returns the length in bytes of the UTF encoding of this
> >> given string.
> >>           */
> >> -        long getUTFLength(String s) {
> >> -            int len = s.length();
> >> +        static long getUTFLength(String s) {
> >> +            char[] value = (char[])unsafe.getObject(s,
> stringValueOffset);
> >> +            int len = value.length;
> >>              long utflen = 0;
> >>              for (int off = 0; off < len; ) {
> >> -                int csize = Math.min(len - off, CHAR_BUF_SIZE);
> >> -                s.getChars(off, off + csize, cbuf, 0);
> >> -                for (int cpos = 0; cpos < csize; cpos++) {
> >> -                    char c = cbuf[cpos];
> >> -                    if (c >= 0x0001 && c <= 0x007F) {
> >> -                        utflen++;
> >> -                    } else if (c > 0x07FF) {
> >> -                        utflen += 3;
> >> -                    } else {
> >> -                        utflen += 2;
> >> -                    }
> >> +                char c = value[off++];
> >> +                if (c >= 0x0001 && c <= 0x007F) {
> >> +                    utflen++;
> >> +                } else if (c > 0x07FF) {
> >> +                    utflen += 3;
> >> +                } else {
> >> +                    utflen += 2;
> >>                  }
> >> -                off += csize;
> >>              }
> >>              return utflen;
> >>          }
> >> @@ -2198,40 +2196,36 @@
> >>           * 8-byte length header) of the UTF encoding for the given
> string.
> >>           */
> >>          private void writeUTFBody(String s) throws IOException {
> >> +            char[] sChars = (char[])unsafe.getObject(s,
> stringValueOffset);
> >> +            int len = sChars.length;
> >>              int limit = MAX_BLOCK_SIZE - 3;
> >> -            int len = s.length();
> >>              for (int off = 0; off < len; ) {
> >> -                int csize = Math.min(len - off, CHAR_BUF_SIZE);
> >> -                s.getChars(off, off + csize, cbuf, 0);
> >> -                for (int cpos = 0; cpos < csize; cpos++) {
> >> -                    char c = cbuf[cpos];
> >> -                    if (pos <= limit) {
> >> -                        if (c <= 0x007F && c != 0) {
> >> -                            buf[pos++] = (byte) c;
> >> -                        } else if (c > 0x07FF) {
> >> -                            buf[pos + 2] = (byte) (0x80 | ((c >> 0) &
> >> 0x3F));
> >> -                            buf[pos + 1] = (byte) (0x80 | ((c >> 6) &
> >> 0x3F));
> >> -                            buf[pos + 0] = (byte) (0xE0 | ((c >> 12) &
> >> 0x0F));
> >> -                            pos += 3;
> >> -                        } else {
> >> -                            buf[pos + 1] = (byte) (0x80 | ((c >> 0) &
> >> 0x3F));
> >> -                            buf[pos + 0] = (byte) (0xC0 | ((c >> 6) &
> >> 0x1F));
> >> -                            pos += 2;
> >> -                        }
> >> -                    } else {    // write one byte at a time to
> >> normalize block
> >> -                        if (c <= 0x007F && c != 0) {
> >> -                            write(c);
> >> -                        } else if (c > 0x07FF) {
> >> -                            write(0xE0 | ((c >> 12) & 0x0F));
> >> -                            write(0x80 | ((c >> 6) & 0x3F));
> >> -                            write(0x80 | ((c >> 0) & 0x3F));
> >> -                        } else {
> >> -                            write(0xC0 | ((c >> 6) & 0x1F));
> >> -                            write(0x80 | ((c >> 0) & 0x3F));
> >> -                        }
> >> +                char c = sChars[off++];
> >> +                if (pos <= limit) {
> >> +                    if (c <= 0x007F && c != 0) {
> >> +                        buf[pos++] = (byte) c;
> >> +                    } else if (c > 0x07FF) {
> >> +                        buf[pos + 2] = (byte) (0x80 | ( c       &
> 0x3F));
> >> +                        buf[pos + 1] = (byte) (0x80 | ((c >> 6) &
> 0x3F));
> >> +                        buf[pos    ] = (byte) (0xE0 | ((c >> 12) &
> 0x0F));
> >> +                        pos += 3;
> >> +                    } else {
> >> +                        buf[pos + 1] = (byte) (0x80 | ( c       &
> 0x3F));
> >> +                        buf[pos    ] = (byte) (0xC0 | ((c >> 6) &
> 0x1F));
> >> +                        pos += 2;
> >> +                    }
> >> +                } else {    // write one byte at a time to normalize
> block
> >> +                    if (c <= 0x007F && c != 0) {
> >> +                        write(c);
> >> +                    } else if (c > 0x07FF) {
> >> +                        write(0xE0 | ((c >> 12) & 0x0F));
> >> +                        write(0x80 | ((c >> 6) & 0x3F));
> >> +                        write(0x80 | ( c       & 0x3F));
> >> +                    } else {
> >> +                        write(0xC0 | ((c >> 6) & 0x1F));
> >> +                        write(0x80 | ( c       & 0x3F));
> >>                      }
> >>                  }
> >> -                off += csize;
> >>              }
> >>          }
> >>      }
> >> @@ -2464,7 +2458,10 @@
> >>              StringBuilder buffer = new StringBuilder();
> >>              if (!stack.isEmpty()) {
> >>                  for(int i = stack.size(); i > 0; i-- ) {
> >> -                    buffer.append(stack.get(i-1) + ((i != 1) ? "\n" :
> ""));
> >> +                    buffer.append(stack.get(i - 1));
> >> +                    if (i!=1) {
> >> +                        buffer.append('\n');
> >> +                    }
> >>                  }
> >>              }
> >>              return buffer.toString();
> >> diff --git a/src/share/classes/java/io/ObjectStreamClass.java
> >> b/src/share/classes/java/io/ObjectStreamClass.java
> >> --- a/src/share/classes/java/io/ObjectStreamClass.java
> >> +++ b/src/share/classes/java/io/ObjectStreamClass.java
> >> @@ -1474,7 +1474,28 @@
> >>      /**
> >>       * Returns JVM type signature for given class.
> >>       */
> >> -    private static String getClassSignature(Class<?> cl) {
> >> +    static String getClassSignature(Class<?> cl) {
> >> +        if (cl.isPrimitive())
> >> +            if (cl == Integer.TYPE) {
> >> +                return "I";
> >> +            } else if (cl == Byte.TYPE) {
> >> +                return "B";
> >> +            } else if (cl == Long.TYPE) {
> >> +                return "J";
> >> +            } else if (cl == Float.TYPE) {
> >> +                return "F";
> >> +            } else if (cl == Double.TYPE) {
> >> +                return "D";
> >> +            } else if (cl == Short.TYPE) {
> >> +                return "S";
> >> +            } else if (cl == Character.TYPE) {
> >> +                return "C";
> >> +            } else if (cl == Boolean.TYPE) {
> >> +                return "Z";
> >> +            } else if (cl == Void.TYPE) {
> >> +                return "V";
> >> +            }
> >> +
> >>          StringBuilder sbuf = new StringBuilder();
> >>          while (cl.isArray()) {
> >>              sbuf.append('[');
> >> @@ -1503,7 +1524,7 @@
> >>                  throw new InternalError();
> >>              }
> >>          } else {
> >> -            sbuf.append('L' + cl.getName().replace('.', '/') + ';');
> >> +            sbuf.append('L').append(cl.getName().replace('.',
> >> '/')).append(';');
> >>          }
> >>          return sbuf.toString();
> >>      }
> >> diff --git a/src/share/classes/java/io/ObjectStreamField.java
> >> b/src/share/classes/java/io/ObjectStreamField.java
> >> --- a/src/share/classes/java/io/ObjectStreamField.java
> >> +++ b/src/share/classes/java/io/ObjectStreamField.java
> >> @@ -91,7 +91,7 @@
> >>          this.name = name;
> >>          this.type = type;
> >>          this.unshared = unshared;
> >> -        signature = getClassSignature(type).intern();
> >> +        signature = ObjectStreamClass.getClassSignature(type).intern();
> >>          field = null;
> >>      }
> >>
> >> @@ -137,7 +137,7 @@
> >>          name = field.getName();
> >>          Class<?> ftype = field.getType();
> >>          type = (showType || ftype.isPrimitive()) ? ftype :
> Object.class;
> >> -        signature = getClassSignature(ftype).intern();
> >> +        signature =
> ObjectStreamClass.getClassSignature(ftype).intern();
> >>      }
> >>
> >>      /**
> >> @@ -286,41 +286,4 @@
> >>      String getSignature() {
> >>          return signature;
> >>      }
> >> -
> >> -    /**
> >> -     * Returns JVM type signature for given class.
> >> -     */
> >> -    private static String getClassSignature(Class<?> cl) {
> >> -        StringBuilder sbuf = new StringBuilder();
> >> -        while (cl.isArray()) {
> >> -            sbuf.append('[');
> >> -            cl = cl.getComponentType();
> >> -        }
> >> -        if (cl.isPrimitive()) {
> >> -            if (cl == Integer.TYPE) {
> >> -                sbuf.append('I');
> >> -            } else if (cl == Byte.TYPE) {
> >> -                sbuf.append('B');
> >> -            } else if (cl == Long.TYPE) {
> >> -                sbuf.append('J');
> >> -            } else if (cl == Float.TYPE) {
> >> -                sbuf.append('F');
> >> -            } else if (cl == Double.TYPE) {
> >> -                sbuf.append('D');
> >> -            } else if (cl == Short.TYPE) {
> >> -                sbuf.append('S');
> >> -            } else if (cl == Character.TYPE) {
> >> -                sbuf.append('C');
> >> -            } else if (cl == Boolean.TYPE) {
> >> -                sbuf.append('Z');
> >> -            } else if (cl == Void.TYPE) {
> >> -                sbuf.append('V');
> >> -            } else {
> >> -                throw new InternalError();
> >> -            }
> >> -        } else {
> >> -            sbuf.append('L' + cl.getName().replace('.', '/') + ';');
> >> -        }
> >> -        return sbuf.toString();
> >> -    }
> >>  }
> >> *Gesendet:* Dienstag, 07. Januar 2014 um 10:05 Uhr
> >> *Von:* "Chris Hegarty" <chris.hegarty at oracle.com>
> >> *An:* "Robert Stupp" <snazy at gmx.de>
> >> *Cc:* "core-libs-dev at openjdk.java.net Libs" <
> core-libs-dev at openjdk.java.net>
> >> *Betreff:* Re: ObjectIn/OutputStream improvements
> >> On 15 Dec 2013, at 10:29, Robert Stupp <snazy at gmx.de> wrote:
> >>
> >> > Hi,
> >> >
> >> > I digged through the object serialization code and found some lines
> >> that could be optimized to reduce the number of calls to
> >> System.arraycopy() and temporary object allocations especially during
> >> string (de)serialization.
> >> > In short sentences the changes are:
> >> > ObjectInputStream:
> >> > - skip primitive/object reading if no primitive/objects in class
> >> (defaultReadFields method)
> >> > - use shared StringBuilder for string reading (prevent superfluous
> >> object allocations of one StingBuilder and one implicit char[] for each
> >> string being deserialized)
> >> > ObjectOutputStream:
> >> > - skip primitive/object writing if no primitive/objects in class
> >> (defaultWriteFields method)
> >> > - use unsafe access to calculate UTF-length
> >> > - use unsafe access in readBytes() and writeChars() methods to access
> >> String value field
> >> > - removed cbuf field
> >> > ObjectStreamClass/ObjectStreamField:
> >> > - minor improvement in getClassSignature ; share method code with
> >> ObjectStreamField (return constant string for primitives)
> >> >
> >> > I have tested the changes in a big Java installation at a customer
> >> (backported the Java8 ObjectIn/OutputStream including the changes to
> >> Java6) and a long running real application performance test resulted in
> >> reduced CPU usage (from about 60% per server to 50%).
> >> > The changes I made in openjdk8 pass all tests.
> >> >
> >> > Since I have no experience how to contribute code to openjdk in form
> >> of a push/changeset I have added the diff (hg export -g) to this email.
> >>
> >> Did you attach the diffs? I don’t see any attachment, it may be that the
> >> attachment was stripped. Can you try resending inline?
> >>
> >> You should take a look at the OpenJDK How to Contribute page [1]. Paying
> >> particular attention to the OCA, without it we will not be able to move
> >> your patch forward.
> >>
> >> Thanks,
> >> -Chris.
> >>
> >> [1] http://openjdk.java.net/contribute/
> >>
> >> >
> >> > Robert
> >> >
> >> >
>
>



More information about the core-libs-dev mailing list