| import java.io.*; |
| |
| import org.codehaus.jackson.*; |
| import org.codehaus.jackson.io.IOContext; |
| import org.codehaus.jackson.map.*; |
| import org.codehaus.jackson.smile.SmileFactory; |
| import org.codehaus.jackson.util.BufferRecycler; |
| |
| // json.org's reference implementation |
| import org.json.*; |
| // Jsontool implementation |
| import com.sdicons.json.parser.JSONParser; |
| // Noggit: |
| //import org.apache.noggit.JSONParser; |
| |
| @SuppressWarnings("unused") |
| public final class TestJsonPerf |
| { |
| private final int REPS; |
| |
| private final static int TEST_PER_GC = 15; |
| |
| final JsonFactory _jsonFactory; |
| |
| final ObjectMapper _mapper; |
| |
| final ObjectMapper _smileMapper; |
| |
| final SmileFactory _smileFactory; |
| |
| final byte[] _jsonData; |
| |
| final byte[] _smileData; |
| |
| protected int mBatchSize; |
| |
| public TestJsonPerf(File f) throws IOException |
| { |
| _jsonFactory = new JsonFactory(); |
| _mapper = new ObjectMapper(_jsonFactory); |
| _smileFactory = new SmileFactory(); |
| _smileMapper = new ObjectMapper(_smileFactory); |
| _jsonData = readData(f); |
| _smileData = convertToSmile(_jsonData); |
| |
| // Let's try to guestimate suitable size... to get to 50 megs parsed |
| REPS = (int) ((double) (50 * 1000 * 1000) / (double) _jsonData.length); |
| |
| System.out.println("Read "+_jsonData.length+" bytes (smile: "+_smileData.length+") from '"+f+"'; will do "+REPS+" reps"); |
| System.out.println(); |
| } |
| |
| public void test() |
| throws Exception |
| { |
| int i = 0; |
| int sum = 0; |
| |
| while (true) { |
| try { Thread.sleep(100L); } catch (InterruptedException ie) { } |
| // Use 9 to test all... |
| int round = (i++ % 2); |
| |
| long curr = System.currentTimeMillis(); |
| String msg; |
| boolean lf = (round == 0); |
| |
| switch (round) { |
| |
| case 0: |
| msg = "Smile/data-bind"; |
| sum += testJacksonDatabind(_smileMapper, _smileData, REPS); |
| break; |
| |
| case 1: |
| msg = "Jackson/data-bind"; |
| sum += testJacksonDatabind(_mapper, _jsonData, REPS); |
| break; |
| |
| /* |
| case 0: |
| msg = "Jackson/smile, stream"; |
| sum += testJacksonStream(REPS, _smileFactory, _smileData, true); |
| break; |
| case 1: |
| msg = "Jackson, stream/byte"; |
| sum += testJacksonStream(REPS, _jsonFactory, _jsonData, true); |
| break; |
| case 2: |
| msg = "Jackson, stream/char"; |
| sum += testJacksonStream(REPS, _jsonFactory, _jsonData, false); |
| break; |
| |
| case 3: |
| msg = "Jackson, Java types"; |
| sum += testJacksonDatabind(_mapper, REPS); |
| break; |
| |
| case 4: |
| msg = "Jackson, JSON types"; |
| sum += testJacksonJsonTypes(_mapper, REPS); |
| break; |
| |
| case 5: |
| msg = "Noggit"; |
| sum += testNoggit(REPS); |
| break; |
| |
| case 6: |
| msg = "Json.org"; |
| sum += testJsonOrg(REPS); |
| break; |
| case 7: |
| msg = "Json-simple"; |
| sum += testJsonSimple(REPS); |
| break; |
| case 8: |
| msg = "JSONTools (berlios.de)"; |
| sum += testJsonTools(REPS); |
| break; |
| */ |
| default: |
| throw new Error("Internal error"); |
| } |
| |
| curr = System.currentTimeMillis() - curr; |
| if (lf) { |
| System.out.println(); |
| } |
| System.out.println("Test '"+msg+"' -> "+curr+" msecs (" |
| +(sum & 0xFF)+")."); |
| |
| |
| if ((i % TEST_PER_GC) == 0) { |
| System.out.println("[GC]"); |
| try { Thread.sleep(100L); } catch (InterruptedException ie) { } |
| System.gc(); |
| try { Thread.sleep(100L); } catch (InterruptedException ie) { } |
| } |
| } |
| } |
| |
| private final byte[] readData(File f) |
| throws IOException |
| { |
| int len = (int) f.length(); |
| byte[] data = new byte[len]; |
| int offset = 0; |
| FileInputStream fis = new FileInputStream(f); |
| |
| while (len > 0) { |
| int count = fis.read(data, offset, len-offset); |
| offset += count; |
| len -= count; |
| } |
| |
| return data; |
| } |
| |
| private byte[] convertToSmile(byte[] json) throws IOException |
| { |
| JsonParser jp = _jsonFactory.createJsonParser(json); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(200); |
| System.out.println("Converting and verifying Smile data..."); |
| JsonGenerator jg = _smileFactory.createJsonGenerator(out); |
| while (jp.nextToken() != null) { |
| jg.copyCurrentEvent(jp); |
| } |
| jp.close(); |
| jg.close(); |
| byte[] smileBytes = out.toByteArray(); |
| System.out.println("Written as "+smileBytes.length+" Smile bytes from "+json.length+" JSON bytes; will verify correctness"); |
| |
| // One more thing: let's actually verify correctness! |
| JsonParser sp = _smileFactory.createJsonParser(new ByteArrayInputStream(smileBytes)); |
| jp = _jsonFactory.createJsonParser(json); |
| while (true) { |
| JsonToken t1 = jp.nextToken(); |
| JsonToken t2; |
| try { |
| t2 = sp.nextToken(); |
| } catch (IOException ioe) { |
| System.err.println("WARN: problem for token matching input token "+t1+" at "+jp.getCurrentLocation()); |
| throw ioe; |
| } |
| if (t1 != t2) { |
| throw new IllegalArgumentException("Error: tokens differ (json: "+t1+", smile "+t2+") at "+jp.getCurrentLocation()); |
| } |
| if (t1 == null) break; |
| if (t1.isScalarValue() || t1 == JsonToken.FIELD_NAME) { |
| String str1 = jp.getText(); |
| String str2 = jp.getText(); |
| if (str1 == null) { |
| throw new IllegalArgumentException("Error: token texts differ (json: null, smile '"+str2+"') at "+jp.getCurrentLocation()); |
| } else if (!str1.equals(str2)) { |
| throw new IllegalArgumentException("Error: token texts differ (json: '"+str1+"', smile '"+str2+"') at "+jp.getCurrentLocation()); |
| } |
| } |
| } |
| System.out.println("Verified Smile data ("+smileBytes.length+"): same as JSON ("+json.length+")"); |
| return smileBytes; |
| } |
| |
| protected int testJsonOrg(int reps) |
| throws Exception |
| { |
| Object ob = null; |
| // Json.org's code only accepts Strings: |
| String input = new String(_jsonData, "UTF-8"); |
| for (int i = 0; i < reps; ++i) { |
| JSONTokener tok = new JSONTokener(input); |
| ob = tok.nextValue(); |
| } |
| return ob.hashCode(); |
| } |
| |
| private int testJsonTools(int reps) |
| throws Exception |
| { |
| Object ob = null; |
| for (int i = 0; i < reps; ++i) { |
| // Json-tools accepts streams, yay! |
| JSONParser jp = new JSONParser(new ByteArrayInputStream(_jsonData), "byte stream"); |
| /* Hmmmh. Will we get just one object for the whole thing? |
| * Or a stream? Seems like just one |
| */ |
| //while ((ob = jp.nextValue()) != null) { ; } |
| ob = jp.nextValue(); |
| } |
| return ob.hashCode(); |
| } |
| |
| private int testJsonSimple(int reps) |
| throws Exception |
| { |
| // Json.org's code only accepts Strings: |
| String input = new String(_jsonData, "UTF-8"); |
| Object ob = null; |
| for (int i = 0; i < reps; ++i) { |
| ob = org.json.simple.JSONValue.parse(input); |
| } |
| return ob.hashCode(); |
| } |
| |
| private int testNoggit(int reps) |
| throws Exception |
| { |
| ByteArrayInputStream bin = new ByteArrayInputStream(_jsonData); |
| |
| char[] cbuf = new char[_jsonData.length]; |
| |
| IOContext ctxt = new IOContext(new BufferRecycler(), this, false); |
| int sum = 0; |
| |
| for (int i = 0; i < reps; ++i) { |
| /* This may be unfair advantage (allocating buffer of exact |
| * size)? But let's do that for now |
| */ |
| //char[] cbuf = new char[mData.length]; |
| //InputStreamReader r = new InputStreamReader(bin, "UTF-8"); |
| byte[] bbuf = ctxt.allocReadIOBuffer(); |
| /* 13-Jan-2009, tatu: Note: Noggit doesn't use our turbo-charged |
| * UTF8 codec by default. But let's make it as fast as we |
| * possibly can... |
| */ |
| UTF8Reader r = new UTF8Reader(ctxt, bin, bbuf, 0, 0); |
| |
| bin.reset(); |
| org.apache.noggit.JSONParser jp = new org.apache.noggit.JSONParser(r, cbuf); |
| int type; |
| while ((type = jp.nextEvent()) != org.apache.noggit.JSONParser.EOF) { |
| if (type == org.apache.noggit.JSONParser.STRING) { |
| sum += jp.getString().length(); |
| } |
| } |
| } |
| return sum; |
| } |
| |
| private int testJacksonStream(int reps, JsonFactory factory, byte[] data, boolean fast) |
| throws Exception |
| { |
| int sum = 0; |
| for (int i = 0; i < reps; ++i) { |
| // note: fast is not used any more |
| JsonParser jp; |
| |
| if (fast) { |
| jp = factory.createJsonParser(data, 0, data.length); |
| } else { |
| jp = factory.createJsonParser(new ByteArrayInputStream(data)); |
| } |
| JsonToken t; |
| while ((t = jp.nextToken()) != null) { |
| /* |
| if (t == JsonToken.FIELD_NAME) System.err.println("'"+jp.getCurrentName()+"'"); |
| else System.err.println(""+t); |
| */ |
| // Field names are always constructed |
| if (t == JsonToken.VALUE_STRING |
| //|| t == JsonToken.FIELD_NAME |
| ) { |
| sum += jp.getText().length(); |
| } |
| } |
| jp.close(); |
| } |
| return sum; |
| } |
| |
| private int testJacksonDatabind(ObjectMapper mapper, byte[] data, int reps) |
| throws Exception |
| { |
| Object ob = null; |
| for (int i = 0; i < reps; ++i) { |
| // This is "untyped"... Maps, Lists etc |
| ob = mapper.readValue(data, 0, data.length, Object.class); |
| } |
| return ob.hashCode(); // just to get some non-optimizable number |
| } |
| |
| private int testJacksonTree(ObjectMapper mapper, byte[] data, int reps) |
| throws Exception |
| { |
| Object ob = null; |
| for (int i = 0; i < reps; ++i) { |
| ob = mapper.readValue(data, 0, data.length, JsonNode.class); |
| } |
| return ob.hashCode(); // just to get some non-optimizable number |
| } |
| |
| public static void main(String[] args) |
| throws Exception |
| { |
| if (args.length != 1) { |
| System.err.println("Usage: java ... <file>"); |
| System.exit(1); |
| } |
| new TestJsonPerf(new File(args[0])).test(); |
| } |
| } |
| |