Updated 2017-02-24 09:45:57 by dbohdan

dbohdan 2017-02-03: This benchmark compares the speed with which various JSON libraries for Tcl can extract a deeply nested string value from a large JSON blob (circa 18 MiB). The comparison may help you choose one out of a growing number of similar JSON-parsing libraries for Tcl, but, as any microbenchmark, it has a limited scope. Libraries may be faster or slower under different circumstances.

How to run the benchmark  edit

Install Git, jq, Tcl 8.6, Tcllib and (optionally) wiki-reaper. Install the Tcl extensions rl_json, SQLite with JSON1, tcl-duktape and yajl-tcl. Then run the POSIX shell commands below.
mkdir jsonbench
cd jsonbench
git clone https://github.com/dbohdan/jimhttp
curl -sf -o AllSets.json.zip https://mtgjson.com/json/AllSets.json.zip
# or wget https://mtgjson.com/json/AllSets.json.zip
unzip AllSets.json.zip
rm AllSets.json.zip
# Instead of running the next command you can manually copy the code from the
# "Code" section of this wiki page and save it as jsonbench.tcl.
wiki-reaper 48500 3 | tee jsonbench.tcl
tclsh jsonbench.tcl

Sample results  edit

These results are from running the benchmark on WSL (Ubuntu 14.04) on a Phenom II X4 955 CPU with no CPU-intensive processes running in the background. The benchmark used version version 3.8.1 (Jan 23, 2017) of MTG JSON data set. It was run with 64-bit Tcl 8.6.5 and jq 1.5.

5 iterations

Package versions:
Tcllib json   --  1.1.2
duktape       --  0.3.0
jimhttp json  --  2.0.0
rl_json       --  0.9.6
sqlite3       --  3.16.2
yajltcl       --  1.6.1

Running the benchmark with 5 iterations for each library
jq:            987.6532
tcl-duktape:   1662.43
jimhttp JSON:  21084.0598
rl_json:       87.88680000000001
Tcllib JSON:   21791.257
SQLite JSON1:  60.934
yajl-tcl:      274.4032

20 iterations

Package versions:
Tcllib json   --  1.1.2
duktape       --  0.3.0
jimhttp json  --  2.0.0
rl_json       --  0.9.6
sqlite3       --  3.16.2
yajltcl       --  1.6.1

Found AllSets.json
Running the benchmark with 20 iterations for each library
jq:            985.70005
tcl-duktape:   1686.35365
jimhttp JSON:  21613.87105
rl_json:       25.7313
Tcllib JSON:   22730.7258
SQLite JSON1:  60.397800000000004
yajl-tcl:      276.35535

Code  edit

#! /usr/bin/env tclsh
# version 0.3.0
package require fileutil
puts {Package versions:}
puts "Tcllib json   --  [package require json]"
source jimhttp/json.tcl
puts "duktape       --  [package require duktape]"
package require duktape::oo
puts "jimhttp json  --  $::json::version"
puts "rl_json       --  [package require rl_json]"
puts "sqlite3       --  [package require sqlite3]"
puts "yajltcl       --  [package require yajltcl]"
puts {}

proc ms timeResult {
    return [expr {[lindex $timeResult 0] / 1000}]
}

proc benchmark {command data times result} {
    return [ms [time {
        set actualResult [$command $data]
        if {$actualResult ne $result} {error "bad result: \"$actualResult\""}
    } $times]]
}

proc jq data {
    return [exec jq -r {.INV.cards[68].flavor} << $data]
}

proc duktape data {
    set j [::duktape::oo::JSON new $::duk $data]
    set result [$j get INV cards 68 flavor]
    $j destroy
    return $result
}

proc jimhttp-json data {
    return [dict get [::json::parse $data] INV cards 68 flavor]
}

proc rl_json data {
    return [::rl_json::json get $data INV cards 68 flavor]
}

proc tcllib-json data {
    return [dict get [lindex [dict get \
            [::json::json2dict $data] INV cards] 68] flavor]
}

proc sqlite3-json1 data {
    set result [lindex [::sq3 eval {
        select json_extract($data, '$.INV.cards[68].flavor')
    }] 0]
    return $result
}

proc yajltcl data {
    return [dict get [lindex [dict get \
            [::yajl::json2dict $data] INV cards] 68] flavor]
}

proc main {} {
    set times 5
    set value {Children claim no two feathers are exactly the same color,\
            then eagerly gather them for proof.}

    set sets [::fileutil::cat AllSets.json]

    puts "Running the benchmark with $times iterations for each library"
    puts "jq:            [benchmark jq $sets $times $value]"
    set ::duk [::duktape::oo::Duktape new]
    puts "tcl-duktape:   [benchmark duktape $sets $times $value]"
    $::duk destroy
    puts "jimhttp JSON:  [benchmark tcllib-json $sets $times $value]"
    puts "rl_json:       [benchmark rl_json $sets $times $value]"
    puts "Tcllib JSON:   [benchmark tcllib-json $sets $times $value]"
    sqlite3 ::sq3 :memory:
    ::sq3 enable_load_extension 1
    puts "SQLite JSON1:  [benchmark sqlite3-json1 $sets $times $value]"
    ::sq3 close
    puts "yajl-tcl:      [benchmark yajltcl $sets $times $value]"
}

main