If you want to turn a JSON file into a java.util.Map, it appears that Boon is the fastest option. When I first read the article, this was not the case. I had some ideas for speeding up the JSON parsing, but never seemed to need to (it seemed fast enough). Then this article came out,
http://www.infoq.com/articles/HIgh-Performance-Parsers-in-Java.
I downloaded the source for the benchmark, and ran the benchmarks.
Then I tweaked Boon JSON parser to be faster than GSON.
I also improved compliance testing of the Boon parser, and was able to tweak performance by 20x to 25x for Boon parser.
Then I added the performance enhancements that I dreamed about, but never implemented.
It appears it is now faster than Jackson and GSON for the use case of turning a JSON string into a java.util.Map.
Boon allows turning a map into a Java object so it can do object serializaiton of JSON, but you have to first convert JSON into Map and then Map into Object.
I have not performance tuned full Object serialization this and I am sure GSON and Jackson must be faster. Boon plans on supporting this so the plan is for it be very fast if not the fastest (eventually).
But tune in later for more benchmarks.
For now, it is just the fastest at serialization to a map.
Boon does not plan on being a pull parser or a tree parser or a event parser... ever. Boon does JSON but not all the ins and outs. If you want a pull parser, use Jackson or GSON. If you want a tree view parser, use Jackson or GSON.
Boon is optimized for REST calls and Websocket messages. It is not a generic JSON parser. I don't want it to be. I don't care.
parsers-in-java
A set of JSON parser benchmarks.
JSON Compliance
testing actionLabel.json
BOON 1 PASSED actionLabel.json
BOON 2 PASSED actionLabel.json
GSON # PASSED actionLabel.json
INFO Q FAILED actionLabel.json
JACK 1 PASSED actionLabel.json
testing menu.json
BOON 1 PASSED menu.json
BOON 2 PASSED menu.json
GSON # PASSED menu.json
INFO Q FAILED menu.json
JACK 1 PASSED menu.json
testing sgml.json
BOON 1 PASSED sgml.json
BOON 2 PASSED sgml.json
GSON # PASSED sgml.json
INFO Q FAILED sgml.json
JACK 1 PASSED sgml.json
testing webxml.json
BOON 1 PASSED webxml.json
BOON 2 PASSED webxml.json
GSON # PASSED webxml.json
INFO Q FAILED webxml.json
JACK 1 PASSED webxml.json
testing widget.json
BOON 1 PASSED widget.json
BOON 2 PASSED widget.json
GSON # PASSED widget.json
INFO Q FAILED widget.json
JACK 1 PASSED widget.json
For background, read this article on how to write fast parsers.
Parse times for small json 10,000,000 runs:
GSON: 8,334 mili second
JACKSON: 7,156
Boon 2: 2,645
Boon 1: 3,799
InfoQ : 11,431
Smaller is better.
The slowest parser was the one from the article on how to write fast parser. I make mistakes too so no harm, no foul. It is still an interesting article and has good ideas. Boon 2 is 3x faster than Jackson for this use case. Boon 1 is almost 2x faster then Jackson for this use case.
Parse times for large json file 1,000,000 runs:
Boon 2: 15,543 mili second
Boon 1: 19,967
JACKSON: 18,985
InfoQ: ParserException
GSON: 25,870
Lower is better. It should be noted that Boon 1 was taking 200+ seconds when I first ran this test. I had to make some changes to get Boon 1 to this level. It was actually one small change. ;) I love profilers.
Here is the JSON for the small json file (large JSON file is below):
{
"debug":"on\toff",
"num":1
}
Source code for benchmark tests:
...
publicclassBoonBench2Mark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/small.json.txt";
if(args.length>0){
fileName=args[0];
}
System.out.println("parsing: "+fileName);
DataCharBufferdataCharBuffer=FileUtil.readFile(fileName);
intiterations=10_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(dataCharBuffer);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(DataCharBufferdataCharBuffer){
Map<String,Object>map=JSONParser2.parseMap(dataCharBuffer.data);
}
}
...
publicclassBoonBenchMark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/small.json.txt";
if(args.length>0){
fileName=args[0];
}
System.out.println("parsing: "+fileName);
DataCharBufferdataCharBuffer=FileUtil.readFile(fileName);
intiterations=10_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(dataCharBuffer);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(DataCharBufferdataCharBuffer){
Map<String,Object>map=JSONParser.parseMap(dataCharBuffer.data);
}
}
...
publicclassJacksonBenchmark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/small.json.txt";
if(args.length>0){
fileName=args[0];
}
System.out.println("parsing: "+fileName);
DataCharBufferdataCharBuffer=FileUtil.readFile(fileName);
ObjectMapperobjectMapper=newObjectMapper();
intiterations=10_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
Stringstr=newString(dataCharBuffer.data);
for(inti=0;i<iterations;i++){
parse(str,objectMapper);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(Stringstr,ObjectMappermapper){
try{
Map<String,Object>map=(Map<String,Object>)mapper.readValue(str,Map.class);
}catch(IOExceptione){
e.printStackTrace();//To change body of catch statement use File | Settings | File Templates.
}
}
...
publicclassGsonBenchmark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/small.json.txt";
if(args.length>0){
fileName=args[0];
}
System.out.println("parsing: "+fileName);
DataCharBufferdataCharBuffer=FileUtil.readFile(fileName);
Gsongson=newGson();
intiterations=10_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(dataCharBuffer,gson);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(DataCharBufferdataCharBuffer,Gsongson){
Map<String,Object>map=(Map<String,Object>)gson.fromJson(
newCharArrayReader(dataCharBuffer.data,0,dataCharBuffer.length),Map.class);
}
}
Source code for large test:
InfoQ
packagecom.jenkov.parsers.round2;
importcom.jenkov.parsers.FileUtil;
importcom.jenkov.parsers.core.DataCharBuffer;
importcom.jenkov.parsers.core.IndexBuffer;
importcom.jenkov.parsers.json.ElementTypes;
importcom.jenkov.parsers.json.JsonParser;
importorg.boon.IO;
importjava.io.IOException;
importjava.util.HashMap;
importjava.util.Map;
importstaticorg.boon.Exceptions.die;
/**
*/
publicclassJsonParserBenchMark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/webxml.json";
StringfileContents=IO.read(fileName);
JsonParserjsonParser=newJsonParser();
IndexBufferjsonElements=newIndexBuffer(1024,true);
intiterations=10_000_000;
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(newDataCharBuffer(fileContents.toCharArray()),jsonParser,jsonElements);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(DataCharBufferdataCharBuffer,JsonParserjsonParser,IndexBufferjsonElements){
jsonParser.parse(dataCharBuffer,jsonElements);
}
}
Jackson
packagecom.jenkov.parsers.round2;
importorg.boon.IO;
importorg.codehaus.jackson.map.ObjectMapper;
importjava.io.IOException;
importjava.util.Map;
publicclassJacksonBenchmark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/webxml.json";
StringfileContents=IO.read(fileName);
ObjectMappermapper=newObjectMapper();
intiterations=1_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(fileContents,mapper);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(StringfileContents,ObjectMappermapper){
try{
Map<String,Object>map=(Map<String,Object>)mapper.readValue(fileContents,Map.class);
}catch(IOExceptione){
e.printStackTrace();//To change body of catch statement use File | Settings | File Templates.
}
}
}
GSON
packagecom.jenkov.parsers.round2;
importcom.google.gson.Gson;
importorg.boon.IO;
importjava.io.IOException;
importjava.util.Map;
publicclassGsonBenchMark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/webxml.json";
StringfileContents=IO.read(fileName);
Gsongson=newGson();
intiterations=1_000_000;//10.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(fileContents,gson);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(StringfileContents,Gsongson){
Map<String,Object>map=(Map<String,Object>)gson.fromJson(fileContents,Map.class);
}
}
BOON 2
packagecom.jenkov.parsers.round2;
importorg.boon.IO;
importorg.boon.json.JSONParser2;
importjava.io.IOException;
importjava.util.Map;
/**
*/
publicclassBoonBenchV2Mark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/webxml.json";
StringfileContents=IO.read(fileName);
intiterations=1_000_000;//1.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(fileContents);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(StringfileContents){
Map<String,Object>map=JSONParser2.parseMap(fileContents);
}
}
Boon 1
packagecom.jenkov.parsers.round2;
importorg.boon.IO;
importorg.boon.json.JSONParser;
importjava.io.IOException;
importjava.util.Map;
publicclassBoonV1BenchMark{
publicstaticvoidmain(String[]args)throwsIOException{
StringfileName="data/webxml.json";
StringfileContents=IO.read(fileName);
intiterations=1_000_000;//1.000.000 iterations to warm up JIT and minimize one-off overheads etc.
longstartTime=System.currentTimeMillis();
for(inti=0;i<iterations;i++){
parse(fileContents);
}
longendTime=System.currentTimeMillis();
longfinalTime=endTime-startTime;
System.out.println("final time: "+finalTime);
}
privatestaticvoidparse(StringfileContents){
Map<String,Object>map=JSONParser.parseMap(fileContents);
}
}
Large json file from json.org examples
{"web-app":{
"servlet":[
{
"servlet-name":"cofaxCDS",
"servlet-class":"org.cofax.cds.CDSServlet",
"init-param":{
"configGlossary:installationAt":"Philadelphia, PA",
"configGlossary:adminEmail":"ksm@pobox.com",
"configGlossary:poweredBy":"Cofax",
"configGlossary:poweredByIcon":"/images/cofax.gif",
"configGlossary:staticPath":"/content/static",
"templateProcessorClass":"org.cofax.WysiwygTemplate",
"templateLoaderClass":"org.cofax.FilesTemplateLoader",
"templatePath":"templates",
"templateOverridePath":"",
"defaultListTemplate":"listTemplate.htm",
"defaultFileTemplate":"articleTemplate.htm",
"useJSP":false,
"jspListTemplate":"listTemplate.jsp",
"jspFileTemplate":"articleTemplate.jsp",
"cachePackageTagsTrack":200,
"cachePackageTagsStore":200,
"cachePackageTagsRefresh":60,
"cacheTemplatesTrack":100,
"cacheTemplatesStore":50,
"cacheTemplatesRefresh":15,
"cachePagesTrack":200,
"cachePagesStore":100,
"cachePagesRefresh":10,
"cachePagesDirtyRead":10,
"searchEngineListTemplate":"forSearchEnginesList.htm",
"searchEngineFileTemplate":"forSearchEngines.htm",
"searchEngineRobotsDb":"WEB-INF/robots.db",
"useDataStore":true,
"dataStoreClass":"org.cofax.SqlDataStore",
"redirectionClass":"org.cofax.SqlRedirection",
"dataStoreName":"cofax",
"dataStoreDriver":"com.microsoft.jdbc.sqlserver.SQLServerDriver",
"dataStoreUrl":"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
"dataStoreUser":"sa",
"dataStorePassword":"dataStoreTestQuery",
"dataStoreTestQuery":"SET NOCOUNT ON;select test='test';",
"dataStoreLogFile":"/usr/local/tomcat/logs/datastore.log",
"dataStoreInitConns":10,
"dataStoreMaxConns":100,
"dataStoreConnUsageLimit":100,
"dataStoreLogLevel":"debug",
"maxUrlLength":500}},
{
"servlet-name":"cofaxEmail",
"servlet-class":"org.cofax.cds.EmailServlet",
"init-param":{
"mailHost":"mail1",
"mailHostOverride":"mail2"}},
{
"servlet-name":"cofaxAdmin",
"servlet-class":"org.cofax.cds.AdminServlet"},
{
"servlet-name":"fileServlet",
"servlet-class":"org.cofax.cds.FileServlet"},
{
"servlet-name":"cofaxTools",
"servlet-class":"org.cofax.cms.CofaxToolsServlet",
"init-param":{
"templatePath":"toolstemplates/",
"log":1,
"logLocation":"/usr/local/tomcat/logs/CofaxTools.log",
"logMaxSize":"",
"dataLog":1,
"dataLogLocation":"/usr/local/tomcat/logs/dataLog.log",
"dataLogMaxSize":"",
"removePageCache":"/content/admin/remove?cache=pages&id=",
"removeTemplateCache":"/content/admin/remove?cache=templates&id=",
"fileTransferFolder":"/usr/local/tomcat/webapps/content/fileTransferFolder",
"lookInContext":1,
"adminGroupID":4,
"betaServer":true}}],
"servlet-mapping":{
"cofaxCDS":"/",
"cofaxEmail":"/cofaxutil/aemail/*",
"cofaxAdmin":"/admin/*",
"fileServlet":"/static/*",
"cofaxTools":"/tools/*"},
"taglib":{
"taglib-uri":"cofax.tld",
"taglib-location":"/WEB-INF/tlds/cofax.tld"}}}
I am not sure what a micro benchmark is, and this benchmark might not be completely fair. Please let me know how to improve it.
Thanks
--Rick Hightower