|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 3
The perftest() method serializes and deserializes an instance of TestClass:
public final class TestClass extends TestBaseClass
implements Serializable
{
public TestClass ()
{
m_int = 4;
m_object1 = new TestBaseClass ();
m_object2 = m_object1;
m_objects = new Object [m_int];
for (int i = 0; i < m_objects.length; ++ i)
m_objects [i] = new TestBaseClass ();
}
public int getInt ()
{
return m_int;
}
public Object getObject1 ()
{
return m_object1;
}
... other accessor methods...
// Uncomment this here and in TestBaseClass to re-run
// SerialVersionUIDTest with the "optimization" enabled:
//private static final long serialVersionUID = 1;
private final int m_int;
private final Object m_object1, m_object2;
private final Object [] m_objects;
} // End of class
public class TestBaseClass
implements Serializable
{
public TestBaseClass ()
{
m_byte = (byte) 1;
m_short = (short) 2;
m_long = 3L;
m_float = 4.0F;
m_double = 5.0;
m_char = '6';
m_boolean = true;
m_int = 8;
m_string = "some string";
m_ints = new int [m_int];
for (int i = 0; i < m_ints.length; ++ i) m_ints [i] = m_int;
m_strings = new String [m_int];
for (int i = 0; i < m_strings.length; ++ i)
m_strings [i] = "string value #" + i;
}
public byte getByte ()
{
return m_byte;
}
public short getShort ()
{
return m_short;
}
... other accessor methods ...
// Uncomment this here and in TestClass to re-run
// SerialVersionUIDTest with the "optimization" enabled:
//private static final long serialVersionUID = 1;
private final byte m_byte;
private final short m_short;
private final long m_long;
private final float m_float;
private final double m_double;
private final char m_char;
private final boolean m_boolean;
private final int m_int;
private final int [] m_ints;
private final String m_string;
private final String [] m_strings;
} // End of class
TestClass and its superclass, TestBaseClass, are distant relatives of their namesakes used for cloning tests in "Attack of the Clones." Together they create a reasonably representative Java object. This object has fields of different types and various accessor
methods, and it involves inheritance and aggregation: in short, it gives something for the SUID computation to work on. The
TestClass instance's serialized size is about 1,800 bytes.
SerialVersionUIDTest records the costs of readObject() and writeObject() method calls and generates some simple statistics: minimum, average, and 95th percentile of the combined cost of serializing
and deserializing a TestClass instance. (I use a percentile instead of the maximum to filter outlying data points due to HotSpot/GC (garbage collection)
activity.)
Let's try this in Sun Microsystems' Java Runtime Environment (JRE) 1.4.1 (on a Windows NT/dual 550-MHz CPU machine; all timings are in milliseconds):
>java -Xms100m -Xmx100m -cp hrtlib.jar;... SerialVersionUIDTest [min/avg/95th percentile, ms] min: 1.692, avg: 1.809, 95%: 1.897
Now, let's add private static final long serialVersionUID = 1; to both TestClass and TestBaseClass and try again:
>java -Xms100m -Xmx100m -cp hrtlib.jar;... SerialVersionUIDTest [min/avg/95th percentile, ms] min: 1.703, avg: 1.817, 95%: 1.855
You can see why it is hard to get excited about these new numbers. The cost differences are not even above the random data noise. If anything, the new numbers appear to speak against the optimization.
Maybe only older JVMs benefit from the serialVersionUID idea? For completeness, here are the results for three Sun JVM versions running the same test code on the same machine:
Serialization cost in milliseconds
|
As you can see, the conclusions remain basically the same across all Sun JVM implementations.
The reason for the above behavior is quite straightforward. It would be very naive for a JVM to recompute SUIDs for the same
classes over and over again every time you serialize an object. If you examine the JDK sources, you will discover that SUID
values are computed once and subsequently kept in a soft cache (the cache is soft so as not to impede class unloading). In
Java 2 Platform, Standard Development Kit (J2SDK) 1.2 and 1.3, this cache is the field descriptorFor of java.io.ObjectStreamClass (this class acts as a Class serialization descriptor and holds the Class's SUID value):
/*
* Cache of Class -> ClassDescriptor Mappings.
*/
static private ObjectStreamClassEntry[] descriptorFor = new ObjectStreamClassEntry[61];
And in J2SDK 1.4, the equivalent cache is maintained by an instance of sun.misc.SoftCache:
/** cache mapping local classes -> descriptors */
private static final SoftCache localDescs = new SoftCache(10);
Things are now much clearer: the (supposedly) expensive SUID computation was done only once for each of my two test classes. This happened inside my warm-up loop, long before the timing measurements started. The steady state of my runtime does not incur the cost of SUID calculation at all.
Declaring an explicit serialVersionUID field in your classes therefore saves you some CPU time only the very first time the JVM process serializes a given Class. Optimizing this (rare) case is precisely the opposite of what smart optimization is all about. This also means that the steady
state cost of serialization is completely dominated by the cost of reading and writing actual class data (so other techniques,
such as reducing the amount of class metadata sent by implementing Externalizable and optimizing the wire data layout, still apply).
The cache map is soft, and no documentation details explain when its entries might clear, but this should rarely happen: perhaps for very stale class descriptors or when the JVM is low on memory. In both cases, the extra hit of recomputing the SUID will rarely be your primary performance concern.
Archived Discussions (Read only)