| package com.dubbo.serialize.benchmark; |
| |
| import java.io.*; |
| import java.net.URLEncoder; |
| import java.util.*; |
| import java.util.regex.Pattern; |
| import java.util.zip.DeflaterOutputStream; |
| |
| import serializers.jackson.*; |
| import serializers.json.JsonGsonDatabind; |
| import serializers.json.JsonArgoTree; |
| import serializers.json.FastJSONDatabind; |
| import serializers.json.FlexjsonDatabind; |
| import serializers.json.JsonGsonManual; |
| import serializers.json.JsonGsonTree; |
| import serializers.json.JsonDotOrgManualTree; |
| import serializers.json.JsonLibJsonDatabind; |
| import serializers.json.JsonPathDeserializerOnly; |
| import serializers.json.JsonSimpleManualTree; |
| import serializers.json.JsonSimpleWithContentHandler; |
| import serializers.json.JsonSmartManualTree; |
| import serializers.json.JsonTwoLattes; |
| import serializers.json.JsonijJpath; |
| import serializers.json.JsonijManualTree; |
| import serializers.json.JsonSvensonDatabind; |
| import serializers.protostuff.Protostuff; |
| import serializers.protostuff.ProtostuffJson; |
| import serializers.protostuff.ProtostuffSmile; |
| import serializers.xml.XmlJavolution; |
| import serializers.xml.XmlStax; |
| import serializers.xml.XmlXStream; |
| |
| public class BenchmarkRunner |
| { |
| public final static int DEFAULT_ITERATIONS = 2000; |
| public final static int DEFAULT_TRIALS = 20; |
| |
| /** |
| * Number of milliseconds to warm up for each operation type for each serializer. Let's |
| * start with 3 seconds. |
| */ |
| final static long DEFAULT_WARMUP_MSECS = 3000; |
| |
| // These tests aren't included by default. Use the "-hidden" flag to enable them. |
| private static final HashSet<String> HIDDEN = new HashSet<String>(); |
| static { |
| // CKS is not included because it's not really publicly released. |
| HIDDEN.add("cks"); |
| HIDDEN.add("cks-text"); |
| } |
| |
| private static final String ERROR_DIVIDER = "-------------------------------------------------------------------"; |
| |
| public static void main(String[] args) |
| { |
| // -------------------------------------------------- |
| // Parse command-line options. |
| |
| Boolean filterIsInclude = null; |
| Set<String> filterStrings = null; |
| Integer iterations = null; |
| Integer trials = null; |
| Long warmupTime = null; |
| boolean printChart = false; |
| boolean prewarm = false; |
| String dataFileName = null; |
| boolean enableHidden = false; |
| |
| Set<String> optionsSeen = new HashSet<String>(); |
| |
| for (String arg : args) { |
| String remainder; |
| if (arg.startsWith("--")) { |
| remainder = arg.substring(2); |
| } |
| else if (arg.startsWith("-")) { |
| remainder = arg.substring(1); |
| } |
| else if (dataFileName == null) { |
| dataFileName = arg; |
| continue; |
| } |
| else { |
| System.err.println("Expecting only one non-option argument (<data-file> = \"" + dataFileName + "\")."); |
| System.err.println("Found a second one: \"" + arg + "\""); |
| System.err.println("Use \"-help\" for usage information."); |
| System.exit(1); return; |
| } |
| |
| String option, value; |
| int eqPos = remainder.indexOf('='); |
| if (eqPos >= 0) { |
| option = remainder.substring(0, eqPos); |
| value = remainder.substring(eqPos+1); |
| } else { |
| option = remainder; |
| value = null; |
| } |
| |
| if (!optionsSeen.add(option)) { |
| System.err.println("Repeated option: \"" + arg + "\""); |
| System.exit(1); return; |
| } |
| |
| if (option.equals("include")) { |
| if (value == null) { |
| System.err.println("The \"include\" option requires a value."); |
| System.exit(1); return; |
| } |
| if (filterIsInclude == null) { |
| filterIsInclude = true; |
| filterStrings = new HashSet<String>(Arrays.asList(value.split(","))); |
| } else { |
| System.err.println("Can't use 'include' and 'exclude' options at the same time."); |
| System.exit(1); return; |
| } |
| } |
| else if (option.equals("exclude")) { |
| if (value == null) { |
| System.err.println("The \"exclude\" option requires a value."); |
| System.exit(1); return; |
| } |
| if (filterIsInclude == null) { |
| filterIsInclude = false; |
| filterStrings = new HashSet<String>(Arrays.asList(value.split(","))); |
| } else { |
| System.err.println("Can't use 'include' and 'exclude' options at the same time."); |
| System.exit(1); return; |
| } |
| } |
| else if (option.equals("iterations")) { |
| if (value == null) { |
| System.err.println("The \"iterations\" option requires a value."); |
| System.exit(1); return; |
| } |
| assert iterations == null; |
| try { |
| iterations = Integer.parseInt(value); |
| } catch (NumberFormatException ex) { |
| System.err.println("Invalid value for \"iterations\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| if (iterations < 1) { |
| System.err.println("Invalid value for \"iterations\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| } |
| else if (option.equals("trials")) { |
| if (value == null) { |
| System.err.println("The \"trials\" option requires a value."); |
| System.exit(1); return; |
| } |
| assert trials == null; |
| try { |
| trials = Integer.parseInt(value); |
| } catch (NumberFormatException ex) { |
| System.err.println("Invalid value for \"trials\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| if (trials < 1) { |
| System.err.println("Invalid value for \"trials\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| } |
| else if (option.equals("warmup-time")) { |
| if (value == null) { |
| System.err.println("The \"warmup-time\" option requires a value."); |
| System.exit(1); return; |
| } |
| assert warmupTime == null; |
| try { |
| warmupTime = Long.parseLong(value); |
| } catch (NumberFormatException ex) { |
| System.err.println("Invalid value for \"warmup-time\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| if (warmupTime < 0) { |
| System.err.println("Invalid value for \"warmup-time\" option: \"" + value + "\""); |
| System.exit(1); return; |
| } |
| } |
| else if (option.equals("pre-warmup")) { |
| if (value != null) { |
| System.err.println("The \"pre-warmup\" option does not take a value: \"" + arg + "\""); |
| System.exit(1); return; |
| } |
| assert !prewarm; |
| prewarm = true; |
| } |
| else if (option.equals("chart")) { |
| if (value != null) { |
| System.err.println("The \"chart\" option does not take a value: \"" + arg + "\""); |
| System.exit(1); return; |
| } |
| assert !printChart; |
| printChart = true; |
| } |
| else if (option.equals("hidden")) { |
| if (value != null) { |
| System.err.println("The \"hidden\" option does not take a value: \"" + arg + "\""); |
| System.exit(1); return; |
| } |
| assert !enableHidden; |
| enableHidden = true; |
| } |
| else if (option.equals("help")) { |
| if (value != null) { |
| System.err.println("The \"help\" option does not take a value: \"" + arg + "\""); |
| System.exit(1); return; |
| } |
| if (args.length != 1) { |
| System.err.println("The \"help\" option cannot be combined with any other option."); |
| System.exit(1); return; |
| } |
| |
| System.out.println(); |
| System.out.println("Usage: run [options] <data-file>"); |
| System.out.println(); |
| System.out.println("Options:"); |
| System.out.println(" -iterations=n [default=" + DEFAULT_ITERATIONS + "]"); |
| System.out.println(" -trials=n [default=" + DEFAULT_TRIALS + "]"); |
| System.out.println(" -warmup-time=millis [default=" + DEFAULT_WARMUP_MSECS + "]"); |
| System.out.println(" -pre-warmup (warm all serializers before the first measurement)"); |
| System.out.println(" -chart (generate a Google Chart URL for the results)"); |
| System.out.println(" -include=impl1,impl2,impl3,..."); |
| System.out.println(" -exclude=impl1,impl2,impl3,..."); |
| System.out.println(" -hidden (enable \"hidden\" serializers)"); |
| System.out.println(" -help"); |
| System.out.println(); |
| System.out.println("Example: run -chart -include=protobuf,thrift data/media.1.cks"); |
| System.out.println(); |
| System.exit(0); return; |
| } |
| else { |
| System.err.println("Unknown option: \"" + arg + "\""); |
| System.err.println("Use \"-help\" for usage information."); |
| System.exit(1); return; |
| } |
| } |
| |
| if (iterations == null) iterations = DEFAULT_ITERATIONS; |
| if (trials == null) trials = DEFAULT_TRIALS; |
| if (warmupTime == null) warmupTime = DEFAULT_WARMUP_MSECS; |
| |
| if (dataFileName == null) { |
| System.err.println("Missing <data-file> argument."); |
| System.err.println("Use \"-help\" for usage information."); |
| System.exit(1); return; |
| } |
| |
| // -------------------------------------------------- |
| // Load serializers. |
| |
| TestGroups groups = new TestGroups(); |
| |
| // Binary Formats; language-specific ones |
| JavaBuiltIn.register(groups); |
| JavaManual.register(groups); |
| Scala.register(groups); |
| // hessian and kryo are Java object serializations |
| Hessian.register(groups); |
| Dubbo.register(groups); |
| Kryo.register(groups); |
| Wobly.register(groups); |
| |
| // Binary formats, generic: protobuf, thrift, avro, kryo, CKS, msgpack |
| Protobuf.register(groups); |
| ActiveMQProtobuf.register(groups); |
| Protostuff.register(groups); |
| Thrift.register(groups); |
| AvroSpecific.register(groups); |
| AvroGeneric.register(groups); |
| CksBinary.register(groups); |
| MsgPack.register(groups); |
| |
| // JSON |
| JacksonJsonManual.register(groups); |
| JacksonJsonTree.register(groups); |
| JacksonJsonTreeWithStrings.register(groups); |
| JacksonJsonDatabind.register(groups); |
| JacksonJsonDatabindWithStrings.register(groups); |
| JsonTwoLattes.register(groups); |
| ProtostuffJson.register(groups); |
| ProtobufJson.register(groups); |
| JsonGsonManual.register(groups); |
| JsonGsonTree.register(groups); |
| JsonGsonDatabind.register(groups); |
| JsonSvensonDatabind.register(groups); |
| FlexjsonDatabind.register(groups); |
| JsonLibJsonDatabind.register(groups); |
| FastJSONDatabind.register(groups); |
| JsonSimpleWithContentHandler.register(groups); |
| JsonSimpleManualTree.register(groups); |
| JsonSmartManualTree.register(groups); |
| JsonDotOrgManualTree.register(groups); |
| JsonijJpath.register(groups); |
| JsonijManualTree.register(groups); |
| JsonArgoTree.register(groups); |
| JsonPathDeserializerOnly.register(groups); |
| // Then JSON-like |
| // CKS text is textual JSON-like format |
| CksText.register(groups); |
| // then binary variants |
| // BSON is binary JSON-like format |
| JacksonBsonManual.register(groups); |
| JacksonBsonDatabind.register(groups); |
| MongoDB.register(groups); |
| // Smile is 1-to-1 binary representation of JSON |
| JacksonSmileManual.register(groups); |
| JacksonSmileDatabind.register(groups); |
| ProtostuffSmile.register(groups); |
| |
| // XML-based formats. |
| XmlStax.register(groups); |
| XmlXStream.register(groups); |
| JacksonXmlDatabind.register(groups); |
| XmlJavolution.register(groups); |
| |
| // -------------------------------------------------- |
| // Load data value. |
| |
| Object dataValue; |
| TestGroup<?> group; |
| { |
| File dataFile = new File(dataFileName); |
| if (!dataFile.exists()) { |
| System.out.println("Couldn't find data file \"" + dataFile.getPath() + "\""); |
| System.exit(1); return; |
| } |
| |
| String[] parts = dataFile.getName().split("\\."); |
| if (parts.length < 3) { |
| System.out.println("Data file \"" + dataFile.getName() + "\" should be of the form \"<type>.<name>.<extension>\""); |
| System.exit(1); return; |
| } |
| |
| String dataType = parts[0]; |
| String extension = parts[parts.length-1]; |
| |
| group = groups.groupMap.get(dataType); |
| if (group == null) { |
| System.out.println("Data file \"" + dataFileName + "\" can't be loaded."); |
| System.out.println("Don't know about data type \"" + dataType + "\""); |
| System.exit(1); return; |
| } |
| |
| TestGroup.Entry<?,Object> loader = group.extensionMap.get(parts[parts.length-1]); |
| if (loader == null) { |
| System.out.println("Data file \"" + dataFileName + "\" can't be loaded."); |
| System.out.println("No deserializer registered for data type \"" + dataType + "\" and file extension \"." + extension + "\""); |
| System.exit(1); return; |
| } |
| |
| |
| Object deserialized; |
| try { |
| byte[] fileBytes = readFile(new File(dataFileName)); // Load entire file into a byte array. |
| deserialized = loader.serializer.deserialize(fileBytes); |
| } |
| catch (Exception ex) { |
| System.err.println("Error loading data from file \"" + dataFileName + "\"."); |
| System.err.println(ex.getMessage()); |
| System.exit(1); return; |
| } |
| |
| dataValue = loader.transformer.reverse(deserialized); |
| } |
| |
| @SuppressWarnings("unchecked") |
| TestGroup<Object> group_ = (TestGroup<Object>) group; |
| |
| // -------------------------------------------------- |
| |
| Set<String> matched = new HashSet<String>(); |
| |
| Iterable<TestGroup.Entry<Object,Object>> available; |
| |
| if (enableHidden) { |
| // Use all of them. |
| available = group_.entries; |
| } else { |
| // Remove the hidden ones. |
| ArrayList<TestGroup.Entry<Object,Object>> unhidden = new ArrayList<TestGroup.Entry<Object,Object>>(); |
| for (TestGroup.Entry<?,Object> entry_ : group.entries) { |
| @SuppressWarnings("unchecked") |
| TestGroup.Entry<Object,Object> entry = (TestGroup.Entry<Object,Object>) entry_; |
| String name = entry.serializer.getName(); |
| if (!HIDDEN.contains(name)) unhidden.add(entry); |
| } |
| available = unhidden; |
| } |
| |
| Iterable<TestGroup.Entry<Object,Object>> matchingEntries; |
| if (filterStrings == null) { |
| matchingEntries = available; |
| } |
| else { |
| ArrayList<TestGroup.Entry<Object,Object>> al = new ArrayList<TestGroup.Entry<Object,Object>>(); |
| matchingEntries = al; |
| |
| for (TestGroup.Entry<?,Object> entry_ : available) { |
| @SuppressWarnings("unchecked") |
| TestGroup.Entry<Object,Object> entry = (TestGroup.Entry<Object,Object>) entry_; |
| |
| String name = entry.serializer.getName(); |
| |
| // See if any of the filters match. |
| boolean found = false; |
| for (String s : filterStrings) { |
| boolean thisOneMatches = match(s, name); |
| if (thisOneMatches) { |
| matched.add(s); |
| found = true; |
| } |
| } |
| |
| if (found == filterIsInclude) { |
| al.add(entry); |
| } |
| } |
| |
| Set<String> unmatched = new HashSet<String>(filterStrings); |
| unmatched.removeAll(matched); |
| for (String s : unmatched) { |
| System.err.println("Warning: there is no implementation name matching the pattern \"" + s + "\""); |
| |
| if (!enableHidden) { |
| for (String hiddenName : HIDDEN) { |
| if (match(s, hiddenName)) { |
| System.err.println("(The \"" + hiddenName + "\", serializer is hidden by default."); |
| System.err.println(" Use the \"-hidden\" option to enable hidden serializers)"); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| EnumMap<measurements, Map<String, Double>> values; |
| StringWriter errors = new StringWriter(); |
| PrintWriter errorsPW = new PrintWriter(errors); |
| try { |
| values = start(errorsPW, iterations, trials, warmupTime, prewarm, matchingEntries, dataValue); |
| } |
| catch (Exception ex) { |
| ex.printStackTrace(System.err); |
| System.exit(1); return; |
| } |
| |
| if (printChart) { |
| printImages(values); |
| } |
| |
| // Print errors after chart. That way you can't miss it. |
| String errorsString = errors.toString(); |
| if (errorsString.length() > 0) { |
| System.out.println(ERROR_DIVIDER); |
| System.out.println("Errors occurred during benchmarking:"); |
| System.out.print(errorsString); |
| System.exit(1); return; |
| } |
| } |
| |
| private static boolean match(String pattern, String name) |
| { |
| StringBuilder regex = new StringBuilder(); |
| |
| while (pattern.length() > 0) { |
| int starPos = pattern.indexOf('*'); |
| if (starPos < 0) { |
| regex.append(Pattern.quote(pattern)); |
| break; |
| } |
| else { |
| String beforeStar = pattern.substring(0, starPos); |
| String afterStar = pattern.substring(starPos + 1); |
| |
| regex.append(Pattern.quote(beforeStar)); |
| regex.append(".*"); |
| pattern = afterStar; |
| } |
| } |
| |
| return Pattern.matches(regex.toString(), name); |
| } |
| |
| // ------------------------------------------------------------------------------------ |
| |
| private static byte[] readFile(File file) |
| throws IOException |
| { |
| FileInputStream fin = new FileInputStream(file); |
| try { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); |
| byte[] data = new byte[1024]; |
| while (true) { |
| int numBytes = fin.read(data); |
| if (numBytes < 0) break; |
| baos.write(data, 0, numBytes); |
| } |
| return baos.toByteArray(); |
| } |
| finally { |
| fin.close(); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------ |
| |
| private static double iterationTime(long delta, int iterations) |
| { |
| return (double) delta / (double) (iterations); |
| } |
| |
| private static final TestCase Create = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| transformer.forward(value); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| private static final TestCase Serialize = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| Object obj = transformer.forward(value); |
| serializer.serialize(obj); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| private static final TestCase SerializeSameObject = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| // let's reuse same instance to reduce overhead |
| Object obj = transformer.forward(value); |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| serializer.serialize(obj); |
| //if (i % 1000 == 0) |
| // doGc(); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| private static final TestCase Deserialize = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| byte[] array = serializer.serialize(transformer.forward(value)); |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| serializer.deserialize(array); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| private static final TestCase DeserializeAndCheck = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| byte[] array = serializer.serialize(transformer.forward(value)); |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| Object obj = serializer.deserialize(array); |
| transformer.reverse(obj); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| private static final TestCase DeserializeAndCheckShallow = new TestCase() |
| { |
| public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception |
| { |
| byte[] array = serializer.serialize(transformer.forward(value)); |
| long start = System.nanoTime(); |
| for (int i = 0; i < iterations; i++) |
| { |
| Object obj = serializer.deserialize(array); |
| transformer.shallowReverse(obj); |
| } |
| return iterationTime(System.nanoTime() - start, iterations); |
| } |
| }; |
| |
| /** |
| * JVM is not required to honor GC requests, but adding bit of sleep around request is |
| * most likely to give it a chance to do it. |
| */ |
| private static void doGc() |
| { |
| try { |
| Thread.sleep(50L); |
| } catch (InterruptedException ie) { |
| System.err.println("Interrupted while sleeping in serializers.BenchmarkRunner.doGc()"); |
| } |
| System.gc(); |
| try { // longer sleep afterwards (not needed by GC, but may help with scheduling) |
| Thread.sleep(200L); |
| } catch (InterruptedException ie) { |
| System.err.println("Interrupted while sleeping in serializers.BenchmarkRunner.doGc()"); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------ |
| |
| private static abstract class TestCase |
| { |
| public abstract <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception; |
| } |
| |
| private static final class TestCaseRunner<J> |
| { |
| private final Transformer<J,Object> transformer; |
| private final Serializer<Object> serializer; |
| private final J value; |
| |
| public TestCaseRunner(Transformer<J,Object> transformer, Serializer<Object> serializer, J value) |
| { |
| this.transformer = transformer; |
| this.serializer = serializer; |
| this.value = value; |
| } |
| |
| public double run(TestCase tc, int iterations) throws Exception |
| { |
| return tc.run(transformer, serializer, value, iterations); |
| } |
| |
| public double runTakeMin(int trials, TestCase tc, int iterations) throws Exception |
| { |
| double minTime = Double.MAX_VALUE; |
| for (int i = 0; i < trials; i++) { |
| double time = tc.run(transformer, serializer, value, iterations); |
| minTime = Math.min(minTime, time); |
| } |
| return minTime; |
| } |
| } |
| |
| enum measurements |
| { |
| timeCreate("create (nanos)"), timeSerializeDifferentObjects("ser (nanos)"), timeSerializeSameObject("ser+same (nanos)"), |
| timeDeserializeNoFieldAccess("deser (nanos)"), timeDeserializeAndCheck("deser+deep (nanos)"), timeDeserializeAndCheckShallow("deser+shal (nanos)"), |
| totalTime("total (nanos)"), length("size (bytes)"), lengthDeflate("size+dfl (bytes)"), |
| ; |
| |
| public final String displayName; |
| |
| measurements(String displayName) |
| { |
| this.displayName = displayName; |
| } |
| } |
| |
| private static <J> EnumMap<measurements, Map<String, Double>> |
| start(PrintWriter errors, int iterations, int trials, long warmupTime, boolean prewarm, Iterable<TestGroup.Entry<J,Object>> groups, J value) throws Exception |
| { |
| // Check correctness first. |
| System.out.println("Checking correctness..."); |
| for (TestGroup.Entry<J,Object> entry : groups) |
| { |
| checkCorrectness(errors, entry.transformer, entry.serializer, value); |
| } |
| System.out.println("[done]"); |
| |
| // Pre-warm. |
| if (prewarm) { |
| System.out.print("Pre-warmup..."); |
| for (TestGroup.Entry<J,Object> entry : groups) |
| { |
| TestCaseRunner<J> runner = new TestCaseRunner<J>(entry.transformer, entry.serializer, value); |
| String name = entry.serializer.getName(); |
| System.out.print(" " + name); |
| |
| warmCreation(runner, warmupTime); |
| warmSerialization(runner, warmupTime); |
| warmDeserialization(runner, warmupTime); |
| } |
| System.out.println(); |
| System.out.println("[done]"); |
| } |
| |
| System.out.printf("%-32s %6s %7s %7s %7s %7s %7s %7s %6s %5s\n", |
| "", |
| "create", |
| "ser", |
| "+same", |
| "deser", |
| "+shal", |
| "+deep", |
| "total", |
| "size", |
| "+dfl"); |
| EnumMap<measurements, Map<String, Double>> values = new EnumMap<measurements, Map<String, Double>>(measurements.class); |
| for (measurements m : measurements.values()) |
| values.put(m, new HashMap<String, Double>()); |
| |
| // Actual tests. |
| for (TestGroup.Entry<J,Object> entry : groups) |
| { |
| TestCaseRunner<J> runner = new TestCaseRunner<J>(entry.transformer, entry.serializer, value); |
| String name = entry.serializer.getName(); |
| try { |
| |
| /* |
| * Should only warm things for the serializer that we test next: HotSpot JIT will |
| * otherwise spent most of its time optimizing slower ones... Use |
| * -XX:CompileThreshold=1 to hint the JIT to start immediately |
| * |
| * Actually: 1 is often not a good value -- threshold is the number |
| * of samples needed to trigger inlining, and there's no point in |
| * inlining everything. Default value is in thousands, so lowering |
| * it to, say, 1000 is usually better. |
| */ |
| warmCreation(runner, warmupTime); |
| |
| doGc(); |
| double timeCreate = runner.runTakeMin(trials, Create, iterations * 100); // do more iteration for object creation because of its short time |
| |
| warmSerialization(runner, warmupTime); |
| |
| doGc(); |
| double timeSerializeDifferentObjects = runner.runTakeMin(trials, Serialize, iterations); |
| |
| doGc(); |
| double timeSerializeSameObject = runner.runTakeMin(trials, SerializeSameObject, iterations); |
| |
| warmDeserialization(runner, warmupTime); |
| |
| doGc(); |
| double timeDeserializeNoFieldAccess = runner.runTakeMin(trials, Deserialize, iterations); |
| |
| doGc(); |
| double timeDeserializeAndCheckShallow = runner.runTakeMin(trials, DeserializeAndCheckShallow, iterations); |
| |
| doGc(); |
| double timeDeserializeAndCheck = runner.runTakeMin(trials, DeserializeAndCheck, iterations); |
| |
| double totalTime = timeSerializeDifferentObjects + timeDeserializeAndCheck; |
| |
| byte[] array = entry.serializer.serialize(entry.transformer.forward(value)); |
| |
| byte[] compressDeflate = compressDeflate(array); |
| |
| System.out.printf("%-32s %6.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %6d %5d\n", |
| name, |
| timeCreate, |
| timeSerializeDifferentObjects, |
| timeSerializeSameObject, |
| timeDeserializeNoFieldAccess, |
| timeDeserializeAndCheckShallow, |
| timeDeserializeAndCheck, |
| totalTime, |
| array.length, |
| compressDeflate.length); |
| |
| addValue(values, name, timeCreate, timeSerializeDifferentObjects, timeSerializeSameObject, |
| timeDeserializeNoFieldAccess, timeDeserializeAndCheckShallow, timeDeserializeAndCheck, totalTime, |
| array.length, compressDeflate.length); |
| } |
| catch (Exception ex) { |
| System.out.println("ERROR: \"" + name + "\" crashed during benchmarking."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" crashed during benchmarking."); |
| ex.printStackTrace(errors); |
| } |
| } |
| |
| return values; |
| } |
| |
| private static byte[] compressDeflate(byte[] data) |
| { |
| try { |
| ByteArrayOutputStream bout = new ByteArrayOutputStream(500); |
| DeflaterOutputStream compresser = new DeflaterOutputStream(bout); |
| compresser.write(data, 0, data.length); |
| compresser.finish(); |
| compresser.flush(); |
| return bout.toByteArray(); |
| } |
| catch (IOException ex) { |
| AssertionError ae = new AssertionError("IOException while writing to ByteArrayOutputStream!"); |
| ae.initCause(ex); |
| throw ae; |
| } |
| } |
| |
| /** |
| * Method that tries to validate correctness of serializer, using |
| * round-trip (construct, serializer, deserialize; compare objects |
| * after steps 1 and 3). |
| */ |
| private static <J> void checkCorrectness(PrintWriter errors, Transformer<J,Object> transformer, Serializer<Object> serializer, J value) |
| throws Exception |
| { |
| Object specialInput; |
| String name = serializer.getName(); |
| |
| try { |
| specialInput = transformer.forward(value); |
| } |
| catch (Exception ex) { |
| System.out.println("ERROR: \"" + name + "\" crashed during forward transformation."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" crashed during forward transformation."); |
| ex.printStackTrace(errors); |
| return; |
| } |
| |
| byte[] array; |
| |
| try { |
| array = serializer.serialize(specialInput); |
| } |
| catch (Exception ex) { |
| System.out.println("ERROR: \"" + name + "\" crashed during serialization."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" crashed during serialization."); |
| ex.printStackTrace(errors); |
| return; |
| } |
| |
| Object specialOutput; |
| |
| try { |
| specialOutput = serializer.deserialize(array); |
| } |
| catch (Exception ex) { |
| System.out.println("ERROR: \"" + name + "\" crashed during deserialization."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" crashed during deserialization."); |
| ex.printStackTrace(errors); |
| return; |
| } |
| |
| J output; |
| try { |
| output = transformer.reverse(specialOutput); |
| } |
| catch (Exception ex) { |
| System.out.println("ERROR: \"" + name + "\" crashed during reverse transformation."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" crashed during reverse transformation."); |
| ex.printStackTrace(errors); |
| return; |
| } |
| |
| |
| if (!value.equals(output)) { |
| System.out.println("ERROR: \"" + name + "\" failed round-trip check."); |
| errors.println(ERROR_DIVIDER); |
| errors.println("\"" + name + "\" failed round-trip check."); |
| errors.println("ORIGINAL: " + value); |
| errors.println("ROUNDTRIP: " + output); |
| } |
| } |
| |
| private static void printImages(EnumMap<measurements, Map<String, Double>> values) |
| { |
| for (measurements m : values.keySet()) { |
| Map<String, Double> map = values.get(m); |
| ArrayList<Map.Entry<String,Double>> list = new ArrayList<Map.Entry<String,Double>>(map.entrySet()); |
| Collections.sort(list, new Comparator<Map.Entry<String,Double>>() { |
| public int compare (Map.Entry<String,Double> o1, Map.Entry<String,Double> o2) { |
| double diff = o1.getValue() - o2.getValue(); |
| return diff > 0 ? 1 : (diff < 0 ? -1 : 0); |
| } |
| }); |
| LinkedHashMap<String, Double> sortedMap = new LinkedHashMap<String, Double>(); |
| for (Map.Entry<String, Double> entry : list) { |
| if( !entry.getValue().isNaN() ) { |
| sortedMap.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| if (!sortedMap.isEmpty()) printImage(sortedMap, m); |
| } |
| } |
| |
| private static String urlEncode(String s) |
| { |
| try { |
| return URLEncoder.encode(s, "UTF-8"); |
| } |
| catch (UnsupportedEncodingException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static void printImage(Map<String, Double> map, measurements m) |
| { |
| StringBuilder valSb = new StringBuilder(); |
| String names = ""; |
| double max = Double.MIN_NORMAL; |
| for (Map.Entry<String, Double> entry : map.entrySet()) |
| { |
| double value = entry.getValue(); |
| valSb.append((int) value).append(','); |
| max = Math.max(max, entry.getValue()); |
| names = urlEncode(entry.getKey()) + '|' + names; |
| } |
| |
| int headerSize = 30; |
| |
| int maxPixels = 300 * 1000; // Limit set by Google's Chart API. |
| |
| int maxHeight = 600; |
| int width = maxPixels / maxHeight; |
| |
| int barThickness = 10; |
| int barSpacing = 10; |
| |
| int height; |
| |
| // Reduce bar thickness and spacing until we can fit in the maximum height. |
| while (true) { |
| height = headerSize + map.size()*(barThickness + barSpacing); |
| if (height <= maxHeight) break; |
| barSpacing--; |
| if (barSpacing == 1) break; |
| |
| height = headerSize + map.size()*(barThickness + barSpacing); |
| if (height <= maxHeight) break; |
| barThickness--; |
| if (barThickness == 1) break; |
| } |
| |
| boolean truncated = false; |
| if (height > maxHeight) { |
| truncated = true; |
| height = maxHeight; |
| } |
| |
| double scale = max * 1.1; |
| System.out.println("<img src='https://chart.googleapis.com/chart?chtt=" |
| + urlEncode(m.displayName) |
| + "&chf=c||lg||0||FFFFFF||1||76A4FB||0|bg||s||EFEFEF&chs="+width+"x"+height+"&chd=t:" |
| + valSb.toString().substring(0, valSb.length() - 1) |
| + "&chds=0,"+ scale |
| + "&chxt=y" |
| + "&chxl=0:|" + names.substring(0, names.length() - 1) |
| + "&chm=N *f*,000000,0,-1,10&lklk&chdlp=t&chco=660000|660033|660066|660099|6600CC|6600FF|663300|663333|663366|663399|6633CC|6633FF|666600|666633|666666&cht=bhg&chbh=" + barThickness + ",0," + barSpacing + "&nonsense=aaa.png'/>"); |
| |
| if (truncated) { |
| System.err.println("WARNING: Not enough room to fit all bars in chart."); |
| } |
| } |
| |
| private static void addValue( |
| EnumMap<measurements, Map<String, Double>> values, |
| String name, |
| double timeCreate, |
| double timeSerializeDifferentObjects, |
| double timeSerializeSameObject, |
| double timeDeserializeNoFieldAccess, |
| double timeDeserializeAndCheckShallow, |
| double timeDeserializeAndCheck, |
| double totalTime, |
| double length, double lengthDeflate) |
| { |
| values.get(measurements.timeSerializeDifferentObjects).put(name, timeSerializeDifferentObjects); |
| values.get(measurements.timeSerializeSameObject).put(name, timeSerializeSameObject); |
| values.get(measurements.timeDeserializeNoFieldAccess).put(name, timeDeserializeNoFieldAccess); |
| values.get(measurements.timeDeserializeAndCheckShallow).put(name, timeDeserializeAndCheckShallow); |
| values.get(measurements.timeDeserializeAndCheck).put(name, timeDeserializeAndCheck); |
| values.get(measurements.totalTime).put(name, totalTime); |
| values.get(measurements.length).put(name, length); |
| values.get(measurements.lengthDeflate).put(name, lengthDeflate); |
| values.get(measurements.timeCreate).put(name, timeCreate); |
| } |
| |
| private static <J> void warmCreation(TestCaseRunner<J> runner, long warmupTime) throws Exception |
| { |
| // Instead of fixed counts, let's try to prime by running for N seconds |
| long endTime = System.currentTimeMillis() + warmupTime; |
| do |
| { |
| runner.run(Create, 10); |
| } |
| while (System.currentTimeMillis() < endTime); |
| } |
| |
| private static <J> void warmSerialization(TestCaseRunner<J> runner, long warmupTime) throws Exception |
| { |
| // Instead of fixed counts, let's try to prime by running for N seconds |
| long endTime = System.currentTimeMillis() + warmupTime; |
| do |
| { |
| runner.run(Serialize, 10); |
| } |
| while (System.currentTimeMillis() < endTime); |
| } |
| |
| private static <J> void warmDeserialization(TestCaseRunner<J> runner, long warmupTime) throws Exception |
| { |
| // Instead of fixed counts, let's try to prime by running for N seconds |
| long endTime = System.currentTimeMillis() + warmupTime; |
| do |
| { |
| runner.run(DeserializeAndCheck, 10); |
| } |
| while (System.currentTimeMillis() < endTime); |
| } |
| } |