Copy object in Java (performance comparison)

This blog came about because I was looking into performance issue and profiling showed that a method that was copying objects was very slow. (NB: The fact that this method was called millions of times was an actual bug not the slowness of the copy routine.)

The method to copy object was performing field by field copy using class.getDeclaredFeilds() method to obtain all fields of the class and then doing copy recursively for all super classes. Code below shows entire thing:


public static void copyFieldByField(Object src, Object dest) {
copyFields(src, dest, src.getClass());
}
private static void copyFields(Object src, Object dest, Class<?> klass) {
Field[] fields = klass.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
copyFieldValue(src, dest, f);
}
klass = klass.getSuperclass();
if (klass != null) {
copyFields(src, dest, klass);
}
}
private static void copyFieldValue(Object src, Object dest, Field f) {
try {
Object value = f.get(src);
f.set(dest, value);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}

Profiling showed that this method spend 70% of it’s time in Field#copy() method. As it turns out that Class#getDeclaredFields() returns a copy of the Field[] array on every call, ouch! BTW, the JavaDoc of Class#getDeclaredFields() does not mention copy behavior at all.

List of approaches

Looking at the profiling results I started wondering what would be a better way to create a shallow copy of the object. Hence I needed to test performance of different approaches. For the simplicity I decided to look only on what is possibly with JDK 7 only (no fancy libraries) and came up with the following list:

  1. Clone object – implement Cloneable interface and publish clone() method
  2. Copy with copy constructor – copy fields from the source object directly in the constructor
  3. Copy via reflection (field by field)
    • getDeclaredFieds() as in original code
    • getDeclaredFields() cached, i.e. call it once and remember list based on the Class
  4. Serialization

    • Default serialization – just implementing Serializable
    • Custom serialization – implement Serialization and implement readObject() and writeObject() methods for reading and writing fields directly
    • Implementing Externalizable interface
  5. MethodHandles
    • Use MethodHandle#invoke(Object... args) method
    • Use MethodHandle#invokeWithArguments(Object... arguments) method
    • Use MethodHandle#invokeExact(Object... args) method

Notes on implementation

Entire project with source code and test results is available on github (copy-object-benchmark.git).

Benchmarks are written using JMH framework from OpenJDK. For each class tested there is a method in the benchmark class that simply invokes copy() method on the constant object representing the class. For example:


@GenerateMicroBenchmark
public Object copyFieldByFieldGetFieldsEveryTime() {
return FieldsCopy.INSTANCE.copy();
}
@GenerateMicroBenchmark
public Object copyFieldByFieldUseCacheFields() {
return CachedFieldsCopy.INSTANCE.copy();
}
@GenerateMicroBenchmark
public Object copyByClone() {
return CloneCopy.INSTANCE.copy();
}

Every approach from the list above was benchmarked for 2 cases:

  • Primitive fields
  • Object fields

Because most of the approaches would incur significant overhead via boxing/unboxing.

For every kind of copy method there is a class that extends common super class (i.e. BaseClass) and implements copy() method. Since there are 2 use cases tests had to be duplicated in 2 different packages.

One last thing that is worth mentioning before I show the results is the handling of MethodHandle#invokeExact() case. This method is very special when it gets to invocation. Here is an example:


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
class Base {
int x = 13;
}
class Derived extends Base {
public long m;
}
public class TestMH {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.lookup().findGetter(Base.class, "x", int.class);
Derived obj = new Derived();
// mh.invokeExact(obj); => throws WrongMethodTypeException:expected(Base)int but found (Derived)void
// mh.invokeExact((Base) obj); => throws WrongMethodTypeException:expected(Base)int but found (Base)void
int value = (int) mh.invokeExact((Base) obj);
}
}

As you can see in order to call MethodHandle#invokeExact() it is necessary to match exactly arguments and return type of the method, otherwise such invocation will fail at runtime.

However it is not possible to know upfront all possible classes and return types if you want to write generic copy method. Therefore we need to adjust MethodHandle so it can be invoked in a generic manner. This is done by adjusting MethodType signatures of the MethodHandle as shown below:


private static MethodHandle prepareGetter(MethodHandle mh) {
return mh.asType(mh.type().changeParameterType(0, Object.class)
.changeReturnType(Object.class));
}
private static MethodHandle prepareSetter(MethodHandle mh) {
return mh.asType(mh.type().changeParameterType(0, Object.class)
.changeParameterType(1, Object.class));
}

Basically this erases original information about declaration class and return types and instead uses Object for both thus allowing calling MethodHandle#invokeExact() providing instance of any class and expecting result as Object.

Results

I was running the benchmarks on my MacBook Pro laptop: OS X 10.9, 2.7 GHz Intel Core i7, 16 GB of RAM. Tests were performed on the following JDKs:

  • 1.7.0_25 (1.7.0_25-b15)
  • 1.7.0_45 (1.7.0_45-b18)
  • JDK 8 build 112 (1.8.0-ea-b112)

Results are reported as throughput (operations/ms) with biggest numbers being the best. All charts are in logarithmic scale.

Results:

  • JDK 1.7.0_25:
    jdk1.7.0_25_results
    Raw results are available here results-1.7.0_25.txt.
    jdk1.7.0_25

    So in this version of JDK the best 3 methods were:

    1. Clone
    2. Copy constructor
    3. Reflection

    Also MethodHandles are very slow comparing to other approaches.

  • JDK 1.7.0_45:
    jdk1.7.0_45_results
    Raw results results-1.7.0_45.txt.
    jdk1.7.0_45

    In the 1.7.0_45 release we see that MethodHandle#invokeExact() got much faster (~5x times) to the point that it made it to top 3 copy methods (for primitives case). Also MethodHandle#invokeWithArguments() got slower. But otherwise the rest remains the same.

  • JDK 8 build 112:
    jdk8_b112_results
    Raw results results-1.8.0-ea-b112.txt.
    jdk8_b112

    JDK 8 build 112 brings significant performance improvements over JDK 7 results in two areas:

    • Reflection
    • MethodHandles

    Most dramatic change was in the MethodHandler#invoke() case which now is more than 120x times faster! The changes were done originally proposed by John Rose on a JDK mailing list and implemented as part of the JDK-8024761: JSR 292 improve performance of generic invocation issue.

    Along the way MethodHandle#invokeWithArguments() got faster as well.

Conclusions

  • New JDK releases bring performance improvements
  • New JDK versions bring new functionality that can be used in place of old approaches (e.g. MethodHandles)
  • JVM is extremely good at optimizing existing code, i.e. reflection is still faster than invoke dynamic (at least for Object case)
  • Read JavaDoc but step through the code in the debugger and not just through your own code but also JDK code
  • Write performance tests upfront to know whether selected approach meets performance requirements
  • Run performance tests against new JDK versions to avoid/detect performance regressions

Updates

I’ve published second blog post Clone vs copy constructor – a closer look in which I analyze performance difference of clone and copy constructor approaches.


7 responses to “Copy object in Java (performance comparison)

Leave a reply to Divyesh Kanzariya Cancel reply

Latency Tip Of The Day

"Nothing is more dangerous than an idea when it is the only one you have." (Emile Chartier)

Psychosomatic, Lobotomy, Saw

"Nothing is more dangerous than an idea when it is the only one you have." (Emile Chartier)

Blog-City.com

"Nothing is more dangerous than an idea when it is the only one you have." (Emile Chartier)

Mechanical Sympathy

"Nothing is more dangerous than an idea when it is the only one you have." (Emile Chartier)