diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java new file mode 100644 index 00000000..59b9e0fe --- /dev/null +++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.metrics; + +import com.google.caliper.Param; +import com.google.caliper.Runner; +import com.google.caliper.SimpleBenchmark; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + +/** + * Measure Gson and Jackson parsing and binding performance. + * + *

This benchmark requires that ParseBenchmarkData.zip is on the classpath. + * That file contains Twitter feed data, which is representative of what + * applications will be parsing. + */ +public final class ParseBenchmark extends SimpleBenchmark { + @Param Document document; + @Param Api api; + + private enum Document { + TWEETS(new TypeToken>() {}, new TypeReference>() {}), + READER_SHORT(new TypeToken() {}, new TypeReference() {}), + READER_LONG(new TypeToken() {}, new TypeReference() {}); + + private final Type gsonType; + private final TypeReference jacksonType; + + private Document(TypeToken typeToken, TypeReference typeReference) { + this.gsonType = typeToken.getType(); + this.jacksonType = typeReference; + } + } + + private enum Api { + JACKSON_STREAM { + @Override Parser newParser() { + return new JacksonStreamParser(); + } + }, + JACKSON_BIND { + @Override Parser newParser() { + return new JacksonBindParser(); + } + }, + GSON_STREAM { + @Override Parser newParser() { + return new GsonStreamParser(); + } + }, + GSON_DOM { + @Override Parser newParser() { + return new GsonDomParser(); + } + }, + GSON_BIND { + @Override Parser newParser() { + return new GsonBindParser(); + } + }; + abstract Parser newParser(); + } + + private char[] text; + private Parser parser; + + @Override protected void setUp() throws Exception { + text = resourceToString("/" + document.name() + ".json").toCharArray(); + parser = api.newParser(); + } + + public void timeParse(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + parser.parse(text, document); + } + } + + private static String resourceToString(String path) throws Exception { + InputStream in = ParseBenchmark.class.getResourceAsStream(path); + if (in == null) { + throw new IllegalArgumentException("No such file: " + path); + } + + Reader reader = new InputStreamReader(in, "UTF-8"); + char[] buffer = new char[8192]; + StringWriter writer = new StringWriter(); + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + reader.close(); + return writer.toString(); + } + + public static void main(String[] args) throws Exception { + Runner.main(ParseBenchmark.class, args); + } + + interface Parser { + void parse(char[] data, Document document) throws Exception; + } + + private static class GsonStreamParser implements Parser { + public void parse(char[] data, Document document) throws Exception { + com.google.gson.stream.JsonReader jsonReader + = new com.google.gson.stream.JsonReader(new CharArrayReader(data)); + readToken(jsonReader); + jsonReader.close(); + } + + public void readObject(com.google.gson.stream.JsonReader reader) throws IOException { + reader.beginObject(); + while (reader.hasNext()) { + reader.nextName(); + readToken(reader); + } + reader.endObject(); + } + + public void readArray(com.google.gson.stream.JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + readToken(reader); + } + reader.endArray(); + } + + private void readToken(com.google.gson.stream.JsonReader reader) throws IOException { + switch (reader.peek()) { + case BEGIN_ARRAY: + readArray(reader); + break; + case BEGIN_OBJECT: + readObject(reader); + break; + case BOOLEAN: + reader.nextBoolean(); + break; + case NULL: + reader.nextNull(); + break; + case NUMBER: + reader.nextLong(); + break; + case STRING: + reader.nextString(); + break; + default: + throw new IllegalArgumentException("Unexpected token" + reader.peek()); + } + } + } + + private static class JacksonStreamParser implements Parser { + public void parse(char[] data, Document document) throws Exception { + JsonFactory jsonFactory = new JsonFactory(); + org.codehaus.jackson.JsonParser jp = jsonFactory.createJsonParser(new CharArrayReader(data)); + jp.configure(org.codehaus.jackson.JsonParser.Feature.CANONICALIZE_FIELD_NAMES, false); + int depth = 0; + do { + switch (jp.nextToken()) { + case START_OBJECT: + case START_ARRAY: + depth++; + break; + case END_OBJECT: + case END_ARRAY: + depth--; + break; + case FIELD_NAME: + jp.getCurrentName(); + break; + case VALUE_STRING: + jp.getText(); + break; + case VALUE_NUMBER_INT: + case VALUE_NUMBER_FLOAT: + jp.getLongValue(); + break; + } + } while (depth > 0); + jp.close(); + } + } + + private static class GsonDomParser implements Parser { + public void parse(char[] data, Document document) throws Exception { + new JsonParser().parse(new CharArrayReader(data)); + } + } + + private static class GsonBindParser implements Parser { + private static Gson gson = new GsonBuilder() + .setDateFormat("EEE MMM dd HH:mm:ss Z yyyy") + .create(); + + public void parse(char[] data, Document document) throws Exception { + gson.fromJson(new CharArrayReader(data), document.gsonType); + } + } + + private static class JacksonBindParser implements Parser { + private static ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, true); + mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy")); + } + + public void parse(char[] data, Document document) throws Exception { + mapper.readValue(new CharArrayReader(data), document.jacksonType); + } + } + + static class Tweet { + @JsonProperty String coordinates; + @JsonProperty boolean favorited; + @JsonProperty Date created_at; + @JsonProperty boolean truncated; + @JsonProperty Tweet retweeted_status; + @JsonProperty String id_str; + @JsonProperty String in_reply_to_id_str; + @JsonProperty String contributors; + @JsonProperty String text; + @JsonProperty long id; + @JsonProperty String retweet_count; + @JsonProperty String in_reply_to_status_id_str; + @JsonProperty Object geo; + @JsonProperty boolean retweeted; + @JsonProperty String in_reply_to_user_id; + @JsonProperty String in_reply_to_screen_name; + @JsonProperty Object place; + @JsonProperty User user; + @JsonProperty String source; + @JsonProperty String in_reply_to_user_id_str; + } + + static class User { + @JsonProperty String name; + @JsonProperty String profile_sidebar_border_color; + @JsonProperty boolean profile_background_tile; + @JsonProperty String profile_sidebar_fill_color; + @JsonProperty Date created_at; + @JsonProperty String location; + @JsonProperty String profile_image_url; + @JsonProperty boolean follow_request_sent; + @JsonProperty String profile_link_color; + @JsonProperty boolean is_translator; + @JsonProperty String id_str; + @JsonProperty int favourites_count; + @JsonProperty boolean contributors_enabled; + @JsonProperty String url; + @JsonProperty boolean default_profile; + @JsonProperty long utc_offset; + @JsonProperty long id; + @JsonProperty boolean profile_use_background_image; + @JsonProperty int listed_count; + @JsonProperty String lang; + @JsonProperty("protected") @SerializedName("protected") boolean isProtected; + @JsonProperty int followers_count; + @JsonProperty String profile_text_color; + @JsonProperty String profile_background_color; + @JsonProperty String time_zone; + @JsonProperty String description; + @JsonProperty boolean notifications; + @JsonProperty boolean geo_enabled; + @JsonProperty boolean verified; + @JsonProperty String profile_background_image_url; + @JsonProperty boolean defalut_profile_image; + @JsonProperty int friends_count; + @JsonProperty int statuses_count; + @JsonProperty String screen_name; + @JsonProperty boolean following; + @JsonProperty boolean show_all_inline_media; + } + + static class Feed { + @JsonProperty String id; + @JsonProperty String title; + @JsonProperty String description; + @JsonProperty("alternate") @SerializedName("alternate") List alternates; + @JsonProperty long updated; + @JsonProperty List items; + + @Override public String toString() { + StringBuilder result = new StringBuilder() + .append(id) + .append("\n").append(title) + .append("\n").append(description) + .append("\n").append(alternates) + .append("\n").append(updated); + int i = 1; + for (Item item : items) { + result.append(i++).append(": ").append(item).append("\n\n"); + } + return result.toString(); + } + } + + static class Link { + @JsonProperty String href; + + @Override public String toString() { + return href; + } + } + + static class Item { + @JsonProperty List categories; + @JsonProperty String title; + @JsonProperty long published; + @JsonProperty long updated; + @JsonProperty("alternate") @SerializedName("alternate") List alternates; + @JsonProperty Content content; + @JsonProperty String author; + @JsonProperty List likingUsers; + + @Override public String toString() { + return title + + "\nauthor: " + author + + "\npublished: " + published + + "\nupdated: " + updated + + "\n" + content + + "\nliking users: " + likingUsers + + "\nalternates: " + alternates + + "\ncategories: " + categories; + } + } + + static class Content { + @JsonProperty String content; + + @Override public String toString() { + return content; + } + } + + static class ReaderUser { + @JsonProperty String userId; + + @Override public String toString() { + return userId; + } + } +} diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmarkData.zip b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmarkData.zip new file mode 100644 index 00000000..58e08bb5 Binary files /dev/null and b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmarkData.zip differ