gson-comments/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java

472 lines
13 KiB
Java

/*
* 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.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;
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.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Measure Gson and Jackson parsing and binding performance.
*
* <p>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 {
@Param Document document;
@Param Api api;
private enum Document {
TWEETS(new TypeToken<List<Tweet>>() {}, new TypeReference<List<Tweet>>() {}),
READER_SHORT(new TypeToken<Feed>() {}, new TypeReference<Feed>() {}),
READER_LONG(new TypeToken<Feed>() {}, new TypeReference<Feed>() {});
@SuppressWarnings("ImmutableEnumChecker")
private final TypeToken<?> gsonType;
@SuppressWarnings("ImmutableEnumChecker")
private final TypeReference<?> jacksonType;
private Document(TypeToken<?> typeToken, TypeReference<?> typeReference) {
this.gsonType = typeToken;
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_SKIP {
@Override
Parser newParser() {
return new GsonSkipParser();
}
},
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;
@BeforeExperiment
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 File getResourceFile(String path) throws Exception {
URL url = ParseBenchmark.class.getResource(path);
if (url == null) {
throw new IllegalArgumentException("Resource " + path + " does not exist");
}
File file = new File(url.toURI());
if (!file.isFile()) {
throw new IllegalArgumentException("Resource " + path + " is not a file");
}
return file;
}
private static String resourceToString(String fileName) throws Exception {
ZipFile zipFile = new ZipFile(getResourceFile("/ParseBenchmarkData.zip"));
try {
ZipEntry zipEntry = zipFile.getEntry(fileName);
Reader reader =
new InputStreamReader(zipFile.getInputStream(zipEntry), StandardCharsets.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();
} finally {
zipFile.close();
}
}
public static void main(String[] args) throws Exception {
NonUploadingCaliperRunner.run(ParseBenchmark.class, args);
}
interface Parser {
void parse(char[] data, Document document) throws Exception;
}
private static class GsonStreamParser implements Parser {
@Override
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();
}
private void readToken(com.google.gson.stream.JsonReader reader) throws IOException {
while (true) {
switch (reader.peek()) {
case BEGIN_ARRAY:
reader.beginArray();
break;
case END_ARRAY:
reader.endArray();
break;
case BEGIN_OBJECT:
reader.beginObject();
break;
case END_OBJECT:
reader.endObject();
break;
case NAME:
reader.nextName();
break;
case BOOLEAN:
reader.nextBoolean();
break;
case NULL:
reader.nextNull();
break;
case NUMBER:
reader.nextLong();
break;
case STRING:
reader.nextString();
break;
case END_DOCUMENT:
return;
default:
throw new IllegalArgumentException("Unexpected token" + reader.peek());
}
}
}
}
private static class GsonSkipParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
com.google.gson.stream.JsonReader jsonReader =
new com.google.gson.stream.JsonReader(new CharArrayReader(data));
jsonReader.skipValue();
jsonReader.close();
}
}
private static class JacksonStreamParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
JsonFactory jsonFactory =
new JsonFactoryBuilder()
.configure(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, false)
.build();
com.fasterxml.jackson.core.JsonParser jp =
jsonFactory.createParser(new CharArrayReader(data));
int depth = 0;
do {
JsonToken token = jp.nextToken();
switch (token) {
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;
case VALUE_TRUE:
case VALUE_FALSE:
jp.getBooleanValue();
break;
case VALUE_NULL:
// Do nothing; nextToken() will advance in stream
break;
default:
throw new IllegalArgumentException("Unexpected token " + token);
}
} while (depth > 0);
jp.close();
}
}
private static class GsonDomParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
JsonParser.parseReader(new CharArrayReader(data));
}
}
private static class GsonBindParser implements Parser {
private static final Gson gson =
new GsonBuilder().setDateFormat("EEE MMM dd HH:mm:ss Z yyyy").create();
@Override
public void parse(char[] data, Document document) throws Exception {
gson.fromJson(new CharArrayReader(data), document.gsonType);
}
}
private static class JacksonBindParser implements Parser {
private static final ObjectMapper mapper;
static {
mapper =
JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(MapperFeature.AUTO_DETECT_FIELDS, true)
.build();
mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH));
}
@Override
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 default_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<Link> alternates;
@JsonProperty long updated;
@JsonProperty List<Item> 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<String> categories;
@JsonProperty String title;
@JsonProperty long published;
@JsonProperty long updated;
@JsonProperty("alternate")
@SerializedName("alternate")
List<Link> alternates;
@JsonProperty Content content;
@JsonProperty String author;
@JsonProperty List<ReaderUser> 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;
}
}
}