mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
392 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cc3b4c650 | |||
| 146191d791 | |||
| 80db053be3 | |||
| 6a86ef5a7d | |||
| 500e5d6759 | |||
| e3f2c9f29a | |||
| 3458e6248b | |||
| 14039d05a9 | |||
| c592da4937 | |||
| 61fb4244f2 | |||
| a503e022d8 | |||
| 0942dc0764 | |||
| 0d6919dd99 | |||
| 3e8e797bc2 | |||
| 0a907ec694 | |||
| f1b4a54991 | |||
| 58e7106df1 | |||
| 7bf391c54a | |||
| 07a6e098c8 | |||
| e4897d6b54 | |||
| 23026d966b | |||
| 3cd98df30d | |||
| 001be23258 | |||
| c85b6682e0 | |||
| bb2bc98a22 | |||
| 43da4f7ccc | |||
| 37e6d1e9c7 | |||
| 1d9926698e | |||
| b04f64e02c | |||
| c6d710b14b | |||
| 2e07850622 | |||
| 1b6888281e | |||
| ce94411dff | |||
| 58a3098cd8 | |||
| 8a68163905 | |||
| 1f98e64b71 | |||
| a9480a768b | |||
| ed94514fc2 | |||
| 1f41cc2030 | |||
| 6455ec36ec | |||
| 676a3d068c | |||
| 5533f8d87b | |||
| 93af7b61ce | |||
| 364ad5529d | |||
| 1974a46fe0 | |||
| 5327ab9903 | |||
| 235d191bae | |||
| 4eb80ec75a | |||
| 2900459222 | |||
| 9088792b0e | |||
| b41fe7beab | |||
| 00f9bb8a07 | |||
| a90e1637cb | |||
| 4bad5c0241 | |||
| ce09d82fc7 | |||
| 5b926b8196 | |||
| c719ccdb48 | |||
| 9f1786aeb1 | |||
| 0bb83e06bc | |||
| 2ba67c2bc2 | |||
| cae91983ec | |||
| 3190ef0c1f | |||
| c7c1372833 | |||
| ba02cda55f | |||
| 512a94afaa | |||
| 6d978c383a | |||
| 938678f87f | |||
| 9dcf5fca5b | |||
| 88f0dc4726 | |||
| 8ae7910aba | |||
| b70e85fd10 | |||
| 99bc591366 | |||
| f4963cd1c5 | |||
| 4859cb8528 | |||
| c42b91980b | |||
| 8b3c8820e0 | |||
| 29bc78a486 | |||
| 30fe86ed32 | |||
| 85cefd5a00 | |||
| b5a328e0ca | |||
| 209a2e8fc3 | |||
| 8b978e6aea | |||
| b4df0e7c9e | |||
| 76aeda6b00 | |||
| 1a0ad6d9c3 | |||
| 6b219e3e25 | |||
| 1e90691013 | |||
| 3577c87c88 | |||
| baf6607e74 | |||
| a700848bae | |||
| 9bbfd5804e | |||
| b234d74f43 | |||
| cd49ff330d | |||
| 49faf7af1a | |||
| c892b83c93 | |||
| 985b52331a | |||
| 021dded9dd | |||
| 8fd0cdc732 | |||
| fe7a4d42d3 | |||
| e89d6353af | |||
| c5bb74d184 | |||
| 874349c928 | |||
| 157604b3a5 | |||
| b935544d65 | |||
| 03271df579 | |||
| 0633d3a07d | |||
| 045377a594 | |||
| b5c8030f19 | |||
| 7c2072789c | |||
| f75e856d2b | |||
| 44d689bc6e | |||
| 9e433c2f19 | |||
| 21b6279b74 | |||
| 4d89076bdc | |||
| 71e4ff7e03 | |||
| d2dfda6583 | |||
| 6a855f528b | |||
| fb93109c2d | |||
| bd190af7a3 | |||
| 44268b0c6b | |||
| 6451e5e7d1 | |||
| 2b3c4c68e4 | |||
| 7030cf2433 | |||
| 26d7881b80 | |||
| 21fe42b28c | |||
| d5ecf68d26 | |||
| 0ddff4ec7d | |||
| 283ac3191f | |||
| 4dd0c80dad | |||
| 3b53c6ca47 | |||
| 4e3b4809ea | |||
| a90b8fb449 | |||
| 1da509027e | |||
| 5b96e4761e | |||
| 311ea79238 | |||
| 98be2c91df | |||
| 2657e5e226 | |||
| cfcb0d4fb7 | |||
| 97d03f3215 | |||
| 4065529bdf | |||
| 0a6260b1d8 | |||
| 12caf2510e | |||
| a58d2f710d | |||
| 6be2db8c42 | |||
| 5cf68416d8 | |||
| ebcb3c6b3b | |||
| 04267e0f6b | |||
| a540e6afc5 | |||
| f1841e48b3 | |||
| 9865bb6904 | |||
| e06ddea784 | |||
| 8b5a89c136 | |||
| da093c1982 | |||
| 048fb6278a | |||
| f1b0778f79 | |||
| 0e584fa4a5 | |||
| f44386008c | |||
| 60c139a844 | |||
| f410213003 | |||
| 19cb5d57db | |||
| 30b912fc81 | |||
| 7fc07e2d5e | |||
| 72c83d9430 | |||
| bfbac12f76 | |||
| 461f7dc9f9 | |||
| 3e5497e2f9 | |||
| 6ecbcc7c19 | |||
| 8cef02e8e8 | |||
| caabfd14b3 | |||
| 2ffbaa9578 | |||
| c40aeaec3a | |||
| 80e84a3ad0 | |||
| 0552335ec1 | |||
| 7aea774b21 | |||
| 4d4ed92055 | |||
| f0ec26992a | |||
| 62e8332b34 | |||
| 0925f71987 | |||
| 4c11652808 | |||
| 5b10c38e43 | |||
| 3316df9195 | |||
| 5d355f1a8b | |||
| 2ff91103ca | |||
| a954d50ad4 | |||
| 5be4d37aff | |||
| 1e6c9dbcfa | |||
| 708a56872d | |||
| 0a2bca3f73 | |||
| 1ec710c985 | |||
| d5a44f9ad4 | |||
| e64dca7144 | |||
| b2779c35df | |||
| 9b11e119d4 | |||
| 988c62baed | |||
| eb3e640003 | |||
| 9475b947f5 | |||
| 18564f1ae2 | |||
| 638f1deb62 | |||
| 7e74d30f45 | |||
| ce8d0f8135 | |||
| 872127b722 | |||
| e180dc44bc | |||
| 268b8845a9 | |||
| 74c47995a3 | |||
| 24f5936cbf | |||
| bdfa8aca28 | |||
| 15eb1ad922 | |||
| 6ec98ee8b1 | |||
| c5862d6de9 | |||
| 57eb55446f | |||
| abd1399a7f | |||
| 57eba21ee5 | |||
| 1b56211a70 | |||
| d8974d53b2 | |||
| 5ecd17f49e | |||
| 6bb99aec3c | |||
| a67e83e24e | |||
| 5a3c3134ec | |||
| ce8b9ee8c4 | |||
| 970dfc9f67 | |||
| 04d39c0961 | |||
| d6339aa015 | |||
| 11076bf337 | |||
| 8a8eea53a2 | |||
| 09bd7e8ef8 | |||
| 9356619380 | |||
| fc15147cf5 | |||
| ab6b7a8044 | |||
| 07c2fe726e | |||
| fa355603fb | |||
| 109bb505d8 | |||
| 4a6eebc0e4 | |||
| 78ce2b473e | |||
| 6beb5f5587 | |||
| 501fed6c4f | |||
| b1bd8e9ee2 | |||
| daeca1bb18 | |||
| 17f6d5208f | |||
| bee4d7a12b | |||
| f32e3e0c7c | |||
| 1b69612246 | |||
| 9e93509a56 | |||
| ef45cd3342 | |||
| 46fe2e6b44 | |||
| 1133c2cc1d | |||
| fde10553e0 | |||
| 83615ff351 | |||
| 352eb4cb6d | |||
| 75c75ac00c | |||
| d9bcf52db2 | |||
| 43f0362e6d | |||
| ed5e313c73 | |||
| c0010f60e6 | |||
| 75301e4cf5 | |||
| 875c8fdcbe | |||
| a0b1642dc0 | |||
| 1d7e54f8c9 | |||
| 77c8581bc0 | |||
| 5dd625916b | |||
| e3d7718cf3 | |||
| 9eccd7b1fb | |||
| 5b05d126b4 | |||
| 03aaf189c1 | |||
| 6ef9395419 | |||
| 3a56e13b78 | |||
| ec28acba3d | |||
| ee6647ce40 | |||
| 03d54f8f6e | |||
| 553e6d7549 | |||
| e6896ee71e | |||
| e6762f9b48 | |||
| 099bb1afef | |||
| 9c33093c91 | |||
| 634d8038b9 | |||
| ad46154f2f | |||
| c7fbb4615c | |||
| fa81068ea8 | |||
| 70c2a1c9f9 | |||
| 164fcb49d9 | |||
| 64cf18aa1e | |||
| 66a68ce264 | |||
| 86162aaddb | |||
| 9cc7a94a94 | |||
| 6bca1225e6 | |||
| 379a4e6a01 | |||
| 460cfcaf3e | |||
| 8e69103822 | |||
| 2f67dab2b6 | |||
| bb65ebd8be | |||
| c46ea0390c | |||
| bc8a6dd2e3 | |||
| b1478c37f6 | |||
| 4e944a9f3c | |||
| c7fa9b5fe8 | |||
| 65148b123b | |||
| 2f92a34bb7 | |||
| 54ed24f481 | |||
| 268df9f67a | |||
| 84dc398d32 | |||
| f6a3205d10 | |||
| 522cb66582 | |||
| f873a140ce | |||
| 36dfc5bbd1 | |||
| f80668e87f | |||
| dcb5d47ee6 | |||
| e95c22eb21 | |||
| 9fb83e61ea | |||
| 1513cdf7bc | |||
| e33af1a3f8 | |||
| 857d77a10a | |||
| 0e431a0250 | |||
| 3acfc0b630 | |||
| 2ce5f69def | |||
| 7d347be902 | |||
| a456d78fe0 | |||
| bf67c967d6 | |||
| 44b7a7145c | |||
| 3867ee71ed | |||
| 464f4813e3 | |||
| af8b52e7e8 | |||
| 796588900c | |||
| 4beb2ed507 | |||
| 0ff6833e96 | |||
| e2cfcc52b3 | |||
| fc8a46025e | |||
| d2bea0c228 | |||
| f016c2b72f | |||
| be62058696 | |||
| af18d5ed81 | |||
| e9c91a1ce2 | |||
| 29767b2886 | |||
| 96a31c69c5 | |||
| 534632dc52 | |||
| 8bf5f3d869 | |||
| c4f92322f5 | |||
| 1e4aa116e5 | |||
| 90cc1411da | |||
| d13ce6768c | |||
| fd4a7f2150 | |||
| 6bd64c6873 | |||
| ba58d868e5 | |||
| c50799ba3b | |||
| 039d82ff1b | |||
| a2f0933d01 | |||
| 77e1e3cc18 | |||
| 7bdd41350a | |||
| 6797a6ab56 | |||
| 86b5928f5e | |||
| 6dbd15aa71 | |||
| 22e5b081c4 | |||
| d848f33c48 | |||
| c64367536d | |||
| fc0102b079 | |||
| 62a39639c2 | |||
| 158aaff384 | |||
| fd836145fe | |||
| 697bafdd0a | |||
| 9675dcac44 | |||
| 48849d7866 | |||
| d0ce2f0b5a | |||
| a19f635a6a | |||
| 676ed59342 | |||
| 4015f46b7d | |||
| 770cee7139 | |||
| a4619a54a7 | |||
| f7d99f97a3 | |||
| 8b7df0c12e | |||
| bd780817f7 | |||
| 82fb45aa2a | |||
| 74870a8189 | |||
| 7a9f6b48f4 | |||
| d3c089130d | |||
| e0f3060527 | |||
| 4c1256acc4 | |||
| 85f6f5bd29 | |||
| 4d9eac663a | |||
| 0ef4d90ad0 | |||
| 1a1e7edb02 | |||
| 51b835f71b | |||
| e38fe3d361 | |||
| cc042c9936 | |||
| 599e3bc937 | |||
| 1fa0d940bc | |||
| 7dc4a9525b | |||
| b6f1f4ef64 | |||
| 3faae67663 | |||
| ccc94c9b05 | |||
| 1fd30db726 | |||
| 0ba76ac066 | |||
| 8b661fe556 | |||
| 077907b7c3 | |||
| 172d669780 |
+4
-2
@@ -8,8 +8,6 @@ environment:
|
||||
matrix:
|
||||
- job_name: VS2019
|
||||
CMAKE_ARGS: -A %Platform%
|
||||
- job_name: VS2019CLANG
|
||||
CMAKE_ARGS: -A %Platform% -T ClangCL
|
||||
- job_name: VS2019ARM
|
||||
CMAKE_ARGS: -A ARM64 -DCMAKE_CROSSCOMPILING=1 -D SIMDJSON_GOOGLE_BENCHMARKS=OFF # Does Google Benchmark builds under VS ARM?
|
||||
- job_name: VS2017 (Static, No Threads)
|
||||
@@ -19,6 +17,10 @@ environment:
|
||||
- job_name: VS2019 (Win32)
|
||||
platform: Win32
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON # This should be the default. Testing anyway.
|
||||
CTEST_ARGS: -E "checkperf|ondemand_basictests"
|
||||
- job_name: VS2015
|
||||
image: Visual Studio 2015
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF
|
||||
CTEST_ARGS: -E checkperf
|
||||
|
||||
build_script:
|
||||
|
||||
+71
-51
@@ -1,24 +1,18 @@
|
||||
version: 2.1
|
||||
|
||||
|
||||
# We constantly run out of memory so please do not use parallelism (-j, -j4).
|
||||
|
||||
# Reusable image / compiler definitions
|
||||
executors:
|
||||
gcc7:
|
||||
docker:
|
||||
- image: gcc:7
|
||||
environment:
|
||||
CXX: g++
|
||||
CC: gcc
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
gcc8:
|
||||
docker:
|
||||
- image: conanio/gcc8
|
||||
environment:
|
||||
CXX: g++-8
|
||||
CC: gcc-8
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
gcc9:
|
||||
docker:
|
||||
@@ -26,8 +20,8 @@ executors:
|
||||
environment:
|
||||
CXX: g++-9
|
||||
CC: gcc-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
gcc10:
|
||||
docker:
|
||||
@@ -35,8 +29,8 @@ executors:
|
||||
environment:
|
||||
CXX: g++-10
|
||||
CC: gcc-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang10:
|
||||
docker:
|
||||
@@ -44,8 +38,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-10
|
||||
CC: clang-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang9:
|
||||
docker:
|
||||
@@ -53,8 +47,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-9
|
||||
CC: clang-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang6:
|
||||
docker:
|
||||
@@ -62,8 +56,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
CC: clang-6.0
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
# Reusable test commands (and initializer for clang 6)
|
||||
commands:
|
||||
@@ -77,19 +71,22 @@ commands:
|
||||
- checkout
|
||||
- run: mkdir -p build
|
||||
|
||||
cmake_build:
|
||||
cmake_build_cache:
|
||||
steps:
|
||||
- cmake_prep
|
||||
- run: |
|
||||
cd build &&
|
||||
cmake $CMAKE_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
make $BUILD_FLAGS all
|
||||
- run: cmake $CMAKE_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination -B build .
|
||||
|
||||
cmake_build:
|
||||
steps:
|
||||
- cmake_build_cache
|
||||
- run: cmake --build build
|
||||
|
||||
cmake_test:
|
||||
steps:
|
||||
- cmake_build
|
||||
- run: |
|
||||
cd build && tools/json2json -h &&
|
||||
cd build &&
|
||||
tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -L acceptance &&
|
||||
ctest $CTEST_FLAGS -LE acceptance -E checkperf
|
||||
|
||||
@@ -97,37 +94,51 @@ commands:
|
||||
steps:
|
||||
- cmake_build
|
||||
- run: |
|
||||
cd build && tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -L acceptance -LE per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=haswell ctest $CTEST_FLAGS -L per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=westmere ctest $CTEST_FLAGS -L per_implementation &&SIMDJSON_FORCE_IMPLEMENTATION=fallback ctest $CTEST_FLAGS -L per_implementation &&
|
||||
cd build &&
|
||||
tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -DSIMDJSON_IMPLEMENTATION="haswell;westmere;fallback" -L acceptance -LE per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=haswell ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=westmere ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=fallback ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
|
||||
|
||||
cmake_perftest:
|
||||
steps:
|
||||
- cmake_build_cache
|
||||
- run: |
|
||||
cmake --build build --target checkperf &&
|
||||
cd build &&
|
||||
ctest --output-on-failure -R checkperf
|
||||
|
||||
# we not only want cmake to build and run tests, but we want also a successful installation from which we can build, link and run programs
|
||||
cmake_install_test: # this version builds, install, test and then verify from the installation
|
||||
steps:
|
||||
- run: cd build && make install
|
||||
- run: echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Ibuild/destination/include -Lbuild/destination/lib -std=c++17 -Wl,-rpath,build/destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
|
||||
cmake_installed_test_cxx20: # assuming that it was installed, this tries to build using C++20
|
||||
steps:
|
||||
- run: echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Ibuild/destination/include -Lbuild/destination/lib -std=c++20 -Wl,-rpath,build/destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
|
||||
jobs:
|
||||
|
||||
# static
|
||||
gcc7:
|
||||
description: Build and run tests on GCC 7 and AVX 2 with a cmake static build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ install_cmake, cmake_test, cmake_install_test ]
|
||||
justlib-gcc10:
|
||||
description: Build just the library, install it and do a basic test
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -SIMDJSON_JUST_LIBRARY=ON }
|
||||
steps: [ cmake_build, cmake_install_test ]
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_JUST_LIBRARY=ON }
|
||||
steps: [ cmake_build, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
gcc10-perftest:
|
||||
description: Build and run performance tests on GCC 10 and AVX 2 with a cmake static build, this test performance regression
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_perftest ]
|
||||
gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake static build, this test performance regression
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake static build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test_all, cmake_install_test ]
|
||||
steps: [ cmake_test, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
clang6:
|
||||
description: Build and run tests on clang 6 and AVX 2 with a cmake static build
|
||||
executor: clang6
|
||||
@@ -137,23 +148,23 @@ jobs:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake static build
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
steps: [ cmake_test, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
# libcpp
|
||||
libcpp-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake static build and libc++
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_USE_LIBCPP=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
steps: [ cmake_test, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
# sanitize
|
||||
sanitize-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: -j4 --output-on-failure -E checkperf }
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
sanitize-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake sanitize build
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: -j4 --output-on-failure -E checkperf }
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
# dynamic
|
||||
@@ -198,19 +209,24 @@ jobs:
|
||||
|
||||
# make (test and checkperf)
|
||||
arch-haswell-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -march=haswell
|
||||
description: Build, run tests and check performance on GCC 10 with -march=haswell
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=haswell }
|
||||
steps: [ cmake_test ]
|
||||
arch-nehalem-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -march=nehalem
|
||||
description: Build, run tests and check performance on GCC 10 with -march=nehalem
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=nehalem }
|
||||
steps: [ cmake_test ]
|
||||
no-computed-goto-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -DSIMDJSON_NO_COMPUTED_GOTO=true
|
||||
sanitize-haswell-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -DSIMDJSON_NO_COMPUTED_GOTO=true }
|
||||
environment: { CXXFLAGS: -march=haswell, CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
sanitize-haswell-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake sanitize build
|
||||
executor: clang10
|
||||
environment: { CXXFLAGS: -march=haswell, CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
workflows:
|
||||
@@ -218,7 +234,7 @@ workflows:
|
||||
build_and_test:
|
||||
jobs:
|
||||
# full multi-implementation tests
|
||||
- gcc7
|
||||
#- gcc7 tested on GitHub actions
|
||||
- gcc10 # do not delete this as it tests our performance
|
||||
- clang6
|
||||
#- clang10 # this gets tested a lot below
|
||||
@@ -241,7 +257,11 @@ workflows:
|
||||
# quicker make single-implementation tests
|
||||
- arch-haswell-gcc10
|
||||
- arch-nehalem-gcc10
|
||||
- no-computed-goto-gcc10
|
||||
|
||||
|
||||
# sanitized single-implementation tests
|
||||
- sanitize-haswell-gcc10
|
||||
- sanitize-haswell-clang10
|
||||
|
||||
# testing "just the library"
|
||||
- justlib-gcc10
|
||||
|
||||
+3
-3
@@ -14,8 +14,8 @@ task:
|
||||
build_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake ..
|
||||
- make -j4
|
||||
- cmake -DSIMDJSON_BASH=OFF -DSIMDJSON_GIT=OFF ..
|
||||
- make
|
||||
test_script:
|
||||
- cd build
|
||||
- ctest -j4 --output-on-failure -E checkperf
|
||||
- ctest --output-on-failure -E checkperf
|
||||
|
||||
+28
-8
@@ -50,7 +50,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -76,7 +76,7 @@ steps:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- mkdir build
|
||||
@@ -140,7 +140,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -165,7 +165,7 @@ steps:
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
@@ -189,7 +189,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -274,7 +274,7 @@ steps:
|
||||
image: gcc:8
|
||||
environment:
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
@@ -299,7 +299,7 @@ steps:
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
@@ -326,7 +326,7 @@ steps:
|
||||
CXX: clang++-9
|
||||
BUILD_FLAGS: -- -j 4
|
||||
CMAKE_FLAGS: -GNinja -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CXXFLAGS: -stdlib=libc++
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -382,6 +382,26 @@ steps:
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-fuzz
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and run fuzzers shortly
|
||||
image: ubuntu:20.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -q -y clang cmake git wget zip ninja-build
|
||||
- wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
- tar xf corpus.tar && rm corpus.tar
|
||||
- fuzz/build_like_ossfuzz.sh
|
||||
- mkdir -p common_out
|
||||
- for fuzzer in build/fuzz/fuzz_* ; do echo $fuzzer;$fuzzer common_out out/* -max_total_time=40; done
|
||||
---
|
||||
kind: pipeline
|
||||
name: stylecheck
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
|
||||
+9
-9
@@ -80,15 +80,15 @@
|
||||
.gitignore export-ignore
|
||||
|
||||
# Sources
|
||||
*.c text diff=c
|
||||
*.cc text diff=cpp
|
||||
*.cxx text diff=cpp
|
||||
*.cpp text diff=cpp
|
||||
*.c++ text diff=cpp
|
||||
*.hpp text diff=cpp
|
||||
*.h text diff=c
|
||||
*.h++ text diff=cpp
|
||||
*.hh text diff=cpp
|
||||
*.c text eol=lf diff=c
|
||||
*.cc text eol=lf diff=cpp
|
||||
*.cxx text eol=lf diff=cpp
|
||||
*.cpp text eol=lf diff=cpp
|
||||
*.c++ text eol=lf diff=cpp
|
||||
*.hpp text eol=lf diff=cpp
|
||||
*.h text eol=lf diff=c
|
||||
*.h++ text eol=lf diff=cpp
|
||||
*.hh text eol=lf diff=cpp
|
||||
|
||||
# Compiled Object files
|
||||
*.slo binary
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
labels: bug (unverified)
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -12,19 +12,27 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
Note that a compiler warning is not a bug.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour: provide a code sample if possible.
|
||||
|
||||
If we cannot reproduce the issue, then we cannot address it.
|
||||
|
||||
Note that a stack trace from your own program is not enough.
|
||||
|
||||
**Configuration (please complete the following information if relevant):**
|
||||
- OS: [e.g. Ubuntu 16.04.6 LTS]
|
||||
- Compiler [e.g. Apple clang version 11.0.3 (clang-1103.0.32.59) x86_64-apple-darwin19.4.0]
|
||||
- Version [e.g. 22]
|
||||
|
||||
We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux systems. Please ensure that your configuration is supported before labelling the issue as a bug. In particular, we do not support legacy 32-bit systems.
|
||||
|
||||
**Indicate whether you are willing or able to provide a bug fix as a pull request**
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -12,6 +12,7 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
We do not make changes to simdjson without clearly identifiable benefits, which typically means either performance improvements, bug fixes or new features. Avoid bike-shedding: we all have opinions about how to write code, but we want to focus on what makes simdjson objectively better.
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
We do not make changes to simdjson without clearly identifiable benefits, which typically means either performance improvements, bug fixes or new features. Avoid bike-shedding: we all have opinions about how to write code, but we want to focus on what makes simdjson objectively better.
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Alpine Linux
|
||||
'on':
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: start docker
|
||||
run: |
|
||||
docker run -w /src -dit --name alpine -v $PWD:/src alpine:latest
|
||||
echo 'docker exec alpine "$@";' > ./alpine.sh
|
||||
chmod +x ./alpine.sh
|
||||
- name: install packages
|
||||
run: |
|
||||
./alpine.sh apk update
|
||||
./alpine.sh apk add build-base cmake g++ linux-headers git bash
|
||||
- name: cmake
|
||||
run: |
|
||||
./alpine.sh cmake -B build_for_alpine
|
||||
- name: build
|
||||
run: |
|
||||
./alpine.sh cmake --build build_for_alpine
|
||||
- name: test
|
||||
run: |
|
||||
./alpine.sh bash -c "cd build_for_alpine && ctest"
|
||||
@@ -1,11 +1,9 @@
|
||||
name: Run fuzzers on stored corpus and test it with valgrind
|
||||
name: Fuzz and run valgrind
|
||||
|
||||
# In the case of a pull request happening at the same time as a cron
|
||||
# job, there is a risk two jobs run at the same time. Therefore,
|
||||
# the corpus is only uploaded for the master branch. Pull requests will
|
||||
# fuzz for a short while, but the results are not uploaded.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: 23 */8 * * *
|
||||
@@ -14,8 +12,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
allfuzzers: parser dump dump_raw_tape print_json
|
||||
artifactsprefix: -artifact_prefix=fuzzfailure/
|
||||
# fuzzers that use the default implementation
|
||||
defaultimplfuzzers: atpointer dump dump_raw_tape element minify parser print_json
|
||||
# fuzzers that loop over the implementations themselves
|
||||
implfuzzers: implementations minifyimpl utf8
|
||||
implementations: haswell westmere fallback
|
||||
UBSAN_OPTIONS: halt_on_error=1
|
||||
MAXLEN: -max_len=4000
|
||||
|
||||
steps:
|
||||
- name: Install packages necessary for building
|
||||
run: |
|
||||
@@ -23,105 +27,126 @@ jobs:
|
||||
sudo apt-get install --quiet ninja-build valgrind zip unzip
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 9
|
||||
sudo ./llvm.sh 10
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Create and prepare the initial seed corpus
|
||||
run: |
|
||||
fuzz/build_corpus.sh
|
||||
mv corpus.zip seed_corpus.zip
|
||||
mkdir seedcorpus
|
||||
unzip -q -d seedcorpus seed_corpus.zip
|
||||
|
||||
- name: Download the corpus from the last run
|
||||
run: |
|
||||
wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
tar xf corpus.tar
|
||||
rm corpus.tar
|
||||
|
||||
- name: List clang versions
|
||||
run: |
|
||||
ls /usr/bin/clang*
|
||||
which clang++
|
||||
clang++ --version
|
||||
|
||||
- name: Build all the variants
|
||||
run: fuzz/build_fuzzer_variants.sh
|
||||
- name: Verify that the oss-fuzz seed corpus passes without problems
|
||||
|
||||
- name: Explore fast (release build, default implementation)
|
||||
run: |
|
||||
mkdir seedcorpus
|
||||
unzip -q -d seedcorpus seed_corpus.zip
|
||||
for buildvariant in noavx withavx; do
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-$buildvariant/fuzz/fuzz_$fuzzer seedcorpus -max_total_time=1
|
||||
done
|
||||
done
|
||||
- name: Run the fastest fuzzer to explore fast
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
mkdir -p out/$fuzzer # in case this is a new fuzzer, or corpus.tar is broken
|
||||
build-ossfuzz-fast9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=30 $artifactsprefix || touch failed
|
||||
# make sure the failing output is visible in the log
|
||||
if [ -e failed ] ; then
|
||||
ls fuzzfailure/* |xargs -n1 base64
|
||||
exit 1
|
||||
fi
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-fast/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=30 $MAXLEN
|
||||
done
|
||||
- name: Run the other fuzzer variants for $fuzzer, with sanitizers etc
|
||||
|
||||
- name: Fuzz default impl. fuzzers with sanitizer+asserts (good at detecting errors)
|
||||
run: |
|
||||
set -x
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-withavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=20 $artifactsprefix || touch failed
|
||||
build-ossfuzz-noavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 $artifactsprefix || touch failed
|
||||
build-ossfuzz-noavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 $artifactsprefix || touch failed
|
||||
if [ -e failed ] ; then
|
||||
# make sure the failing output is visible in the log
|
||||
ls fuzzfailure/* |xargs -n1 base64
|
||||
exit 1
|
||||
fi
|
||||
echo disable msan runs, it fails inside the fuzzing engine and not the fuzzed code!
|
||||
echo build-ossfuzz-msan-noavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
echo build-ossfuzz-msan-withavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers; do
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
for implementation in $implementations; do
|
||||
export SIMDJSON_FORCE_IMPLEMENTATION=$implementation
|
||||
build-sanitizers/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=20 $MAXLEN
|
||||
done
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
- name: Minimize the corpus with the fast fuzzer
|
||||
|
||||
- name: Fuzz differential impl. fuzzers with sanitizer+asserts (good at detecting errors)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
set -eux
|
||||
for fuzzer in $implfuzzers; do
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-sanitizers/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=20 $MAXLEN
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
|
||||
- name: Minimize the corpus with the fast fuzzer on the default implementation
|
||||
run: |
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
mkdir -p out/cmin/$fuzzer
|
||||
build-ossfuzz-fast9/fuzz/fuzz_$fuzzer -merge=1 out/cmin/$fuzzer out/$fuzzer
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-fast/fuzz/fuzz_$fuzzer -merge=1 $MAXLEN out/cmin/$fuzzer out/$fuzzer $others seedcorpus
|
||||
rm -rf out/$fuzzer
|
||||
mv out/cmin/$fuzzer out/$fuzzer
|
||||
done
|
||||
|
||||
- name: Package the corpus into an artifact
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
tar rf corpus.tar out/$fuzzer
|
||||
done
|
||||
|
||||
- name: Save the corpus as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: corpus
|
||||
path: corpus.tar
|
||||
- name: Run the corpus through valgrind (normal build)
|
||||
|
||||
# This takes a subset of the minimized corpus and run it through valgrind. It is slow,
|
||||
# therefore take a "random" subset. The random selection is accomplished by sorting on filenames,
|
||||
# which are hashes of the content.
|
||||
- name: Run some of the minimized corpus through valgrind (replay build, default implementation)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-noavx/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-noavx.txt
|
||||
done
|
||||
- name: Run the corpus through valgrind (noavx build)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-normal/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-normal.txt
|
||||
done
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
find out/$fuzzer -type f |sort|head -n200|xargs -n40 valgrind build-replay/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer.txt
|
||||
done
|
||||
|
||||
- name: Compress the valgrind output
|
||||
run: tar cf valgrind.tar valgrind-*.txt
|
||||
|
||||
- name: Save valgrind output as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: valgrindresults
|
||||
path: valgrind.tar
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Upload the corpus and results to bintray if we are on master
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
run: |
|
||||
if [ $(git rev-parse --verify HEAD) = $(git rev-parse --verify origin/master) ] ; then
|
||||
echo uploading each artifact twice, otherwise it will not be published
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
else
|
||||
echo "not on master, won't upload to bintray"
|
||||
fi
|
||||
echo uploading each artifact twice, otherwise it will not be published
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
|
||||
- name: Archive any crashes as an artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: crashes
|
||||
path: |
|
||||
crash-*
|
||||
leak-*
|
||||
timeout-*
|
||||
if-no-files-found: ignore
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: MinGW32-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
# Important: scoop will either install 32-bit GCC or 64-bit GCC, not both.
|
||||
|
||||
# It is important to build static libraries because cmake is not smart enough under Windows/mingw to take care of the path. So
|
||||
# with a dynamic library, you could get failures due to the fact that the EXE can't find its DLL.
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-gcc
|
||||
runs-on: windows-2016
|
||||
|
||||
env:
|
||||
CMAKE_GENERATOR: Ninja # This is critical, try ' cmake -GNinja-DSIMDJSON_BUILD_STATIC=ON .. ' if using the command line
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
|
||||
steps: # To reproduce what is below, start a powershell with administrative rights, using scoop *is* a good idea
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2 # we cache the scoop setup with 32-bit GCC
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
C:\ProgramData\scoop
|
||||
key: scoop32 # static key: should be good forever
|
||||
- name: Setup Windows # This should almost never run if the cache works.
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||
scoop install sudo --global
|
||||
sudo scoop install git --global
|
||||
sudo scoop install ninja --global
|
||||
sudo scoop install cmake --global
|
||||
sudo scoop install gcc --arch 32bit --global
|
||||
$env:path
|
||||
Write-Host 'Everything has been installed, you are good!'
|
||||
- name: Build and Test 32-bit x86
|
||||
shell: powershell
|
||||
run: |
|
||||
$ENV:PATH="C:\ProgramData\scoop\shims;C:\ProgramData\scoop\apps\gcc\current\bin;C:\ProgramData\scoop\apps\ninja\current;$ENV:PATH"
|
||||
g++ --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
git --version
|
||||
mkdir build32
|
||||
cd build32
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
@@ -0,0 +1,59 @@
|
||||
name: MinGW64-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
# Important: scoop will either install 32-bit GCC or 64-bit GCC, not both.
|
||||
|
||||
# It is important to build static libraries because cmake is not smart enough under Windows/mingw to take care of the path. So
|
||||
# with a dynamic library, you could get failures due to the fact that the EXE can't find its DLL.
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-gcc
|
||||
runs-on: windows-2016
|
||||
|
||||
env:
|
||||
CMAKE_GENERATOR: Ninja # This is critical, try ' cmake -GNinja-DSIMDJSON_BUILD_STATIC=ON .. ' if using the command line
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
|
||||
steps: # To reproduce what is below, start a powershell with administrative rights, using scoop *is* a good idea
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2 # we cache the scoop setup with 64-bit GCC
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
C:\ProgramData\scoop
|
||||
key: scoop64 # static key: should be good forever
|
||||
- name: Setup Windows # This should almost never run if the cache works.
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||
scoop install sudo --global
|
||||
sudo scoop install git --global
|
||||
sudo scoop install ninja --global
|
||||
sudo scoop install cmake --global
|
||||
sudo scoop install gcc --arch 64bit --global
|
||||
$env:path
|
||||
Write-Host 'Everything has been installed, you are good!'
|
||||
- name: Build and Test 64-bit x64
|
||||
shell: powershell
|
||||
run: |
|
||||
$ENV:PATH="C:\ProgramData\scoop\shims;C:\ProgramData\scoop\apps\gcc\current\bin;C:\ProgramData\scoop\apps\ninja\current;$ENV:PATH"
|
||||
g++ --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
git --version
|
||||
mkdir build64
|
||||
cd build64
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
cd ..
|
||||
mkdir build64debug
|
||||
cd build64debug
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
@@ -0,0 +1,44 @@
|
||||
name: MSYS2-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
windows-mingw:
|
||||
name: ${{ matrix.msystem }}
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- msystem: "MINGW64"
|
||||
install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
|
||||
type: Release
|
||||
- msystem: "MINGW32"
|
||||
install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc
|
||||
type: Release
|
||||
- msystem: "MINGW64"
|
||||
install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
|
||||
type: Debug
|
||||
- msystem: "MINGW32"
|
||||
install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc
|
||||
type: Debug
|
||||
env:
|
||||
CMAKE_GENERATOR: Ninja
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
update: true
|
||||
msystem: ${{ matrix.msystem }}
|
||||
install: ${{ matrix.install }}
|
||||
- name: Build and Test
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT=ON ..
|
||||
cmake --build . --verbose
|
||||
ctest -j4 --output-on-failure -E checkperf
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Performance check on Ubuntu 18.04 CI (GCC 7)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . --target checkperf &&
|
||||
ctest --output-on-failure -R checkperf ubuntu18-checkperf.yml
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Ubuntu 18.04 CI (GCC 7)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . &&
|
||||
ctest -j --output-on-failure -E checkperf &&
|
||||
make install &&
|
||||
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Performance check on Ubuntu 20.04 CI (GCC 9)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . --target checkperf &&
|
||||
ctest --output-on-failure -R checkperf
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Ubuntu 20.04 CI (GCC 9)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . &&
|
||||
ctest -j --output-on-failure -E checkperf &&
|
||||
make install &&
|
||||
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
@@ -0,0 +1,26 @@
|
||||
name: VS16-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-vs16
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: 'Run CMake with VS16'
|
||||
uses: lukka/run-cmake@v2
|
||||
with:
|
||||
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
|
||||
cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
|
||||
buildDirectory: "${{ github.workspace }}/../../_temp/windows"
|
||||
cmakeBuildType: Release
|
||||
buildWithCMake: true
|
||||
cmakeGenerator: VS16Win64
|
||||
cmakeAppendedArgs: -DSIMDJSON_COMPETITION=OFF
|
||||
buildWithCMakeArgs: --config Release
|
||||
|
||||
- name: 'Run CTest'
|
||||
run: ctest -C Release -E checkperf --output-on-failure
|
||||
working-directory: "${{ github.workspace }}/../../_temp/windows"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
name: VS16-CLANG-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-vs16
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: 'Run CMake with VS16'
|
||||
uses: lukka/run-cmake@v2
|
||||
with:
|
||||
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
|
||||
cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
|
||||
buildDirectory: "${{ github.workspace }}/../../_temp/windows"
|
||||
cmakeBuildType: Release
|
||||
buildWithCMake: true
|
||||
cmakeGenerator: VS16Win64
|
||||
cmakeAppendedArgs: -T ClangCL -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_BUILD_STATIC=ON
|
||||
buildWithCMakeArgs: --config Release
|
||||
|
||||
- name: 'Run CTest'
|
||||
run: ctest -C Release -E checkperf --output-on-failure
|
||||
working-directory: "${{ github.workspace }}/../../_temp/windows"
|
||||
@@ -0,0 +1,26 @@
|
||||
name: VS16-Ninja-CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-vs16
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: 'Run CMake with VS16'
|
||||
uses: lukka/run-cmake@v2
|
||||
with:
|
||||
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
|
||||
cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
|
||||
buildDirectory: "${{ github.workspace }}/../../_temp/windows"
|
||||
cmakeBuildType: Release
|
||||
buildWithCMake: true
|
||||
cmakeGenerator: VS16Win64
|
||||
cmakeAppendedArgs: -G Ninja -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_BUILD_STATIC=ON
|
||||
buildWithCMakeArgs: --config Release
|
||||
|
||||
- name: 'Run CTest'
|
||||
run: ctest -C Release -E checkperf --output-on-failure
|
||||
working-directory: "${{ github.workspace }}/../../_temp/windows"
|
||||
|
||||
@@ -34,3 +34,9 @@
|
||||
[submodule "dependencies/cxxopts"]
|
||||
path = dependencies/cxxopts
|
||||
url = https://github.com/jarro2783/cxxopts
|
||||
[submodule "dependencies/boost.json"]
|
||||
path = dependencies/boost.json
|
||||
url = https://github.com/CPPAlliance/json.git
|
||||
[submodule "dependencies/yyjson"]
|
||||
path = dependencies/yyjson
|
||||
url = https://github.com/ibireme/yyjson.git
|
||||
|
||||
+9
-12
@@ -6,10 +6,11 @@ project(simdjson
|
||||
)
|
||||
|
||||
set(PROJECT_VERSION_MAJOR 0)
|
||||
set(PROJECT_VERSION_MINOR 4)
|
||||
set(PROJECT_VERSION_MINOR 6)
|
||||
set(PROJECT_VERSION_PATCH 1)
|
||||
set(SIMDJSON_LIB_VERSION "0.4.1" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "2" CACHE STRING "simdjson library soversion")
|
||||
set(SIMDJSON_SEMANTIC_VERSION "0.6.1" CACHE STRING "simdjson semantic version")
|
||||
set(SIMDJSON_LIB_VERSION "4.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "4" CACHE STRING "simdjson library soversion")
|
||||
set(SIMDJSON_GITHUB_REPOSITORY https://github.com/simdjson/simdjson)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
@@ -20,20 +21,15 @@ include(cmake/simdjson-user-cmakecache.cmake)
|
||||
|
||||
if(SIMDJSON_JUST_LIBRARY)
|
||||
message( STATUS "Building just the library, omitting all tests, tools and benchmarks." )
|
||||
endif()
|
||||
|
||||
#
|
||||
# Set up test data
|
||||
#
|
||||
if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
else(SIMDJSON_JUST_LIBRARY)
|
||||
# Setup tests
|
||||
enable_testing()
|
||||
add_subdirectory(jsonchecker)
|
||||
add_subdirectory(jsonexamples)
|
||||
add_library(test-data INTERFACE)
|
||||
target_link_libraries(test-data INTERFACE jsonchecker-data jsonexamples-data)
|
||||
endif()
|
||||
target_link_libraries(test-data INTERFACE jsonchecker-data jsonchecker-minefield-data jsonexamples-data)
|
||||
endif(SIMDJSON_JUST_LIBRARY)
|
||||
|
||||
#
|
||||
# Create the top level simdjson library (must be done at this level to use both src/ and include/
|
||||
# directories) and tools
|
||||
#
|
||||
@@ -45,6 +41,7 @@ if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
add_subdirectory(tools) ## This needs to be before tests because of cxxopts
|
||||
add_subdirectory(singleheader)
|
||||
endif()
|
||||
install(FILES singleheader/simdjson.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
#
|
||||
# Compile tools / tests / benchmarks
|
||||
|
||||
@@ -41,6 +41,7 @@ We have few hard rules, but we have some:
|
||||
- Printing to standard output or standard error (`stderr`, `stdout`, `std::cerr`, `std::cout`) in the core library is forbidden. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Compiled code should not write to stdout or stderr".
|
||||
- Calls to `abort()` are forbidden in the core library. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Under no circumstances should your compiled code ever call abort or exit".
|
||||
- All source code files (.h, .cpp) must be ASCII.
|
||||
- All C macros introduced in public headers need to be prefixed with either `SIMDJSON_` or `simdjson_`.
|
||||
|
||||
Tools, tests and benchmarks are not held to these same strict rules.
|
||||
|
||||
@@ -71,6 +72,12 @@ Pull requests are always invited. However, we ask that you follow these guidelin
|
||||
|
||||
If the benefits of your proposed code remain unclear, we may choose to discard your code: that is not an insult, we frequently discard our own code. We may also consider various alternatives and choose another path. Again, that is not an insult or a sign that you have wasted your time.
|
||||
|
||||
Style
|
||||
-----
|
||||
|
||||
Our formatting style is inspired by the LLVM style.
|
||||
The simdjson library is written using the snake case: when a variable or a function is a phrase, each space is replaced by an underscore character, and the first letter of each word written in lowercase. Compile-time constants are written entirely in uppercase with the same underscore convention.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "0.4.1"
|
||||
PROJECT_NUMBER = "0.6.1"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
+58
-418
@@ -1,11 +1,38 @@
|
||||
Hacking simdjson
|
||||
================
|
||||
|
||||
Here is wisdom about how to build, test and run simdjson from within the repository. *Users* of
|
||||
simdjson should use the released simdjson.h and simdjson.cpp files.
|
||||
Here is wisdom about how to build, test and run simdjson from within the repository. This is mostly useful for people who plan to contribute simdjson, or maybe study the design.
|
||||
|
||||
If you plan to contribute to simdjson, please read our [CONTRIBUTING](https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md) guide.
|
||||
|
||||
|
||||
Design notes
|
||||
------------------------------
|
||||
|
||||
The parser works in two stages:
|
||||
|
||||
- Stage 1. (Find marks) Identifies quickly structure elements, strings, and so forth. We validate UTF-8 encoding at that stage.
|
||||
- Stage 2. (Structure building) Involves constructing a "tree" of sort (materialized as a tape) to navigate through the data. Strings and numbers are parsed at this stage.
|
||||
|
||||
|
||||
The role of stage 1 is to identify pseudo-structural characters as quickly as possible. A character is pseudo-structural if and only if:
|
||||
|
||||
1. Not enclosed in quotes, AND
|
||||
2. Is a non-whitespace character, AND
|
||||
3. Its preceding character is either:
|
||||
(a) a structural character, OR
|
||||
(b) whitespace OR
|
||||
(c) the final quote in a string.
|
||||
|
||||
This helps as we redefine some new characters as pseudo-structural such as the characters 1, G, n in the following:
|
||||
|
||||
> { "foo" : 1.5, "bar" : 1.5 GEOFF_IS_A_DUMMY bla bla , "baz", null }
|
||||
|
||||
Stage 1 also does unicode validation.
|
||||
|
||||
Stage 2 handles all of the rest: number parsings, recognizing atoms like true, false, null, and so forth.
|
||||
|
||||
|
||||
Directory Structure and Source
|
||||
------------------------------
|
||||
|
||||
@@ -13,13 +40,13 @@ simdjson's source structure, from the top level, looks like this:
|
||||
|
||||
* **CMakeLists.txt:** The main build system.
|
||||
* **include:** User-facing declarations and inline definitions (most user-facing functions are inlined).
|
||||
* simdjson.h: A "master include" that includes files from include/simdjson/. This is equivalent to
|
||||
* simdjson.h: A "main include" that includes files from include/simdjson/. This is equivalent to
|
||||
the distributed simdjson.h.
|
||||
* simdjson/*.h: Declarations for public simdjson classes and functions.
|
||||
* simdjson/inline/*.h: Definitions for public simdjson classes and functions.
|
||||
* simdjson/*-inl.h: Definitions for public simdjson classes and functions.
|
||||
* **src:** The source files for non-inlined functionality (e.g. the architecture-specific parser
|
||||
implementations).
|
||||
* simdjson.cpp: A "master source" that includes all implementation files from src/. This is
|
||||
* simdjson.cpp: A "main source" that includes all implementation files from src/. This is
|
||||
equivalent to the distributed simdjson.cpp.
|
||||
* arm64/|fallback/|haswell/|westmere/: Architecture-specific implementations. All functions are
|
||||
Each architecture defines its own namespace, e.g. simdjson::haswell.
|
||||
@@ -38,7 +65,9 @@ Other important files and directories:
|
||||
* **.drone.yml:** Definitions for Drone CI.
|
||||
* **.appveyor.yml:** Definitions for Appveyor CI (Windows).
|
||||
* **.circleci:** Definitions for Circle CI.
|
||||
* **amalgamate.sh:** Generates singleheader/simdjson.h and singleheader/simdjson.cpp for release.
|
||||
* **.github/workflows:** Definitions for GitHub Actions (CI).
|
||||
* **singleheader:** Contains generated `simdjson.h` and `simdjson.cpp` that we release. The files `singleheader/simdjson.h` and `singleheader/simdjson.cpp` should never be edited by hand.
|
||||
* **singleheader/amalgamate.sh:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (bash script).
|
||||
* **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the
|
||||
cardinal rule is don't regress performance without knowing exactly why, and what you're trading
|
||||
for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnnecessary system calls. We recommend checking the performance as follows:
|
||||
@@ -64,18 +93,24 @@ Other important files and directories:
|
||||
* **jsonchecker:** A set of JSON files used to check different functionality of the parser.
|
||||
* **pass*.json:** Files that should pass validation.
|
||||
* **fail*.json:** Files that should fail validation.
|
||||
* **jsonchecker/minefield/y_*.json:** Files that should pass validation.
|
||||
* **jsonchecker/minefield/n_*.json:** Files that should fail validation.
|
||||
* **jsonexamples:** A wide spread of useful, real-world JSON files with different characteristics
|
||||
and sizes.
|
||||
* **singleheader:** Contains generated simdjson.h and simdjson.cpp that we release.
|
||||
* **test:** The tests are here. basictests.cpp and errortests.cpp are the primary ones.
|
||||
* **tools:** Source for executables that can be distributed with simdjson
|
||||
* **tools:** Source for executables that can be distributed with simdjson. Some examples:
|
||||
* `json2json mydoc.json` parses the document, constructs a model and then dumps back the result to standard output.
|
||||
* `json2json -d mydoc.json` parses the document, constructs a model and then dumps model (as a tape) to standard output. The tape format is described in the accompanying file `tape.md`.
|
||||
* `minify mydoc.json` minifies the JSON document, outputting the result to standard output. Minifying means to remove the unneeded white space characters.
|
||||
*`jsonpointer mydoc.json <jsonpath> <jsonpath> ... <jsonpath>` parses the document, constructs a model and then processes a series of [JSON Pointer paths](https://tools.ietf.org/html/rfc6901). The result is itself a JSON document.
|
||||
|
||||
|
||||
> **Don't modify the files in singleheader/ directly; these are automatically generated.**
|
||||
>
|
||||
> While we distribute those files on release, we *maintain* the files under include/ and src/.
|
||||
|
||||
|
||||
While simdjson distributes just two files from the singleheader/ directory, we *maintain* the code in
|
||||
multiple files under include/ and src/. include/simdjson.h and src/simdjson.cpp are the "spine" for
|
||||
these, and you can include
|
||||
multiple files under include/ and src/. The files include/simdjson.h and src/simdjson.cpp are the "spine" for
|
||||
these, and you can include them as if they were the corresponding singleheader/ files.
|
||||
|
||||
|
||||
|
||||
@@ -124,11 +159,11 @@ We also handle the special case where a user is compiling using LLVM clang under
|
||||
|
||||
|
||||
|
||||
Regenerating Single Headers From Master
|
||||
Regenerating Single-Header Files
|
||||
---------------------------------------
|
||||
|
||||
simdjson.h and simdjson.cpp are not always up to date in master. To ensure you have the latest copy,
|
||||
you can regenerate them by running this at the top level:
|
||||
The simdjson.h and simdjson.cpp files in the singleheader directory are not always up-to-date with the rest of the code; they are only ever
|
||||
systematically regenerated on releases. To ensure you have the latest code, you can regenerate them by running this at the top level:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
@@ -137,13 +172,15 @@ cmake ..
|
||||
cmake --build . --target amalgamate
|
||||
```
|
||||
|
||||
The amalgamator is at `amalgamate.sh` at the top level. It generates singleheader/simdjson.h by
|
||||
You need to have a working bash on your system.
|
||||
|
||||
The amalgamator script is `amalgamate.sh` generates singleheader/simdjson.h by
|
||||
reading through include/simdjson.h, copy/pasting each header file into the amalgamated file at the
|
||||
point it gets included (but only once per header). singleheader/simdjson.cpp is generated from
|
||||
src/simdjson.cpp the same way, except files under generic/ may be included and copy/pasted multiple
|
||||
times.
|
||||
|
||||
### Usage (CMake on 64-bit platforms like Linux, freeBSD or macOS)
|
||||
### Usage (CMake on 64-bit platforms like Linux, FreeBSD or macOS)
|
||||
|
||||
Requirements: In addition to git, we require a recent version of CMake as well as bash.
|
||||
|
||||
@@ -156,7 +193,7 @@ brew install cmake
|
||||
apt-get update -qq
|
||||
apt-get install -y cmake
|
||||
```
|
||||
3. On freeBSD, you might be able to install bash and CMake as follows:
|
||||
3. On FreeBSD, you might be able to install bash and CMake as follows:
|
||||
```
|
||||
pkg update -f
|
||||
pkg install bash
|
||||
@@ -206,15 +243,15 @@ Note that the name of directory (`build`) is arbitrary, you can name it as you w
|
||||
|
||||
|
||||
|
||||
### Usage (CMake on 64-bit Windows using Visual Studio)
|
||||
### Usage (CMake on 64-bit Windows using Visual Studio 2019)
|
||||
|
||||
We assume you have a common 64-bit Windows PC with at least Visual Studio 2017 and an x64 processor with AVX2 support (2013 Intel Haswell or later) or SSE 4.2 + CLMUL (2010 Westmere or later).
|
||||
We assume you have a common 64-bit Windows PC with at least Visual Studio 2019.
|
||||
|
||||
- Grab the simdjson code from GitHub, e.g., by cloning it using [GitHub Desktop](https://desktop.github.com/).
|
||||
- Install [CMake](https://cmake.org/download/). When you install it, make sure to ask that `cmake` be made available from the command line. Please choose a recent version of cmake.
|
||||
- Create a subdirectory within simdjson, such as `build`.
|
||||
- Using a shell, go to this newly created directory. You can start a shell directly from GitHub Desktop (Repository > Open in Command Prompt).
|
||||
- Type `cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..` in the shell while in the `build` repository. (Alternatively, if you want to build a DLL, you may use the command line `cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF ..`.)
|
||||
- Type `cmake ..` in the shell while in the `build` repository.
|
||||
- This last command (`cmake ...`) created a Visual Studio solution file in the newly created directory (e.g., `simdjson.sln`). Open this file in Visual Studio. You should now be able to build the project and run the tests. For example, in the `Solution Explorer` window (available from the `View` menu), right-click `ALL_BUILD` and select `Build`. To test the code, still in the `Solution Explorer` window, select `RUN_TESTS` and select `Build`.
|
||||
|
||||
|
||||
@@ -235,380 +272,11 @@ Furthermore, if you have installed LLVM clang on Windows, for example as a compo
|
||||
- `cmake --build . -config Release`
|
||||
|
||||
|
||||
### Usage (Using `vcpkg` on 64-bit Windows, Linux and macOS)
|
||||
|
||||
[vcpkg](https://github.com/Microsoft/vcpkg) users on Windows, Linux and macOS can download and install `simdjson` with one single command from their favorite shell.
|
||||
|
||||
On 64-bit Linux and macOS:
|
||||
|
||||
```
|
||||
$ ./vcpkg install simdjson
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
On Windows (64-bit):
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a shared library.
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows-static
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
These commands will also print out instructions on how to use the library from MSBuild or CMake-based projects.
|
||||
|
||||
If you find the version of `simdjson` shipped with `vcpkg` is out-of-date, feel free to report it to
|
||||
`vcpkg` community either by submitting an issue or by creating a PR.
|
||||
|
||||
### Usage (Docker)
|
||||
|
||||
One can run tests and benchmarks using docker. It especially makes sense under Linux. Privileged
|
||||
access may be needed to get performance counters.
|
||||
|
||||
```
|
||||
git clone https://github.com/simdjson/simdjson.git
|
||||
cd simdjson
|
||||
docker build -t simdjson .
|
||||
docker run --privileged -t simdjson
|
||||
```
|
||||
|
||||
## Architecture and Design Notes
|
||||
|
||||
### Requirements
|
||||
|
||||
- 64-bit platforms like Linux or macOS, as well as Windows through Visual Studio 2017 or later.
|
||||
- Any 64-bit processor:
|
||||
- AVX2 (i.e., Intel processors starting with the Haswell microarchitecture released 2013 and AMD
|
||||
processors starting with the Zen microarchitecture released 2017),
|
||||
- SSE 4.2 and CLMUL (i.e., Intel processors going back to Westmere released in 2010 or AMD
|
||||
processors starting with the Jaguar used in the PS4 and XBox One),
|
||||
- 64-bit ARM processor (ARMv8-A NEON): this covers a wide range of mobile processors, including
|
||||
all Apple processors currently available for sale, going as far back as the iPhone 5s (2013).
|
||||
- Any 64-bit processor (simdjson has a fallback generic 64-bit implementation that is still super
|
||||
fast).
|
||||
- A recent C++ compiler (e.g., GNU GCC or LLVM CLANG or Visual Studio 2017), we assume C++17. GNU
|
||||
GCC 7 or better or LLVM's clang 6 or better.
|
||||
- Some benchmark scripts assume bash and other common utilities, but they are optional.
|
||||
|
||||
### Scope
|
||||
|
||||
We provide a fast parser, that fully validates an input according to various specifications.
|
||||
The parser builds a useful immutable (read-only) DOM (document-object model) which can be later accessed.
|
||||
|
||||
To simplify the engineering, we make some assumptions.
|
||||
|
||||
- We support UTF-8 (and thus ASCII), nothing else (no Latin, no UTF-16). We do not believe this is a
|
||||
genuine limitation, because we do not think there is any serious application that needs to process
|
||||
JSON data without an ASCII or UTF-8 encoding. If the UTF-8 contains a leading BOM, it should be
|
||||
omitted: the user is responsible for detecting and skipping the BOM; UTF-8 BOMs are discouraged.
|
||||
- All strings in the JSON document may have up to 4294967295 bytes in UTF-8 (4GB). To enforce this
|
||||
constraint, we refuse to parse a document that contains more than 4294967295 bytes (4GB). This
|
||||
should accommodate most JSON documents.
|
||||
- As allowed by the specification, we allow repeated keys within an object (other parsers like
|
||||
sajson do the same).
|
||||
- [The simdjson library is fast for JSON documents spanning a few bytes up to many megabytes](https://github.com/lemire/simdjson/issues/312).
|
||||
|
||||
_We do not aim to provide a general-purpose JSON library._ A library like RapidJSON offers much more
|
||||
than just parsing, it helps you generate JSON and offers various other convenient functions. We
|
||||
merely parse the document. This may change in the future.
|
||||
|
||||
### Features
|
||||
|
||||
- The input string is unmodified. (Parsers like sajson and RapidJSON use the input string as a buffer.)
|
||||
- We parse integers and floating-point numbers as separate types which allows us to support large signed 64-bit integers in [-9223372036854775808,9223372036854775808), like a Java `long` or a C/C++ `long long` and large unsigned integers up to the value 18446744073709551615. Among the parsers that differentiate between integers and floating-point numbers, not all support 64-bit integers. (For example, sajson rejects JSON files with integers larger than or equal to 2147483648. RapidJSON will parse a file containing an overly long integer like 18446744073709551616 as a floating-point number.) When we cannot represent exactly an integer as a signed or unsigned 64-bit value, we reject the JSON document.
|
||||
- We support the full range of 64-bit floating-point numbers (binary64). The values range from ` std::numeric_limits<double>::lowest()` to `std::numeric_limits<double>::max()`, so from -1.7976e308 all the way to 1.7975e308. Extreme values (less or equal to -1e308, greater or equal to 1e308) are rejected: we refuse to parse the input document.
|
||||
- We test for accurate float parsing with a perfect accuracy (ULP 0). Many parsers offer only approximate floating parsing. For example, RapidJSON also offers the option of accurate float parsing (`kParseFullPrecisionFlag`) but it comes at a significant performance penalty compared to the default settings. By default, RapidJSON tolerates an error of 3 ULP.
|
||||
- We do full UTF-8 validation as part of the parsing. (Parsers like fastjson, gason and dropbox json11 do not do UTF-8 validation. The sajson parser does incomplete UTF-8 validation, accepting code point
|
||||
sequences like 0xb1 0x87.)
|
||||
- We fully validate the numbers. (Parsers like gason and ultranjson will accept `[0e+]` as valid JSON.)
|
||||
- We validate string content for unescaped characters. (Parsers like fastjson and ultrajson accept unescaped line breaks and tabs in strings.)
|
||||
- We fully validate the white-space characters outside of the strings. Parsers like RapidJSON will accept JSON documents with null characters outside of strings.
|
||||
|
||||
### Architecture
|
||||
|
||||
The parser works in two stages:
|
||||
|
||||
- Stage 1. (Find marks) Identifies quickly structure elements, strings, and so forth. We validate UTF-8 encoding at that stage.
|
||||
- Stage 2. (Structure building) Involves constructing a "tree" of sort (materialized as a tape) to navigate through the data. Strings and numbers are parsed at this stage.
|
||||
|
||||
### Remarks on JSON parsing
|
||||
|
||||
- The JSON spec defines what a JSON parser is:
|
||||
> A JSON parser transforms a JSON text into another representation. A JSON parser MUST accept all texts that conform to the JSON grammar. A JSON parser MAY accept non-JSON forms or extensions. An implementation may set limits on the size of texts that it accepts. An implementation may set limits on the maximum depth of nesting. An implementation may set limits on the range and precision of numbers. An implementation may set limits on the length and character contents of strings.
|
||||
|
||||
* JSON is not JavaScript:
|
||||
|
||||
> All JSON is Javascript but NOT all Javascript is JSON. So {property:1} is invalid because property does not have double quotes around it. {'property':1} is also invalid, because it's single quoted while the only thing that can placate the JSON specification is double quoting. JSON is even fussy enough that {"property":.1} is invalid too, because you should have of course written {"property":0.1}. Also, don't even think about having comments or semicolons, you guessed it: they're invalid. (credit:https://github.com/elzr/vim-json)
|
||||
|
||||
* The structural characters are:
|
||||
|
||||
begin-array = [ left square bracket
|
||||
begin-object = { left curly bracket
|
||||
end-array = ] right square bracket
|
||||
end-object = } right curly bracket
|
||||
name-separator = : colon
|
||||
value-separator = , comma
|
||||
|
||||
### Pseudo-structural elements
|
||||
|
||||
A character is pseudo-structural if and only if:
|
||||
|
||||
1. Not enclosed in quotes, AND
|
||||
2. Is a non-whitespace character, AND
|
||||
3. Its preceding character is either:
|
||||
(a) a structural character, OR
|
||||
(b) whitespace.
|
||||
|
||||
This helps as we redefine some new characters as pseudo-structural such as the characters 1, G, n in the following:
|
||||
|
||||
> { "foo" : 1.5, "bar" : 1.5 GEOFF_IS_A_DUMMY bla bla , "baz", null }
|
||||
|
||||
|
||||
|
||||
### UTF-8 validation (lookup2)
|
||||
|
||||
The simdjson library relies on the lookup2 algorithm for UTF-8 validation on x64 platforms.
|
||||
|
||||
This algorithm validate the length of multibyte characters (that each multibyte character has the right number of continuation characters, and that all continuation characters are part of a multibyte character).
|
||||
|
||||
#### Algorithm
|
||||
|
||||
This algorithm compares *expected* continuation characters with *actual* continuation bytes, and emits an error anytime there is a mismatch.
|
||||
|
||||
For example, in the string "𝄞₿֏ab", which has a 4-, 3-, 2- and 1-byte
|
||||
characters, the file will look like this:
|
||||
|
||||
| Character | 𝄞 | | | | ₿ | | | ֏ | | a | b |
|
||||
|-----------------------|----|----|----|----|----|----|----|----|----|----|----|
|
||||
| Character Length | 4 | | | | 3 | | | 2 | | 1 | 1 |
|
||||
| Byte | F0 | 9D | 84 | 9E | E2 | 82 | BF | D6 | 8F | 61 | 62 |
|
||||
| is_second_byte | | X | | | | X | | | X | | |
|
||||
| is_third_byte | | | X | | | | X | | | | |
|
||||
| is_fourth_byte | | | | X | | | | | | | |
|
||||
| expected_continuation | | X | X | X | | X | X | | X | | |
|
||||
| is_continuation | | X | X | X | | X | X | | X | | |
|
||||
|
||||
The errors here are basically (Second Byte OR Third Byte OR Fourth Byte == Continuation):
|
||||
|
||||
- **Extra Continuations:** Any continuation that is not a second, third or fourth byte is not
|
||||
part of a valid 2-, 3- or 4-byte character and is thus an error. It could be that it's just
|
||||
floating around extra outside of any character, or that there is an illegal 5-byte character,
|
||||
or maybe it's at the beginning of the file before any characters have started; but it's an
|
||||
error in all these cases.
|
||||
- **Missing Continuations:** Any second, third or fourth byte that *isn't* a continuation is an error, because that means
|
||||
we started a new character before we were finished with the current one.
|
||||
|
||||
#### Getting the Previous Bytes
|
||||
|
||||
Because we want to know if a byte is the *second* (or third, or fourth) byte of a multibyte
|
||||
character, we need to "shift the bytes" to find that out. This is what they mean:
|
||||
|
||||
- `is_continuation`: if the current byte is a continuation.
|
||||
- `is_second_byte`: if 1 byte back is the start of a 2-, 3- or 4-byte character.
|
||||
- `is_third_byte`: if 2 bytes back is the start of a 3- or 4-byte character.
|
||||
- `is_fourth_byte`: if 3 bytes back is the start of a 4-byte character.
|
||||
|
||||
We use shuffles to go n bytes back, selecting part of the current `input` and part of the
|
||||
`prev_input` (search for `.prev<1>`, `.prev<2>`, etc.). These are passed in by the caller
|
||||
function, because the 1-byte-back data is used by other checks as well.
|
||||
|
||||
#### Getting the Continuation Mask
|
||||
|
||||
Once we have the right bytes, we have to get the masks. To do this, we treat UTF-8 bytes as
|
||||
numbers, using signed `<` and `>` operations to check if they are continuations or leads.
|
||||
In fact, we treat the numbers as *signed*, partly because it helps us, and partly because
|
||||
Intel's SIMD presently only offers signed `<` and `>` operations (not unsigned ones).
|
||||
|
||||
In UTF-8, bytes that start with the bits 110, 1110 and 11110 are 2-, 3- and 4-byte "leads,"
|
||||
respectively, meaning they expect to have 1, 2 and 3 "continuation bytes" after them.
|
||||
Continuation bytes start with 10, and ASCII (1-byte characters) starts with 0.
|
||||
|
||||
When treated as signed numbers, they look like this:
|
||||
|
||||
| Type | High Bits | Binary Range | Signed |
|
||||
|--------------|------------|--------------|--------|
|
||||
| ASCII | `0` | `01111111` | 127 |
|
||||
| | | `00000000` | 0 |
|
||||
| 4+-Byte Lead | `1111` | `11111111` | -1 |
|
||||
| | | `11110000 | -16 |
|
||||
| 3-Byte Lead | `1110` | `11101111` | -17 |
|
||||
| | | `11100000 | -32 |
|
||||
| 2-Byte Lead | `110` | `11011111` | -33 |
|
||||
| | | `11000000 | -64 |
|
||||
| Continuation | `10` | `10111111` | -65 |
|
||||
| | | `10000000 | -128 |
|
||||
|
||||
This makes it pretty easy to get the continuation mask! It's just a single comparison:
|
||||
|
||||
```
|
||||
is_continuation = input < -64`
|
||||
```
|
||||
|
||||
We can do something similar for the others, but it takes two comparisons instead of one: "is
|
||||
the start of a 4-byte character" is `< -32` and `> -65`, for example. And 2+ bytes is `< 0` and
|
||||
`> -64`. Surely we can do better, they're right next to each other!
|
||||
|
||||
#### Getting the is_xxx Masks: Shifting the Range
|
||||
|
||||
Notice *why* continuations were a single comparison. The actual *range* would require two
|
||||
comparisons--`< -64` and `> -129`--but all characters are always greater than -128, so we get
|
||||
that for free. In fact, if we had *unsigned* comparisons, 2+, 3+ and 4+ comparisons would be
|
||||
just as easy: 4+ would be `> 239`, 3+ would be `> 223`, and 2+ would be `> 191`.
|
||||
|
||||
Instead, we add 128 to each byte, shifting the range up to make comparison easy. This wraps
|
||||
ASCII down into the negative, and puts 4+-Byte Lead at the top:
|
||||
|
||||
| Type | High Bits | Binary Range | Signed |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 4+-Byte Lead (+ 127) | `0111` | `01111111` | 127 |
|
||||
| | | `01110000 | 112 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 3-Byte Lead (+ 127) | `0110` | `01101111` | 111 |
|
||||
| | | `01100000 | 96 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 2-Byte Lead (+ 127) | `010` | `01011111` | 95 |
|
||||
| | | `01000000 | 64 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| Continuation (+ 127) | `00` | `00111111` | 63 |
|
||||
| | | `00000000 | 0 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| ASCII (+ 127) | `1` | `11111111` | -1 |
|
||||
| | | `10000000` | -128 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
|
||||
*Now* we can use signed `>` on all of them:
|
||||
|
||||
```
|
||||
prev1 = input.prev<1>
|
||||
prev2 = input.prev<2>
|
||||
prev3 = input.prev<3>
|
||||
prev1_flipped = input.prev<1>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
prev2_flipped = input.prev<2>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
prev3_flipped = input.prev<3>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
is_second_byte = prev1_flipped > 63;2+-byte lead
|
||||
is_third_byte = prev2_flipped > 95;3+-byte lead
|
||||
is_fourth_byte = prev3_flipped > 111; // 4+-byte lead
|
||||
```
|
||||
|
||||
NOTE: we use `^ 0x80` instead of `+ 128` in the code, which accomplishes the same thing, and even takes the same number
|
||||
of cycles as `+`, but on many Intel architectures can be parallelized better (you can do 3
|
||||
`^`'s at a time on Haswell, but only 2 `+`'s).
|
||||
|
||||
That doesn't look like it saved us any instructions, did it? Well, because we're adding the
|
||||
same number to all of them, we can save one of those `+ 128` operations by assembling
|
||||
`prev2_flipped` out of prev 1 and prev 3 instead of assembling it from input and adding 128
|
||||
to it. One more instruction saved!
|
||||
|
||||
```
|
||||
prev1 = input.prev<1>
|
||||
prev3 = input.prev<3>
|
||||
prev1_flipped = prev1 ^ 0x80; // Same as `+ 128`
|
||||
prev3_flipped = prev3 ^ 0x80; // Same as `+ 128`
|
||||
prev2_flipped = prev1_flipped.concat<2>(prev3_flipped): // <shuffle: take the first 2 bytes from prev1 and the rest from prev3
|
||||
```
|
||||
|
||||
#### Bringing It All Together: Detecting the Errors
|
||||
|
||||
At this point, we have `is_continuation`, `is_first_byte`, `is_second_byte` and `is_third_byte`.
|
||||
All we have left to do is check if they match!
|
||||
|
||||
```
|
||||
return (is_second_byte | is_third_byte | is_fourth_byte) ^ is_continuation;
|
||||
```
|
||||
|
||||
But wait--there's more. The above statement is only 3 operations, but they *cannot be done in
|
||||
parallel*. You have to do 2 `|`'s and then 1 `&`. Haswell, at least, has 3 ports that can do
|
||||
bitwise operations, and we're only using 1!
|
||||
|
||||
#### Epilogue: Addition For Booleans
|
||||
|
||||
There is one big case the above code doesn't explicitly talk about--what if is_second_byte
|
||||
and is_third_byte are BOTH true? That means there is a 3-byte and 2-byte character right next
|
||||
to each other (or any combination), and the continuation could be part of either of them!
|
||||
Our algorithm using `&` and `|` won't detect that the continuation byte is problematic.
|
||||
|
||||
Never fear, though. If that situation occurs, we'll already have detected that the second
|
||||
leading byte was an error, because it was supposed to be a part of the preceding multibyte
|
||||
character, but it *wasn't a continuation*.
|
||||
|
||||
We could stop here, but it turns out that we can fix it using `+` and `-` instead of `|` and
|
||||
`&`, which is both interesting and possibly useful (even though we're not using it here). It
|
||||
exploits the fact that in SIMD, a *true* value is -1, and a *false* value is 0. So those
|
||||
comparisons were giving us numbers!
|
||||
|
||||
Given that, if you do `is_second_byte + is_third_byte + is_fourth_byte`, under normal
|
||||
circumstances you will either get 0 (0 + 0 + 0) or -1 (-1 + 0 + 0, etc.). Thus,
|
||||
`(is_second_byte + is_third_byte + is_fourth_byte) - is_continuation` will yield 0 only if
|
||||
*both* or *neither* are 0 (0-0 or -1 - -1). You'll get 1 or -1 if they are different. Because
|
||||
*any* nonzero value is treated as an error (not just -1), we're just fine here :)
|
||||
|
||||
Further, if *more than one* multibyte character overlaps,
|
||||
`is_second_byte + is_third_byte + is_fourth_byte` will be -2 or -3! Subtracting `is_continuation`
|
||||
from *that* is guaranteed to give you a nonzero value (-1, -2 or -3). So it'll always be
|
||||
considered an error.
|
||||
|
||||
One reason you might want to do this is parallelism. ^ and | are not associative, so
|
||||
(A | B | C) ^ D will always be three operations in a row: either you do A | B -> | C -> ^ D, or
|
||||
you do B | C -> | A -> ^ D. But addition and subtraction *are* associative: (A + B + C) - D can
|
||||
be written as `(A + B) + (C - D)`. This means you can do A + B and C - D at the same time, and
|
||||
then adds the result together. Same number of operations, but if the processor can run
|
||||
independent things in parallel (which most can), it runs faster.
|
||||
|
||||
This doesn't help us on Intel, but might help us elsewhere: on Haswell, at least, | and ^ have
|
||||
a super nice advantage in that more of them can be run at the same time (they can run on 3
|
||||
ports, while + and - can run on 2)! This means that we can do A | B while we're still doing C,
|
||||
saving us the cycle we would have earned by using +. Even more, using an instruction with a
|
||||
wider array of ports can help *other* code run ahead, too, since these instructions can "get
|
||||
out of the way," running on a port other instructions can't.
|
||||
|
||||
#### Epilogue II: One More Trick
|
||||
|
||||
There's one more relevant trick up our sleeve, it turns out: it turns out on Intel we can "pay
|
||||
for" the (prev<1> + 128) instruction, because it can be used to save an instruction in
|
||||
check_special_cases()--but we'll talk about that there :)
|
||||
|
||||
|
||||
|
||||
|
||||
## About the Project
|
||||
|
||||
### Bindings and Ports of simdjson
|
||||
|
||||
We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything).
|
||||
|
||||
|
||||
- [ZippyJSON](https://github.com/michaeleisel/zippyjson): Swift bindings for the simdjson project.
|
||||
- [pysimdjson](https://github.com/TkTech/pysimdjson): Python bindings for the simdjson project.
|
||||
- [simdjson-rs](https://github.com/Licenser/simdjson-rs): Rust port.
|
||||
- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings).
|
||||
- [SimdJsonSharp](https://github.com/EgorBo/SimdJsonSharp): C# version for .NET Core (bindings and full port).
|
||||
- [simdjson_nodejs](https://github.com/luizperes/simdjson_nodejs): Node.js bindings for the simdjson project.
|
||||
- [simdjson_php](https://github.com/crazyxman/simdjson_php): PHP bindings for the simdjson project.
|
||||
- [simdjson_ruby](https://github.com/saka1/simdjson_ruby): Ruby bindings for the simdjson project.
|
||||
- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly.
|
||||
- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings.
|
||||
|
||||
### Tools
|
||||
|
||||
- `json2json mydoc.json` parses the document, constructs a model and then dumps back the result to standard output.
|
||||
- `json2json -d mydoc.json` parses the document, constructs a model and then dumps model (as a tape) to standard output. The tape format is described in the accompanying file `tape.md`.
|
||||
- `minify mydoc.json` minifies the JSON document, outputting the result to standard output. Minifying means to remove the unneeded white space characters.
|
||||
- `jsonpointer mydoc.json <jsonpath> <jsonpath> ... <jsonpath>` parses the document, constructs a model and then processes a series of [JSON Pointer paths](https://tools.ietf.org/html/rfc6901). The result is itself a JSON document.
|
||||
|
||||
|
||||
### Various References
|
||||
|
||||
- [Google double-conv](https://github.com/google/double-conversion/)
|
||||
- [How to implement atoi using SIMD?](https://stackoverflow.com/questions/35127060/how-to-implement-atoi-using-simd)
|
||||
- [Parsing JSON is a Minefield 💣](http://seriot.ch/parsing_json.php)
|
||||
- https://tools.ietf.org/html/rfc7159
|
||||
- The Mison implementation in rust https://github.com/pikkr/pikkr
|
||||
- http://rapidjson.org/md_doc_sax.html
|
||||
- https://github.com/Geal/parser_benchmarks/tree/master/json
|
||||
- Gron: A command line tool that makes JSON greppable https://news.ycombinator.com/item?id=16727665
|
||||
@@ -622,31 +290,3 @@ Inspiring links:
|
||||
- https://auth0.com/blog/beating-json-performance-with-protobuf/
|
||||
- https://gist.github.com/shijuvar/25ad7de9505232c87034b8359543404a
|
||||
- https://github.com/frankmcsherry/blog/blob/master/posts/2018-02-11.md
|
||||
|
||||
Validating UTF-8 takes no more than 0.7 cycles per byte:
|
||||
|
||||
- https://github.com/lemire/fastvalidate-utf-8 https://lemire.me/blog/2018/05/16/validating-utf-8-strings-using-as-little-as-0-7-cycles-per-byte/
|
||||
|
||||
### Academic References
|
||||
|
||||
- T.Mühlbauer, W.Rödiger, R.Seilbeck, A.Reiser, A.Kemper, and T.Neumann. Instant loading for main memory databases. PVLDB, 6(14):1702–1713, 2013. (SIMD-based CSV parsing)
|
||||
- Mytkowicz, Todd, Madanlal Musuvathi, and Wolfram Schulte. "Data-parallel finite-state machines." ACM SIGARCH Computer Architecture News. Vol. 42. No. 1. ACM, 2014.
|
||||
- Lu, Yifan, et al. "Tree structured data processing on GPUs." Cloud Computing, Data Science & Engineering-Confluence, 2017 7th International Conference on. IEEE, 2017.
|
||||
- Sidhu, Reetinder. "High throughput, tree automata based XML processing using FPGAs." Field-Programmable Technology (FPT), 2013 International Conference on. IEEE, 2013.
|
||||
- Dai, Zefu, Nick Ni, and Jianwen Zhu. "A 1 cycle-per-byte XML parsing accelerator." Proceedings of the 18th annual ACM/SIGDA international symposium on Field programmable gate arrays. ACM, 2010.
|
||||
- Lin, Dan, et al. "Parabix: Boosting the efficiency of text processing on commodity processors." High Performance Computer Architecture (HPCA), 2012 IEEE 18th International Symposium on. IEEE, 2012. http://parabix.costar.sfu.ca/export/1783/docs/HPCA2012/final_ieee/final.pdf
|
||||
- Deshmukh, V. M., and G. R. Bamnote. "An empirical evaluation of optimization parameters in XML parsing for performance enhancement." Computer, Communication and Control (IC4), 2015 International Conference on. IEEE, 2015.
|
||||
- Moussalli, Roger, et al. "Efficient XML Path Filtering Using GPUs." ADMS@ VLDB. 2011.
|
||||
- Jianliang, Ma, et al. "Parallel speculative dom-based XML parser." High Performance Computing and Communication & 2012 IEEE 9th International Conference on Embedded Software and Systems (HPCC-ICESS), 2012 IEEE 14th International Conference on. IEEE, 2012.
|
||||
- Li, Y., Katsipoulakis, N.R., Chandramouli, B., Goldstein, J. and Kossmann, D., 2017. Mison: a fast JSON parser for data analytics. Proceedings of the VLDB Endowment, 10(10), pp.1118-1129. http://www.vldb.org/pvldb/vol10/p1118-li.pdf
|
||||
- Cameron, Robert D., et al. "Parallel scanning with bitstream addition: An xml case study." European Conference on Parallel Processing. Springer, Berlin, Heidelberg, 2011.
|
||||
- Cameron, Robert D., Kenneth S. Herdy, and Dan Lin. "High performance XML parsing using parallel bit stream technology." Proceedings of the 2008 conference of the center for advanced studies on collaborative research: meeting of minds. ACM, 2008.
|
||||
- Shah, Bhavik, et al. "A data parallel algorithm for XML DOM parsing." International XML Database Symposium. Springer, Berlin, Heidelberg, 2009.
|
||||
- Cameron, Robert D., and Dan Lin. "Architectural support for SWAR text processing with parallel bit streams: the inductive doubling principle." ACM Sigplan Notices. Vol. 44. No. 3. ACM, 2009.
|
||||
- Amagasa, Toshiyuki, Mana Seino, and Hiroyuki Kitagawa. "Energy-Efficient XML Stream Processing through Element-Skipping Parsing." Database and Expert Systems Applications (DEXA), 2013 24th International Workshop on. IEEE, 2013.
|
||||
- Medforth, Nigel Woodland. "icXML: Accelerating Xerces-C 3.1. 1 using the Parabix Framework." (2013).
|
||||
- Zhang, Qiang Scott. Embedding Parallel Bit Stream Technology Into Expat. Diss. Simon Fraser University, 2010.
|
||||
- Cameron, Robert D., et al. "Fast Regular Expression Matching with Bit-parallel Data Streams."
|
||||
- Lin, Dan. Bits filter: a high-performance multiple string pattern matching algorithm for malware detection. Diss. School of Computing Science-Simon Fraser University, 2010.
|
||||
- Yang, Shiyang. Validation of XML Document Based on Parallel Bit Stream Technology. Diss. Applied Sciences: School of Computing Science, 2013.
|
||||
- N. Nakasato, "Implementation of a parallel tree method on a GPU", Journal of Computational Science, vol. 3, no. 3, pp. 132-141, 2012.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[](https://cloud.drone.io/simdjson/simdjson)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2)
|
||||
[](https://ci.appveyor.com/project/lemire/simdjson-jmmti/branch/master)
|
||||
[](https://cirrus-ci.com/github/simdjson/simdjson)
|
||||
[![][license img]][license] [](https://simdjson.org/api/0.3.1/index.html)
|
||||
/badge.svg)
|
||||
[/badge.svg)](https://simdjson.org/plots.html)
|
||||

|
||||

|
||||
[![][license img]][license] [](https://simdjson.org/api/0.6.0/index.html)
|
||||
|
||||
simdjson : Parsing gigabytes of JSON per second
|
||||
===============================================
|
||||
@@ -10,13 +11,16 @@ simdjson : Parsing gigabytes of JSON per second
|
||||
<img src="images/logo.png" width="10%" style="float: right">
|
||||
JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh
|
||||
approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms
|
||||
to parse JSON 2.5x faster than anything else out there.
|
||||
to parse JSON 2.5x faster than RapidJSON and 25x faster than JSON for Modern C++.
|
||||
|
||||
* **Fast:** Over 2.5x faster than other production-grade JSON parsers.
|
||||
* **Easy:** First-class, easy to use API.
|
||||
* **Fast:** Over 2.5x faster than commonly used production-grade JSON parsers.
|
||||
* **Record Breaking Features:** Minify JSON at 6 GB/s, validate UTF-8 at 13 GB/s, NDJSON at 3.5 GB/s.
|
||||
* **Easy:** First-class, easy to use and carefully documented APIs.
|
||||
* **Beyond DOM:** Try the new On Demand API for twice the speed (>4GB/s).
|
||||
* **Strict:** Full JSON and UTF-8 validation, lossless parsing. Performance with no compromises.
|
||||
* **Automatic:** Selects a CPU-tailored parser at runtime. No configuration needed.
|
||||
* **Reliable:** From memory allocation to error handling, simdjson's design avoids surprises.
|
||||
* **Peer Reviewed:** Our research appears in venues like VLDB Journal, Software: Practice and Experience.
|
||||
|
||||
This library is part of the [Awesome Modern C++](https://awesomecpp.com) list.
|
||||
|
||||
@@ -69,7 +73,7 @@ Usage documentation is available:
|
||||
* [Performance](doc/performance.md) shows some more advanced scenarios and how to tune for them.
|
||||
* [Implementation Selection](doc/implementation-selection.md) describes runtime CPU detection and
|
||||
how you can work with it.
|
||||
* [API](https://simdjson.org/api/0.3.1/annotated.html) contains the automatically generated API documentation.
|
||||
* [API](https://simdjson.org/api/0.6.0/annotated.html) contains the automatically generated API documentation.
|
||||
|
||||
Performance results
|
||||
-------------------
|
||||
@@ -118,15 +122,21 @@ or larger files (e.g., 3MB). The following plot presents parsing
|
||||
speed for [synthetic files over various sizes generated with a script](https://github.com/simdjson/simdjson_experiments_vldb2019/blob/master/experiments/growing/gen.py) on a 3.4 GHz Skylake processor (GNU GCC 9, -O3).
|
||||
<img src="doc/growing.png" width="90%">
|
||||
|
||||
|
||||
[All our experiments are reproducible](https://github.com/simdjson/simdjson_experiments_vldb2019).
|
||||
|
||||
|
||||
You can go beyond 4 GB/s with our new [On Demand API](https://github.com/simdjson/simdjson/blob/master/doc/ondemand.md).
|
||||
For NDJSON files, we can exceed 3 GB/s with [our multithreaded parsing functions](https://github.com/simdjson/simdjson/blob/master/doc/parse_many.md).
|
||||
|
||||
|
||||
|
||||
Real-world usage
|
||||
----------------
|
||||
|
||||
- [Microsoft FishStore](https://github.com/microsoft/FishStore)
|
||||
- [Yandex ClickHouse](https://github.com/yandex/ClickHouse)
|
||||
- [Clang Build Analyzer](https://github.com/aras-p/ClangBuildAnalyzer)
|
||||
- [Shopify HeapProfiler](https://github.com/Shopify/heap-profiler)
|
||||
|
||||
If you are planning to use simdjson in a product, please work from one of our releases.
|
||||
|
||||
@@ -136,6 +146,7 @@ Bindings and Ports of simdjson
|
||||
We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything).
|
||||
|
||||
- [ZippyJSON](https://github.com/michaeleisel/zippyjson): Swift bindings for the simdjson project.
|
||||
- [libpy_simdjson](https://github.com/gerrymanoim/libpy_simdjson/): high-speed Python bindings for simdjson using [libpy](https://github.com/quantopian/libpy).
|
||||
- [pysimdjson](https://github.com/TkTech/pysimdjson): Python bindings for the simdjson project.
|
||||
- [simdjson-rs](https://github.com/simd-lite): Rust port.
|
||||
- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings).
|
||||
@@ -143,8 +154,11 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
|
||||
- [simdjson_nodejs](https://github.com/luizperes/simdjson_nodejs): Node.js bindings for the simdjson project.
|
||||
- [simdjson_php](https://github.com/crazyxman/simdjson_php): PHP bindings for the simdjson project.
|
||||
- [simdjson_ruby](https://github.com/saka1/simdjson_ruby): Ruby bindings for the simdjson project.
|
||||
- [fast_jsonparser](https://github.com/anilmaurya/fast_jsonparser): Ruby bindings for the simdjson project.
|
||||
- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly.
|
||||
- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings.
|
||||
- [simdjson_erlang](https://github.com/ChomperT/simdjson_erlang): erlang bindings.
|
||||
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
@@ -154,8 +168,12 @@ instructions, reducing branch misprediction, and reducing data dependency to tak
|
||||
CPU's multiple execution cores.
|
||||
|
||||
Some people [enjoy reading our paper](https://arxiv.org/abs/1902.08318): A description of the design
|
||||
and implementation of simdjson is in our research article: Geoff Langdale, Daniel
|
||||
Lemire, [Parsing Gigabytes of JSON per Second](https://arxiv.org/abs/1902.08318), VLDB Journal 28 (6), 2019.
|
||||
and implementation of simdjson is in our research article:
|
||||
- Geoff Langdale, Daniel Lemire, [Parsing Gigabytes of JSON per Second](https://arxiv.org/abs/1902.08318), VLDB Journal 28 (6), 2019.
|
||||
|
||||
We have an in-depth paper focused on the UTF-8 validation:
|
||||
|
||||
- John Keiser, Daniel Lemire, [Validating UTF-8 In Less Than One Instruction Per Byte](https://arxiv.org/abs/2010.03090), Software: Practice & Experience (to appear)
|
||||
|
||||
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
|
||||
|
||||
|
||||
+36
@@ -1,3 +1,39 @@
|
||||
# 0.5
|
||||
|
||||
## Highlights
|
||||
|
||||
Performance
|
||||
* Faster and simpler UTF-8 validation with the lookup4 algorithm https://github.com/simdjson/simdjson/pull/993
|
||||
* We improved the performance of simdjson under Visual Studio by about 25%. Users will still get better performance with clang-cl (+30%) but the gap has been reduced. https://github.com/simdjson/simdjson/pull/1031
|
||||
|
||||
Code usability
|
||||
* In `parse_many`, when parsing streams of JSON documetns, we give to the users runtime control as to whether threads are used (via the parser.threaded attribute). https://github.com/simdjson/simdjson/issues/925
|
||||
* Prefixed public macros to avoid name clashes with other libraries. https://github.com/simdjson/simdjson/issues/1035
|
||||
* Better documentation regarding package managers (brew, MSYS2, conan, apt, vcpkg, FreeBSD package manager, etc.).
|
||||
* Better documentation regarding CMake usage.
|
||||
|
||||
Standards
|
||||
* We improved standard compliance with respect to both the JSON RFC 8259 and JSON Pointer RFC 6901. We added the at_pointer method to nodes for standard-compliant JSON Pointer queries. The legacy `at(std::string_view)` method remains but is deprecated since it is not standard-compliant as per RFC 6901.
|
||||
* We removed computed GOTOs without sacrificing performance thus improving the C++ standard compliance (since computed GOTOs are compiler-specific extensions).
|
||||
* Better support for C++20 https://github.com/simdjson/simdjson/pull/1050
|
||||
|
||||
# 0.4
|
||||
|
||||
## Highlights
|
||||
|
||||
- Test coverage has been greatly improved and we have resolved many static-analysis warnings on different systems.
|
||||
- We added a fast (8GB/s) minifier that works directly on JSON strings.
|
||||
- We added fast (10GB/s) UTF-8 validator that works directly on strings (any strings, including non-JSON).
|
||||
- The array and object elements have a constant-time size() method.
|
||||
- Performance improvements to the API (type(), get<>()).
|
||||
- The parse_many function (ndjson) has been entirely reworked. It now uses a single secondary thread instead of several new threads.
|
||||
- We have introduced a faster UTF-8 validation algorithm (lookup3) for all kernels (ARM, x64 SSE, x64 AVX).
|
||||
- C++11 support for older compilers and systems.
|
||||
- FreeBSD support (and tests).
|
||||
- We support the clang front-end compiler (clangcl) under Visual Studio.
|
||||
- It is now possible to target ARM platforms under Visual Studio.
|
||||
- The simdjson library will never abort or print to standard output/error.
|
||||
|
||||
# 0.3
|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
include_directories( . linux )
|
||||
link_libraries(simdjson simdjson-flags simdjson-windows-headers test-data)
|
||||
link_libraries(simdjson-windows-headers test-data)
|
||||
|
||||
|
||||
if (TARGET benchmark::benchmark)
|
||||
add_executable(bench_sax bench_sax.cpp)
|
||||
target_link_libraries(bench_sax simdjson-internal-flags simdjson-include-source benchmark::benchmark)
|
||||
endif (TARGET benchmark::benchmark)
|
||||
|
||||
link_libraries(simdjson simdjson-flags)
|
||||
add_executable(benchfeatures benchfeatures.cpp)
|
||||
add_executable(get_corpus_benchmark get_corpus_benchmark.cpp)
|
||||
add_executable(perfdiff perfdiff.cpp)
|
||||
@@ -14,24 +22,29 @@ target_compile_definitions(parse_nonumberparsing PRIVATE SIMDJSON_SKIPNUMBERPARS
|
||||
add_executable(parse_nostringparsing parse.cpp)
|
||||
target_compile_definitions(parse_nostringparsing PRIVATE SIMDJSON_SKIPSTRINGPARSING)
|
||||
|
||||
if (TARGET benchmark::benchmark)
|
||||
link_libraries(benchmark::benchmark)
|
||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||
endif()
|
||||
|
||||
if (TARGET competition-all)
|
||||
add_executable(distinctuseridcompetition distinctuseridcompetition.cpp)
|
||||
target_link_libraries(distinctuseridcompetition competition-core)
|
||||
|
||||
add_executable(minifiercompetition minifiercompetition.cpp)
|
||||
target_link_libraries(minifiercompetition competition-core)
|
||||
|
||||
add_executable(parseandstatcompetition parseandstatcompetition.cpp)
|
||||
target_link_libraries(parseandstatcompetition competition-core)
|
||||
|
||||
add_executable(parsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(parsingcompetition competition-core)
|
||||
|
||||
add_executable(allparsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(allparsingcompetition competition-all)
|
||||
target_compile_definitions(allparsingcompetition PRIVATE ALLPARSER)
|
||||
endif()
|
||||
|
||||
if (TARGET benchmark::benchmark)
|
||||
link_libraries(benchmark::benchmark)
|
||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||
add_executable(bench_ondemand bench_ondemand.cpp)
|
||||
endif()
|
||||
|
||||
include(checkperf.cmake)
|
||||
|
||||
+202
-22
@@ -11,6 +11,186 @@ const padded_string EMPTY_ARRAY("[]", 2);
|
||||
const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
const char *NUMBERS_JSON = SIMDJSON_BENCHMARK_DATA_DIR "numbers.json";
|
||||
|
||||
static void recover_one_string(State& state) {
|
||||
dom::parser parser;
|
||||
const std::string_view data = "\"one string\"";
|
||||
padded_string docdata{data};
|
||||
// we do not want mem. alloc. in the loop.
|
||||
auto error = parser.allocate(docdata.size());
|
||||
if(error) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse string" << error << endl;
|
||||
return;
|
||||
}
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::string_view v;
|
||||
error = doc.get(v);
|
||||
if (error) {
|
||||
cerr << "could not get string" << error << endl;
|
||||
return;
|
||||
}
|
||||
benchmark::DoNotOptimize(v);
|
||||
}
|
||||
}
|
||||
BENCHMARK(recover_one_string);
|
||||
|
||||
|
||||
static void serialize_twitter(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
if((error = parser.allocate(docdata.size()))) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::string serial = simdjson::minify(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// we validate the result
|
||||
{
|
||||
auto serial = simdjson::minify(doc);
|
||||
dom::element doc2; // we parse the minified output
|
||||
if ((error = parser.parse(serial).get(doc2))) { throw std::runtime_error("serialization error"); }
|
||||
auto serial2 = simdjson::minify(doc2); // we minify a second time
|
||||
if(serial != serial2) { throw std::runtime_error("serialization mismatch"); }
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void serialize_big_string_to_string(State& state) {
|
||||
dom::parser parser;
|
||||
std::vector<char> content;
|
||||
content.push_back('\"');
|
||||
for(size_t i = 0 ; i < 100000; i ++) {
|
||||
content.push_back('0' + char(i%10)); // we add what looks like a long list of digits
|
||||
}
|
||||
content.push_back('\"');
|
||||
dom::element doc;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.parse(content.data(), content.size()).get(doc))) {
|
||||
cerr << "could not parse big string" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto serial = simdjson::to_string(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_big_string_to_string)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void serialize_twitter_to_string(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
if((error = parser.allocate(docdata.size()))) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto serial = simdjson::to_string(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// we validate the result
|
||||
{
|
||||
auto serial = simdjson::to_string(doc);
|
||||
dom::element doc2; // we parse the stringify output
|
||||
if ((error = parser.parse(serial).get(doc2))) { throw std::runtime_error("serialization error"); }
|
||||
auto serial2 = simdjson::to_string(doc2); // we stringify again
|
||||
if(serial != serial2) { throw std::runtime_error("serialization mismatch"); }
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter_to_string)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
static void serialize_twitter_string_builder(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
if((error = parser.allocate(docdata.size()))) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
simdjson::internal::string_builder<> sb;// not part of our public API, for internal use
|
||||
for (simdjson_unused auto _ : state) {
|
||||
sb.clear();
|
||||
sb.append(doc);
|
||||
std::string_view serial = sb.str();
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter_string_builder)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void numbers_scan(State& state) {
|
||||
@@ -22,7 +202,7 @@ static void numbers_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
@@ -44,7 +224,7 @@ static void numbers_size_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -70,7 +250,7 @@ static void numbers_type_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array" << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
@@ -96,7 +276,7 @@ static void numbers_type_size_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -121,7 +301,7 @@ static void numbers_load_scan(State& state) {
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
@@ -144,7 +324,7 @@ static void numbers_load_size_scan(State& state) {
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array" << endl;
|
||||
@@ -173,7 +353,7 @@ static void numbers_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (double x : arr) {
|
||||
container.push_back(x);
|
||||
@@ -188,7 +368,7 @@ static void numbers_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -208,7 +388,7 @@ static void numbers_type_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
@@ -227,7 +407,7 @@ static void numbers_type_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -248,7 +428,7 @@ BENCHMARK(numbers_type_exceptions_size_scan);
|
||||
static void numbers_exceptions_load_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
@@ -264,7 +444,7 @@ BENCHMARK(numbers_exceptions_load_scan);
|
||||
static void numbers_exceptions_load_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
@@ -285,7 +465,7 @@ static void twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
@@ -298,7 +478,7 @@ static void iterator_twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
padded_string json = padded_string::load(TWITTER_JSON);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
ParsedJson::Iterator iter(pj);
|
||||
// uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (!iter.move_to_key("search_metadata")) { return; }
|
||||
@@ -316,7 +496,7 @@ static void twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::object user = tweet["user"];
|
||||
@@ -334,7 +514,7 @@ static void twitter_image_sizes(State& state) {
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
simdjson::error_code error;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::array media;
|
||||
@@ -359,7 +539,7 @@ static void error_code_twitter_count(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
uint64_t value;
|
||||
if ((error = doc["search_metadata"]["count"].get(value))) { return; }
|
||||
if (value != 100) { return; }
|
||||
@@ -373,7 +553,7 @@ static void error_code_twitter_default_profile(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
|
||||
dom::array tweets;
|
||||
@@ -403,7 +583,7 @@ static void iterator_twitter_default_profile(State& state) {
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
@@ -444,7 +624,7 @@ static void error_code_twitter_image_sizes(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
dom::array statuses;
|
||||
if ((error = doc["statuses"].get(statuses))) { return; }
|
||||
@@ -476,7 +656,7 @@ static void iterator_twitter_image_sizes(State& state) {
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
@@ -541,7 +721,7 @@ static void print_json(State& state) noexcept {
|
||||
|
||||
int code = json_parse(json, parser);
|
||||
if (code) { cerr << error_message(code) << endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::stringstream s;
|
||||
if (!parser.print_json(s)) { cerr << "print_json failed" << endl; return; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "simdjson.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <benchmark/benchmark.h>
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#include "partial_tweets/ondemand.h"
|
||||
#include "partial_tweets/iter.h"
|
||||
#include "partial_tweets/dom.h"
|
||||
|
||||
#include "largerandom/ondemand.h"
|
||||
#include "largerandom/iter.h"
|
||||
#include "largerandom/dom.h"
|
||||
|
||||
#include "kostya/ondemand.h"
|
||||
#include "kostya/iter.h"
|
||||
#include "kostya/dom.h"
|
||||
|
||||
#include "distinctuserid/ondemand.h"
|
||||
#include "distinctuserid/dom.h"
|
||||
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -10,6 +10,36 @@ const char *GSOC_JSON = SIMDJSON_BENCHMARK_DATA_DIR "gsoc-2018.json";
|
||||
|
||||
|
||||
|
||||
static void unicode_validate_twitter(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
error = parser.allocate(docdata.size());
|
||||
if(error) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
bool is_ok = simdjson::validate_utf8(docdata.data(), docdata.size());
|
||||
bytes += docdata.size();
|
||||
benchmark::DoNotOptimize(is_ok);
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(unicode_validate_twitter)->Repetitions(10)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
static void parse_twitter(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
@@ -25,10 +55,9 @@ static void parse_twitter(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::element doc;
|
||||
bytes += docdata.size();
|
||||
;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
@@ -61,7 +90,7 @@ static void parse_gsoc(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
bytes += docdata.size();
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
@@ -87,7 +116,7 @@ SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void json_parse(State& state) {
|
||||
ParsedJson pj;
|
||||
if (!pj.allocate_capacity(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto error = json_parse(EMPTY_ARRAY, pj);
|
||||
if (error) { return; }
|
||||
}
|
||||
@@ -97,7 +126,7 @@ BENCHMARK(json_parse);
|
||||
static void parser_parse_error_code(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
}
|
||||
@@ -109,9 +138,9 @@ BENCHMARK(parser_parse_error_code);
|
||||
static void parser_parse_exception(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
try {
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
simdjson_unused dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
@@ -125,7 +154,7 @@ BENCHMARK(parser_parse_exception);
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void build_parsed_json(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::parser parser = simdjson::build_parsed_json(EMPTY_ARRAY);
|
||||
if (!parser.valid) { return; }
|
||||
}
|
||||
@@ -134,7 +163,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
BENCHMARK(build_parsed_json);
|
||||
static void document_parse_error_code(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::parser parser;
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
@@ -145,10 +174,10 @@ BENCHMARK(document_parse_error_code);
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void document_parse_exception(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
try {
|
||||
dom::parser parser;
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
simdjson_unused dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "simdjson.h"
|
||||
#include "simdjson.cpp"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <benchmark/benchmark.h>
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#include "partial_tweets/sax.h"
|
||||
#include "largerandom/sax.h"
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -96,8 +96,14 @@ struct option_struct {
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a':
|
||||
simdjson::active_implementation = simdjson::available_implementations[optarg];
|
||||
case 'a': {
|
||||
auto impl = simdjson::available_implementations[optarg];
|
||||
if(impl && impl->supported_by_runtime_system()) {
|
||||
simdjson::active_implementation = impl;
|
||||
} else {
|
||||
std::cerr << "implementation " << optarg << " not found or not supported " << std::endl;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
@@ -158,7 +164,7 @@ struct feature_benchmarker {
|
||||
|
||||
}
|
||||
|
||||
really_inline void run_iterations(size_t iterations, bool stage1_only=false) {
|
||||
simdjson_really_inline void run_iterations(size_t iterations, bool stage1_only=false) {
|
||||
struct7.run_iterations(iterations, stage1_only);
|
||||
struct7_miss.run_iterations(iterations, stage1_only);
|
||||
struct7_full.run_iterations(iterations, stage1_only);
|
||||
|
||||
+37
-34
@@ -12,9 +12,9 @@
|
||||
#define BEST_TIME(name, test, expected, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
std::printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
std::printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
@@ -23,48 +23,50 @@
|
||||
std::atomic_thread_fence(std::memory_order_acquire); \
|
||||
collector.start(); \
|
||||
if (test != expected) { \
|
||||
fprintf(stderr, "not expected (%d , %d )", (int)test, (int)expected); \
|
||||
std::fprintf(stderr, "not expected (%d , %d )", (int)test, \
|
||||
(int)expected); \
|
||||
break; \
|
||||
} \
|
||||
std::atomic_thread_fence(std::memory_order_release); \
|
||||
event_count allocate_count = collector.end(); \
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
uint64_t S = size; \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
std::printf(" cycles/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
std::printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
std::printf("\t"); \
|
||||
} \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
std::printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
std::printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
std::printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
std::printf("\n"); \
|
||||
std::fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
// like BEST_TIME, but no check
|
||||
#define BEST_TIME_NOCHECK(name, test, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
std::printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
std::printf("\"%-40s\"", name); \
|
||||
std::fflush(NULL); \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) { \
|
||||
@@ -76,31 +78,32 @@
|
||||
event_count allocate_count = collector.end(); \
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
uint64_t S = size; \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
std::printf(" cycles/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
std::printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
std::printf("\t"); \
|
||||
} \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
std::printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
std::printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
std::printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
std::printf("\n"); \
|
||||
std::fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
+37
-26
@@ -225,8 +225,19 @@ struct progress_bar {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The speed at which we can allocate memory is strictly system specific.
|
||||
* It depends on the OS and the runtime library. It is subject to various
|
||||
* system-specific knobs. It is not something that we can reasonably
|
||||
* benchmark with crude timings.
|
||||
* If someone wants to optimize how simdjson allocate memory, then it will
|
||||
* almost surely require a distinct benchmarking tool. What is meant by
|
||||
* "memory allocation" also requires a definition. Doing "new char[size]" can
|
||||
* do many different things depending on the system.
|
||||
*/
|
||||
|
||||
enum class BenchmarkStage {
|
||||
ALL,
|
||||
ALL, // This excludes allocation
|
||||
ALLOCATE,
|
||||
STAGE1,
|
||||
STAGE2
|
||||
@@ -234,7 +245,7 @@ enum class BenchmarkStage {
|
||||
|
||||
const char* benchmark_stage_name(BenchmarkStage stage) {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return "All";
|
||||
case BenchmarkStage::ALL: return "All (Without Allocation)";
|
||||
case BenchmarkStage::ALLOCATE: return "Allocate";
|
||||
case BenchmarkStage::STAGE1: return "Stage 1";
|
||||
case BenchmarkStage::STAGE2: return "Stage 2";
|
||||
@@ -253,8 +264,8 @@ struct benchmarker {
|
||||
// Statistics about the JSON file independent of its speed (amount of utf-8, structurals, etc.).
|
||||
// Loaded on first parse.
|
||||
json_stats* stats;
|
||||
// Speed and event summary for full parse (including allocation, stage 1 and stage 2)
|
||||
event_aggregate all_stages{};
|
||||
// Speed and event summary for full parse (stage 1 and stage 2, but *excluding* allocation)
|
||||
event_aggregate all_stages_without_allocation{};
|
||||
// Speed and event summary for stage 1
|
||||
event_aggregate stage1{};
|
||||
// Speed and event summary for stage 2
|
||||
@@ -285,23 +296,24 @@ struct benchmarker {
|
||||
|
||||
const event_aggregate& operator[](BenchmarkStage stage) const {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return this->all_stages;
|
||||
case BenchmarkStage::ALL: return this->all_stages_without_allocation;
|
||||
case BenchmarkStage::STAGE1: return this->stage1;
|
||||
case BenchmarkStage::STAGE2: return this->stage2;
|
||||
case BenchmarkStage::ALLOCATE: return this->allocate_stage;
|
||||
default: exit_error("Unknown stage"); return this->all_stages;
|
||||
default: exit_error("Unknown stage"); return this->all_stages_without_allocation;
|
||||
}
|
||||
}
|
||||
|
||||
int iterations() const {
|
||||
return all_stages.iterations;
|
||||
return all_stages_without_allocation.iterations;
|
||||
}
|
||||
|
||||
really_inline void run_iteration(bool stage1_only, bool hotbuffers=false) {
|
||||
simdjson_really_inline void run_iteration(bool stage1_only, bool hotbuffers=false) {
|
||||
// Allocate dom::parser
|
||||
collector.start();
|
||||
dom::parser parser;
|
||||
error_code error = parser.allocate(json.size());
|
||||
// We always allocate at least 64KB. Smaller allocations may actually be slower under some systems.
|
||||
error_code error = parser.allocate(json.size() < 65536 ? 65536 : json.size());
|
||||
if (error) {
|
||||
exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(error));
|
||||
}
|
||||
@@ -329,7 +341,7 @@ struct benchmarker {
|
||||
// Stage 2 (unified machine) and the rest
|
||||
|
||||
if (stage1_only) {
|
||||
all_stages << stage1_count;
|
||||
all_stages_without_allocation << stage1_count;
|
||||
} else {
|
||||
event_count stage2_count;
|
||||
collector.start();
|
||||
@@ -339,7 +351,7 @@ struct benchmarker {
|
||||
}
|
||||
stage2_count = collector.end();
|
||||
stage2 << stage2_count;
|
||||
all_stages << allocate_count + stage1_count + stage2_count;
|
||||
all_stages_without_allocation << stage1_count + stage2_count;
|
||||
}
|
||||
// Calculate stats the first time we parse
|
||||
if (stats == NULL) {
|
||||
@@ -372,7 +384,7 @@ struct benchmarker {
|
||||
loop << all_loop_count;
|
||||
}
|
||||
|
||||
really_inline void run_iterations(size_t iterations, bool stage1_only, bool hotbuffers=false) {
|
||||
simdjson_really_inline void run_iterations(size_t iterations, bool stage1_only, bool hotbuffers=false) {
|
||||
for (size_t i = 0; i<iterations; i++) {
|
||||
run_iteration(stage1_only, hotbuffers);
|
||||
}
|
||||
@@ -386,7 +398,7 @@ struct benchmarker {
|
||||
prefix,
|
||||
"Speed",
|
||||
stage.elapsed_ns() / static_cast<double>(stats->blocks), // per block
|
||||
percent(stage.elapsed_sec(), all_stages.elapsed_sec()), // %
|
||||
percent(stage.elapsed_sec(), all_stages_without_allocation.elapsed_sec()), // %
|
||||
stage.elapsed_ns() / static_cast<double>(stats->bytes), // per byte
|
||||
stage.elapsed_ns() / static_cast<double>(stats->structurals), // per structural
|
||||
(static_cast<double>(json.size()) / 1000000000.0) / stage.elapsed_sec() // GB/s
|
||||
@@ -397,7 +409,7 @@ struct benchmarker {
|
||||
prefix,
|
||||
"Cycles",
|
||||
stage.cycles() / static_cast<double>(stats->blocks),
|
||||
percent(stage.cycles(), all_stages.cycles()),
|
||||
percent(stage.cycles(), all_stages_without_allocation.cycles()),
|
||||
stage.cycles() / static_cast<double>(stats->bytes),
|
||||
stage.cycles() / static_cast<double>(stats->structurals),
|
||||
(stage.cycles() / stage.elapsed_sec()) / 1000000000.0
|
||||
@@ -406,7 +418,7 @@ struct benchmarker {
|
||||
prefix,
|
||||
"Instructions",
|
||||
stage.instructions() / static_cast<double>(stats->blocks),
|
||||
percent(stage.instructions(), all_stages.instructions()),
|
||||
percent(stage.instructions(), all_stages_without_allocation.instructions()),
|
||||
stage.instructions() / static_cast<double>(stats->bytes),
|
||||
stage.instructions() / static_cast<double>(stats->structurals),
|
||||
stage.instructions() / static_cast<double>(stage.cycles())
|
||||
@@ -417,9 +429,9 @@ struct benchmarker {
|
||||
prefix,
|
||||
"Misses",
|
||||
stage.branch_misses(),
|
||||
percent(stage.branch_misses(), all_stages.branch_misses()),
|
||||
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
|
||||
stage.cache_misses(),
|
||||
percent(stage.cache_misses(), all_stages.cache_misses()),
|
||||
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
|
||||
stage.cache_references()
|
||||
);
|
||||
}
|
||||
@@ -456,14 +468,14 @@ struct benchmarker {
|
||||
allocate_stage.best.cycles() / static_cast<double>(json.size()),
|
||||
stage1.best.cycles() / static_cast<double>(json.size()),
|
||||
stage2.best.cycles() / static_cast<double>(json.size()),
|
||||
all_stages.best.cycles() / static_cast<double>(json.size()),
|
||||
gb / all_stages.best.elapsed_sec(),
|
||||
all_stages_without_allocation.best.cycles() / static_cast<double>(json.size()),
|
||||
gb / all_stages_without_allocation.best.elapsed_sec(),
|
||||
gb / stage1.best.elapsed_sec(),
|
||||
gb / stage2.best.elapsed_sec());
|
||||
} else {
|
||||
printf("\"%s\"\t\t\t\t\t%f\t%f\t%f\n",
|
||||
base,
|
||||
gb / all_stages.best.elapsed_sec(),
|
||||
gb / all_stages_without_allocation.best.elapsed_sec(),
|
||||
gb / stage1.best.elapsed_sec(),
|
||||
gb / stage2.best.elapsed_sec());
|
||||
}
|
||||
@@ -490,10 +502,10 @@ struct benchmarker {
|
||||
stats->blocks_with_16_structurals_flipped, percent(stats->blocks_with_16_structurals_flipped, stats->blocks));
|
||||
}
|
||||
printf("\n");
|
||||
printf("All Stages\n");
|
||||
print_aggregate("| " , all_stages.best);
|
||||
printf("All Stages (excluding allocation)\n");
|
||||
print_aggregate("| " , all_stages_without_allocation.best);
|
||||
// frequently, allocation is a tiny fraction of the running time so we omit it
|
||||
if(allocate_stage.best.elapsed_sec() > 0.01 * all_stages.best.elapsed_sec()) {
|
||||
if(allocate_stage.best.elapsed_sec() > 0.01 * all_stages_without_allocation.best.elapsed_sec()) {
|
||||
printf("|- Allocation\n");
|
||||
print_aggregate("| ", allocate_stage.best);
|
||||
}
|
||||
@@ -504,17 +516,16 @@ struct benchmarker {
|
||||
if (collector.has_events()) {
|
||||
double freq1 = (stage1.best.cycles() / stage1.best.elapsed_sec()) / 1000000000.0;
|
||||
double freq2 = (stage2.best.cycles() / stage2.best.elapsed_sec()) / 1000000000.0;
|
||||
double freqall = (all_stages.best.cycles() / all_stages.best.elapsed_sec()) / 1000000000.0;
|
||||
double freqall = (all_stages_without_allocation.best.cycles() / all_stages_without_allocation.best.elapsed_sec()) / 1000000000.0;
|
||||
double freqmin = min(freq1, freq2);
|
||||
double freqmax = max(freq1, freq2);
|
||||
if((freqall < 0.95 * freqmin) or (freqall > 1.05 * freqmax)) {
|
||||
printf("\nWarning: The processor frequency fluctuates in an expected way!!!\n"
|
||||
"Expect the overall speed not to match stage 1 and stage 2 speeds.\n"
|
||||
"Range for stage 1 and stage 2 : [%.3f GHz, %.3f GHz], overall: %.3f GHz.\n",
|
||||
freqmin, freqmax, freqall);
|
||||
}
|
||||
}
|
||||
printf("\n%.1f documents parsed per second (best)\n", 1.0/static_cast<double>(all_stages.best.elapsed_sec()));
|
||||
printf("\n%.1f documents parsed per second (best)\n", 1.0/static_cast<double>(all_stages_without_allocation.best.elapsed_sec()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
# Clone the repository if it's not there
|
||||
find_package(Git QUIET)
|
||||
if (SIMDJSON_IS_UNDER_GIT AND Git_FOUND AND (GIT_VERSION_STRING VERSION_GREATER "2.1.4") AND (NOT CMAKE_GENERATOR MATCHES Ninja) ) # We use "-C" which requires a recent git
|
||||
if (SIMDJSON_IS_UNDER_GIT AND SIMDJSON_GIT AND Git_FOUND AND (GIT_VERSION_STRING VERSION_GREATER "2.1.4") AND (NOT CMAKE_GENERATOR MATCHES Ninja) ) # We use "-C" which requires a recent git
|
||||
message(STATUS "Git is available and it is recent. We are enabling checkperf targets.")
|
||||
# sync_git_repository(myrepo ...) creates two targets:
|
||||
# myrepo - if the repo does not exist, creates and syncs it against the origin branch
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include "event_counter.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
|
||||
bool equals(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
|
||||
|
||||
void remove_duplicates(std::vector<int64_t> &v) {
|
||||
std::sort(v.begin(), v.end());
|
||||
auto last = std::unique(v.begin(), v.end());
|
||||
v.erase(last, v.end());
|
||||
}
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace distinct_user_id {
|
||||
template<typename T> static void DistinctUserID(benchmark::State &state);
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
#include "dom.h"
|
||||
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
template<typename T> static void DistinctUserID(benchmark::State &state) {
|
||||
//
|
||||
// Load the JSON file
|
||||
//
|
||||
constexpr const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
error_code error;
|
||||
padded_string json;
|
||||
if ((error = padded_string::load(TWITTER_JSON).get(json))) {
|
||||
std::cerr << error << std::endl;
|
||||
state.SkipWithError("error loading");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonBenchmark<T, Dom>(state, json);
|
||||
}
|
||||
|
||||
} // namespace distinct_user_id
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "distinctuserid.h"
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element);
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::array array) {
|
||||
for (auto child : array) {
|
||||
simdjson_recurse(v, child);
|
||||
}
|
||||
}
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::object object) {
|
||||
for (auto [key, value] : object) {
|
||||
if((key.size() == 4) && (memcmp(key.data(), "user", 4) == 0)) {
|
||||
// we are in an object under the key "user"
|
||||
simdjson::error_code error;
|
||||
simdjson::dom::object child_object;
|
||||
simdjson::dom::object child_array;
|
||||
if (not (error = value.get(child_object))) {
|
||||
for (auto [child_key, child_value] : child_object) {
|
||||
if((child_key.size() == 2) && (memcmp(child_key.data(), "id", 2) == 0)) {
|
||||
int64_t x;
|
||||
if (not (error = child_value.get(x))) {
|
||||
v.push_back(x);
|
||||
}
|
||||
}
|
||||
simdjson_recurse(v, child_value);
|
||||
}
|
||||
} else if (not (error = value.get(child_array))) {
|
||||
simdjson_recurse(v, child_array);
|
||||
}
|
||||
// end of: we are in an object under the key "user"
|
||||
} else {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
simdjson_unused simdjson::error_code error;
|
||||
simdjson::dom::array array;
|
||||
simdjson::dom::object object;
|
||||
if (not (error = element.get(array))) {
|
||||
simdjson_recurse(v, array);
|
||||
} else if (not (error = element.get(object))) {
|
||||
simdjson_recurse(v, object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<int64_t> &Result() { return ids; }
|
||||
simdjson_really_inline size_t ItemCount() { return ids.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<int64_t> ids{};
|
||||
|
||||
};
|
||||
void print_vec(const std::vector<int64_t> &v) {
|
||||
for (auto i : v) {
|
||||
std::cout << i << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
ids.clear();
|
||||
dom::element doc = parser.parse(json);
|
||||
simdjson_recurse(ids, doc);
|
||||
remove_duplicates(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(DistinctUserID, Dom);
|
||||
|
||||
} // namespace distinct_user_id
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "distinctuserid.h"
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
OnDemand() {
|
||||
if(!displayed_implementation) {
|
||||
std::cout << "On Demand implementation: " << builtin_implementation()->name() << std::endl;
|
||||
displayed_implementation = true;
|
||||
}
|
||||
}
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<int64_t> &Result() { return ids; }
|
||||
simdjson_really_inline size_t ItemCount() { return ids.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<int64_t> ids{};
|
||||
|
||||
static inline bool displayed_implementation = false;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
ids.clear();
|
||||
// Walk the document, parsing as we go
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
// We believe that all statuses have a matching
|
||||
// user, and we are willing to throw when they do not:
|
||||
//
|
||||
// You might think that you do not need the braces, but
|
||||
// you do, otherwise you will get the wrong answer. That is
|
||||
// because you can only have one active object or array
|
||||
// at a time.
|
||||
{
|
||||
ondemand::object user = tweet["user"];
|
||||
int64_t id = user["id"];
|
||||
ids.push_back(id);
|
||||
}
|
||||
// Not all tweets have a "retweeted_status", but when they do
|
||||
// we want to go and find the user within.
|
||||
auto retweet = tweet["retweeted_status"];
|
||||
if(!retweet.error()) {
|
||||
ondemand::object retweet_content = retweet;
|
||||
ondemand::object reuser = retweet_content["user"];
|
||||
int64_t rid = reuser["id"];
|
||||
ids.push_back(rid);
|
||||
}
|
||||
}
|
||||
remove_duplicates(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(DistinctUserID, OnDemand);
|
||||
|
||||
} // namespace distinct_user_id
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -38,7 +38,7 @@ void print_vec(const std::vector<int64_t> &v) {
|
||||
|
||||
// clang-format off
|
||||
|
||||
// simdjson_recurse below come be implemented like so but it is slow:
|
||||
// simdjson_recurse below can be implemented like so but it is slow:
|
||||
/*void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
error_code error;
|
||||
if (element.is_array()) {
|
||||
@@ -65,7 +65,7 @@ void print_vec(const std::vector<int64_t> &v) {
|
||||
// clang-format on
|
||||
|
||||
|
||||
really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element);
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element);
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::array array) {
|
||||
for (auto child : array) {
|
||||
simdjson_recurse(v, child);
|
||||
@@ -97,8 +97,8 @@ void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::object object) {
|
||||
}
|
||||
}
|
||||
}
|
||||
really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
UNUSED simdjson::error_code error;
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
simdjson_unused simdjson::error_code error;
|
||||
simdjson::dom::array array;
|
||||
simdjson::dom::object object;
|
||||
if (not (error = element.get(array))) {
|
||||
@@ -108,7 +108,7 @@ really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::ele
|
||||
}
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
simdjson_just_dom(simdjson::dom::element doc) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson_recurse(answer, doc);
|
||||
@@ -116,7 +116,7 @@ simdjson_just_dom(simdjson::dom::element doc) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson::dom::parser parser;
|
||||
@@ -129,7 +129,7 @@ simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline simdjson::error_code
|
||||
simdjson_really_inline simdjson::error_code
|
||||
simdjson_just_parse(const simdjson::padded_string &p) {
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(p).error();
|
||||
@@ -187,7 +187,7 @@ void sajson_traverse(std::vector<int64_t> &answer, const sajson::value &node) {
|
||||
}
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
sasjon_just_dom(sajson::document &d) {
|
||||
std::vector<int64_t> answer;
|
||||
sajson_traverse(answer, d.get_root());
|
||||
@@ -195,7 +195,7 @@ sasjon_just_dom(sajson::document &d) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
@@ -212,7 +212,7 @@ sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline bool
|
||||
simdjson_really_inline bool
|
||||
sasjon_just_parse(const simdjson::padded_string &p) {
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
@@ -263,7 +263,7 @@ void rapid_traverse(std::vector<int64_t> &answer, const rapidjson::Value &v) {
|
||||
}
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
rapid_just_dom(rapidjson::Document &d) {
|
||||
std::vector<int64_t> answer;
|
||||
rapid_traverse(answer, d);
|
||||
@@ -271,7 +271,7 @@ rapid_just_dom(rapidjson::Document &d) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_really_inline std::vector<int64_t>
|
||||
rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
@@ -289,7 +289,7 @@ rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
really_inline bool
|
||||
simdjson_really_inline bool
|
||||
rapid_just_parse(const simdjson::padded_string &p) {
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
|
||||
@@ -116,30 +116,30 @@ struct event_collector {
|
||||
|
||||
#if defined(__linux__)
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
|
||||
event_collector() : linux_events(vector<int>{
|
||||
event_collector(bool quiet = false) : linux_events(vector<int>{
|
||||
PERF_COUNT_HW_CPU_CYCLES,
|
||||
PERF_COUNT_HW_INSTRUCTIONS,
|
||||
PERF_COUNT_HW_BRANCH_MISSES,
|
||||
PERF_COUNT_HW_CACHE_REFERENCES,
|
||||
PERF_COUNT_HW_CACHE_MISSES
|
||||
}) {}
|
||||
}, quiet) {}
|
||||
bool has_events() {
|
||||
return linux_events.is_working();
|
||||
}
|
||||
#else
|
||||
event_collector() {}
|
||||
event_collector(simdjson_unused bool _quiet = false) {}
|
||||
bool has_events() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
really_inline void start() {
|
||||
simdjson_really_inline void start() {
|
||||
#if defined(__linux)
|
||||
linux_events.start();
|
||||
#endif
|
||||
start_clock = steady_clock::now();
|
||||
}
|
||||
really_inline event_count& end() {
|
||||
simdjson_really_inline event_count& end() {
|
||||
time_point<steady_clock> end_clock = steady_clock::now();
|
||||
#if defined(__linux)
|
||||
linux_events.end(count.event_counts);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <iostream>
|
||||
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
never_inline
|
||||
simdjson_never_inline
|
||||
double bench(std::string filename, simdjson::padded_string& p) {
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_clock =
|
||||
std::chrono::steady_clock::now();
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
template<typename B, typename R> static void JsonBenchmark(benchmark::State &state, const simdjson::padded_string &json) {
|
||||
event_collector collector(true);
|
||||
event_aggregate events;
|
||||
|
||||
// Warmup and equality check (make sure the data is right!)
|
||||
B bench;
|
||||
if (!bench.Run(json)) { state.SkipWithError("warmup tweet reading failed"); return; }
|
||||
{
|
||||
R reference;
|
||||
if (!reference.Run(json)) { state.SkipWithError("reference tweet reading failed"); return; }
|
||||
if (bench.Result() != reference.Result()) { state.SkipWithError("results are not the same"); return; }
|
||||
}
|
||||
|
||||
// Run the benchmark
|
||||
for (simdjson_unused auto _ : state) {
|
||||
collector.start();
|
||||
|
||||
if (!bench.Run(json)) { state.SkipWithError("tweet reading failed"); return; }
|
||||
|
||||
events << collector.end();
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(json.size() * state.iterations());
|
||||
state.SetItemsProcessed(bench.ItemCount() * state.iterations());
|
||||
state.counters["best_bytes_per_sec"] = benchmark::Counter(double(json.size()) / events.best.elapsed_sec());
|
||||
state.counters["best_items_per_sec"] = benchmark::Counter(double(bench.ItemCount()) / events.best.elapsed_sec());
|
||||
|
||||
state.counters["docs_per_sec"] = benchmark::Counter(1.0, benchmark::Counter::kIsIterationInvariantRate);
|
||||
state.counters["best_docs_per_sec"] = benchmark::Counter(1.0 / events.best.elapsed_sec());
|
||||
|
||||
if (collector.has_events()) {
|
||||
state.counters["instructions"] = events.instructions();
|
||||
state.counters["cycles"] = events.cycles();
|
||||
state.counters["branch_miss"] = events.branch_misses();
|
||||
state.counters["cache_miss"] = events.cache_misses();
|
||||
state.counters["cache_ref"] = events.cache_references();
|
||||
|
||||
state.counters["instructions_per_byte"] = events.instructions() / double(json.size());
|
||||
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
|
||||
state.counters["cycles_per_byte"] = events.cycles() / double(json.size());
|
||||
state.counters["frequency"] = benchmark::Counter(events.cycles(), benchmark::Counter::kIsIterationInvariantRate);
|
||||
|
||||
state.counters["best_instructions"] = events.best.instructions();
|
||||
state.counters["best_cycles"] = events.best.cycles();
|
||||
state.counters["best_branch_miss"] = events.best.branch_misses();
|
||||
state.counters["best_cache_miss"] = events.best.cache_misses();
|
||||
state.counters["best_cache_ref"] = events.best.cache_references();
|
||||
|
||||
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(json.size());
|
||||
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
|
||||
state.counters["best_cycles_per_byte"] = events.best.cycles() / double(json.size());
|
||||
state.counters["best_frequency"] = events.best.cycles() / events.best.elapsed_sec();
|
||||
}
|
||||
state.counters["bytes"] = benchmark::Counter(double(json.size()));
|
||||
state.counters["items"] = benchmark::Counter(double(bench.ItemCount()));
|
||||
|
||||
// Build the label
|
||||
using namespace std;
|
||||
stringstream label;
|
||||
label << fixed << setprecision(2);
|
||||
label << "[best:";
|
||||
label << " throughput=" << setw(6) << (double(json.size()) / 1000000000.0 / events.best.elapsed_sec()) << " GB/s";
|
||||
label << " doc_throughput=" << setw(6) << uint64_t(1.0 / events.best.elapsed_sec()) << " docs/s";
|
||||
|
||||
if (collector.has_events()) {
|
||||
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
|
||||
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
|
||||
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
|
||||
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
|
||||
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
|
||||
}
|
||||
|
||||
label << " items=" << setw(10) << bench.ItemCount() << setw(0);
|
||||
label << " avg_time=" << setw(10) << uint64_t(events.elapsed_ns()) << setw(0) << " ns";
|
||||
label << "]";
|
||||
|
||||
state.SetLabel(label.str());
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
for (auto point : parser.parse(json)["coordinates"]) {
|
||||
container.emplace_back(my_point{point["x"], point["y"], point["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, Dom);
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
sum = { 0, 0, 0 };
|
||||
count = 0;
|
||||
|
||||
for (auto coord : parser.parse(json)["coordinates"]) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, Dom);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
|
||||
simdjson_really_inline simdjson_result<double> first_double(ondemand::json_iterator &iter, const char *key) {
|
||||
if (!iter.start_object() || ondemand::raw_json_string(iter.field_key()) != key || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
simdjson_really_inline simdjson_result<double> next_double(ondemand::json_iterator &iter, const char *key) {
|
||||
if (!iter.has_next_field() || ondemand::raw_json_string(iter.field_key()) != key || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("coordinates")) { cerr << "find coordinates field failed" << endl; return false; }
|
||||
if (iter.start_array()) {
|
||||
do {
|
||||
container.emplace_back(my_point{first_double(iter, "x"), next_double(iter, "y"), next_double(iter, "z")});
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the coordinates object
|
||||
} while (iter.has_next_element());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, Iter);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("coordinates")) { return false; }
|
||||
if (!iter.start_array()) { return false; }
|
||||
do {
|
||||
if (!iter.start_object() || !iter.find_field_raw("x")) { return false; }
|
||||
sum.x += iter.consume_double();
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("y")) { return false; }
|
||||
sum.y += iter.consume_double();
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("z")) { return false; }
|
||||
sum.z += iter.consume_double();
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the coordinates object
|
||||
count++;
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, Iter);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace kostya {
|
||||
template<typename T> static void Kostya(benchmark::State &state);
|
||||
namespace sum {
|
||||
template<typename T> static void KostyaSum(benchmark::State &state);
|
||||
}
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
static void append_coordinate(std::default_random_engine &e, std::uniform_real_distribution<> &dis, std::stringstream &myss) {
|
||||
using std::endl;
|
||||
myss << R"( {)" << endl;
|
||||
myss << R"( "x": )" << dis(e) << "," << endl;
|
||||
myss << R"( "y": )" << dis(e) << "," << endl;
|
||||
myss << R"( "z": )" << dis(e) << "," << endl;
|
||||
myss << R"( "name": ")" << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << " " << int(dis(e)*10000) << "\"," << endl;
|
||||
myss << R"( "opts": {)" << endl;
|
||||
myss << R"( "1": [)" << endl;
|
||||
myss << R"( 1,)" << endl;
|
||||
myss << R"( true)" << endl;
|
||||
myss << R"( ])" << endl;
|
||||
myss << R"( })" << endl;
|
||||
myss << R"( })";
|
||||
}
|
||||
|
||||
static std::string build_json_array(size_t N) {
|
||||
using namespace std;
|
||||
default_random_engine e;
|
||||
uniform_real_distribution<> dis(0, 1);
|
||||
stringstream myss;
|
||||
myss << R"({)" << endl;
|
||||
myss << R"( "coordinates": [)" << endl;
|
||||
for (size_t i=1; i<N; i++) {
|
||||
append_coordinate(e, dis, myss); myss << "," << endl;
|
||||
}
|
||||
append_coordinate(e, dis, myss); myss << endl;
|
||||
myss << R"( ],)" << endl;
|
||||
myss << R"( "info": "some info")" << endl;
|
||||
myss << R"(})" << endl;
|
||||
string answer = myss.str();
|
||||
cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << endl;
|
||||
return answer;
|
||||
}
|
||||
|
||||
static const padded_string &get_built_json_array() {
|
||||
static padded_string json = build_json_array(524288);
|
||||
return json;
|
||||
}
|
||||
|
||||
struct my_point {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
simdjson_really_inline bool operator==(const my_point &other) const {
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const my_point &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const my_point &p) {
|
||||
return o << p.x << "," << p.y << "," << p.z << std::endl;
|
||||
}
|
||||
|
||||
} // namespace kostya
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "dom.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
template<typename T> static void Kostya(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
namespace sum {
|
||||
template<typename T> static void KostyaSum(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc["coordinates"]) {
|
||||
container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, OnDemand);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc["coordinates"]) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, OnDemand);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
for (auto point : parser.parse(json)) {
|
||||
container.emplace_back(my_point{point["x"], point["y"], point["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Dom);
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
sum = { 0, 0, 0 };
|
||||
count = 0;
|
||||
|
||||
for (auto coord : parser.parse(json)) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, Dom);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
|
||||
simdjson_really_inline double first_double(ondemand::json_iterator &iter) {
|
||||
if (iter.start_object().error() || iter.field_key().error() || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
simdjson_really_inline double next_double(ondemand::json_iterator &iter) {
|
||||
if (!iter.has_next_field() || iter.field_key().error() || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (iter.start_array()) {
|
||||
do {
|
||||
container.emplace_back(my_point{first_double(iter), next_double(iter), next_double(iter)});
|
||||
if (iter.has_next_field()) { throw "Too many fields"; }
|
||||
} while (iter.has_next_element());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Iter);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_array()) { return false; }
|
||||
do {
|
||||
if (!iter.start_object() || iter.field_key().value() != "x" || iter.field_value()) { return false; }
|
||||
sum.x += iter.consume_double();
|
||||
if (!iter.has_next_field() || iter.field_key().value() != "y" || iter.field_value()) { return false; }
|
||||
sum.y += iter.consume_double();
|
||||
if (!iter.has_next_field() || iter.field_key().value() != "z" || iter.field_value()) { return false; }
|
||||
sum.z += iter.consume_double();
|
||||
if (*iter.advance() != '}') { return false; }
|
||||
count++;
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, Iter);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace largerandom {
|
||||
template<typename T> static void LargeRandom(benchmark::State &state);
|
||||
namespace sum {
|
||||
template<typename T> static void LargeRandomSum(benchmark::State &state);
|
||||
}
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
static std::string build_json_array(size_t N) {
|
||||
std::default_random_engine e;
|
||||
std::uniform_real_distribution<> dis(0, 1);
|
||||
std::stringstream myss;
|
||||
myss << "[" << std::endl;
|
||||
if(N > 0) {
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}" << std::endl;
|
||||
}
|
||||
for(size_t i = 1; i < N; i++) {
|
||||
myss << "," << std::endl;
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}";
|
||||
}
|
||||
myss << std::endl;
|
||||
myss << "]" << std::endl;
|
||||
std::string answer = myss.str();
|
||||
std::cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << std::endl;
|
||||
return answer;
|
||||
}
|
||||
|
||||
static const padded_string &get_built_json_array() {
|
||||
static padded_string json = build_json_array(1000000);
|
||||
return json;
|
||||
}
|
||||
|
||||
struct my_point {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
simdjson_really_inline bool operator==(const my_point &other) const {
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const my_point &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const my_point &p) {
|
||||
return o << p.x << "," << p.y << "," << p.z << std::endl;
|
||||
}
|
||||
|
||||
} // namespace largerandom
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "dom.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
template<typename T> static void LargeRandom(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
namespace sum {
|
||||
|
||||
template<typename T> static void LargeRandomSum(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc) {
|
||||
container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, OnDemand);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc.get_array()) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, OnDemand);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
class Sax {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
simdjson_really_inline error_code RunNoExcept(const padded_string &json) noexcept;
|
||||
error_code Allocate(size_t new_capacity);
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
size_t capacity{};
|
||||
dom_parser_implementation dom_parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
struct sax_point_reader_visitor {
|
||||
public:
|
||||
std::vector<my_point> &points;
|
||||
enum {GOT_X=0, GOT_Y=1, GOT_Z=2, GOT_SOMETHING_ELSE=4};
|
||||
size_t idx{GOT_SOMETHING_ELSE};
|
||||
double buffer[3]={};
|
||||
|
||||
explicit sax_point_reader_visitor(std::vector<my_point> &_points) : points(_points) {}
|
||||
|
||||
simdjson_really_inline error_code visit_object_start(json_iterator &) {
|
||||
idx = 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code visit_primitive(json_iterator &, const uint8_t *value) {
|
||||
if(idx == GOT_SOMETHING_ELSE) { return simdjson::SUCCESS; }
|
||||
return numberparsing::parse_double(value).get(buffer[idx]);
|
||||
}
|
||||
simdjson_really_inline error_code visit_object_end(json_iterator &) {
|
||||
points.emplace_back(my_point{buffer[0], buffer[1], buffer[2]});
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code visit_document_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_key(json_iterator &, const uint8_t * key) {
|
||||
switch(key[1]) {
|
||||
// Technically, we should check the other characters
|
||||
// in the key, but we are cheating to go as fast
|
||||
// as possible.
|
||||
case 'x':
|
||||
idx = GOT_X;
|
||||
break;
|
||||
case 'y':
|
||||
idx = GOT_Y;
|
||||
break;
|
||||
case 'z':
|
||||
idx = GOT_Z;
|
||||
break;
|
||||
default:
|
||||
idx = GOT_SOMETHING_ELSE;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code visit_array_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_array_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_document_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_array(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_object(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_root_primitive(json_iterator &, const uint8_t *) { return SUCCESS; }
|
||||
simdjson_really_inline error_code increment_count(json_iterator &) { return SUCCESS; }
|
||||
};
|
||||
|
||||
// NOTE: this assumes the dom_parser is already allocated
|
||||
bool Sax::Run(const padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
error_code Sax::RunNoExcept(const padded_string &json) noexcept {
|
||||
container.clear();
|
||||
|
||||
// Allocate capacity if needed
|
||||
if (capacity < json.size()) {
|
||||
SIMDJSON_TRY( Allocate(json.size()) );
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
SIMDJSON_TRY( dom_parser.stage1(json.u8data(), json.size(), false) );
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_point_reader_visitor visitor(container);
|
||||
SIMDJSON_TRY( iter.walk_document<false>(visitor) );
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
error_code Sax::Allocate(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Sax);
|
||||
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -1,14 +1,22 @@
|
||||
// https://github.com/WojciechMula/toys/blob/master/000helpers/linux-perf-events.h
|
||||
#pragma once
|
||||
#ifdef __linux__
|
||||
|
||||
#ifdef __has_include
|
||||
#if __has_include(<asm/unistd.h>)
|
||||
#include <asm/unistd.h> // for __NR_perf_event_open
|
||||
#else
|
||||
#warning "Header asm/unistd.h cannot be found though it is a linux system. Are linux headers missing?"
|
||||
#endif
|
||||
#else // no __has_include
|
||||
// Please insure that linux headers have been installed.
|
||||
#include <asm/unistd.h> // for __NR_perf_event_open
|
||||
#endif
|
||||
#include <linux/perf_event.h> // for perf event constants
|
||||
#include <sys/ioctl.h> // for ioctl
|
||||
#include <unistd.h> // for syscall
|
||||
|
||||
#include <cerrno> // for errno
|
||||
#include <cstring> // for memset
|
||||
#include <cstring> // for std::memset
|
||||
#include <stdexcept>
|
||||
|
||||
#include <iostream>
|
||||
@@ -21,10 +29,11 @@ template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
|
||||
size_t num_events{};
|
||||
std::vector<uint64_t> temp_result_vec{};
|
||||
std::vector<uint64_t> ids{};
|
||||
bool quiet;
|
||||
|
||||
public:
|
||||
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
|
||||
memset(&attribs, 0, sizeof(attribs));
|
||||
explicit LinuxEvents(std::vector<int> config_vec, bool _quiet=false) : fd(0), working(true), quiet{_quiet} {
|
||||
std::memset(&attribs, 0, sizeof(attribs));
|
||||
attribs.type = TYPE;
|
||||
attribs.size = sizeof(attribs);
|
||||
attribs.disabled = 1;
|
||||
@@ -93,8 +102,11 @@ public:
|
||||
|
||||
private:
|
||||
void report_error(const std::string &context) {
|
||||
if (working)
|
||||
std::cerr << (context + ": " + std::string(strerror(errno))) << std::endl;
|
||||
if (!quiet) {
|
||||
if (working) {
|
||||
std::cerr << (context + ": " + std::string(strerror(errno))) << std::endl;
|
||||
}
|
||||
}
|
||||
working = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +48,9 @@ std::string rapid_stringme(char *json) {
|
||||
std::string simdjson_stringme(simdjson::padded_string & json) {
|
||||
std::stringstream ss;
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse(json);
|
||||
dom::element doc;
|
||||
auto error = parser.parse(json).get(doc);
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
ss << simdjson::minify(doc);
|
||||
return ss.str();
|
||||
}
|
||||
@@ -93,6 +95,10 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
char *buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
if(buffer == nullptr) {
|
||||
std::cerr << "Out of memory!" << std::endl;
|
||||
abort();
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
|
||||
@@ -121,9 +127,11 @@ int main(int argc, char *argv[]) {
|
||||
size_t outlength;
|
||||
uint8_t *cbuffer = (uint8_t *)buffer;
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) == simdjson::SUCCESS ? outlength : -1),
|
||||
if(imple->supported_by_runtime_system()) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) == simdjson::SUCCESS ? outlength : -1),
|
||||
outlength, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
}
|
||||
|
||||
printf("minisize = %zu, original size = %zu (minified down to %.2f percent "
|
||||
@@ -139,6 +147,10 @@ int main(int argc, char *argv[]) {
|
||||
!just_data);
|
||||
|
||||
char *mini_buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
if(mini_buffer == nullptr) {
|
||||
std::cerr << "Out of memory" << std::endl;
|
||||
abort();
|
||||
}
|
||||
size_t minisize;
|
||||
auto minierror = minify(p.data(), p.size(),mini_buffer, minisize);
|
||||
if (!minierror) { std::cerr << minierror << std::endl; exit(1); }
|
||||
|
||||
+8
-4
@@ -67,7 +67,9 @@ void print_usage(ostream& out) {
|
||||
out << "-a IMPL - Use the given parser implementation. By default, detects the most advanced" << endl;
|
||||
out << " implementation supported on the host machine." << endl;
|
||||
for (auto impl : simdjson::available_implementations) {
|
||||
out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl;
|
||||
if(impl->supported_by_runtime_system()) {
|
||||
out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +117,13 @@ struct option_struct {
|
||||
break;
|
||||
case 'a': {
|
||||
const implementation *impl = simdjson::available_implementations[optarg];
|
||||
if (!impl) {
|
||||
if ((!impl) || (!impl->supported_by_runtime_system())) {
|
||||
std::string exit_message = string("Unsupported option value -a ") + optarg + ": expected -a with one of ";
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
exit_message += imple->name();
|
||||
exit_message += " ";
|
||||
if(imple->supported_by_runtime_system()) {
|
||||
exit_message += imple->name();
|
||||
exit_message += " ";
|
||||
}
|
||||
}
|
||||
exit_usage(exit_message);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ void print_stat(const stat_t &s) {
|
||||
s.true_count, s.false_count);
|
||||
}
|
||||
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson_really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<double>()) {
|
||||
s.number_count++;
|
||||
@@ -102,7 +102,7 @@ void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
|
||||
}
|
||||
}
|
||||
|
||||
never_inline stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
simdjson_never_inline stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t s{};
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
@@ -131,23 +131,34 @@ struct Stat {
|
||||
size_t stringLength; // Number of code units in all strings
|
||||
};
|
||||
|
||||
static void GenStatPlus(Stat &stat, const dom::element &v) {
|
||||
static error_code GenStatPlus(Stat &stat, const dom::element &v);
|
||||
static error_code GenStatPlus(Stat &stat, const simdjson_result<dom::element> &r) {
|
||||
dom::element v;
|
||||
SIMDJSON_TRY( r.get(v) );
|
||||
return GenStatPlus(stat, v);
|
||||
}
|
||||
static error_code GenStatPlus(Stat &stat, const dom::element &v) {
|
||||
switch (v.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
for (dom::element child : dom::array(v)) {
|
||||
case dom::element_type::ARRAY: {
|
||||
dom::array a;
|
||||
SIMDJSON_TRY( v.get(a) )
|
||||
for (auto child : a) {
|
||||
GenStatPlus(stat, child);
|
||||
stat.elementCount++;
|
||||
}
|
||||
stat.arrayCount++;
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
for (dom::key_value_pair kv : dom::object(v)) {
|
||||
GenStatPlus(stat, dom::element(kv.value));
|
||||
} break;
|
||||
case dom::element_type::OBJECT: {
|
||||
dom::object o;
|
||||
SIMDJSON_TRY( v.get(o) );
|
||||
for (dom::key_value_pair kv : o) {
|
||||
GenStatPlus(stat, kv.value);
|
||||
stat.stringLength += kv.key.size();
|
||||
stat.memberCount++;
|
||||
stat.stringCount++;
|
||||
}
|
||||
stat.objectCount++;
|
||||
break;
|
||||
} break;
|
||||
case dom::element_type::INT64:
|
||||
case dom::element_type::UINT64:
|
||||
case dom::element_type::DOUBLE:
|
||||
@@ -155,20 +166,24 @@ static void GenStatPlus(Stat &stat, const dom::element &v) {
|
||||
break;
|
||||
case dom::element_type::STRING: {
|
||||
stat.stringCount++;
|
||||
auto sv = std::string_view(v);
|
||||
std::string_view sv;
|
||||
SIMDJSON_TRY( v.get(sv) );
|
||||
stat.stringLength += sv.size();
|
||||
} break;
|
||||
case dom::element_type::BOOL:
|
||||
if (bool(v)) {
|
||||
case dom::element_type::BOOL: {
|
||||
bool b;
|
||||
SIMDJSON_TRY( v.get(b) );
|
||||
if (b) {
|
||||
stat.trueCount++;
|
||||
} else {
|
||||
stat.falseCount++;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
++stat.nullCount;
|
||||
break;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void RapidGenStat(Stat &stat, const rapidjson::Value &v) {
|
||||
@@ -211,16 +226,17 @@ static void RapidGenStat(Stat &stat, const rapidjson::Value &v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
never_inline Stat rapidjson_compute_stats_ref(const rapidjson::Value &doc) {
|
||||
simdjson_never_inline Stat rapidjson_compute_stats_ref(const rapidjson::Value &doc) {
|
||||
Stat s{};
|
||||
RapidGenStat(s, doc);
|
||||
return s;
|
||||
}
|
||||
|
||||
never_inline Stat
|
||||
simdjson_never_inline Stat
|
||||
simdjson_compute_stats_refplus(const simdjson::dom::element &doc) {
|
||||
Stat s{};
|
||||
GenStatPlus(s, doc);
|
||||
auto error = GenStatPlus(s, doc);
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -267,7 +283,7 @@ void sajson_traverse(stat_t &stats, const sajson::value &node) {
|
||||
}
|
||||
}
|
||||
|
||||
never_inline stat_t sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
simdjson_never_inline stat_t sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
if (buffer == nullptr) {
|
||||
@@ -328,7 +344,7 @@ void rapid_traverse(stat_t &stats, const rapidjson::Value &v) {
|
||||
}
|
||||
}
|
||||
|
||||
never_inline stat_t rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
simdjson_never_inline stat_t rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
if (buffer == nullptr) {
|
||||
@@ -354,7 +370,7 @@ never_inline stat_t rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
never_inline stat_t
|
||||
simdjson_never_inline stat_t
|
||||
rapid_accurate_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
@@ -468,9 +484,7 @@ int main(int argc, char *argv[]) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (error) {
|
||||
std::cerr << error << std::endl;
|
||||
}
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
size_t refval = simdjson_compute_stats_refplus(doc).objectCount;
|
||||
|
||||
BEST_TIME("simdjson ",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include "yyjson.h"
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
@@ -26,6 +27,9 @@ SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include <boost/json/parser.hpp>
|
||||
#include <boost/json/monotonic_resource.hpp>
|
||||
|
||||
#ifdef ALLPARSER
|
||||
|
||||
#include "fastjson.cpp"
|
||||
@@ -53,7 +57,7 @@ using namespace rapidjson;
|
||||
|
||||
#ifdef ALLPARSER
|
||||
// fastjson has a tricky interface
|
||||
void on_json_error(void *, UNUSED const fastjson::ErrorContext &ec) {
|
||||
void on_json_error(void *, simdjson_unused const fastjson::ErrorContext &ec) {
|
||||
// std::cerr<<"ERROR: "<<ec.mesg<<std::endl;
|
||||
}
|
||||
bool fastjson_parse(const char *input) {
|
||||
@@ -65,32 +69,35 @@ bool fastjson_parse(const char *input) {
|
||||
// end of fastjson stuff
|
||||
#endif
|
||||
|
||||
never_inline size_t sum_line_lengths(std::stringstream & is) {
|
||||
simdjson_never_inline size_t sum_line_lengths(std::stringstream &is) {
|
||||
std::string line;
|
||||
size_t sumofalllinelengths{0};
|
||||
while(std::getline(is, line)) {
|
||||
while (std::getline(is, line)) {
|
||||
sumofalllinelengths += line.size();
|
||||
}
|
||||
return sumofalllinelengths;
|
||||
}
|
||||
|
||||
inline void reset_stream(std::stringstream & is) {
|
||||
inline void reset_stream(std::stringstream &is) {
|
||||
is.clear();
|
||||
is.seekg(0,std::ios::beg);
|
||||
is.seekg(0, std::ios::beg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool bench(const char *filename, bool verbose, bool just_data, double repeat_multiplier) {
|
||||
bool bench(const char *filename, bool verbose, bool just_data,
|
||||
double repeat_multiplier) {
|
||||
simdjson::padded_string p;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << ": " << error << std::endl;
|
||||
std::cerr << "Could not load the file " << filename << ": " << error
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int repeat = static_cast<int>((50000000 * repeat_multiplier) / static_cast<double>(p.size()));
|
||||
if (repeat < 10) { repeat = 10; }
|
||||
int repeat = static_cast<int>((50000000 * repeat_multiplier) /
|
||||
static_cast<double>(p.size()));
|
||||
if (repeat < 10) {
|
||||
repeat = 10;
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input " << filename << " has ";
|
||||
@@ -104,68 +111,97 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
}
|
||||
size_t volume = p.size();
|
||||
if (just_data) {
|
||||
printf("%-42s %20s %20s %20s %20s \n", "name", "cycles_per_byte",
|
||||
"cycles_per_byte_err", "gb_per_s", "gb_per_s_err");
|
||||
std::printf("%-42s %20s %20s %20s %20s \n", "name", "cycles_per_byte",
|
||||
"cycles_per_byte_err", "gb_per_s", "gb_per_s_err");
|
||||
}
|
||||
if (!just_data) {
|
||||
const std::string inputcopy(p.data(), p.data()+p.size());
|
||||
const std::string inputcopy(p.data(), p.data() + p.size());
|
||||
std::stringstream is;
|
||||
is.str(inputcopy);
|
||||
const size_t lc = sum_line_lengths(is);
|
||||
BEST_TIME("getline ",sum_line_lengths(is) , lc, reset_stream(is),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("getline ", sum_line_lengths(is), lc, reset_stream(is), repeat,
|
||||
volume, !just_data);
|
||||
}
|
||||
|
||||
if (!just_data) {
|
||||
auto parse_dynamic=[](auto& str){
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(str).error();
|
||||
auto parse_dynamic = [](auto &str) {
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(str).error();
|
||||
};
|
||||
BEST_TIME("simdjson (dynamic mem) ", parse_dynamic(p), simdjson::SUCCESS,
|
||||
, repeat, volume, !just_data);
|
||||
BEST_TIME("simdjson (dynamic mem) ", parse_dynamic(p), simdjson::SUCCESS, ,
|
||||
repeat, volume, !just_data);
|
||||
}
|
||||
// (static alloc)
|
||||
simdjson::dom::parser parser;
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat, volume,
|
||||
!just_data);
|
||||
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat,
|
||||
volume, !just_data);
|
||||
|
||||
rapidjson::Document d;
|
||||
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
char *buffer = (char *)std::malloc(p.size() + 1);
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON ",
|
||||
d.Parse<kParseValidateEncodingFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
false, , repeat, volume, !just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON (accurate number parsing) ",
|
||||
d.Parse<kParseValidateEncodingFlag|kParseFullPrecisionFlag>((const char *)buffer)
|
||||
d.Parse<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(
|
||||
(const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
false, , repeat, volume, !just_data);
|
||||
}
|
||||
BEST_TIME("RapidJSON (insitu)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME(
|
||||
"RapidJSON (insitu)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError(), false,
|
||||
std::memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON (insitu, accurate number parsing)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag|kParseFullPrecisionFlag>(buffer).HasParseError(),
|
||||
d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(
|
||||
buffer)
|
||||
.HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
std::memcpy(buffer, p.data(), p.size()) &&
|
||||
(buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
|
||||
{
|
||||
const boost::json::string_view sv(p.data(), p.size());
|
||||
boost::json::parser p;
|
||||
auto execute = [&p](auto sv) -> bool {
|
||||
boost::json::error_code ec;
|
||||
boost::json::monotonic_resource mr;
|
||||
p.reset( &mr );
|
||||
p.write(sv,ec);
|
||||
if(!ec)
|
||||
auto jv=p.release();
|
||||
return !!ec;
|
||||
};
|
||||
|
||||
BEST_TIME("Boost.json", execute(sv), false, , repeat, volume, !just_data);
|
||||
}
|
||||
{
|
||||
|
||||
auto execute = [&p]() -> bool {
|
||||
yyjson_doc *doc = yyjson_read(p.data(), p.size(), 0);
|
||||
bool is_ok = doc != nullptr;
|
||||
yyjson_doc_free(doc);
|
||||
return is_ok;
|
||||
};
|
||||
|
||||
BEST_TIME("yyjson", execute(), true, , repeat, volume, !just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
@@ -173,59 +209,61 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
sajson::parse(sajson::dynamic_allocation(),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
true, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
size_t ast_buffer_size = p.size();
|
||||
size_t *ast_buffer = (size_t *)malloc(ast_buffer_size * sizeof(size_t));
|
||||
size_t *ast_buffer = (size_t *)std::malloc(ast_buffer_size * sizeof(size_t));
|
||||
// (static alloc, insitu)
|
||||
BEST_TIME(
|
||||
"sajson",
|
||||
sajson::parse(sajson::bounded_allocation(ast_buffer, ast_buffer_size),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
true, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
size_t expected = json::parse(p.data(), p.data() + p.size()).size();
|
||||
BEST_TIME("nlohmann-json", json::parse(buffer, buffer + p.size()).size(),
|
||||
expected, , repeat, volume,
|
||||
!just_data);
|
||||
expected, , repeat, volume, !just_data);
|
||||
|
||||
#ifdef ALLPARSER
|
||||
std::string json11err;
|
||||
BEST_TIME("dropbox (json11) ",
|
||||
((json11::Json::parse(buffer, json11err).is_null()) ||
|
||||
(!json11err.empty())),
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
false, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
BEST_TIME("fastjson ", fastjson_parse(buffer), true,
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
JsonValue value;
|
||||
JsonAllocator allocator;
|
||||
char *endptr;
|
||||
BEST_TIME("gason ", jsonParse(buffer, &endptr, &value, allocator),
|
||||
JSON_OK, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
JSON_OK, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
void *state;
|
||||
BEST_TIME("ultrajson ",
|
||||
(UJDecode(buffer, p.size(), NULL, &state) == NULL), false,
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
{
|
||||
std::unique_ptr<jsmntok_t[]> tokens =
|
||||
std::make_unique<jsmntok_t[]>(p.size());
|
||||
jsmn_parser jparser;
|
||||
jsmn_init(&jparser);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
BEST_TIME(
|
||||
"jsmn ",
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(), static_cast<unsigned int>(p.size())) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
BEST_TIME("jsmn ",
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(),
|
||||
static_cast<unsigned int>(p.size())) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
cJSON *tree = cJSON_Parse(buffer);
|
||||
BEST_TIME("cJSON ", ((tree = cJSON_Parse(buffer)) != NULL), true,
|
||||
@@ -243,12 +281,13 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
#endif
|
||||
if (!just_data)
|
||||
BEST_TIME("memcpy ",
|
||||
(memcpy(buffer, p.data(), p.size()) == buffer), true, , repeat,
|
||||
volume, !just_data);
|
||||
(std::memcpy(buffer, p.data(), p.size()) == buffer), true, ,
|
||||
repeat, volume, !just_data);
|
||||
#ifdef __linux__
|
||||
if (!just_data) {
|
||||
printf("\n \n <doing additional analysis with performance counters (Linux "
|
||||
"only)>\n");
|
||||
std::printf(
|
||||
"\n \n <doing additional analysis with performance counters (Linux "
|
||||
"only)>\n");
|
||||
std::vector<int> evts;
|
||||
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
|
||||
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
|
||||
@@ -265,64 +304,84 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
unified.start();
|
||||
auto parse_error = parser.parse(p).error();
|
||||
if (parse_error)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("simdjson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
std::printf(
|
||||
"simdjson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::fill(stats.begin(), stats.end(), 0);
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
unified.start();
|
||||
if (d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError() !=
|
||||
false)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("RapidJSON: cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
std::printf(
|
||||
"RapidJSON: cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::fill(stats.begin(), stats.end(), 0); // unnecessary
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
unified.start();
|
||||
if (sajson::parse(sajson::bounded_allocation(ast_buffer, ast_buffer_size),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid() != true)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("sajson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::printf(
|
||||
"sajson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
free(ast_buffer);
|
||||
free(buffer);
|
||||
std::free(ast_buffer);
|
||||
std::free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -349,13 +408,17 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
|
||||
std::cerr << "The '-t' flag outputs a table." << std::endl;
|
||||
std::cerr << "The '-r <N>' flag sets the repeat multiplier: set it above 1 to do more iterations, and below 1 to do fewer." << std::endl;
|
||||
std::cerr << "The '-r <N>' flag sets the repeat multiplier: set it above 1 "
|
||||
"to do more iterations, and below 1 to do fewer."
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
int result = EXIT_SUCCESS;
|
||||
for (int fileind = optind; fileind < argc; fileind++) {
|
||||
if (!bench(argv[fileind], verbose, just_data, repeat_multiplier)) { result = EXIT_FAILURE; }
|
||||
printf("\n\n");
|
||||
if (!bench(argv[fileind], verbose, just_data, repeat_multiplier)) {
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
std::printf("\n\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(dom::element element) {
|
||||
if (element.is_null()) { return 0; }
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
for (dom::element tweet : parser.parse(json)["statuses"]) {
|
||||
auto user = tweet["user"];
|
||||
tweets.emplace_back(partial_tweets::tweet{
|
||||
tweet["created_at"],
|
||||
tweet["id"],
|
||||
tweet["text"],
|
||||
nullable_int(tweet["in_reply_to_status_id"]),
|
||||
{ user["id"], user["screen_name"] },
|
||||
tweet["retweet_count"],
|
||||
tweet["favorite_count"]
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Dom);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class DomNoExcept {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const simdjson::padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline simdjson_result<uint64_t> nullable_int(simdjson_result<dom::element> result) noexcept {
|
||||
dom::element element;
|
||||
SIMDJSON_TRY( result.get(element) );
|
||||
if (element.is_null()) { return 0; }
|
||||
return element.get_uint64();
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code RunNoExcept(const simdjson::padded_string &json) noexcept;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool DomNoExcept::Run(const simdjson::padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code DomNoExcept::RunNoExcept(const simdjson::padded_string &json) noexcept {
|
||||
tweets.clear();
|
||||
|
||||
dom::array tweet_array;
|
||||
SIMDJSON_TRY( parser.parse(json)["statuses"].get_array().get(tweet_array) );
|
||||
|
||||
for (auto tweet_element : tweet_array) {
|
||||
dom::object tweet;
|
||||
SIMDJSON_TRY( tweet_element.get_object().get(tweet) );
|
||||
|
||||
dom::object user;
|
||||
SIMDJSON_TRY( tweet["user"].get_object().get(user) );
|
||||
|
||||
partial_tweets::tweet t;
|
||||
SIMDJSON_TRY( tweet["created_at"] .get_string().get(t.created_at) );
|
||||
SIMDJSON_TRY( tweet["id"] .get_uint64().get(t.id) );
|
||||
SIMDJSON_TRY( tweet["text"] .get_string().get(t.text) );
|
||||
SIMDJSON_TRY( nullable_int(tweet["in_reply_to_status_id"]).get(t.in_reply_to_status_id) );
|
||||
SIMDJSON_TRY( user["id"] .get_uint64().get(t.user.id) );
|
||||
SIMDJSON_TRY( user["screen_name"] .get_string().get(t.user.screen_name) );
|
||||
SIMDJSON_TRY( tweet["retweet_count"] .get_uint64().get(t.retweet_count) );
|
||||
SIMDJSON_TRY( tweet["favorite_count"].get_uint64().get(t.favorite_count) );
|
||||
|
||||
tweets.push_back(t);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(ondemand::value && value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
simdjson_really_inline twitter_user read_user(ondemand::object && user) {
|
||||
// Move user into a local object so it gets destroyed (and moves the iterator)
|
||||
ondemand::object u = std::move(user);
|
||||
return { u["id"], u["screen_name"] };
|
||||
}
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
// Walk the document, parsing the tweets as we go
|
||||
|
||||
// { "statuses":
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("statuses")) { return false; }
|
||||
// { "statuses": [
|
||||
if (!iter.start_array()) { return false; }
|
||||
|
||||
do {
|
||||
tweet tweet;
|
||||
|
||||
if (!iter.start_object() || !iter.find_field_raw("created_at")) { return false; }
|
||||
tweet.created_at = iter.consume_string();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("id")) { return false; }
|
||||
tweet.id = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("text")) { return false; }
|
||||
tweet.text = iter.consume_string();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("in_reply_to_status_id")) { return false; }
|
||||
if (!iter.is_null()) {
|
||||
tweet.in_reply_to_status_id = iter.consume_uint64();
|
||||
}
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("user")) { return false; }
|
||||
{
|
||||
if (!iter.start_object() || !iter.find_field_raw("id")) { return false; }
|
||||
tweet.user.id = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("screen_name")) { return false; }
|
||||
tweet.user.screen_name = iter.consume_string();
|
||||
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the user object
|
||||
}
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("retweet_count")) { return false; }
|
||||
tweet.retweet_count = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("favorite_count")) { return false; }
|
||||
tweet.favorite_count = iter.consume_uint64();
|
||||
|
||||
tweets.push_back(tweet);
|
||||
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the tweet object
|
||||
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Iter);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
OnDemand() {
|
||||
if(!displayed_implementation) {
|
||||
std::cout << "On Demand implementation: " << builtin_implementation()->name() << std::endl;
|
||||
displayed_implementation = true;
|
||||
}
|
||||
}
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(ondemand::value && value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
simdjson_really_inline twitter_user read_user(ondemand::object && user) {
|
||||
// Move user into a local object so it gets destroyed (and moves the iterator)
|
||||
ondemand::object u = std::move(user);
|
||||
return { u["id"], u["screen_name"] };
|
||||
}
|
||||
static inline bool displayed_implementation = false;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
// Walk the document, parsing the tweets as we go
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
tweets.emplace_back(partial_tweets::tweet{
|
||||
tweet["created_at"],
|
||||
tweet["id"],
|
||||
tweet["text"],
|
||||
nullable_int(tweet["in_reply_to_status_id"]),
|
||||
read_user(tweet["user"]),
|
||||
tweet["retweet_count"],
|
||||
tweet["favorite_count"]
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, OnDemand);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace partial_tweets {
|
||||
template<typename T> static void PartialTweets(benchmark::State &state);
|
||||
} // namespace partial_tweets
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
#include "tweet.h"
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "domnoexcept.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
template<typename T> static void PartialTweets(benchmark::State &state) {
|
||||
//
|
||||
// Load the JSON file
|
||||
//
|
||||
constexpr const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
error_code error;
|
||||
padded_string json;
|
||||
if ((error = padded_string::load(TWITTER_JSON).get(json))) {
|
||||
std::cerr << error << std::endl;
|
||||
state.SkipWithError("error loading");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonBenchmark<T, DomNoExcept>(state, json);
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "partial_tweets.h"
|
||||
#include "sax_tweet_reader_visitor.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
class Sax {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
simdjson_really_inline error_code RunNoExcept(const padded_string &json) noexcept;
|
||||
error_code Allocate(size_t new_capacity);
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
size_t capacity{};
|
||||
dom_parser_implementation dom_parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
};
|
||||
|
||||
// NOTE: this assumes the dom_parser is already allocated
|
||||
bool Sax::Run(const padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
error_code Sax::RunNoExcept(const padded_string &json) noexcept {
|
||||
tweets.clear();
|
||||
|
||||
// Allocate capacity if needed
|
||||
if (capacity < json.size()) {
|
||||
SIMDJSON_TRY( Allocate(json.size()) );
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
SIMDJSON_TRY( dom_parser.stage1((uint8_t *)json.data(), json.size(), false) );
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_tweet_reader_visitor visitor(tweets, string_buf.get());
|
||||
SIMDJSON_TRY( iter.walk_document<false>(visitor) );
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
error_code Sax::Allocate(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Sax);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "tweet.h"
|
||||
#include <vector>
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
struct sax_tweet_reader_visitor {
|
||||
public:
|
||||
simdjson_really_inline sax_tweet_reader_visitor(std::vector<tweet> &tweets, uint8_t *string_buf);
|
||||
|
||||
simdjson_really_inline error_code visit_document_start(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_object_start(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_key(json_iterator &iter, const uint8_t *key);
|
||||
simdjson_really_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value);
|
||||
simdjson_really_inline error_code visit_array_start(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_array_end(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_object_end(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_document_end(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_empty_array(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_empty_object(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value);
|
||||
simdjson_really_inline error_code increment_count(json_iterator &iter);
|
||||
|
||||
private:
|
||||
// Since we only care about one thing at each level, we just use depth as the marker for what
|
||||
// object/array we're nested inside.
|
||||
enum class containers {
|
||||
document = 0, //
|
||||
top_object = 1, // {
|
||||
statuses = 2, // { "statuses": [
|
||||
tweet = 3, // { "statuses": [ {
|
||||
user = 4 // { "statuses": [ { "user": {
|
||||
};
|
||||
/**
|
||||
* The largest depth we care about.
|
||||
* There can be things at lower depths.
|
||||
*/
|
||||
static constexpr uint32_t MAX_SUPPORTED_DEPTH = uint32_t(containers::user);
|
||||
static constexpr const char *STATE_NAMES[] = {
|
||||
"document",
|
||||
"top object",
|
||||
"statuses",
|
||||
"tweet",
|
||||
"user"
|
||||
};
|
||||
enum class field_type {
|
||||
any,
|
||||
unsigned_integer,
|
||||
string,
|
||||
nullable_unsigned_integer,
|
||||
object,
|
||||
array
|
||||
};
|
||||
struct field {
|
||||
const char * key{};
|
||||
size_t len{0};
|
||||
size_t offset;
|
||||
containers container{containers::document};
|
||||
field_type type{field_type::any};
|
||||
};
|
||||
|
||||
std::vector<tweet> &tweets;
|
||||
containers container{containers::document};
|
||||
uint8_t *current_string_buf_loc;
|
||||
const uint8_t *current_key{};
|
||||
|
||||
simdjson_really_inline bool in_container(json_iterator &iter);
|
||||
simdjson_really_inline bool in_container_child(json_iterator &iter);
|
||||
simdjson_really_inline void start_container(json_iterator &iter);
|
||||
simdjson_really_inline void end_container(json_iterator &iter);
|
||||
simdjson_really_inline error_code parse_nullable_unsigned(json_iterator &iter, const uint8_t *value, const field &f);
|
||||
simdjson_really_inline error_code parse_unsigned(json_iterator &iter, const uint8_t *value, const field &f);
|
||||
simdjson_really_inline error_code parse_string(json_iterator &iter, const uint8_t *value, const field &f);
|
||||
|
||||
struct field_lookup {
|
||||
field entries[256]{};
|
||||
|
||||
field_lookup();
|
||||
simdjson_really_inline field get(const uint8_t * key, containers container);
|
||||
private:
|
||||
simdjson_really_inline uint8_t hash(const char * key, uint32_t depth);
|
||||
simdjson_really_inline void add(const char * key, size_t len, containers container, field_type type, size_t offset);
|
||||
simdjson_really_inline void neg(const char * const key, uint32_t depth);
|
||||
};
|
||||
static field_lookup fields;
|
||||
}; // sax_tweet_reader_visitor
|
||||
|
||||
simdjson_really_inline sax_tweet_reader_visitor::sax_tweet_reader_visitor(std::vector<tweet> &_tweets, uint8_t *_string_buf)
|
||||
: tweets{_tweets},
|
||||
current_string_buf_loc{_string_buf} {
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_document_start(json_iterator &iter) {
|
||||
start_container(iter);
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_array_start(json_iterator &iter) {
|
||||
// If we're not in a container we care about, don't bother with the rest
|
||||
if (!in_container_child(iter)) { return SUCCESS; }
|
||||
|
||||
// Handle fields first
|
||||
if (current_key) {
|
||||
switch (fields.get(current_key, container).type) {
|
||||
case field_type::array: // { "statuses": [
|
||||
start_container(iter);
|
||||
current_key = nullptr;
|
||||
return SUCCESS;
|
||||
case field_type::any:
|
||||
return SUCCESS;
|
||||
case field_type::object:
|
||||
case field_type::unsigned_integer:
|
||||
case field_type::nullable_unsigned_integer:
|
||||
case field_type::string:
|
||||
iter.log_error("unexpected array field");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
// We're not in a field, so it must be a child of an array. We support any of those.
|
||||
iter.log_error("unexpected array");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_object_start(json_iterator &iter) {
|
||||
// If we're not in a container we care about, don't bother with the rest
|
||||
if (!in_container_child(iter)) { return SUCCESS; }
|
||||
|
||||
// Handle known fields
|
||||
if (current_key) {
|
||||
auto f = fields.get(current_key, container);
|
||||
switch (f.type) {
|
||||
case field_type::object: // { "statuses": [ { "user": {
|
||||
start_container(iter);
|
||||
return SUCCESS;
|
||||
case field_type::any:
|
||||
return SUCCESS;
|
||||
case field_type::array:
|
||||
case field_type::unsigned_integer:
|
||||
case field_type::nullable_unsigned_integer:
|
||||
case field_type::string:
|
||||
iter.log_error("unexpected object field");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
// It's not a field, so it's a child of an array or document
|
||||
switch (container) {
|
||||
case containers::document: // top_object: {
|
||||
case containers::statuses: // tweet: { "statuses": [ {
|
||||
start_container(iter);
|
||||
return SUCCESS;
|
||||
case containers::top_object:
|
||||
case containers::tweet:
|
||||
case containers::user:
|
||||
iter.log_error("unexpected object");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
SIMDJSON_UNREACHABLE();
|
||||
return UNINITIALIZED;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_key(json_iterator &, const uint8_t *key) {
|
||||
current_key = key;
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_primitive(json_iterator &iter, const uint8_t *value) {
|
||||
// Don't bother unless we're in a container we care about
|
||||
if (!in_container(iter)) { return SUCCESS; }
|
||||
|
||||
// Handle fields first
|
||||
if (current_key) {
|
||||
auto f = fields.get(current_key, container);
|
||||
switch (f.type) {
|
||||
case field_type::unsigned_integer:
|
||||
return parse_unsigned(iter, value, f);
|
||||
case field_type::nullable_unsigned_integer:
|
||||
return parse_nullable_unsigned(iter, value, f);
|
||||
case field_type::string:
|
||||
return parse_string(iter, value, f);
|
||||
case field_type::any:
|
||||
return SUCCESS;
|
||||
case field_type::array:
|
||||
case field_type::object:
|
||||
iter.log_error("unexpected primitive");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
current_key = nullptr;
|
||||
}
|
||||
|
||||
// If it's not a field, it's a child of an array.
|
||||
// The only array we support is statuses, which must contain objects.
|
||||
iter.log_error("unexpected primitive");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_array_end(json_iterator &iter) {
|
||||
if (in_container(iter)) { end_container(iter); }
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_object_end(json_iterator &iter) {
|
||||
current_key = nullptr;
|
||||
if (in_container(iter)) { end_container(iter); }
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_document_end(json_iterator &) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_empty_array(json_iterator &) {
|
||||
current_key = nullptr;
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_empty_object(json_iterator &) {
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_root_primitive(json_iterator &iter, const uint8_t *) {
|
||||
iter.log_error("unexpected root primitive");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::increment_count(json_iterator &) { return SUCCESS; }
|
||||
|
||||
simdjson_really_inline bool sax_tweet_reader_visitor::in_container(json_iterator &iter) {
|
||||
return iter.depth == uint32_t(container);
|
||||
}
|
||||
simdjson_really_inline bool sax_tweet_reader_visitor::in_container_child(json_iterator &iter) {
|
||||
return iter.depth == uint32_t(container) + 1;
|
||||
}
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::start_container(json_iterator &iter) {
|
||||
SIMDJSON_ASSUME(iter.depth <= MAX_SUPPORTED_DEPTH); // Asserts in debug mode
|
||||
container = containers(iter.depth);
|
||||
if (logger::LOG_ENABLED) { iter.log_value(STATE_NAMES[iter.depth]); }
|
||||
if (container == containers::tweet) { tweets.push_back({}); }
|
||||
}
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::end_container(json_iterator &) {
|
||||
container = containers(int(container) - 1);
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_nullable_unsigned(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
if (auto error = numberparsing::parse_unsigned(value).get(*i)) {
|
||||
// If number parsing failed, check if it's null before returning the error
|
||||
if (!atomparsing::is_valid_null_atom(value)) { iter.log_error("expected number or null"); return error; }
|
||||
i = 0;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_unsigned(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
return numberparsing::parse_unsigned(value).get(*i);
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_string(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto s = reinterpret_cast<std::string_view *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
return stringparsing::parse_string_to_buffer(value, current_string_buf_loc, *s);
|
||||
}
|
||||
|
||||
sax_tweet_reader_visitor::field_lookup sax_tweet_reader_visitor::fields{};
|
||||
|
||||
simdjson_really_inline uint8_t sax_tweet_reader_visitor::field_lookup::hash(const char * key, uint32_t depth) {
|
||||
// These shift numbers were chosen specifically because this yields only 2 collisions between
|
||||
// keys in twitter.json, leaves 0 as a distinct value, and has 0 collisions between keys we
|
||||
// actually care about.
|
||||
return uint8_t((key[0] << 0) ^ (key[1] << 3) ^ (key[2] << 3) ^ (key[3] << 1) ^ depth);
|
||||
}
|
||||
simdjson_really_inline sax_tweet_reader_visitor::field sax_tweet_reader_visitor::field_lookup::get(const uint8_t * key, containers c) {
|
||||
auto index = hash((const char *)key, uint32_t(c));
|
||||
auto entry = entries[index];
|
||||
// TODO if any key is > SIMDJSON_PADDING, this will access inaccessible memory!
|
||||
if (c != entry.container || memcmp(key, entry.key, entry.len)) { return entries[0]; }
|
||||
return entry;
|
||||
}
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::field_lookup::add(const char * key, size_t len, containers c, field_type type, size_t offset) {
|
||||
auto index = hash(key, uint32_t(c));
|
||||
if (index == 0) {
|
||||
fprintf(stderr, "%s (depth %d) hashes to zero, which is used as 'missing value'\n", key, int(c));
|
||||
assert(false);
|
||||
}
|
||||
if (entries[index].key) {
|
||||
fprintf(stderr, "%s (depth %d) collides with %s (depth %d) !\n", key, int(c), entries[index].key, int(entries[index].container));
|
||||
assert(false);
|
||||
}
|
||||
entries[index] = { key, len, offset, c, type };
|
||||
}
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::field_lookup::neg(const char * const key, uint32_t depth) {
|
||||
auto index = hash(key, depth);
|
||||
if (entries[index].key) {
|
||||
fprintf(stderr, "%s (depth %d) conflicts with %s (depth %d) !\n", key, depth, entries[index].key, int(entries[index].container));
|
||||
}
|
||||
}
|
||||
|
||||
sax_tweet_reader_visitor::field_lookup::field_lookup() {
|
||||
add("\"statuses\"", std::strlen("\"statuses\""), containers::top_object, field_type::array, 0); // { "statuses": [...]
|
||||
#define TWEET_FIELD(KEY, TYPE) add("\"" #KEY "\"", std::strlen("\"" #KEY "\""), containers::tweet, TYPE, offsetof(tweet, KEY));
|
||||
TWEET_FIELD(id, field_type::unsigned_integer);
|
||||
TWEET_FIELD(in_reply_to_status_id, field_type::nullable_unsigned_integer);
|
||||
TWEET_FIELD(retweet_count, field_type::unsigned_integer);
|
||||
TWEET_FIELD(favorite_count, field_type::unsigned_integer);
|
||||
TWEET_FIELD(text, field_type::string);
|
||||
TWEET_FIELD(created_at, field_type::string);
|
||||
TWEET_FIELD(user, field_type::object)
|
||||
#undef TWEET_FIELD
|
||||
#define USER_FIELD(KEY, TYPE) add("\"" #KEY "\"", std::strlen("\"" #KEY "\""), containers::user, TYPE, offsetof(tweet, user)+offsetof(twitter_user, KEY));
|
||||
USER_FIELD(id, field_type::unsigned_integer);
|
||||
USER_FIELD(screen_name, field_type::string);
|
||||
#undef USER_FIELD
|
||||
|
||||
// Check for collisions with other (unused) hash keys in typical twitter JSON
|
||||
#define NEG(key, depth) neg("\"" #key "\"", depth);
|
||||
NEG(display_url, 9);
|
||||
NEG(expanded_url, 9);
|
||||
neg("\"h\":", 9);
|
||||
NEG(indices, 9);
|
||||
NEG(resize, 9);
|
||||
NEG(url, 9);
|
||||
neg("\"w\":", 9);
|
||||
NEG(display_url, 8);
|
||||
NEG(expanded_url, 8);
|
||||
neg("\"h\":", 8);
|
||||
NEG(indices, 8);
|
||||
NEG(large, 8);
|
||||
NEG(medium, 8);
|
||||
NEG(resize, 8);
|
||||
NEG(small, 8);
|
||||
NEG(thumb, 8);
|
||||
NEG(url, 8);
|
||||
neg("\"w\":", 8);
|
||||
NEG(display_url, 7);
|
||||
NEG(expanded_url, 7);
|
||||
NEG(id_str, 7);
|
||||
NEG(id, 7);
|
||||
NEG(indices, 7);
|
||||
NEG(large, 7);
|
||||
NEG(media_url_https, 7);
|
||||
NEG(media_url, 7);
|
||||
NEG(medium, 7);
|
||||
NEG(name, 7);
|
||||
NEG(sizes, 7);
|
||||
NEG(small, 7);
|
||||
NEG(source_status_id_str, 7);
|
||||
NEG(source_status_id, 7);
|
||||
NEG(thumb, 7);
|
||||
NEG(type, 7);
|
||||
NEG(url, 7);
|
||||
NEG(urls, 7);
|
||||
NEG(description, 6);
|
||||
NEG(display_url, 6);
|
||||
NEG(expanded_url, 6);
|
||||
NEG(id_str, 6);
|
||||
NEG(id, 6);
|
||||
NEG(indices, 6);
|
||||
NEG(media_url_https, 6);
|
||||
NEG(media_url, 6);
|
||||
NEG(name, 6);
|
||||
NEG(sizes, 6);
|
||||
NEG(source_status_id_str, 6);
|
||||
NEG(source_status_id, 6);
|
||||
NEG(type, 6);
|
||||
NEG(url, 6);
|
||||
NEG(urls, 6);
|
||||
NEG(contributors_enabled, 5);
|
||||
NEG(default_profile_image, 5);
|
||||
NEG(default_profile, 5);
|
||||
NEG(description, 5);
|
||||
NEG(entities, 5);
|
||||
NEG(favourites_count, 5);
|
||||
NEG(follow_request_sent, 5);
|
||||
NEG(followers_count, 5);
|
||||
NEG(following, 5);
|
||||
NEG(friends_count, 5);
|
||||
NEG(geo_enabled, 5);
|
||||
NEG(hashtags, 5);
|
||||
NEG(id_str, 5);
|
||||
NEG(id, 5);
|
||||
NEG(is_translation_enabled, 5);
|
||||
NEG(is_translator, 5);
|
||||
NEG(iso_language_code, 5);
|
||||
NEG(lang, 5);
|
||||
NEG(listed_count, 5);
|
||||
NEG(location, 5);
|
||||
NEG(media, 5);
|
||||
NEG(name, 5);
|
||||
NEG(notifications, 5);
|
||||
NEG(profile_background_color, 5);
|
||||
NEG(profile_background_image_url_https, 5);
|
||||
NEG(profile_background_image_url, 5);
|
||||
NEG(profile_background_tile, 5);
|
||||
NEG(profile_banner_url, 5);
|
||||
NEG(profile_image_url_https, 5);
|
||||
NEG(profile_image_url, 5);
|
||||
NEG(profile_link_color, 5);
|
||||
NEG(profile_sidebar_border_color, 5);
|
||||
NEG(profile_sidebar_fill_color, 5);
|
||||
NEG(profile_text_color, 5);
|
||||
NEG(profile_use_background_image, 5);
|
||||
NEG(protected, 5);
|
||||
NEG(result_type, 5);
|
||||
NEG(statuses_count, 5);
|
||||
NEG(symbols, 5);
|
||||
NEG(time_zone, 5);
|
||||
NEG(url, 5);
|
||||
NEG(urls, 5);
|
||||
NEG(user_mentions, 5);
|
||||
NEG(utc_offset, 5);
|
||||
NEG(verified, 5);
|
||||
NEG(contributors_enabled, 4);
|
||||
NEG(contributors, 4);
|
||||
NEG(coordinates, 4);
|
||||
NEG(default_profile_image, 4);
|
||||
NEG(default_profile, 4);
|
||||
NEG(description, 4);
|
||||
NEG(entities, 4);
|
||||
NEG(favorited, 4);
|
||||
NEG(favourites_count, 4);
|
||||
NEG(follow_request_sent, 4);
|
||||
NEG(followers_count, 4);
|
||||
NEG(following, 4);
|
||||
NEG(friends_count, 4);
|
||||
NEG(geo_enabled, 4);
|
||||
NEG(geo, 4);
|
||||
NEG(hashtags, 4);
|
||||
NEG(id_str, 4);
|
||||
NEG(in_reply_to_screen_name, 4);
|
||||
NEG(in_reply_to_status_id_str, 4);
|
||||
NEG(in_reply_to_user_id_str, 4);
|
||||
NEG(in_reply_to_user_id, 4);
|
||||
NEG(is_translation_enabled, 4);
|
||||
NEG(is_translator, 4);
|
||||
NEG(iso_language_code, 4);
|
||||
NEG(lang, 4);
|
||||
NEG(listed_count, 4);
|
||||
NEG(location, 4);
|
||||
NEG(media, 4);
|
||||
NEG(metadata, 4);
|
||||
NEG(name, 4);
|
||||
NEG(notifications, 4);
|
||||
NEG(place, 4);
|
||||
NEG(possibly_sensitive, 4);
|
||||
NEG(profile_background_color, 4);
|
||||
NEG(profile_background_image_url_https, 4);
|
||||
NEG(profile_background_image_url, 4);
|
||||
NEG(profile_background_tile, 4);
|
||||
NEG(profile_banner_url, 4);
|
||||
NEG(profile_image_url_https, 4);
|
||||
NEG(profile_image_url, 4);
|
||||
NEG(profile_link_color, 4);
|
||||
NEG(profile_sidebar_border_color, 4);
|
||||
NEG(profile_sidebar_fill_color, 4);
|
||||
NEG(profile_text_color, 4);
|
||||
NEG(profile_use_background_image, 4);
|
||||
NEG(protected, 4);
|
||||
NEG(result_type, 4);
|
||||
NEG(retweeted, 4);
|
||||
NEG(source, 4);
|
||||
NEG(statuses_count, 4);
|
||||
NEG(symbols, 4);
|
||||
NEG(time_zone, 4);
|
||||
NEG(truncated, 4);
|
||||
NEG(url, 4);
|
||||
NEG(urls, 4);
|
||||
NEG(user_mentions, 4);
|
||||
NEG(utc_offset, 4);
|
||||
NEG(verified, 4);
|
||||
NEG(contributors, 3);
|
||||
NEG(coordinates, 3);
|
||||
NEG(entities, 3);
|
||||
NEG(favorited, 3);
|
||||
NEG(geo, 3);
|
||||
NEG(id_str, 3);
|
||||
NEG(in_reply_to_screen_name, 3);
|
||||
NEG(in_reply_to_status_id_str, 3);
|
||||
NEG(in_reply_to_user_id_str, 3);
|
||||
NEG(in_reply_to_user_id, 3);
|
||||
NEG(lang, 3);
|
||||
NEG(metadata, 3);
|
||||
NEG(place, 3);
|
||||
NEG(possibly_sensitive, 3);
|
||||
NEG(retweeted_status, 3);
|
||||
NEG(retweeted, 3);
|
||||
NEG(source, 3);
|
||||
NEG(truncated, 3);
|
||||
NEG(completed_in, 2);
|
||||
NEG(count, 2);
|
||||
NEG(max_id_str, 2);
|
||||
NEG(max_id, 2);
|
||||
NEG(next_results, 2);
|
||||
NEG(query, 2);
|
||||
NEG(refresh_url, 2);
|
||||
NEG(since_id_str, 2);
|
||||
NEG(since_id, 2);
|
||||
NEG(search_metadata, 1);
|
||||
#undef NEG
|
||||
}
|
||||
|
||||
// sax_tweet_reader_visitor::field_lookup::find_min() {
|
||||
// int min_count = 100000;
|
||||
// for (int a=0;a<4;a++) {
|
||||
// for (int b=0;b<4;b++) {
|
||||
// for (int c=0;c<4;c++) {
|
||||
// sax_tweet_reader_visitor::field_lookup fields(a,b,c);
|
||||
// if (fields.collision_count) { continue; }
|
||||
// if (fields.zero_emission) { continue; }
|
||||
// if (fields.conflict_count < min_count) { printf("min=%d,%d,%d (%d)", a, b, c, fields.conflict_count); }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "twitter_user.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
// {
|
||||
// "statuses": [
|
||||
// {
|
||||
// "created_at": "Sun Aug 31 00:29:15 +0000 2014",
|
||||
// "id": 505874924095815700,
|
||||
// "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
|
||||
// "in_reply_to_status_id": null,
|
||||
// "user": {
|
||||
// "id": 1186275104,
|
||||
// "screen_name": "ayuu0123"
|
||||
// },
|
||||
// "retweet_count": 0,
|
||||
// "favorite_count": 0
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
struct tweet {
|
||||
std::string_view created_at{};
|
||||
uint64_t id{};
|
||||
std::string_view text{};
|
||||
uint64_t in_reply_to_status_id{};
|
||||
twitter_user user{};
|
||||
uint64_t retweet_count{};
|
||||
uint64_t favorite_count{};
|
||||
simdjson_really_inline bool operator==(const tweet &other) const {
|
||||
return created_at == other.created_at &&
|
||||
id == other.id &&
|
||||
text == other.text &&
|
||||
in_reply_to_status_id == other.in_reply_to_status_id &&
|
||||
user == other.user &&
|
||||
retweet_count == other.retweet_count &&
|
||||
favorite_count == other.favorite_count;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const tweet &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const tweet &t) {
|
||||
o << "created_at: " << t.created_at << std::endl;
|
||||
o << "id: " << t.id << std::endl;
|
||||
o << "text: " << t.text << std::endl;
|
||||
o << "in_reply_to_status_id: " << t.in_reply_to_status_id << std::endl;
|
||||
o << "user.id: " << t.user.id << std::endl;
|
||||
o << "user.screen_name: " << t.user.screen_name << std::endl;
|
||||
o << "retweet_count: " << t.retweet_count << std::endl;
|
||||
o << "favorite_count: " << t.favorite_count << std::endl;
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "simdjson.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
struct twitter_user {
|
||||
uint64_t id{};
|
||||
std::string_view screen_name{};
|
||||
|
||||
bool operator==(const twitter_user &other) const {
|
||||
return id == other.id &&
|
||||
screen_name == other.screen_name;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -41,7 +41,7 @@ using stat_t = struct stat_s;
|
||||
|
||||
|
||||
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson_really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<int64_t>()) {
|
||||
s.integer_count++;
|
||||
|
||||
+91
-16
@@ -53,6 +53,9 @@ endif()
|
||||
option(SIMDJSON_COMPETITION "Compile competitive benchmarks" ON)
|
||||
|
||||
option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark benchmarks" ON)
|
||||
if(SIMDJSON_COMPETITION)
|
||||
message(STATUS "Using SIMDJSON_GOOGLE_BENCHMARKS")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake")
|
||||
|
||||
@@ -72,37 +75,115 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
#endif()
|
||||
|
||||
|
||||
option(SIMDJSON_VISUAL_STUDIO_BUILD_WITH_DEBUG_INFO_FOR_PROFILING "Under Visual Studio, add Zi to the compile flag and DEBUG to the link file to add debugging information to the release build for easier profiling inside tools like VTune" OFF)
|
||||
if(MSVC)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE /WX /W3 /sdl)
|
||||
if("${MSVC_TOOLSET_VERSION}" STREQUAL "140")
|
||||
# Visual Studio 2015 issues warnings and we tolerate it, cmake -G"Visual Studio 14" ..
|
||||
target_compile_options(simdjson-internal-flags INTERFACE /W0 /sdl)
|
||||
else()
|
||||
# Recent version of Visual Studio expected (2017, 2019...). Prior versions are unsupported.
|
||||
target_compile_options(simdjson-internal-flags INTERFACE /WX /W3 /sdl /w34714) # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019
|
||||
endif()
|
||||
if(SIMDJSON_VISUAL_STUDIO_BUILD_WITH_DEBUG_INFO_FOR_PROFILING)
|
||||
target_link_options(simdjson-flags INTERFACE /DEBUG )
|
||||
target_compile_options(simdjson-flags INTERFACE /Zi)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -fPIC)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Werror -Wall -Wextra -Weffc++)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Optional flags
|
||||
#
|
||||
|
||||
#
|
||||
# Implementation selection
|
||||
#
|
||||
set(SIMDJSON_ALL_IMPLEMENTATIONS "fallback;westmere;haswell;arm64")
|
||||
|
||||
set(SIMDJSON_IMPLEMENTATION "" CACHE STRING "Semicolon-separated list of implementations to include (${SIMDJSON_ALL_IMPLEMENTATIONS}). If this is not set, any implementations that are supported at compile time and may be selected at runtime will be included.")
|
||||
foreach(implementation ${SIMDJSON_IMPLEMENTATION})
|
||||
if(NOT (implementation IN_LIST SIMDJSON_ALL_IMPLEMENTATIONS))
|
||||
message(ERROR "Implementation ${implementation} not supported by simdjson. Possible implementations: ${SIMDJSON_ALL_IMPLEMENTATIONS}")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
set(SIMDJSON_EXCLUDE_IMPLEMENTATION "" CACHE STRING "Semicolon-separated list of implementations to exclude (haswell/westmere/arm64/fallback). By default, excludes any implementations that are unsupported at compile time or cannot be selected at runtime.")
|
||||
foreach(implementation ${SIMDJSON_EXCLUDE_IMPLEMENTATION})
|
||||
if(NOT (implementation IN_LIST SIMDJSON_ALL_IMPLEMENTATIONS))
|
||||
message(ERROR "Implementation ${implementation} not supported by simdjson. Possible implementations: ${SIMDJSON_ALL_IMPLEMENTATIONS}")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
foreach(implementation ${SIMDJSON_ALL_IMPLEMENTATIONS})
|
||||
string(TOUPPER ${implementation} implementation_upper)
|
||||
if(implementation IN_LIST SIMDJSON_EXCLUDE_IMPLEMENTATION)
|
||||
message(STATUS "Excluding implementation ${implementation} due to SIMDJSON_EXCLUDE_IMPLEMENTATION=${SIMDJSON_EXCLUDE_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=0")
|
||||
elseif(implementation IN_LIST SIMDJSON_IMPLEMENTATION)
|
||||
message(STATUS "Including implementation ${implementation} due to SIMDJSON_IMPLEMENTATION=${SIMDJSON_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=1")
|
||||
elseif(SIMDJSON_IMPLEMENTATION)
|
||||
message(STATUS "Excluding implementation ${implementation} due to SIMDJSON_IMPLEMENTATION=${SIMDJSON_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=0")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
# TODO make it so this generates the necessary compiler flags to select the given implementation as the builtin automatically!
|
||||
option(SIMDJSON_BUILTIN_IMPLEMENTATION "Select the implementation that will be used for user code. Defaults to the most universal implementation in SIMDJSON_IMPLEMENTATION (in the order ${SIMDJSON_ALL_IMPLEMENTATIONS}) if specified; otherwise, by default the compiler will pick the best implementation that can always be selected given the compiler flags." "")
|
||||
if(SIMDJSON_BUILTIN_IMPLEMENTATION)
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_BUILTIN_IMPLEMENTATION=${SIMDJSON_BUILTIN_IMPLEMENTATION}")
|
||||
else()
|
||||
# Pick the most universal implementation out of the selected implementations (if any)
|
||||
foreach(implementation ${SIMDJSON_ALL_IMPLEMENTATIONS})
|
||||
if(implementation IN_LIST SIMDJSON_IMPLEMENTATION AND NOT (implementation IN_LIST SIMDJSON_EXCLUDE_IMPLEMENTATION))
|
||||
message(STATUS "Selected implementation ${implementation} as builtin implementation based on ${SIMDJSON_IMPLEMENTATION}.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_BUILTIN_IMPLEMENTATION=${implementation}")
|
||||
break()
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
endif(SIMDJSON_BUILTIN_IMPLEMENTATION)
|
||||
|
||||
option(SIMDJSON_IMPLEMENTATION_HASWELL "Include the haswell implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_HASWELL)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_HASWELL=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_HASWELL is deprecated. Use SIMDJSON_IMPLEMENTATION=-haswell instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_HASWELL=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_WESTMERE "Include the westmere implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_WESTMERE)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_WESTMERE=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_WESTMERE is deprecated. SIMDJSON_IMPLEMENTATION=-westmere instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_WESTMERE=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_ARM64 "Include the arm64 implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_ARM64)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_ARM64=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_ARM64 is deprecated. Use SIMDJSON_IMPLEMENTATION=-arm64 instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_ARM64=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_FALLBACK "Include the fallback implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_FALLBACK)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_FALLBACK=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_FALLBACK is deprecated. Use SIMDJSON_IMPLEMENTATION=-fallback instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_FALLBACK=0)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Other optional flags
|
||||
#
|
||||
option(SIMDJSON_ONDEMAND_SAFETY_RAILS "Validate ondemand user code at runtime to ensure it is being used correctly. Defaults to ON for debug builds, OFF for release builds." $<IF:$<CONFIG:DEBUG>,ON,OFF>)
|
||||
if(SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
message(STATUS "Ondemand safety rails enabled. Ondemand user code will be checked at runtime. This will be slower than normal!")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
endif(SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
|
||||
option(SIMDJSON_BASH "Allow usage of bash within CMake" ON)
|
||||
|
||||
option(SIMDJSON_GIT "Allow usage of git within CMake" ON)
|
||||
|
||||
option(SIMDJSON_EXCEPTIONS "Enable simdjson's exception-throwing interface" ON)
|
||||
if(NOT SIMDJSON_EXCEPTIONS)
|
||||
message(STATUS "simdjson exception interface turned off. Code that does not check error codes will not compile.")
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_EXCEPTIONS=0)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_EXCEPTIONS=0)
|
||||
endif()
|
||||
|
||||
option(SIMDJSON_ENABLE_THREADS "Link with thread support" ON)
|
||||
@@ -116,15 +197,9 @@ if(SIMDJSON_ENABLE_THREADS)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_THREADS_ENABLED=1) # This will be set in the code automatically.
|
||||
endif()
|
||||
|
||||
# Some users compile simdjson with thread support but still do not want simdjson to use threads.
|
||||
#
|
||||
# Important : Expect this option to disappear in the future.
|
||||
#
|
||||
option(SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT "Whether we enabled thread support or not (SIMDJSON_ENABLE_THREADS), do not use threads.\
|
||||
This option does nothing when thread support is not enabled. We reserve the right to remove this option in a future release in\
|
||||
favor of a runtime approach." OFF)
|
||||
if(SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT=1)
|
||||
option(SIMDJSON_VERBOSE_LOGGING, "Enable verbose logging for internal simdjson library development." OFF)
|
||||
if (SIMDJSON_VERBOSE_LOGGING)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_VERBOSE_LOGGING=1)
|
||||
endif()
|
||||
|
||||
if(SIMDJSON_USE_LIBCPP)
|
||||
|
||||
Vendored
+14
-4
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
find_package(Git QUIET) # We want the library to build even if git is missing
|
||||
if ((Git_FOUND) AND (SIMDJSON_IS_UNDER_GIT))
|
||||
if ((Git_FOUND) AND SIMDJSON_GIT AND (SIMDJSON_IS_UNDER_GIT))
|
||||
message(STATUS "Git is available.")
|
||||
# Does NOT attempt to update or otherwise modify git submodules that are already initialized.
|
||||
function(initialize_submodule DIRECTORY)
|
||||
@@ -18,6 +18,7 @@ if ((Git_FOUND) AND (SIMDJSON_IS_UNDER_GIT))
|
||||
endfunction(initialize_submodule)
|
||||
|
||||
if (SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
message (STATUS "'SIMDJSON_GOOGLE_BENCHMARKS' is requested, configuring..." )
|
||||
option(BENCHMARK_ENABLE_TESTING OFF)
|
||||
set(BENCHMARK_ENABLE_TESTING OFF)
|
||||
option(BENCHMARK_ENABLE_INSTALL OFF)
|
||||
@@ -66,8 +67,17 @@ if ((Git_FOUND) AND (SIMDJSON_IS_UNDER_GIT))
|
||||
add_library(competition-ujson4c ujson4c/src/ujdecode.c)
|
||||
target_include_directories(competition-ujson4c PUBLIC ujson4c/3rdparty ujson4c/src)
|
||||
|
||||
initialize_submodule(boost.json)
|
||||
add_library(boostjson boost.json/src/src.cpp)
|
||||
target_compile_definitions(boostjson PUBLIC BOOST_JSON_STANDALONE)
|
||||
target_include_directories(boostjson PUBLIC boost.json/include)
|
||||
|
||||
initialize_submodule(yyjson)
|
||||
add_library(yyjson yyjson/src/yyjson.c)
|
||||
target_include_directories(yyjson PUBLIC yyjson/src)
|
||||
|
||||
add_library(competition-core INTERFACE)
|
||||
target_link_libraries(competition-core INTERFACE competition-json competition-rapidjson competition-sajson competition-cJSON competition-jsmn)
|
||||
target_link_libraries(competition-core INTERFACE competition-json competition-rapidjson competition-sajson competition-cJSON competition-jsmn boostjson yyjson)
|
||||
|
||||
add_library(competition-all INTERFACE)
|
||||
target_link_libraries(competition-all INTERFACE competition-core competition-jsoncppdist competition-json11 competition-fastjson competition-gason competition-ujson4c)
|
||||
@@ -80,9 +90,9 @@ if ((Git_FOUND) AND (SIMDJSON_IS_UNDER_GIT))
|
||||
else()
|
||||
message(STATUS "Git is unavailable.")
|
||||
if(SIMDJSON_COMPETITION)
|
||||
message (STATUS "'SIMDJSON_COMPETITION' is requested, but we cannot download the remote repositories." )
|
||||
message (STATUS "'SIMDJSON_COMPETITION' is requested, but we cannot download the remote repositories." )
|
||||
endif()
|
||||
if(SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
message (STATUS "'SIMDJSON_GOOGLE_BENCHMARKS' is requested, but we cannot download the remote repositories." )
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
+1
Submodule dependencies/boost.json added at a0983f788b
+1
Submodule dependencies/yyjson added at aa33ec5a47
+129
-28
@@ -8,8 +8,8 @@ An overview of what you need to know to use simdjson, with examples.
|
||||
* [Using simdjson as a CMake dependency](#using-simdjson-as-a-cmake-dependency)
|
||||
* [The Basics: Loading and Parsing JSON Documents](#the-basics-loading-and-parsing-json-documents)
|
||||
* [Using the Parsed JSON](#using-the-parsed-json)
|
||||
* [C++11 Support and string_view](#c++11-support-and-string_view)
|
||||
* [C++17 Support](#c++17-support)
|
||||
* [C++11 Support and string_view](#c11-support-and-string_view)
|
||||
* [C++17 Support](#c17-support)
|
||||
* [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
|
||||
* [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
* [JSON Pointer](#json-pointer)
|
||||
@@ -19,13 +19,14 @@ An overview of what you need to know to use simdjson, with examples.
|
||||
* [Tree Walking and JSON Element Types](#tree-walking-and-json-element-types)
|
||||
* [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
|
||||
* [Thread Safety](#thread-safety)
|
||||
* [Standard Compliance](#standard-compliance)
|
||||
|
||||
|
||||
Requirements
|
||||
------------------
|
||||
|
||||
- A recent compiler (LLVM clang6 or better, GNU GCC 7 or better) on a 64-bit (ARM or x64 Intel/AMD) POSIX systems such as macOS, freeBSD or Linux. We require that the compiler supports the C++11 standard or better.
|
||||
- Visual Studio 2017 or better under 64-bit Windows. Users should target a 64-bit build (x64) instead of a 32-bit build (x86). We support the LLVM clang compiler under Visual Studio (clangcl) as well as as the regular Visual Studio compiler.
|
||||
- A recent compiler (LLVM clang6 or better, GNU GCC 7.4 or better) on a 64-bit (ARM or x64 Intel/AMD) POSIX systems such as macOS, freeBSD or Linux. We require that the compiler supports the C++11 standard or better.
|
||||
- Visual Studio 2017 or better under 64-bit Windows. Users should target a 64-bit build (x64) instead of a 32-bit build (x86). We support the LLVM clang compiler under Visual Studio (clangcl) as well as as the regular Visual Studio compiler. We also support MinGW 64-bit under Windows.
|
||||
|
||||
Including simdjson
|
||||
------------------
|
||||
@@ -45,28 +46,51 @@ c++ myproject.cpp simdjson.cpp
|
||||
```
|
||||
|
||||
Note:
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ myproject.cpp simdjson.cpp`).
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ -std=c++17 myproject.cpp simdjson.cpp`).
|
||||
- Visual Studio users should compile with the `_CRT_SECURE_NO_WARNINGS` flag to avoid warnings with respect to our use of standard C functions such as `fopen`.
|
||||
|
||||
|
||||
Using simdjson with package managers
|
||||
------------------
|
||||
|
||||
You can install the simdjson library on your system or in your project using multiple package managers such as MSYS2, the conan package manager, vcpkg, brew, the apt package manager (debian-based Linux systems), the FreeBSD package manager (FreeBSD), and so on. [Visit our wiki for more details](https://github.com/simdjson/simdjson/wiki/Installing-simdjson-with-a-package-manager).
|
||||
|
||||
Using simdjson as a CMake dependency
|
||||
------------------
|
||||
|
||||
You can include the simdjson repository as a folder in your CMake project. In the parent
|
||||
`CMakeLists.txt`, include the following lines:
|
||||
You can include the simdjson as a CMake dependency by including the following lines in your `CMakeLists.txt`:
|
||||
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
simdjson
|
||||
GIT_REPOSITORY https://github.com/simdjson/simdjson.git
|
||||
GIT_TAG v0.5.0
|
||||
GIT_SHALLOW TRUE)
|
||||
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE INTERNAL "")
|
||||
set(SIMDJSON_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
|
||||
FetchContent_MakeAvailable(simdjson)
|
||||
```
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE STRING "Build just the library, nothing else." FORCE)
|
||||
add_subdirectory(simdjson EXCLUDE_FROM_ALL)
|
||||
```
|
||||
|
||||
You should replace `GIT_TAG v0.5.0` by the version you need. If you omit `GIT_TAG v0.5.0`, you will work from the main branch of simdjson: we recommend that if you are working on production code,
|
||||
|
||||
Elsewhere in your project, you can declare dependencies on simdjson with lines such as these:
|
||||
|
||||
```
|
||||
```cmake
|
||||
add_executable(myprogram myprogram.cpp)
|
||||
target_link_libraries(myprogram simdjson)
|
||||
```
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmakedemo).
|
||||
We recommend CMake version 3.15 or better.
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmake_demo_single_file). It works under Linux, FreeBSD, macOS and Windows (including Visual Studio).
|
||||
|
||||
|
||||
|
||||
The CMake build in simdjson can be taylored with a few variables. You can see the available variables and their default values by entering the `cmake -LA` command.
|
||||
|
||||
The Basics: Loading and Parsing JSON Documents
|
||||
----------------------------------------------
|
||||
@@ -84,7 +108,7 @@ SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string, the _padded suffix creates a simdjson::padded_string instance
|
||||
```
|
||||
|
||||
The parsed document resulting from the `parser.load` and `parser.parse` calls depends on the `parser` instance. Thus the `parser` instance must remain in scope. Furthermore, you must have at most one parsed document in play per `parser` instance.
|
||||
@@ -93,6 +117,9 @@ During the`load` or `parse` calls, neither the input file nor the input string a
|
||||
|
||||
For best performance, a `parser` instance should be reused over several files: otherwise you will needlessly reallocate memory, an expensive process. It is also possible to avoid entirely memory allocations during parsing when using simdjson. [See our performance notes for details](performance.md).
|
||||
|
||||
If you need a lower-level interface, you may call the function `parser.parse(const char * p, size_t l)` on a pointer `p` while specifying the
|
||||
length of your input `l` in bytes. To see how to get the very best performance from a low-level approach, you way want to read our [performance notes](https://github.com/simdjson/simdjson/blob/master/doc/performance.md#padding-and-temporary-copies) on this topic (see the Padding and Temporary Copies section).
|
||||
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
@@ -102,7 +129,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Extracting Values (with exceptions):** You can cast a JSON element to a native type: `double(element)` or
|
||||
`double x = json_element`. This works for double, uint64_t, int64_t, bool,
|
||||
dom::object and dom::array. An exception is thrown if the cast is not possible.
|
||||
* **Extracting Values (without expceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
* **Extracting Values (without exceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
`dom::object` and `dom::array`) and pass it by reference to `get()` which gives you back an error code: e.g.,
|
||||
```c++
|
||||
simdjson::error_code error;
|
||||
@@ -125,10 +152,11 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
|
||||
with the `size()` method.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type`.
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
|
||||
* **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`).
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
The following code illustrates all of the above:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
@@ -238,7 +266,7 @@ padded_string json = R"( { "foo": 1, "bar": 2 } )"_padded;
|
||||
dom::parser parser;
|
||||
dom::object object;
|
||||
auto error = parser.parse(json).get(object);
|
||||
if (!error) { cerr << error << endl; return; }
|
||||
if (error) { cerr << error << endl; return; }
|
||||
for (dom::key_value_pair field : object) {
|
||||
cout << field.key << " = " << field.value << endl;
|
||||
}
|
||||
@@ -247,16 +275,15 @@ for (dom::key_value_pair field : object) {
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`minify`). This function does not validate your content, and it does not parse it. Instead, it assumes that your string is valid UTF-8. It is much faster than parsing the string and re-serializing it in minified form. Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is slightly overallocated (by `simdjson::SIMDJSON_PADDING`) compared to the original string length. The input pointer and input length are read, but not written to.
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`simdjson::minify(const char * input, size_t length, const char * output, size_t& new_length)`). This function does not validate your content, and it does not parse it. It is much faster than parsing the string and re-serializing it in minified form (`simdjson::minify(parser.parse())`). Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is as large as the original string length. The input pointer and input length are read, but not written to.
|
||||
|
||||
```C++
|
||||
// Starts with a valid JSON document as a string.
|
||||
// It does not have to be null-terminated.
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room,
|
||||
// including some padding (simdjson::SIMDJSON_PADDING).
|
||||
std::unique_ptr<char[]> buffer{new(std::nothrow) char[length + simdjson::SIMDJSON_PADDING]};
|
||||
size_t length = std::strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room (length bytes).
|
||||
std::unique_ptr<char[]> buffer{new char[length]};
|
||||
size_t new_length{}; // It will receive the minified length.
|
||||
auto error = simdjson::minify(some_string, length, buffer.get(), new_length);
|
||||
// The buffer variable now has "[1,2,3,4]" and new_length has value 9.
|
||||
@@ -272,7 +299,7 @@ The simdjson library has fast functions to validate UTF-8 strings. They are many
|
||||
|
||||
```C++
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
size_t length = std::strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
@@ -284,7 +311,7 @@ JSON Pointer
|
||||
------------
|
||||
|
||||
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the
|
||||
at() method, letting you reach further down into the document in a single call:
|
||||
`at_pointer()` method, letting you reach further down into the document in a single call:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
@@ -294,9 +321,39 @@ auto cars_json = R"( [
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9
|
||||
cout << cars.at_pointer("/0/tire_pressure/1") << endl; // Prints 39.9
|
||||
```
|
||||
|
||||
A JSON Path is a sequence of segments each starting with the '/' character. Within arrays, an integer
|
||||
index allows you to select the indexed node. Within objects, the string value of the key allows you to
|
||||
select the value. If your keys contain the characters '/' or '~', they must be escaped as '~1' and
|
||||
'~0' respectively. An empty JSON Path refers to the whole document.
|
||||
|
||||
We also extend the JSON Pointer support to include *relative* paths.
|
||||
You can apply a JSON path to any node and the path gets interpreted relatively, as if the currrent node were a whole JSON document.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at_pointer("/0/tire_pressure/1") << endl; // Prints 39.9
|
||||
for (dom::element car_element : cars) {
|
||||
dom::object car;
|
||||
simdjson::error_code error;
|
||||
if ((error = car_element.get(car))) { std::cerr << error << std::endl; return; }
|
||||
double x = car.at_pointer("/tire_pressure/1");
|
||||
cout << x << endl; // Prints 39.9, 31 and 30
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
@@ -528,7 +585,7 @@ format. If your JSON documents all contain arrays or objects, we even support di
|
||||
concatenation without whitespace. The concatenated file has no size restrictions (including larger
|
||||
than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
Here is a simple example, given `x.json` with this content:
|
||||
|
||||
```json
|
||||
{ "foo": 1 }
|
||||
@@ -538,17 +595,42 @@ Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
dom::document_stream docs = parser.load_many("x.json");
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`.
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`:
|
||||
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
auto json = R"({ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 })"_padded;
|
||||
dom::document_stream docs = parser.parse_many(json);
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
|
||||
Unlike `parser.parse`, both `parser.load_many(filename)` and `parser.parse_many(string)` may parse
|
||||
"on demand" (lazily). That is, no parsing may have been done before you enter the loop
|
||||
`for (dom::element doc : docs) {` and you should expect the parser to only ever fully parse one JSON
|
||||
document at a time.
|
||||
|
||||
1. When calling `parser.load_many(filename)`, the file's content is loaded up in a memory buffer owned by the `parser`'s instance. Thus the file can be safely deleted after calling `parser.load_many(filename)` as the parser instance owns all of the data.
|
||||
2. When calling `parser.parse_many(string)`, no copy is made of the provided string input. The provided memory buffer may be accessed each time a JSON document is parsed. Calling `parser.parse_many(string)` on a temporary string buffer (e.g., `docs = parser.parse_many("[1,2,3]"_padded)`) is unsafe (and will not compile) because the `document_stream` instance needs access to the buffer to return the JSON documents. In constrast, calling `doc = parser.parse("[1,2,3]"_padded)` is safe because `parser.parse` eagerly parses the input.
|
||||
|
||||
|
||||
Both `load_many` and `parse_many` take an optional parameter `size_t batch_size` which defines the window processing size. It is set by default to a large value (`1000000` corresponding to 1 MB). None of your JSON documents should exceed this window size, or else you will get the error `simdjson::CAPACITY`. You cannot set this window size larger than 4 GB: you will get the error `simdjson::CAPACITY`. The smaller the window size is, the less memory the function will use. Setting the window size too small (e.g., less than 100 kB) may also impact performance negatively. Leaving it to 1 MB is expected to be a good choice, unless you have some larger documents.
|
||||
|
||||
If your documents are large (e.g., larger than a megabyte), then the `load_many` and `parse_many` functions are maybe ill-suited. They are really meant to support reading efficiently streams of relatively small documents (e.g., a few kilobytes each). If you have larger documents, you should use other functions like `parse`.
|
||||
|
||||
See [parse_many.md](parse_many.md) for detailed information and design.
|
||||
|
||||
Thread Safety
|
||||
@@ -566,6 +648,25 @@ The parsed results (`dom::document`, `dom::element`, `array`, `object`) depend o
|
||||
The CPU detection, which runs the first time parsing is attempted and switches to the fastest
|
||||
parser for your CPU, is transparent and thread-safe.
|
||||
|
||||
|
||||
Standard Compliance
|
||||
--------------------
|
||||
|
||||
The simdjson library is fully compliant with the [RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html) JSON specification.
|
||||
|
||||
- The only insignificant whitespace characters allowed are the space, the horizontal tab, the line feed and the carriage return. In particular, a JSON document may not contain an unespaced null character.
|
||||
- A single string or a single number is considered to be a valid JSON document.
|
||||
- We fully validate the numbers according to the JSON specification. For example, the string `01` is not valid JSON document since the specification states that *leading zeros are not allowed*.
|
||||
- The specification allows implementations to set limits on the range and precision of numbers accepted. We support 64-bit floating-point numbers as well as integer values.
|
||||
- We parse integers and floating-point numbers as separate types which allows us to support all signed (two complement's) 64-bit integers, like a Java `long` or a C/C++ `long long` and all 64-bit unsigned integers. When we cannot represent exactly an integer as a signed or unsigned 64-bit value, we reject the JSON document.
|
||||
- We support the full range of 64-bit floating-point numbers (binary64). The values range from `std::numeric_limits<double>::lowest()` to `std::numeric_limits<double>::max()`, so from -1.7976e308 all the way to 1.7975e308. Extreme values (less or equal to -1e308, greater or equal to 1e308) are rejected: we refuse to parse the input document. Numbers are parsed with with a perfect accuracy (ULP 0): the nearest floating-point value is chosen, rounding to even when needed. If you serialized your floating-point numbers with 17 significant digits in a standard compliant manner, the simdjson library is guaranteed to recovere the example same numbers, exactly.
|
||||
- The specification states that JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8. The simdjson library does full UTF-8 validation as part of the parsing. The specification states that implementations MUST NOT add a byte order mark: the simdjson library rejects documents starting with a byte order mark.
|
||||
- The simdjson library validates string content for unescaped characters. Unescaped line breaks and tabs in strings are not allowed.
|
||||
- The simdjson library accepts objects with repeated keys: all of the name/value pairs, including duplicates, are reported. We do not enforce key uniqueness.
|
||||
- The specification states that an implementation may set limits on the size of texts that it accepts. The simdjson library limits single JSON documents to 4 GiB. It will refuse to parse a JSON document larger than 4294967295 bytes. (This limitation does not apply to streams of JSON documents, only to single JSON documents.)
|
||||
- The specification states that an implementation may set limits on the maximum depth of nesting. By default, the simdjson will refuse to parse documents with a depth exceeding 1024.
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
-----------------------
|
||||
|
||||
|
||||
+100
-22
@@ -28,20 +28,36 @@ c++ myproject.cpp simdjson.cpp
|
||||
```
|
||||
|
||||
Note:
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ myproject.cpp simdjson.cpp`).
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ -std=c++17 myproject.cpp simdjson.cpp`).
|
||||
- Visual Studio users should compile with the `_CRT_SECURE_NO_WARNINGS` flag to avoid warnings with respect to our use of standard C functions such as `fopen`.
|
||||
|
||||
Using simdjson with package managers
|
||||
------------------
|
||||
|
||||
You can install the simdjson library on your system or in your project using multiple package managers such as MSYS2, the conan package manager, vcpkg, brew, the apt package manager (debian-based Linux systems), the FreeBSD package manager (FreeBSD), and so on. [Visit our wiki for more details](https://github.com/simdjson/simdjson/wiki/Installing-simdjson-with-a-package-manager).
|
||||
|
||||
Using simdjson as a CMake dependency
|
||||
------------------
|
||||
|
||||
You can include the simdjson repository as a folder in your CMake project. In the parent
|
||||
`CMakeLists.txt`, include the following lines:
|
||||
You can include the simdjson as a CMake dependency by including the following lines in your `CMakeLists.txt`:
|
||||
|
||||
```
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE STRING "Build just the library, nothing else." FORCE)
|
||||
add_subdirectory(simdjson EXCLUDE_FROM_ALL)
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
simdjson
|
||||
GIT_REPOSITORY https://github.com/simdjson/simdjson.git
|
||||
GIT_TAG v0.4.7
|
||||
GIT_SHALLOW TRUE)
|
||||
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE INTERNAL "")
|
||||
set(SIMDJSON_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
|
||||
FetchContent_MakeAvailable(simdjson)
|
||||
```
|
||||
|
||||
You should replace `GIT_TAG v0.5.0` by the version you need. If you omit `GIT_TAG v0.5.0`, you will work from the main branch of simdjson: we recommend that if you are working on production code,
|
||||
|
||||
Elsewhere in your project, you can declare dependencies on simdjson with lines such as these:
|
||||
|
||||
```
|
||||
@@ -49,7 +65,13 @@ add_executable(myprogram myprogram.cpp)
|
||||
target_link_libraries(myprogram simdjson)
|
||||
```
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmakedemo).
|
||||
We recommend CMake version 3.15 or better.
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmake_demo_single_file). It works under Linux, FreeBSD, macOS and Windows (including Visual Studio).
|
||||
|
||||
The CMake build in simdjson can be taylored with a few variables. You can see the available variables and their default values by entering the `cmake -LA` command.
|
||||
|
||||
|
||||
|
||||
The Basics: Loading and Parsing JSON Documents
|
||||
----------------------------------------------
|
||||
@@ -67,7 +89,7 @@ SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string, the _padded suffix creates a simdjson::padded_string instance
|
||||
```
|
||||
|
||||
The parsed document resulting from the `parser.load` and `parser.parse` calls depends on the `parser` instance. Thus the `parser` instance must remain in scope. Furthermore, you must have at most one parsed document in play per `parser` instance.
|
||||
@@ -77,6 +99,9 @@ During the`load` or `parse` calls, neither the input file nor the input string a
|
||||
For best performance, a `parser` instance should be reused over several files: otherwise you will needlessly reallocate memory, an expensive process. It is also possible to avoid entirely memory allocations during parsing when using simdjson.
|
||||
|
||||
|
||||
If you need a lower-level interface, you may call the function `parser.parse(const char * p, size_t l)` on a pointer `p` while specifying the
|
||||
length of your input `l` in bytes. To see how to get the very best performance from a low-level approach, you way want to read our [performance notes](https://github.com/simdjson/simdjson/blob/master/doc/performance.md#padding-and-temporary-copies) on this topic (see the Padding and Temporary Copies section).
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
@@ -85,7 +110,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Extracting Values (with exceptions):** You can cast a JSON element to a native type: `double(element)` or
|
||||
`double x = json_element`. This works for double, uint64_t, int64_t, bool,
|
||||
dom::object and dom::array. An exception is thrown if the cast is not possible.
|
||||
* **Extracting Values (without expceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
* **Extracting Values (without exceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
`dom::object` and `dom::array`) and pass it by reference to `get()` which gives you back an error code: e.g.,
|
||||
```
|
||||
simdjson::error_code error;
|
||||
@@ -108,10 +133,11 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
|
||||
with the `size()` method.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type`.
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
|
||||
* **Output to Streams and Strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`).
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
The following code illustrates all of the above:
|
||||
|
||||
```
|
||||
auto cars_json = R"( [
|
||||
@@ -230,16 +256,16 @@ for (dom::key_value_pair field : object) {
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`minify`). This function does not validate your content, and it does not parse it. Instead, it assumes that your string is valid UTF-8. It is much faster than parsing the string and re-serializing it in minified form. Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is slightly overallocated (by `simdjson::SIMDJSON_PADDING`) compared to the original string length. The input pointer and input length are read, but not written to.
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`simdjson::minify(const char * input, size_t length, const char * output, size_t& new_length)`). This function does not validate your content, and it does not parse it. It is much faster than parsing the string and re-serializing it in minified form (`simdjson::minify(parser.parse())`). Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is as large as the original string length. The input pointer and input length are read, but not written to.
|
||||
|
||||
|
||||
```
|
||||
// Starts with a valid JSON document as a string.
|
||||
// It does not have to be null-terminated.
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room,
|
||||
// including some padding (simdjson::SIMDJSON_PADDING).
|
||||
std::unique_ptr<char[]> buffer{new(std::nothrow) char[length + simdjson::SIMDJSON_PADDING]};
|
||||
size_t length = std::strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room (length bytes).
|
||||
std::unique_ptr<char[]> buffer{new char[length]};
|
||||
size_t new_length{}; // It will receive the minified length.
|
||||
auto error = simdjson::minify(some_string, length, buffer.get(), new_length);
|
||||
// The buffer variable now has "[1,2,3,4]" and new_length has value 9.
|
||||
@@ -255,7 +281,7 @@ The simdjson library has fast functions to validate UTF-8 strings. They are many
|
||||
|
||||
```
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
size_t length = std::strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
@@ -267,9 +293,9 @@ JSON Pointer
|
||||
------------
|
||||
|
||||
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the
|
||||
at() method, letting you reach further down into the document in a single call:
|
||||
`at_pointer()` method, letting you reach further down into the document in a single call:
|
||||
|
||||
```
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
@@ -277,9 +303,38 @@ auto cars_json = R"( [
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9
|
||||
cout << cars.at_pointer("/0/tire_pressure/1") << endl; // Prints 39.9
|
||||
```
|
||||
|
||||
A JSON Path is a sequence of segments each starting with the '/' character. Within arrays, an integer
|
||||
index allows you to select the indexed node. Within objects, the string value of the key allows you to
|
||||
select the value. If your keys contain the characters '/' or '~', they must be escaped as '~1' and
|
||||
'~0' respectively. An empty JSON Path refers to the whole document.
|
||||
|
||||
We also extend the JSON Pointer support to include *relative* paths.
|
||||
You can apply a JSON path to any node and the path gets interpreted relatively, as if the currrent node were a whole JSON document.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at_pointer("/0/tire_pressure/1") << endl; // Prints 39.9
|
||||
for (dom::element car_element : cars) {
|
||||
dom::object car;
|
||||
simdjson::error_code error;
|
||||
if ((error = car_element.get(car))) { std::cerr << error << std::endl; return; }
|
||||
double x = car.at_pointer("/tire_pressure/1");
|
||||
cout << x << endl; // Prints 39.9, 31 and 30
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
@@ -513,7 +568,7 @@ than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```json
|
||||
```
|
||||
{ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 }
|
||||
@@ -521,14 +576,37 @@ Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
dom::document_stream docs = parser.load_many("x.json");
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`.
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`:
|
||||
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
auto json = R"({ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 })"_padded;
|
||||
dom::document_stream docs = parser.parse_many(json);
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
|
||||
Unlike `parser.parse`, both `parser.load_many(filename)` and `parser.parse_many(string)` may parse
|
||||
"on demand" (lazily). That is, no parsing may have been done before you enter the loop
|
||||
`for (dom::element doc : docs) {` and you should expect the parser to only ever fully parse one JSON
|
||||
document at a time.
|
||||
|
||||
1. When calling `parser.load_many(filename)`, the file's content is loaded up in a memory buffer owned by the `parser`'s instance. Thus the file can be safely deleted after calling `parser.load_many(filename)` as the parser instance owns all of the data.
|
||||
2. When calling `parser.parse_many(string)`, no copy is made of the provided string input. The provided memory buffer may be accessed each time a JSON document is parsed. Calling `parser.parse_many(string)` on a temporary string buffer (e.g., `docs = parser.parse_many("[1,2,3]"_padded)`) is unsafe (and will not compile) because the `document_stream` instance needs access to the buffer to return the JSON documents. In constrast, calling `doc = parser.parse("[1,2,3]"_padded)` is safe because `parser.parse` eagerly parses the input.
|
||||
|
||||
Both `load_many` and `parse_many` take an optional parameter `size_t batch_size` which defines the window processing size. It is set by default to a large value (`1000000` corresponding to 1 MB). None of your JSON documents should exceed this window size, or else you will get the error `simdjson::CAPACITY`. You cannot set this window size larger than 4 GB: you will get the error `simdjson::CAPACITY`. The smaller the window size is, the less memory the function will use. Setting the window size too small (e.g., less than 100 kB) may also impact performance negatively. Leaving it to 1 MB is expected to be a good choice, unless you have some larger documents.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ CPU Architecture-Specific Implementations
|
||||
* [Inspecting the Detected Implementation](#inspecting-the-detected-implementation)
|
||||
* [Querying Available Implementations](#querying-available-implementations)
|
||||
* [Manually Selecting the Implementation](#manually-selecting-the-implementation)
|
||||
* [Checking that an Implementation can Run on your System](#checking-that-an-implementation-can-run-on-your-system)
|
||||
|
||||
Overview
|
||||
--------
|
||||
@@ -47,7 +48,7 @@ Inspecting the Detected Implementation
|
||||
You can check what implementation is running with `active_implementation`:
|
||||
|
||||
```c++
|
||||
cout << "simdjson v" << #SIMDJSON_VERSION << endl;
|
||||
cout << "simdjson v" << STRINGIFY(SIMDJSON_VERSION) << endl;
|
||||
cout << "Detected the best implementation for your machine: " << simdjson::active_implementation->name();
|
||||
cout << "(" << simdjson::active_implementation->description() << ")" << endl;
|
||||
```
|
||||
@@ -70,6 +71,14 @@ And look them up by name:
|
||||
```c++
|
||||
cout << simdjson::available_implementations["fallback"]->description() << endl;
|
||||
```
|
||||
Though the fallback implementation should always be available, others might be missing. When
|
||||
an implementation is not available, the bracket call `simdjson::available_implementations[name]`
|
||||
will return the null pointer.
|
||||
|
||||
The available implementations have been compiled but may not necessarily be run safely on your system
|
||||
see [Checking that an Implementation can Run on your System](#checking-that-an-implementation-can-run-on-your-system).
|
||||
|
||||
|
||||
|
||||
Manually Selecting the Implementation
|
||||
-------------------------------------
|
||||
@@ -81,3 +90,30 @@ can select the CPU architecture yourself:
|
||||
// Use the fallback implementation, even though my machine is fast enough for anything
|
||||
simdjson::active_implementation = simdjson::available_implementations["fallback"];
|
||||
```
|
||||
|
||||
You are responsible for ensuring that the requirements of the selected implementation match your current system.
|
||||
Furthermore, you should check that the implementation is available before setting it to `simdjson::active_implementation`
|
||||
by comparing it with the null pointer.
|
||||
|
||||
```c++
|
||||
auto my_implementation = simdjson::available_implementations["haswell"];
|
||||
if(! my_implementation) { exit(1); }
|
||||
if(! my_implementation->supported_by_runtime_system()) { exit(1); }
|
||||
simdjson::active_implementation = my_implementation;
|
||||
```
|
||||
|
||||
Checking that an Implementation can Run on your System
|
||||
-------------------------------------
|
||||
|
||||
You should call `supported_by_runtime_system()` to compare the processor's features with the need of the implementation.
|
||||
|
||||
```c++
|
||||
for (auto implementation : simdjson::available_implementations) {
|
||||
if(implementation->supported_by_runtime_system()) {
|
||||
cout << implementation->name() << ": " << implementation->description() << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The call to `supported_by_runtime_system()` maybe relatively expensive. Do not call `supported_by_runtime_system()` each
|
||||
time you parse a JSON input (for example). It is meant to be called a handful of times at most in the life of a program.
|
||||
+629
@@ -0,0 +1,629 @@
|
||||
|
||||
A Better Way to Parse Documents?
|
||||
====================
|
||||
|
||||
Whether we parse JSON or XML, or any other serialized format, there are relatively few common strategies:
|
||||
|
||||
- The most established approach is the construction of document-object-model (DOM).
|
||||
- Another established approach is a event-based approach (like SAX, SAJ).
|
||||
- Another popular approach is the schema-based deserialization model.
|
||||
|
||||
We propose an approach that is as easy to use and often as flexible as the DOM approach, yet as fast and
|
||||
efficient as the schema-based or event-based approaches. We call this new approach "On Demand". The
|
||||
simdjson On Demand API offers a familiar, friendly DOM API and
|
||||
provides the performance of just-in-time parsing on top of the simdjson superior performance.
|
||||
|
||||
To achieve ease of use, we mimicked the *form* of a traditional DOM API: you can iterate over
|
||||
arrays, look up fields in objects, and extract native values like `double`, `uint64_t`, `string` and `bool`.
|
||||
|
||||
To achieve performance, we introduced some key limitations that make the DOM API *streaming*:
|
||||
array/object iteration cannot be restarted, and fields must be looked up in order, and string/number
|
||||
values can only be parsed once. If these limitations are acceptable to you, the On Demand API could
|
||||
help you write maintainable applications with a computation efficiency that is difficult to surpass.
|
||||
|
||||
A code example illustrates our API from a programmer's point of view:
|
||||
|
||||
```c++
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
for (auto tweet : doc["statuses"]) {
|
||||
std::string_view text = tweet["text"];
|
||||
std::string_view screen_name = tweet["user"]["screen_name"];
|
||||
std::string_view screen_name;
|
||||
{
|
||||
ondemand::object user = tweet["user"];
|
||||
screen_name = user["screen_name"];
|
||||
}
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
cout << screen_name << " (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
}
|
||||
```
|
||||
|
||||
Such code would be apply to a JSON document such as the following JSON mimicking a sample result from the Twitter API:
|
||||
|
||||
```json
|
||||
{
|
||||
"statuses": [{
|
||||
"text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
|
||||
"user": {
|
||||
"name": "AYUMI",
|
||||
"screen_name": "ayuu0123",
|
||||
"followers_count": 262,
|
||||
"friends_count": 252
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"favorite_count": 0
|
||||
},
|
||||
{
|
||||
"text": "RT @KATANA77: えっそれは・・・(一同) http://t.co/PkCJAcSuYK",
|
||||
"user": {
|
||||
"name": "RT&ファボ魔のむっつんさっm",
|
||||
"screen_name": "yuttari1998",
|
||||
"followers_count": 95,
|
||||
"friends_count": 158
|
||||
},
|
||||
"retweet_count": 82,
|
||||
"favorite_count": 42
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This streaming approach means that unused fields and values are not parsed or
|
||||
converted, thus saving space and time. In our example, the `"name"`, `"followers_count"`,
|
||||
and `"friends_count"` keys and matching values are skipped.
|
||||
|
||||
Further, the On Demand API does not parse a value *at all* until you try to convert it (e.g., to `double`,
|
||||
`int`, `string`, or `bool`). In our example, when accessing the key-value pair `"retweet_count": 82`, the parser
|
||||
may not convert the pair of characters `82` to the binary integer 82. Because the programmer specifies the data
|
||||
type, we avoid branch mispredictions related to data type determination and improve the performance.
|
||||
|
||||
|
||||
We expect users of an On Demand API to work in terms of a JSON dialect, which is a set of expectations and
|
||||
specifications that come in addition to the [JSON specification](https://www.rfc-editor.org/rfc/rfc8259.txt).
|
||||
The On Demand approach is designed around several principles:
|
||||
|
||||
* **Streaming (\*):** It avoids preparsing values, keeping the memory usage and the latency down.
|
||||
* **Forward-Only:** To prevent reiteration of the same values and to keep the number of variables down (literally), only a single index is maintained and everything uses it (even if you have nested for loops). This means when you are going through an array of arrays, for example, that the inner array loop will advance the index to the next comma, and the array can just pick it up and look at it.
|
||||
* **Natural Iteration:** A JSON array or object can be iterated with a normal C++ for loop. Nested arrays and objects are supported by nested for loops.
|
||||
* **Use-Specific Parsing:** Parsing is always specific to the type required by the programmer. For example, if the programmer asks for an unsigned integer, we just start parsing digits. If there were no digits, we toss an error. There are even different parsers for `double`, `uint64_t` and `int64_t` values. This use-specific parsing avoids the branchiness of a generic "type switch," and makes the code more inlineable and compact.
|
||||
* **Validate What You Use:** On Demand deliberately validates the values you use and the structure leading to it, but nothing else. The goal is a guarantee that the value you asked for is the correct one and is not malformed: there must be no confusion over whether you got the right value.
|
||||
|
||||
|
||||
|
||||
To understand why On Demand is different, it is helpful to review the major
|
||||
approaches to parsing and parser APIs in use today.
|
||||
|
||||
### DOM Parsers
|
||||
|
||||
Many of the most usable, popular JSON APIs (including simdjson) deserialize into a **DOM**: an intermediate tree of
|
||||
objects, arrays and values. In this model, we convert the input data all at once into a tree-like structure (the DOM).
|
||||
The DOM is then accessed by the programmer like any other in-memory data structure. The resulting API let
|
||||
you refer to each array or object separately, using familiar techniques like iteration (`for (auto value : array)`)
|
||||
or indexing (`object["key"]`). In some cases, the values are even deserialized directly into familiar C++ constructs like vectors and
|
||||
maps.
|
||||
|
||||
The DOM approach is conceptually simple and "programmer friendly". Using the
|
||||
DOM tree is often easy enough that many users use the DOM as-is instead of creating their own
|
||||
their own custom data structures.
|
||||
|
||||
The DOM approach was the only way to parse JSON documents up to version 0.6 of the simdjson library.
|
||||
Our DOM API looks similar to our On Demand example, except
|
||||
it calls `parse` instead of `iterate`:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
auto doc = parser.parse(json);
|
||||
for (auto tweet : doc["statuses"]) {
|
||||
std::string_view text = tweet["text"];
|
||||
std::string_view screen_name = tweet["user"]["screen_name"];
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
cout << screen_name << " (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
}
|
||||
```
|
||||
|
||||
Pros of the DOM approach:
|
||||
* Straightforward, programmer-friendly interface (arrays and objects).
|
||||
* Safe: all of the input data has been validated before it is accessed.
|
||||
* All of the JSON document is available at once to the programmer.
|
||||
|
||||
Cons of the DOM approach:
|
||||
* The memory usage scales linearly with the size of the input document.
|
||||
* Parses and stores everything, using memory and CPU cycles even on unused values.
|
||||
* Performance drain from [type blindness](#type-blindness).
|
||||
|
||||
|
||||
What the simdjson library demonstrates is that a DOM API may be quite fast indeed: we can parse files at speeds
|
||||
of several gigabytes per second. However, in some instances, it may be possible to achieve even higher speeds.
|
||||
|
||||
### Event-Based Parsers (SAX, SAJ, etc.)
|
||||
|
||||
|
||||
The event-based model (originally from the "Streaming API for XML") uses streaming to eliminate the cost of
|
||||
parsing and storing the entire JSON. In the event-based model, a core JSON engine parses the JSON document
|
||||
piece by piece, but instead of stuffing values in a DOM tree, it passes each value to a callback function,
|
||||
letting the user decide for themselves how to handle it. In such a model, the programmer may need to provide functions
|
||||
for all possible events (a number, a string, a new object, a new array, the array ends, the object ends, and so on).
|
||||
This allows programmers to work with much larger files without running out of memory.
|
||||
|
||||
The drawback is complexity: event-based APIs generally have you define a single callback for each type
|
||||
(e.g. `string_field(std::string_view key, std::string_view value)`). Because of this, the programmer suffers
|
||||
from context blindness: when they find a string they have to check where it is before they know what to
|
||||
do with it. Is this string the text of the tweet, the screen name, or something else? Are we even in
|
||||
a tweet right now, or is this from some other place in the document
|
||||
entirely? Though an event-based approach may allow superior performance, it is demanding of the programmer
|
||||
who must efficiently keep track of its current state within the JSON input.
|
||||
|
||||
The following is event-based example of the Twitter problem we have reviewed in the DOM and On Demand
|
||||
examples. To make it short enough to use as an example at all, it has heavily redacted: it only solves
|
||||
a part of the problem (does not get user.screen_name), it has bugs (it does not handle sub-objects
|
||||
in a tweet at all), and it uses a theoretical, simple event-based API that minimizes ceremony.
|
||||
|
||||
```c++
|
||||
struct twitter_callbacks {
|
||||
bool in_statuses;
|
||||
bool in_tweet;
|
||||
std::string_view text;
|
||||
uint64_t retweets;
|
||||
uint64_t favorites;
|
||||
void start_object_field(std::string_view key) {
|
||||
if (key == "statuses") { in_statuses = true; }
|
||||
}
|
||||
void start_object() {
|
||||
if (in_statuses) { in_tweet = true; }
|
||||
}
|
||||
void string_field(std::string_view key, std::string_view value) {
|
||||
if (in_tweet && key == "text") { text = value; }
|
||||
}
|
||||
void number_field(std::string_view key, uint64_t value) {
|
||||
if (in_tweet) {
|
||||
if (key == "retweet_count") { retweets = value; }
|
||||
if (key == "favorite_count") { favorites = value; }
|
||||
}
|
||||
}
|
||||
void end_object() {
|
||||
if (in_tweet) {
|
||||
cout << "[redacted] (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
in_tweet = false;
|
||||
} else if (in_statuses) {
|
||||
in_statuses = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
sax::parser parser;
|
||||
parser.parse(twitter_callbacks());
|
||||
```
|
||||
|
||||
This is a large amount of code, requiring mental gymnastics even to read. An actual implementation is harder to write
|
||||
and to maintain.
|
||||
|
||||
|
||||
Pros of the event-based approach:
|
||||
* Speed and space benefits from low, predictable memory usage.
|
||||
* Parsing can be done more lazily: the API can delegate work to the programmer for better performance.
|
||||
* It is highly flexible: given enough effort, most tasks can be accomplished efficiently.
|
||||
|
||||
Cons of the event-based approach:
|
||||
* Performance drain from context blindness (e.g., switch statements for "where am I in the document")
|
||||
* Difficult to use (high code complexity, high maintenance, difficult to debug)
|
||||
* Lacks the safety of DOM: malformed documents could be ingested.
|
||||
|
||||
Though an event-based approach might have its niche uses, we believe that it is rarely ideally suited. We suspect that it is mostly used when performance and memory is a concern, and no other option (except DOM) is readily available.
|
||||
|
||||
### Schema-Based Parser Generators
|
||||
|
||||
|
||||
In a schema-based model, the programmer provides a description of a data structure, and the parser constructs the data structure in question during parsing. These parsers take a schema--a description of
|
||||
your JSON, with field names, types, everything--and generate classes/structs in your language of
|
||||
choice, as well as a parser to deserialize the JSON into those structs. Some such parsers let you
|
||||
define your own data structures (`struct`) and they let a preprocessor inspects it and generates a custom JSON parser for it.
|
||||
Though not all of these schema-based parser generators generate a parser or even optimize for
|
||||
streaming, but they are *able* to in principle. Unlike the DOM and the event-based models, a schema-based approach assumes
|
||||
that the structure of the document is known at compile-time.
|
||||
|
||||
|
||||
Pros of the schema-based approach:
|
||||
* Ease of Use is on par with DOM
|
||||
* Parsers that generate iterators and lazy values in structs can keep memory pressure down to event-based levels.
|
||||
* Type Blindness can be entirely solved with specific parsers for each type, saving many branches.
|
||||
* Context Blindness can be solved, especially if object fields are required and in order, saving even more branches.
|
||||
* Can be made a safe as DOM: the input can be entirely validated prior to ingestion.
|
||||
|
||||
Cons of the schema-based approach:
|
||||
* It is less flexible than the DOM or event-based approaches, sometimes limited to a deserialization-to-objects scenario.
|
||||
* The structure of the data must be fully known at compile-time.
|
||||
|
||||
|
||||
### Type Blindness and Branch Misprediction
|
||||
|
||||
The DOM and event-based parsing model suffer from **type
|
||||
blindness**: even when the programmer knows exactly what fields and what types are in the JSON document,
|
||||
the parser does not. This means it has to look at each value blind with a big "switch"
|
||||
statement, asking "is this a number? A string? A boolean? An array? An object?"
|
||||
|
||||
In modern processors, this kind of switch statement can make your program run slower
|
||||
than it needs to because of the high cost of branch misprediction. Indeed, modern processor
|
||||
cores rely on speculative execution for speed. They "read ahead" in your program, predicting
|
||||
which instructions to run as soon as the data is available. A single-threaded program can
|
||||
execute 2, 3 or even more instructions per cycle--largely because of speculative execution.
|
||||
|
||||
Unfortunately, when the processor mispredicts the instructions, typically due to a mispredicted
|
||||
branch, all of the work done from the misprediction has be discarded and started anew. The
|
||||
processor may have been executing 3 or 4 instructions per cycle, and consuming the corresponding
|
||||
power, but all of the work may have been wasteful.
|
||||
|
||||
Type blindness means that the processor has to guess, for every JSON value, whether it will be an array,
|
||||
an object, number, string or boolean since these correspond to distinct code paths.
|
||||
Though some JSON files have predictable content, we find in practice that many JSON files
|
||||
stress the branch prediction. Though branch predictors improve with each new generation of processors,
|
||||
the cost of branch mispredictions also tends to increase as pipelines expand, and the processors become
|
||||
able to schedule longer streams of instructions.
|
||||
|
||||
On Demand parsing is tailor-made to solve this problem at the source, parsing values only after the
|
||||
user declares their type by asking for a `double`, an `int`, a `string`, etc. It attempts to do so while
|
||||
preserving most of the flexibility of DOM parsing.
|
||||
|
||||
Algorithm
|
||||
---------
|
||||
|
||||
To help visualize the algorithm, we'll walk through the example C++ given at the top, for this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"statuses": [
|
||||
{ "id": 1, "text": "first!", "user": { "screen_name": "lemire", "name": "Daniel" }, "favorite_count": 100, "retweet_count": 40 },
|
||||
{ "id": 2, "text": "second!", "user": { "screen_name": "jkeiser2", "name": "John" }, "favorite_count": 2, "retweet_count": 3 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Starting the iteration
|
||||
|
||||
1. First, we declare a parser object that keeps internal buffers necessary for parsing. This can be
|
||||
reused to parse multiple JSON files, so you do not pay the high cost of allocating memory every
|
||||
time (and so it can stay in cache!).
|
||||
|
||||
This declaration does not allocate any memory; that will happen in the next step.
|
||||
|
||||
```c++
|
||||
ondemand::parser parser;
|
||||
```
|
||||
|
||||
2. We then start iterating the JSON document by allocating internal parser buffers, preprocessing
|
||||
the JSON, and initializing the iterator.
|
||||
|
||||
```c++
|
||||
auto doc = parser.iterate(json);
|
||||
```
|
||||
|
||||
Since this is the first time this parser has been used, `iterate()` first allocates internal
|
||||
parser buffers if this is the first time through. When reusing an existing parser, allocation
|
||||
only happens if the new document is bigger than internal buffers can handle. The On Demand
|
||||
API only ever allocates memory in the `iterate()` function call.
|
||||
|
||||
The simdjson library then preprocesses the JSON text at high speed, finding all tokens (i.e. the starting
|
||||
position of any JSON value, as well as any important operators like `,`, `:`, `]` or `}`).
|
||||
|
||||
Finally, a `document` iterator is created, initialized at the position of the first value in the
|
||||
`json` text input. The document iterator is bumped forward by array / object iterators and
|
||||
object[] lookup, and must be kept around until iteration is complete.
|
||||
|
||||
This operation can fail as this stage if the document in invalid! The result type is `simdjson_result<document>`.
|
||||
The simdjson library uses `simdjson_result` when a value needs to be returned by a function that can fail given improper inputs.
|
||||
The `simdjson_result` value contain an `error_code` and a `document`, and it was designed to allow you to use either error code
|
||||
checking or C++ exceptions via a direct cast `document(parser.iterate(json))` you can use `get()`
|
||||
to check the error and cast to a value, or cast directly to a value. However, the simdjson library
|
||||
rely on error chaining, so it is possible to delay error checks: we shall shortly explain error
|
||||
chaining more fully.
|
||||
|
||||
NOTE: You should always have such a `document` instance (here `doc`) and it should remain in scope for the duration
|
||||
of your parsing function. E.g., you should not use the returned document as a temporary (e.g., `auto x = parser.iterate(json).get_object();`)
|
||||
followed by other operations as the destruction of the `document` instance makes all of the derived instances
|
||||
ill-defined.
|
||||
|
||||
|
||||
3. We iterate over the "statuses" field using a typical C++ iterator, reading past the initial
|
||||
`{ "statuses": [ {`.
|
||||
|
||||
```c++
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
```
|
||||
This shorthand does much, and it is helpful to see what it expands to.
|
||||
Comments in front of each one explain what's going on:
|
||||
```c++
|
||||
// Validate that the top-level value is an object: check for {
|
||||
ondemand::object top = doc.get_object();
|
||||
|
||||
// Find the field statuses by:
|
||||
// 1. Check whether the object is empty (check for }). (We do not really need to do this unless the key lookup fails!)
|
||||
// 2. Check if we're at the field by looking for the string "statuses" using byte-by-byte comparison.
|
||||
// 3. Validate that there is a `:` after it.
|
||||
auto tweets_field = top["statuses"];
|
||||
|
||||
// Validate that the field value is an array: check for [
|
||||
// Also mark the array as finished if there is a ] next, which would cause the while () statement to exit immediately.
|
||||
ondemand::array tweets = tweets_field.get_array();
|
||||
// These three method calls do nothing substantial (the real checking happens in get_array() and ++)
|
||||
// != checks whether the array is marked as finished (if we have found a ]).
|
||||
ondemand::array_iterator tweets_iter = tweets.begin();
|
||||
while (tweets_iter != tweets.end()) {
|
||||
auto tweet_value = *tweets_iter;
|
||||
|
||||
// Validate that the array element is an object: check for {
|
||||
ondemand::object tweet = tweet_value.get_object();
|
||||
...
|
||||
}
|
||||
```
|
||||
What is not explained in this code expansion is *error chaining*.
|
||||
Generally, you can use `document` methods on a `simdjson_result<...>` value; any errors will
|
||||
just be passed down the chain. Many method calls
|
||||
can be chained in this manner. So `for (object tweet : doc["statuses"])`, which is the equivalent of
|
||||
`object tweet = *(doc.get_object()["statuses"].get_array().begin()).get_object()`, could fail in any of
|
||||
6 method calls, and the error will only be checked at the end,
|
||||
when you attempt to cast the final `simdjson_result<object>` to object. Upon casting, an exception is
|
||||
thrown if there was an error.
|
||||
|
||||
NOTE: while the document can be queried once for a key as if it were an object, it is not an actual object
|
||||
instance. If you need to treat it as an object (e.g., to query more than one keys), you can cast it as
|
||||
such `ondemand::object root_object = doc.get_object();`.
|
||||
|
||||
|
||||
4. We get the `"text"` field as a string.
|
||||
|
||||
```c++
|
||||
std::string_view text = tweet["text"];
|
||||
```
|
||||
|
||||
First, `["text"]` skips the `"id"` field because it does not match: skips the key, `:` and
|
||||
value (`1`). We then check whether there are more fields by looking for either `,`
|
||||
or `}`.
|
||||
|
||||
The second field is matched (`"text"`), so we validate the `:` and move to the actual value.
|
||||
|
||||
NOTE: `["text"]` does a *raw match*, comparing the key directly against the raw JSON. This means
|
||||
that keys with escapes in them may not be matched and the letter case must match exactly.
|
||||
|
||||
To convert to a string, we check for `"` and use simdjson's fast unescaping algorithm to copy
|
||||
`first!` (plus a terminating `\0`) into a buffer managed by the `document`. This buffer stores
|
||||
all strings from a single iteration. The next string will be written after the `\0`.
|
||||
|
||||
A `string_view` is returned which points to that buffer, and contains the length.
|
||||
|
||||
4. We get the `"screen_name"` from the `"user"` object.
|
||||
|
||||
```c++
|
||||
ondemand::object user = tweet["user"];
|
||||
screen_name = user["screen_name"];
|
||||
```
|
||||
|
||||
First, `["user"]` checks whether there are any more object fields by looking for either `,` or
|
||||
`}`. Then it matches `"user"` and validates the `:`.
|
||||
|
||||
`["screen_name"]` then converts to object, checking for `{`, and finds `"screen_name"`.
|
||||
|
||||
To convert the result to usable string (i.e., the screen name `lemire`), the characters are written to the document's
|
||||
string buffer (after possibly escaping them), which now has *two* string_views pointing into it, and looks like `first!\0lemire\0`.
|
||||
|
||||
Finally, the temporary user object is destroyed, causing it to skip the remainder of the object
|
||||
(`}`).
|
||||
|
||||
NOTE: You may only have one active array or object active at any given time. An array or an object becomes
|
||||
active when the `ondemand::object` or `ondemand::array` is created, and it releases its 'focus' when
|
||||
its destructor is called. If you create an array or an object located inside a parent object or array,
|
||||
the child array or object becomes active while the parent becomes temporarily inactive. If you access
|
||||
several sibling objects or arrays, you must ensure that the destructor is called by scoping each access
|
||||
(see Iteration Safety section below for further details).
|
||||
|
||||
5. We get `"retweet_count"` and `"favorite_count"` as unsigned integers.
|
||||
|
||||
```c++
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
```
|
||||
|
||||
6. We loop to the next tweet.
|
||||
|
||||
```c++
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The relevant parts of the loop are:
|
||||
|
||||
```c++
|
||||
while (iter != statuses.end()) {
|
||||
ondemand::object tweet = *iter;
|
||||
...
|
||||
iter++;
|
||||
}
|
||||
```
|
||||
|
||||
First, the `tweet` destructor runs, skipping the remainder of the object which in this case is
|
||||
just `}`.
|
||||
|
||||
Next, `iter++` checks whether there are more values and finds `,`. The loop continues.
|
||||
|
||||
Finally, `ondemand::object tweet = *iter` checks for `{` and returns the object.
|
||||
|
||||
This tweet is processed just like the previous one.
|
||||
|
||||
7. We finish the last tweet.
|
||||
|
||||
At the end of the loop, the `tweet` is first destroyed, skipping the remainder of the tweet
|
||||
object (`}`).
|
||||
|
||||
The `iter++` instruction from `for (ondemand::object tweet : doc["statuses"])` then checks whether there are
|
||||
more values and finds that there are none (`]`). It marks the array iteration as finished and the for
|
||||
loop terminates.
|
||||
|
||||
Then the outer object is destroyed, skipping everything up to the `}`.
|
||||
|
||||
Design Features
|
||||
---------------
|
||||
|
||||
### String Parsing
|
||||
|
||||
When the user requests strings, we unescape them to a single string buffer much like the DOM parser
|
||||
so that users enjoy the same string performance as the core simdjson. We do not write the length to the
|
||||
string buffer, however; that is stored in the `string_view` instance we return to the user.
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
std::set<std::string_view> default_users;
|
||||
ondemand::array tweets = doc["statuses"].get_array();
|
||||
for (auto tweet_value : tweets) {
|
||||
auto tweet = tweet_value.get_object();
|
||||
ondemand::object user = tweet["user"].get_object();
|
||||
std::string_view screen_name = user["screen_name"].get_string();
|
||||
bool default_profile = user["default_profile"].get_bool();
|
||||
if (default_profile) { default_users.insert(screen_name); }
|
||||
}
|
||||
```
|
||||
|
||||
By using `string_view` instances, we avoid the high cost of allocating many small strings (as would be the
|
||||
case with `std::string`) but be mindful that the life cycle of these `string_view` instances is tied to the
|
||||
parser instance. If the parser instance is destroyed or reused for a new JSON document, these strings are no longer valid.
|
||||
|
||||
We iterate through object instances using `field` instances which represent key-value pairs. The value
|
||||
is accessible by the `value()` method whereas the key is accessible by the `key()` method.
|
||||
The keys are treated differently than values are made available as as special type `raw_json_string`
|
||||
which is a lightweight type that is meant to be used on a temporary basis, amost solely for
|
||||
direct raw ASCII comparisons (`field.key() == "mykey"`). If you occasionally need to access and store the
|
||||
unescaped key values, you may use the `unescaped_key()` method. Once you have called `unescaped_key()` method,
|
||||
neither the `key()` nor the `unescaped_key()` methods should be called: the current field instance
|
||||
has no longer a key (that is by design). Like other strings, the resulting `std::string_view` generated
|
||||
from the `unescaped_key()` method has a lifecycle tied to the `parser` instance: once the parser
|
||||
is destroyed or reused with another document, the `std::string_view` instance becomes invalid.
|
||||
|
||||
|
||||
```C++
|
||||
auto doc = parser.iterate(json);
|
||||
for(auto field : doc.get_object()) {
|
||||
std::string_view keyv = field.unescaped_key();
|
||||
}
|
||||
```
|
||||
|
||||
### Iteration Safety
|
||||
|
||||
The On Demand API is powerful. To compensate, we add some safeguards to ensure that it can be used without fear
|
||||
in production systems:
|
||||
|
||||
- If the value fails to be parsed as one type, the program can try to parse it as something else until the program succeeds. Thus
|
||||
the programmer can engineer fall back routines.
|
||||
- If the value succeeds in being parsed or converted to a type, the program cannot try again. An attempt to parse the same node twice will
|
||||
cause the program to abort. We put this safety measure in the API to prevent double iteration of an array which
|
||||
would cause inconsistent iterator state or double-unescaping a string which may cause memory
|
||||
overruns if done.
|
||||
- Guaranteed Iteration: If you discard a value without using it--perhaps you just wanted to know
|
||||
if it was `nullptr` but did not care what the actual value was--it will iterate. The destructor automates
|
||||
the iteration.
|
||||
|
||||
Some care is needed when using the On Demand API in scenarios where you need to access several sibling arrays or objects because
|
||||
only one object or array can be active at any one time. Let us consider the following example:
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::object parent = doc["parent"];
|
||||
// parent owns the focus
|
||||
ondemand::object c1 = parent["child1"];
|
||||
// c1 owns the focus
|
||||
//
|
||||
if(std::string_view(c1["name"]) != "John") { ... }
|
||||
// c2 attempts to grab the focus from parent but fails
|
||||
ondemand::object c2 = parent["child2"];
|
||||
// c2 is now in an unsafe state and the following line would be unsafe
|
||||
// if(std::string_view(c2["name"]) != "Daniel") { return false; }
|
||||
```
|
||||
|
||||
A correct usage is given by the following example:
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::object parent = doc["parent"];
|
||||
// At this point, parent owns the focus
|
||||
{
|
||||
ondemand::object c1 = parent["child1"];
|
||||
// c1 grabbed the focus from parent
|
||||
if(std::string_view(c1["name"]) != "John") { return false; }
|
||||
}
|
||||
// c1 went out of scope, so its destructor was called and the focus
|
||||
// was handed back to parent.
|
||||
{
|
||||
ondemand::object c2 = parent["child2"];
|
||||
// c2 grabbed the focus from parent
|
||||
// the following is safe:
|
||||
if(std::string_view(c2["name"]) != "Daniel") { return false; }
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits of the On Demand Approach
|
||||
|
||||
We expect that the On Demand approach has many of the performance benefits of the schema-based approach, while providing a flexibility that is similar to that of the DOM-based approach.
|
||||
|
||||
* Faster than DOM in some cases. Reduced memory usage.
|
||||
* Straightforward, programmer-friendly interface (arrays and objects).
|
||||
* Highly expressive, beyond deserialization and pointer queries: many tasks can be accomplished with little code.
|
||||
|
||||
### Limitations of the On Demand Approach
|
||||
|
||||
The On Demand approach has some limitations:
|
||||
|
||||
* Because it operates in streaming mode, you only have access to the current element in the JSON document. Furthermore, the document is traversed in order so the code is sensitive to the order of the JSON nodes in the same manner as an event-based approach (e.g., SAX).
|
||||
* The On Demand approach is less safe than DOM: we only validate the components of the JSON document that are used and it is possible to begin ingesting an invalid document only to find out later that the document is invalid. Are you fine ingesting a large JSON document that starts with well formed JSON but ends with invalid JSON content?
|
||||
|
||||
There are currently additional technical limitations which we expect to resolve in future releases of the simdjson library:
|
||||
|
||||
* The simdjson library offers runtime dispatching which allows you to compile one binary and have it run at full speed on different processors, taking advantage of the specific features of the processor. The On Demand API does not have runtime dispatch support at this time. To benefit from the On Demand API, you must compile your code for a specific processor. E.g., if your processor supports AVX2 instructions, you should compile your binary executable with AVX2 instruction support (by using your compiler's commands). If you are sufficiently technically proficient, you can implement runtime dispatching within your application, by compiling your On Demand code for different processors.
|
||||
* There is an initial phase which scans the entire document quickly, irrespective of the size of the document. We plan to break this phase into distinct steps for large files in a future release as we have done with other components of our API (e.g., `parse_many`).
|
||||
* The On Demand API does not support JSON Pointer. This capability is currently limited to our core API.
|
||||
* You should be mindful that the though your software might write the keys in a consistent manner, the [JSON specification](https://www.rfc-editor.org/rfc/rfc8259.txt) states that "JSON parsing libraries have been observed to differ as to whether or not they make the ordering of object members visible". The On Demand API will help the programmer handle unexpected JSON dialects by throwing an exception when the unexpected occurs, but the programmer is responsible for handling such cases: e.g., by rejecting the JSON input that does not follow the expected JSON dialect. We intend to help users who wish to use the On Demand API but require support for order-insensitive semantics, but in our current implementation support for out-of-order keys (if needed) must be provided by the programmer. Currently, one might proceed in the following manner as a fallback measure if keys can appear in any order:
|
||||
```C++
|
||||
for (ondemand::object my_object : doc["mykey"]) {
|
||||
for (auto field : my_object) {
|
||||
if (field.key() == "key_value1") { process1(field.value()); }
|
||||
else if (field.key() == "key_value2") { process2(field.value()); }
|
||||
else if (field.key() == "key_value3") { process3(field.value()); }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Applicability of the On Demand Approach
|
||||
|
||||
At this time we recommend the On Demand API in the following cases:
|
||||
|
||||
1. The 64-bit hardware (CPU) used to run the software is known at compile time. If you need runtime dispatching because you cannot be certain of the hardware used to run your software, you will be better served with the core simdjson API. (This only applies to x64 (AMD/Intel). On 64-bit ARM hardware, runtime dispatching is unnecessary.)
|
||||
2. The used parts of JSON files do not need to be validated and the layout of the nodes follows a strict JSON dialect. If you are receiving JSON from other systems, you might be better served with core simdjson API as it fully validates the JSON inputs and allows you to navigate through the document at will.
|
||||
3. Speed and efficiency are of the utmost importance. Keep in mind that the core simdjson API is highly efficient so adopting the On Demand API is not necessary for high efficiency.
|
||||
4. As a developer, you value a clean, flexible and maintainable API.
|
||||
|
||||
Good applications for the On Demand API might be:
|
||||
|
||||
* You are working from pre-existing large JSON files that have been vetted. You expect them to be well formed according to a known JSON dialect and to have a consistent layout. For example, you might be doing biomedical research or machine learning on top of static data dumps in JSON.
|
||||
* You have a closed system on predetermined hardware. Both the generation and the consumption of JSON data is within your system. Your team controls both the software that produces the JSON and the software the parses it, your team knows and control the hardware. Thus you can fully test your system.
|
||||
* You are working with stable JSON APIs which have a consistent layout and JSON dialect.
|
||||
|
||||
## Checking Your CPU Selection
|
||||
|
||||
Given that the On Demand API does not offer runtime dispatching, your code is compiled against a specific CPU target. You should
|
||||
verify that the code is compiled against the target you expect: `haswell` (AVX2 x64 processors), `westmere` (SSE4 x64 processors), `arm64` (64-bit ARM), `fallback` (others). Under x64 processors, many programmers will want to target `haswell` whereas under ARM,
|
||||
most programmers will want to target `arm64`. The `fallback` is probably only good for testing purposes, not for deployment.
|
||||
|
||||
```C++
|
||||
std::cout << simdjson::builtin_implementation()->name() << std::endl;
|
||||
```
|
||||
|
||||
If you are using CMake for your C++ project, then you can pass compilation flags to your compiler by using
|
||||
the `CMAKE_CXX_FLAGS` variable:
|
||||
|
||||
```
|
||||
cmake -DCMAKE_CXX_FLAGS="-march=haswell" -B build_haswell
|
||||
cmake --build build_haswell
|
||||
```
|
||||
+9
-9
@@ -1,7 +1,7 @@
|
||||
parse_many
|
||||
==========
|
||||
|
||||
An interface providing features to work with files or streams containing multiple JSON documents.
|
||||
An interface providing features to work with files or streams containing multiple small JSON documents.
|
||||
As fast and convenient as possible.
|
||||
|
||||
Contents
|
||||
@@ -14,16 +14,16 @@ Contents
|
||||
- [API](#api)
|
||||
- [Use cases](#use-cases)
|
||||
|
||||
Motivations
|
||||
Motivation
|
||||
-----------
|
||||
|
||||
The main motivation for this piece of software is to achieve maximum speed and offer a
|
||||
better quality of life in parsing files containing multiple JSON documents.
|
||||
better quality of life in parsing files containing multiple small JSON documents.
|
||||
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a very handy
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a handy
|
||||
serialization format. However, when serializing a large sequence of
|
||||
values as an array, or a possibly indeterminate-length or never-
|
||||
ending sequence of values, JSON becomes difficult to work with.
|
||||
ending sequence of values, JSON may be inconvenient.
|
||||
|
||||
Consider a sequence of one million values, each possibly one kilobyte
|
||||
when encoded -- roughly one gigabyte. It is often desirable to process such a dataset incrementally
|
||||
@@ -32,9 +32,9 @@ without having to first read all of it before beginning to produce results.
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Here is a chart comparing the speed of the different alternatives to parse a multiline JSON.
|
||||
The simdjson library provides a threaded and non-threaded parse_many() implementation. As the
|
||||
figure below shows, if you can, use threads, but if you can't, it's still pretty fast!
|
||||
The following is a chart comparing the speed of the different alternatives to parse a multiline JSON.
|
||||
The simdjson library provides a threaded and non-threaded `parse_many()` implementation. As the
|
||||
figure below shows, if you can, use threads, but if you cannot, the unthreaded mode is still fast!
|
||||
[](/doc/Multiline_JSON_Parse_Competition.png)
|
||||
|
||||
How it works
|
||||
@@ -71,7 +71,7 @@ allocate enough memory so that all the documents can fit. This value is what we
|
||||
As of right now, we need to manually specify a value for this batch size, it has to be at least as
|
||||
big as the biggest document in your file, but not too big so that it submerges the cached memory.
|
||||
The bigger the batch size, the fewer we need to make allocations. We found that 1MB is somewhat a
|
||||
sweet spot for now.
|
||||
sweet spot.
|
||||
|
||||
1. When the user calls `parse_many`, we return a `document_stream` which the user can iterate over
|
||||
to receive parsed documents.
|
||||
|
||||
+45
-17
@@ -8,10 +8,11 @@ are still some scenarios where tuning can enhance performance.
|
||||
* [Keeping documents around for longer](#keeping-documents-around-for-longer)
|
||||
* [Server Loops: Long-Running Processes and Memory Capacity](#server-loops-long-running-processes-and-memory-capacity)
|
||||
* [Large files and huge page support](#large-files-and-huge-page-support)
|
||||
* [Computed GOTOs](#computed-gotos)
|
||||
* [Number parsing](#number-parsing)
|
||||
* [Visual Studio](#visual-studio)
|
||||
* [Downclocking](#downclocking)
|
||||
* [Best Use of the DOM API](#best-use-of-the-dom-api)
|
||||
* [Padding and Temporary Copies](#padding-and-temporary-copies)
|
||||
|
||||
|
||||
Reusing the parser for maximum efficiency
|
||||
@@ -122,18 +123,6 @@ use the `-H` flag to omit the memory allocation cost from the benchmark results.
|
||||
./parse -H largefile # without memory allocation
|
||||
```
|
||||
|
||||
Computed GOTOs
|
||||
--------------
|
||||
|
||||
For best performance, we use a technique called "computed goto" when the compiler supports it, it is
|
||||
also sometimes described as "Labels as Values". Though it is not part of the C++ standard, it is
|
||||
supported by many major compilers and it brings measurable performance benefits that are difficult
|
||||
to achieve otherwise. The computed gotos are automatically disabled under Visual Studio.
|
||||
|
||||
If you wish to forcefully disable computed gotos, you can do so by compiling the code with
|
||||
`-DSIMDJSON_NO_COMPUTED_GOTO=1`. It is not recommended to disable computed gotos if your compiler
|
||||
supports it. In fact, you should almost never need to be concerned with computed gotos.
|
||||
|
||||
Number parsing
|
||||
--------------
|
||||
|
||||
@@ -154,19 +143,58 @@ Visual Studio
|
||||
|
||||
On Intel and AMD Windows platforms, Microsoft Visual Studio enables programmers to build either 32-bit (x86) or 64-bit (x64) binaries. We urge you to always use 64-bit mode. Visual Studio 2019 should default on 64-bit builds when you have a 64-bit version of Windows, which we recommend.
|
||||
|
||||
We do not recommend that you compile simdjson with architecture-specific flags such as `arch:AVX2`. The simdjson library automatically selects the best execution kernel at runtime.
|
||||
When compiling with Visual Studio, we recommend the flags `/Ob2 /O2` or better. We do not recommend that you compile simdjson with architecture-specific flags such as `arch:AVX2`. The simdjson library automatically selects the best execution kernel at runtime.
|
||||
|
||||
Recent versions of Microsoft Visual Studio on Windows provides support for the LLVM Clang compiler. You only need to install the "Clang compiler" optional component. You may also get a copy of the 64-bit LLVM CLang compiler for [Windows directly from LLVM](https://releases.llvm.org/download.html). The simdjson library fully supports the LLVM Clang compiler under Windows. In fact, you may get better performance out of simdjson with the LLVM Clang compiler than with the regular Visual Studio compiler.
|
||||
Recent versions of Microsoft Visual Studio on Windows provides support for the LLVM Clang compiler. You only need to install the "Clang compiler" optional component (ClangCL). You may also get a copy of the 64-bit LLVM CLang compiler for [Windows directly from LLVM](https://releases.llvm.org/download.html). The simdjson library fully supports the LLVM Clang compiler under Windows. In fact, you may get better performance out of simdjson with the LLVM Clang compiler than with the regular Visual Studio compiler. Meanwhile the [LLVM CLang compiler is binary compatible with Visual Studio](https://clang.llvm.org/docs/MSVCCompatibility.html) which means that you can combine their binaries (executables and libraries).
|
||||
|
||||
Under Windows, we also support the GNU GCC compiler via MSYS2. The performance of 64-bit MSYS2 under Windows excellent (on par with Linux).
|
||||
|
||||
|
||||
Downclocking
|
||||
--------------
|
||||
|
||||
You should not expect the simdjson library to cause downclocking of your recent Intel CPU cores.
|
||||
|
||||
|
||||
SIMD instructions are the public transportation of computing. Instead of using 4 distinct instructions to add numbers, you can replace them with a single instruction that does the same work. Though the one instruction is slightly more expensive, the energy used per unit of work is much less with SIMD. If you can increase your speed using SIMD instructions (NEON, SSE, AVX), you should expect to reduce your power usage.
|
||||
|
||||
The SIMD instructions that simdjson relies upon (SSE and AVX under x64, NEON under ARM) are routinely part of runtime libraries (e.g., [Go](https://golang.org/src/runtime/memmove_amd64.s), [Glibc](https://github.com/ihtsae/glibc/commit/5f3d0b78e011d2a72f9e88b0e9ef5bc081d18f97), [LLVM](https://github.com/llvm/llvm-project/blob/96f3ea0d21b48ca088355db10d4d1a2e9bc9f884/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.cpp), [Rust](https://github.com/rust-lang/rust/commit/070fad1701fb36b112853b0a6a9787a7bb7ff34c), [Java](http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/c1374141598c/src/cpu/x86/vm/stubGenerator_x86_64.cpp#l1297), [PHP](https://github.com/php/php-src/blob/e5cb53ec68603d4dbdd780fd3ecfca943b4fd383/ext/standard/string.c)). What distinguishes the simdjson library is that it is built from the ground up to benefit from these instructions.
|
||||
|
||||
|
||||
You should not expect the simdjson library to cause *downclocking* of your recent Intel CPU cores.
|
||||
|
||||
On some Intel processors, using SIMD instructions in a sustained manner on the same CPU core may result in a phenomenon called downclocking whereas the processor initially runs these instructions at a slow speed before reducing the frequency of the core for a short time (milliseconds). Intel refers to these states as licenses. On some current Intel processors, it occurs under two scenarios:
|
||||
|
||||
- [Whenever 512-bit AVX-512 instructions are used](https://lemire.me/blog/2018/09/07/avx-512-when-and-how-to-use-these-new-instructions/).
|
||||
- Whenever heavy 256-bit or wider instructions are used. Heavy instructions are those involving floating point operations or integer multiplications (since these execute on the floating point unit).
|
||||
|
||||
The simdjson library does not currently support AVX-512 instructions and it does not make use of heavy 256-bit instructions. Thus there should be no downclocking due to simdjson on recent processors. You may still be worried about which SIMD instruction set is used by simdjson. Thankfully, [you can always determine and change which architecture-specific implementation is used](implementation-selection.md). Thus even if your CPU supports AVX2, you do not need to use AVX2. You are in control.
|
||||
The simdjson library does not currently support AVX-512 instructions and it does not make use of heavy 256-bit instructions. We do use vectorized multiplications, but only using 128-bit registers. Thus there should be no downclocking due to simdjson on recent processors.
|
||||
|
||||
You may still be worried about which SIMD instruction set is used by simdjson. Thankfully, [you can always determine and change which architecture-specific implementation is used](implementation-selection.md) by simdjson. Thus even if your CPU supports AVX2, you do not need to use AVX2. You are in control.
|
||||
|
||||
Best Use of the DOM API
|
||||
-------------------------
|
||||
|
||||
The simdjson API provides access to the JSON DOM (document-object-model) content as a tree of `dom::element` instances, each representing an object, an array or an atomic type (null, true, false, number). These `dom::element` instances are lightweight objects (e.g., spanning 16 bytes) and it might be advantageous to pass them by value, as opposed to passing them by reference or by pointer.
|
||||
|
||||
Padding and Temporary Copies
|
||||
--------------
|
||||
|
||||
The simdjson function `parser.parse` reads data from a padded buffer, containing SIMDJSON_PADDING extra bytes added at the end.
|
||||
If you are passing a `padded_string` to `parser.parse` or loading the JSON directly from
|
||||
disk (`parser.load`), padding is automatically handled.
|
||||
When calling `parser.parse` on a pointer (e.g., `parser.parse(my_char_pointer, my_length_in_bytes)`) a temporary copy is made by default with adequate padding and you, again, do not need to be concerned with padding.
|
||||
|
||||
Some users may not be able use our `padded_string` class or to load the data directly from disk (`parser.load`). They may need to pass data pointers to the library. If these users wish to avoid temporary copies and corresponding temporary memory allocations, they may want to call `parser.parse` with the `realloc_if_needed` parameter set to false (e.g., `parser.parse(my_char_pointer, my_length_in_bytes, false)`). In such cases, they need to ensure that there are at least SIMDJSON_PADDING extra bytes at the end that can be safely accessed and read. They do not need to initialize the padded bytes to any value in particular. The following example is safe:
|
||||
|
||||
|
||||
```C++
|
||||
const char *json = R"({"key":"value"})";
|
||||
const size_t json_len = std::strlen(json);
|
||||
std::unique_ptr<char[]> padded_json_copy{new char[json_len + SIMDJSON_PADDING]};
|
||||
memcpy(padded_json_copy.get(), json, json_len);
|
||||
memset(padded_json_copy.get() + json_len, 0, SIMDJSON_PADDING);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len, false);
|
||||
````
|
||||
|
||||
Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
|
||||
+14
-9
@@ -28,10 +28,6 @@ if(ENABLE_FUZZING)
|
||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
||||
set(SIMDJSON_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||
|
||||
add_custom_target(print_all_fuzz_targets
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ${SOURCES}
|
||||
)
|
||||
|
||||
# Fuzzer build flags and libraries
|
||||
add_library(simdjson-fuzzer INTERFACE)
|
||||
if (SIMDJSON_FUZZ_LINKMAIN)
|
||||
@@ -46,18 +42,27 @@ if(ENABLE_FUZZING)
|
||||
# Define the fuzzers
|
||||
add_custom_target(all_fuzzers)
|
||||
|
||||
set(fuzzernames)
|
||||
function(implement_fuzzer name)
|
||||
add_executable(${name} ${name}.cpp)
|
||||
target_link_libraries(${name} PRIVATE simdjson-fuzzer)
|
||||
add_dependencies(all_fuzzers ${name})
|
||||
add_test(${name} ${name})
|
||||
set_property(TEST ${name} APPEND PROPERTY LABELS fuzz)
|
||||
set(fuzzernames ${fuzzernames} ${name} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
implement_fuzzer(fuzz_parser)
|
||||
implement_fuzzer(fuzz_minify)
|
||||
implement_fuzzer(fuzz_atpointer)
|
||||
implement_fuzzer(fuzz_dump)
|
||||
implement_fuzzer(fuzz_print_json)
|
||||
implement_fuzzer(fuzz_dump_raw_tape)
|
||||
implement_fuzzer(fuzz_element)
|
||||
implement_fuzzer(fuzz_implementations) # parses and serializes again, compares across implementations
|
||||
implement_fuzzer(fuzz_minify) # minify *with* parsing
|
||||
implement_fuzzer(fuzz_minifyimpl) # minify *without* parsing, plus compare implementations
|
||||
implement_fuzzer(fuzz_parser)
|
||||
implement_fuzzer(fuzz_print_json)
|
||||
implement_fuzzer(fuzz_utf8) # utf8 verification, compares across implementations
|
||||
|
||||
# to be able to get a list of all fuzzers from within a script
|
||||
add_custom_target(print_all_fuzzernames
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ${fuzzernames})
|
||||
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#ifndef SIMDJSON_FUZZUTILS_H
|
||||
#define SIMDJSON_FUZZUTILS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <cstring> //memcpy
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
// view data as a char pointer
|
||||
template <typename T> inline const char* as_chars(const T* data) {
|
||||
return static_cast<const char*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Splits the input into strings, using a four byte separator which is human
|
||||
// readable. Makes for nicer debugging of fuzz data.
|
||||
// See https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#magic-separator
|
||||
// for background. Note: don't use memmem, it is not standard C++.
|
||||
inline std::vector<std::string_view> split(const char* Data, size_t Size) {
|
||||
|
||||
std::vector<std::string_view> ret;
|
||||
|
||||
using namespace std::literals;
|
||||
constexpr auto sep="\n~~\n"sv;
|
||||
|
||||
std::string_view all(Data,Size);
|
||||
auto pos=all.find(sep);
|
||||
while(pos!=std::string_view::npos) {
|
||||
ret.push_back(all.substr(0,pos));
|
||||
all=all.substr(pos+sep.size());
|
||||
pos=all.find(sep);
|
||||
}
|
||||
ret.push_back(all);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Generic helper to split fuzz data into usable parts, like ints etc.
|
||||
// Note that it does not throw, instead it sets the data pointer to null
|
||||
// if the input is exhausted.
|
||||
struct FuzzData {
|
||||
// data may not be null, even if size is zero.
|
||||
FuzzData(const uint8_t* data,
|
||||
size_t size) : Data(data),Size(size){}
|
||||
|
||||
///range is inclusive
|
||||
template<int Min, int Max>
|
||||
int getInt() {
|
||||
static_assert (Min<Max,"min must be <max");
|
||||
|
||||
// make this constexpr, can't overflow because that is UB and is forbidden
|
||||
// in constexpr evaluation
|
||||
constexpr int range=(Max-Min)+1;
|
||||
constexpr unsigned int urange=range;
|
||||
|
||||
// don't use std::uniform_int_distribution, we don't want to pay for
|
||||
// over consumption of random data. Accept the slightly non-uniform distribution.
|
||||
if(range<256)
|
||||
return Min+static_cast<int>(get<uint8_t>()%urange);
|
||||
if(range<65536)
|
||||
return Min+static_cast<int>(get<uint16_t>()%urange);
|
||||
|
||||
return Min+static_cast<int>(get<uint32_t>()%urange);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get() {
|
||||
const auto Nbytes=sizeof(T);
|
||||
T ret{};
|
||||
if(Size<Nbytes) {
|
||||
//don't throw, signal with null instead.
|
||||
Data=nullptr;
|
||||
Size=0;
|
||||
return ret;
|
||||
}
|
||||
std::memcpy(&ret,Data,Nbytes);
|
||||
Data+=Nbytes;
|
||||
Size-=Nbytes;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// gets a string view with length in [Min,Max]
|
||||
template<int Min, int Max>
|
||||
std::string_view get_stringview() {
|
||||
static_assert (Min>=0,"Min must be positive");
|
||||
const int len=getInt<Min,Max>();
|
||||
const unsigned int ulen=static_cast<unsigned int>(len);
|
||||
if(ulen<Size) {
|
||||
std::string_view ret(chardata(),ulen);
|
||||
Data+=len;
|
||||
Size-=ulen;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//mark that there is too little data to fulfill the request
|
||||
Data=nullptr;
|
||||
Size=0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// split the remainder of the data into string views,
|
||||
std::vector<std::string_view> splitIntoStrings() {
|
||||
std::vector<std::string_view> ret;
|
||||
if(Size>0) {
|
||||
ret=split(chardata(),Size);
|
||||
// all data consumed.
|
||||
Data+=Size;
|
||||
Size=0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//are we good?
|
||||
explicit operator bool() const { return Data!=nullptr;}
|
||||
|
||||
//we are a URBG
|
||||
// https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator
|
||||
//The type G satisfies UniformRandomBitGenerator if Given
|
||||
// T, the type named by G::result_type
|
||||
// g, a value of type G
|
||||
//
|
||||
// The following expressions must be valid and have their specified effects
|
||||
// Expression Return type Requirements
|
||||
// G::result_type T T is an unsigned integer type
|
||||
using result_type=uint8_t;
|
||||
// G::min() T Returns the smallest value that G's operator() may return. The value is strictly less than G::max(). The function must be constexpr.
|
||||
static constexpr result_type min() {return 0;}
|
||||
// G::max() T Returns the largest value that G's operator() may return. The value is strictly greater than G::min(). The function must be constexpr.
|
||||
static constexpr result_type max() {return 255;}
|
||||
// g() T Returns a value in the closed interval [G::min(), G::max()]. Has amortized constant complexity.
|
||||
result_type operator()() {
|
||||
if(Size==0) {
|
||||
// return something varying, otherwise uniform_int_distribution may get
|
||||
// stuck
|
||||
return failcount++;
|
||||
}
|
||||
const result_type ret=Data[0];
|
||||
Data++;
|
||||
Size--;
|
||||
return ret;
|
||||
}
|
||||
// returns a pointer to data as const char* to avoid those cstyle casts
|
||||
const char* chardata() const {return static_cast<const char*>(static_cast<const void*>(Data));}
|
||||
// members
|
||||
const uint8_t* Data;
|
||||
size_t Size;
|
||||
uint8_t failcount=0;
|
||||
};
|
||||
|
||||
|
||||
#endif // SIMDJSON_FUZZUTILS_H
|
||||
+45
-46
@@ -9,6 +9,43 @@
|
||||
|
||||
The simdjson library tries to follow [fuzzing best practises](https://google.github.io/oss-fuzz/advanced-topics/ideal-integration/#summary).
|
||||
|
||||
There is both "normal" fuzzers just feeding the api with fuzz data, as well as **differential** fuzzers. The differential fuzzers feed the same data to the multiple implementations (haswell, westmere and fallback) and ensure the same results are achieved. This makes sure the user will always get the same answer regardless of which implementation is in use.
|
||||
|
||||
The fuzzers are used in several ways.
|
||||
|
||||
* local fuzzing - for developers testing their changes before pushing and/or during development of the fuzzers themselves.
|
||||
* CI fuzzing - for weeding out those easy to find bugs in pull requests, before they are merged.
|
||||
* oss-fuzz - heavy duty 24/7 fuzzing provided by the google driven oss-fuzz project
|
||||
|
||||
## Local fuzzing
|
||||
Just invoke fuzz/quick_check.sh, it will download the latest corpus from bintray (kept up to date by the CI fuzzers) and run the fuzzers for a short time. In case you want to run the fuzzers for longer, modify the timeout value in the script or invoke the fuzzer directly.
|
||||
|
||||
This requires linux with clang and cmake installed (recent Debian and Ubuntu are known to work fine).
|
||||
|
||||
It is also possible to run the full oss-fuzz setup by following [these oss-fuzz instructions](https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally) with PROJECT_NAME set to simdjson. You will need rights to run docker.
|
||||
|
||||
## Fuzzing as a CI job - x64
|
||||
|
||||
There is a CI job which builds and runs the fuzzers. This is aimed to catch the "easy to fuzz" bugs quickly, without having to wait until pull requests are merged and eventually built and run by oss-fuzz.
|
||||
|
||||
The CI job does the following
|
||||
- builds a fast fuzzer, with full optimization but less checks which is good at rapidly exploring the input space
|
||||
- builds a heavily sanitized fuzzer, which is good at detecting errors
|
||||
- downloads the stored corpus
|
||||
- runs the fast fuzzer build for a while, to grow the corpus
|
||||
- runs the sanitizer fuzzer for a while, using the input found by the fast fuzzer
|
||||
- using a reproduce build (uninstrumented), executes a subset of the test cases in the corpus through valgrind
|
||||
- minimizes the corpus and uploads it (if on the master branch)
|
||||
- stores the corpus and valgrind output as artifacts
|
||||
|
||||
The job is available under the actions tab, here is a [direct link](https://github.com/simdjson/simdjson/actions?query=workflow%3A%22Fuzz+and+run+valgrind%22).
|
||||
|
||||
The corpus will grow over time and easy to find bugs will be detected already during the pull request stage. Also, it will keep the fuzzer builds from bit rot.
|
||||
|
||||
## Fuzzing as a CI job - arm64
|
||||
There is also a job running the fuzzers on arm64 (see .drone.yml) to make sure also the arm specific parts are fuzzed. This does not update the corpus, it just reuses what the x64 job finds.
|
||||
|
||||
## Fuzzing on oss-fuzz
|
||||
The simdjson library is continuously fuzzed on [oss-fuzz](https://github.com/google/oss-fuzz). In case a bug is found, the offending input is minimized and tested for reproducibility. A report with the details is automatically filed, and the contact persons at simdjson are notified via email. An issue is opened at the oss-fuzz bugtracker with restricted view access. When the bug is fixed, the issue is automatically closed.
|
||||
|
||||
Bugs are automatically made visible to the public after a period of time. An example of a bug that was found, fixed and closed can be seen here: [oss-fuzz 18714](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18714).
|
||||
@@ -16,8 +53,7 @@ Bugs are automatically made visible to the public after a period of time. An exa
|
||||
|
||||
## Currently open bugs
|
||||
|
||||
|
||||
You can find the currently opened bugs, if any at [bugs.chromium.org](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2): make sure not to miss the "Open Issues" selector. Bugs that are fixed by follow-up commits are automatically closed.
|
||||
You can find the currently open bugs (if any) at [bugs.chromium.org](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2): make sure not to miss the "Open Issues" selector. Bugs that are fixed by follow-up commits are automatically closed.
|
||||
|
||||
## Integration with oss-fuzz
|
||||
|
||||
@@ -26,24 +62,6 @@ Changes to the integration with oss-fuzz are made by making pull requests agains
|
||||
As little code as possible is kept at oss-fuzz since it is inconvenient to change. The [oss-fuzz build script](https://github.com/google/oss-fuzz/blob/b96dd54183f727a5d90c786e0fb01ec986c74d30/projects/simdjson/build.sh#L18) invokes [the script from the simdjson repo](https://github.com/simdjson/simdjson/blob/master/fuzz/ossfuzz.sh).
|
||||
|
||||
|
||||
|
||||
## Fuzzing as a CI job
|
||||
|
||||
There is a CI job which builds and runs the fuzzers. This is aimed to catch the "easy to fuzz" bugs quickly, without having to wait until pull requests are merged and eventually built and run by oss-fuzz.
|
||||
|
||||
The CI job does the following
|
||||
- builds several variants (with/without avx, with/without sanitizers, a fast fuzzer)
|
||||
- downloads the stored corpus
|
||||
- runs the fastest fuzzer build for 30 seconds, to grow the corpus
|
||||
- runs each build variant for 10 seconds on each fuzzer
|
||||
- using a reproduce build (uninstrumented), executes all the test cases in the corpus through valgrind
|
||||
- minimizes the corpus and upload it (if on the master branch)
|
||||
- store the corpus and valgrind output as artifacts
|
||||
|
||||
The job is available under the actions tab, here is a [direct link](https://github.com/simdjson/simdjson/actions?query=workflow%3A%22Run+fuzzers+on+stored+corpus+and+test+it+with+valgrind%22).
|
||||
|
||||
The corpus will grow over time and easy to find bugs will be detected already during the pull request stage. Also, it will keep the fuzzer builds from bit rot.
|
||||
|
||||
## Corpus
|
||||
|
||||
The simdjson library does not benefit from a corpus as much as other projects, because the library is very fast and explores the input space very well. With that said, it is still beneficial to have one. The CI job stores the corpus on bintray between runs, and is available at [bintray](https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar).
|
||||
@@ -55,32 +73,13 @@ One can also grab the corpus as an artifact from the github actions job. Pick a
|
||||
The code coverage from fuzzing is most easily viewed on the [oss-fuzz status panel](https://oss-fuzz.com/fuzzer-stats). Viewing the coverage does not require login, but the direct link is not easy to find. Substitute the date in the URL to get a more recent link:
|
||||
[https://storage.googleapis.com/oss-fuzz-coverage/simdjson/reports/20200411/linux/src/simdjson/report.html](https://storage.googleapis.com/oss-fuzz-coverage/simdjson/reports/20200411/linux/src/simdjson/report.html)
|
||||
|
||||
|
||||
## Running the fuzzers locally
|
||||
|
||||
This has only been tested on Linux (Debian and Ubuntu are known to work).
|
||||
|
||||
Make sure you have clang and cmake installed.
|
||||
The easiest way to get started is to run the following, standing in the root of the checked out repo:
|
||||
```
|
||||
fuzz/build_like_ossfuzz.sh
|
||||
```
|
||||
|
||||
Then invoke a fuzzer as shown by the following example:
|
||||
```
|
||||
mkdir -p out/parser
|
||||
build/fuzz/fuzz_parser out/parser/
|
||||
```
|
||||
|
||||
You can also use the more extensive fuzzer build script to get a variation of builds by using
|
||||
```
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
```
|
||||
|
||||
It is also possible to run the full oss-fuzz setup by following [these oss-fuzz instructions](https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally) with PROJECT_NAME set to simdjson. You will need rights to run docker.
|
||||
Keeping the coverage up is a never ending job. See [issue 368](https://github.com/simdjson/simdjson/issues/368)
|
||||
|
||||
## Reproducing
|
||||
To reproduce a test case, build the fuzzers, then invoke it with the testcase as a command line argument:
|
||||
```
|
||||
build/fuzz/fuzz_parser my_testcase.json
|
||||
To reproduce a test case, use the local build instruction. Then invoke the fuzzer (the fuzz_parser is shown as an example below) with the testcase as a command line argument:
|
||||
```shell
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
build-sanitizers/fuzz/fuzz_parser my_testcase.json
|
||||
```
|
||||
In case this does not reproduce the bug, you may want to proceed with reproducing using the oss-fuzz tools. See the instructions [here](https://google.github.io/oss-fuzz/advanced-topics/reproducing/).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Builds a corpus from all json files in the source directory.
|
||||
# Builds a corpus from all small json files in the source directory.
|
||||
# The files are renamed to the sha1 of their content, and suffixed
|
||||
# .json. The files are zipped into a flat file named corpus.zip
|
||||
|
||||
@@ -10,9 +10,9 @@ tmp=$(mktemp -d)
|
||||
|
||||
root=$(readlink -f "$(dirname "$0")/..")
|
||||
|
||||
find $root -type f -name "*.json" | while read -r json; do
|
||||
find $root -type f -size -4k -name "*.json" | while read -r json; do
|
||||
cp "$json" "$tmp"/$(sha1sum < "$json" |cut -f1 -d' ').json
|
||||
done
|
||||
|
||||
zip --junk-paths -r corpus.zip "$tmp"
|
||||
zip --quiet --junk-paths -r corpus.zip "$tmp"
|
||||
rm -rf "$tmp"
|
||||
|
||||
+21
-121
@@ -12,163 +12,63 @@ unset CXX CC CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
me=$(basename $0)
|
||||
|
||||
# A reproduce build, without avx but otherwise as plain
|
||||
# as it gets. No sanitizers or optimization.
|
||||
variant=plain-noavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
# common options
|
||||
CLANGVER=-9
|
||||
COMMON="-GNinja -DCMAKE_CXX_COMPILER=clang++$CLANGVER -DCMAKE_C_COMPILER=clang$CLANGVER -DSIMDJSON_BUILD_STATIC=Off -DENABLE_FUZZING=On -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_GIT=Off"
|
||||
|
||||
# A reproduce build as plain as it gets. Everythings tunable is
|
||||
# using the defaults.
|
||||
variant=plain-normal
|
||||
# A replay build, as plain as it gets. For use with valgrind/gdb.
|
||||
variant=replay
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
$COMMON \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# a fuzzer with sanitizers, built with avx disabled.
|
||||
variant=ossfuzz-noavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx "
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# A fuzzer with sanitizers. For improved capability to find bugs.
|
||||
variant=sanitizers
|
||||
|
||||
# a fuzzer with sanitizers, built with avx disabled.
|
||||
variant=ossfuzz-noavx9
|
||||
if which clang++-9 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang-9
|
||||
export CXX="clang++-9"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx "
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
$COMMON \
|
||||
-DCMAKE_CXX_FLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined" \
|
||||
-DCMAKE_C_FLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined" \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
-DSIMDJSON_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-9 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
# a fuzzer with sanitizers, default built
|
||||
variant=ossfuzz-withavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# a fast fuzzer, for fast exploration
|
||||
variant=ossfuzz-fast9
|
||||
if which clang++-9 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
export CC=clang-9
|
||||
export CXX="clang++-9"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
# A fast fuzzer, for fast exploration rather than finding bugs.
|
||||
variant=fast
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE= \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
$COMMON \
|
||||
-DCMAKE_CXX_FLAGS="-fsanitize=fuzzer-no-link" \
|
||||
-DCMAKE_C_FLAGS="-fsanitize=fuzzer-no-link" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
-DSIMDJSON_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
ninja all_fuzzers
|
||||
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-9 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ export OUT=$(pwd)/ossfuzz-out
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -O1"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
$ossfuzz
|
||||
|
||||
echo "look at the results in $OUT"
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "FuzzUtils.h"
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// Split data into two strings, json pointer and the document string.
|
||||
// Might end up with none, either or both being empty, important for
|
||||
// covering edge cases such as
|
||||
// https://github.com/simdjson/simdjson/issues/1142 Inputs missing the
|
||||
// separator line will get an empty json pointer but the all the input put in
|
||||
// the document string. This means test data from other fuzzers that take json
|
||||
// input works for this fuzzer as well.
|
||||
FuzzData fd(Data, Size);
|
||||
auto strings = fd.splitIntoStrings();
|
||||
while (strings.size() < 2) {
|
||||
strings.emplace_back();
|
||||
}
|
||||
assert(strings.size() >= 2);
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
|
||||
// parse without exceptions, for speed
|
||||
auto res = parser.parse(strings[0]);
|
||||
if (res.error())
|
||||
return 0;
|
||||
|
||||
simdjson::dom::element root;
|
||||
if (res.get(root))
|
||||
return 0;
|
||||
|
||||
auto maybe_leaf = root.at_pointer(strings[1]);
|
||||
if (maybe_leaf.error())
|
||||
return 0;
|
||||
|
||||
simdjson::dom::element leaf;
|
||||
if (maybe_leaf.get(leaf))
|
||||
return 0;
|
||||
|
||||
std::string_view sv;
|
||||
if (leaf.get_string().get(sv))
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
+1
-1
@@ -52,7 +52,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(Data, Size).get(elem);
|
||||
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
NulOStream os;
|
||||
//std::ostream& os(std::cout);
|
||||
print_json(os,elem);
|
||||
|
||||
@@ -10,9 +10,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(Data, Size).get(elem);
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
|
||||
NulOStream os;
|
||||
UNUSED auto dumpstatus = elem.dump_raw_tape(os);
|
||||
simdjson_unused auto dumpstatus = elem.dump_raw_tape(os);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "FuzzUtils.h"
|
||||
#include "NullBuffer.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
FuzzData fd(Data, Size);
|
||||
const int action = fd.getInt<0, 31>();
|
||||
|
||||
// there will be some templatized functions like is() which need to be tested
|
||||
// on a type. select one dynamically and create a function that will invoke
|
||||
// with that type
|
||||
const int selecttype=fd.getInt<0,7>();
|
||||
auto invoke_with_type=[selecttype](auto cb) {
|
||||
using constcharstar=const char*;
|
||||
switch(selecttype) {
|
||||
case 0: cb(bool{});break;
|
||||
case 1: cb(double{});break;
|
||||
case 2: cb(uint64_t{});break;
|
||||
case 3: cb(int64_t{});break;
|
||||
case 4: cb(std::string_view{});break;
|
||||
case 5: cb(constcharstar{});break;
|
||||
case 6: cb(simdjson::dom::array{});break;
|
||||
case 7: cb(simdjson::dom::object{});break;
|
||||
}
|
||||
};
|
||||
|
||||
const auto index = fd.get<size_t>();
|
||||
|
||||
// split the remainder of the document into strings
|
||||
auto strings = fd.splitIntoStrings();
|
||||
while (strings.size() < 2) {
|
||||
strings.emplace_back();
|
||||
}
|
||||
const auto str = strings[0];
|
||||
|
||||
// exit if there was too little data
|
||||
if (!fd)
|
||||
return 0;
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
simdjson_unused simdjson::dom::element elem;
|
||||
simdjson_unused auto error = parser.parse(strings[1]).get(elem);
|
||||
|
||||
if (error)
|
||||
return 0;
|
||||
|
||||
#define CASE(num, fun) \
|
||||
case num: { \
|
||||
simdjson_unused auto v = elem.fun(); \
|
||||
break; \
|
||||
}
|
||||
#define CASE2(num, fun) \
|
||||
case num: { \
|
||||
simdjson_unused auto v = elem fun; \
|
||||
break; \
|
||||
}
|
||||
try {
|
||||
|
||||
switch (action) {
|
||||
CASE(0, type);
|
||||
CASE(1, get_array);
|
||||
CASE(2, get_object);
|
||||
CASE(3, get_c_str);
|
||||
CASE(4, get_string_length);
|
||||
CASE(5, get_string);
|
||||
CASE(6, get_int64);
|
||||
CASE(7, get_uint64);
|
||||
CASE(8, get_double);
|
||||
CASE(9, get_bool);
|
||||
CASE(10, is_array);
|
||||
CASE(11, is_object);
|
||||
CASE(12, is_string);
|
||||
CASE(13, is_int64);
|
||||
CASE(14, is_uint64);
|
||||
CASE(15, is_double);
|
||||
CASE(16, is_number);
|
||||
CASE(17, is_bool);
|
||||
CASE(18, is_null);
|
||||
// element.is<>() :
|
||||
case 19: {
|
||||
invoke_with_type([&elem](auto t){ simdjson_unused auto v = elem.is<decltype (t)>(); });
|
||||
} break;
|
||||
|
||||
// CASE(xx,get);
|
||||
case 20: {
|
||||
invoke_with_type([&elem](auto t){ simdjson_unused auto v = elem.get<decltype (t)>(); });
|
||||
} break;
|
||||
|
||||
// CASE(xx,tie);
|
||||
case 21: {
|
||||
invoke_with_type([&elem](auto t){
|
||||
simdjson::error_code ec;
|
||||
simdjson::dom::element{elem}.tie(t,ec); });
|
||||
} break;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
// cast to type
|
||||
case 22: {
|
||||
invoke_with_type([&elem](auto t){
|
||||
using T=decltype(t);
|
||||
simdjson_unused auto v = static_cast<T>(elem); });
|
||||
} break;
|
||||
|
||||
CASE(23, begin);
|
||||
CASE(24, end);
|
||||
#endif
|
||||
CASE2(25, [str]);
|
||||
CASE2(26, .at_pointer(str));
|
||||
// CASE2(xx,at(str)); deprecated
|
||||
CASE2(28, .at(index));
|
||||
CASE2(29, .at_key(str));
|
||||
CASE2(30, .at_key_case_insensitive(str));
|
||||
case 31: { NulOStream os;
|
||||
simdjson_unused auto dumpstatus = elem.dump_raw_tape(os);} ;break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#undef CASE
|
||||
#undef CASE2
|
||||
|
||||
} catch (std::exception &) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200909 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
|
||||
// store each implementation along with it's intermediate results,
|
||||
// which would make things easier to debug in case this fuzzer ever
|
||||
// catches anything
|
||||
struct Impl {
|
||||
explicit Impl(const simdjson::implementation* im=nullptr) : impl(im),parser(),element(),error(),output(){}
|
||||
//silence -Weffc++
|
||||
Impl(const Impl&)=delete;
|
||||
Impl& operator=(const Impl&)=delete;
|
||||
|
||||
const simdjson::implementation* impl;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element element;
|
||||
simdjson::error_code error;
|
||||
std::string output;
|
||||
};
|
||||
|
||||
template<class Iterator>
|
||||
void showErrorAndAbort(Iterator first, Iterator last) {
|
||||
auto it=first;
|
||||
while(it!=last) {
|
||||
std::cerr<<"Implementation: "<<it->impl->name()<<"\tError:"<<it->error<<'\n';
|
||||
it++;
|
||||
}
|
||||
std::cerr.flush();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
template<class Iterator>
|
||||
void showOutputAndAbort(Iterator first, Iterator last) {
|
||||
|
||||
for(auto it=first;it!=last;++it) {
|
||||
std::cerr<<"Implementation: "<<it->impl->name()<<"\tOutput: "<<it->output<<'\n';
|
||||
}
|
||||
|
||||
// show the pairwise results
|
||||
for(auto it1=first; it1!=last; ++it1) {
|
||||
for(auto it2=it1; it2!=last; ++it2) {
|
||||
if(it1!=it2) {
|
||||
const bool matches=(it1->output==it2->output);
|
||||
std::cerr<<"Implementation "<<it1->impl->name()<<" and "<<it2->impl->name()<<(matches?" match.":" do NOT match.")<<'\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cerr.flush();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto supported_implementations=get_runtime_supported_implementations();
|
||||
|
||||
|
||||
// make this dynamic, so it works regardless of how it was compiled
|
||||
// or what hardware it runs on
|
||||
constexpr std::size_t Nimplementations_max=3;
|
||||
const std::size_t Nimplementations = supported_implementations.size();
|
||||
|
||||
if(Nimplementations>Nimplementations_max) {
|
||||
//there is another backend added, please bump Nimplementations_max!
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// get pointers to the backend implementation
|
||||
std::array<Impl,Nimplementations_max> implementations;
|
||||
{
|
||||
std::size_t i=0;
|
||||
for(auto& e: supported_implementations) {
|
||||
implementations[i++].impl=e;
|
||||
}
|
||||
}
|
||||
|
||||
// let each implementation parse and store the result
|
||||
std::size_t nerrors=0;
|
||||
for(std::size_t i=0; i<Nimplementations; ++i) {
|
||||
auto& e=implementations[i];
|
||||
simdjson::active_implementation=e.impl;
|
||||
e.error=e.parser.parse(Data,Size).get(e.element);
|
||||
if(e.error) {
|
||||
++nerrors;
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss<<e.element;
|
||||
e.output=oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
//we should either have no errors, or all should error
|
||||
if(nerrors!=0) {
|
||||
if(nerrors!=Nimplementations) {
|
||||
showErrorAndAbort(implementations.begin(),
|
||||
implementations.begin()+Nimplementations);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//parsing went well for all. compare the output against the first.
|
||||
const std::string& reference=implementations[0].output;
|
||||
for(std::size_t i=1; i<Nimplementations; ++i) {
|
||||
if(implementations[i].output!=reference) {
|
||||
showOutputAndAbort(implementations.begin(),
|
||||
implementations.begin()+Nimplementations);
|
||||
}
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
#include "simdjson.h"
|
||||
#include "FuzzUtils.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* Minifies by first parsing, then minifying.
|
||||
*/
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
auto begin = (const char *)Data;
|
||||
auto begin = as_chars(Data);
|
||||
auto end = begin + Size;
|
||||
|
||||
std::string str(begin, end);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(str).get(elem);
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
|
||||
std::string minified=simdjson::minify(elem);
|
||||
(void)minified;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Minifies using the minify() function directly, without parsing.
|
||||
*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200912 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto implementations=get_runtime_supported_implementations();
|
||||
|
||||
using Buffer=std::vector<uint8_t>;
|
||||
auto minify=[Data,Size](const simdjson::implementation* impl) -> Buffer {
|
||||
Buffer ret(Size);
|
||||
std::size_t retsize=0;
|
||||
auto err=impl->minify(Data,Size,ret.data(),retsize);
|
||||
if(err) {
|
||||
std::string tmp = error_message(err);
|
||||
ret.assign(tmp.begin(),tmp.end());
|
||||
} else {
|
||||
assert(retsize<=Size && "size should not grow by minimize()!");
|
||||
ret.resize(retsize);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto const first = implementations.begin();
|
||||
auto const last = implementations.end();
|
||||
|
||||
const auto reference=minify(*first);
|
||||
|
||||
bool failed=false;
|
||||
for(auto it=first+1;it != last; ++it) {
|
||||
const auto current=minify(*it);
|
||||
if(current!=reference) {
|
||||
failed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if(failed) {
|
||||
std::cerr<<std::boolalpha<<"Mismatch between implementations of minify() found:\n";
|
||||
for(const auto& e:implementations) {
|
||||
const auto current=minify(e);
|
||||
std::string tmp(current.begin(),current.end());
|
||||
std::cerr<<e->name()<<" returns "<<tmp<<std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
UNUSED simdjson::dom::element elem;
|
||||
UNUSED auto error = parser.parse(Data, Size).get(elem);
|
||||
simdjson_unused simdjson::dom::element elem;
|
||||
simdjson_unused auto error = parser.parse(Data, Size).get(elem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200912 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto supported_implementations=get_runtime_supported_implementations();
|
||||
|
||||
|
||||
auto utf8verify=[Data,Size](const simdjson::implementation* impl) -> bool {
|
||||
return impl->validate_utf8((const char*)Data,Size);
|
||||
};
|
||||
|
||||
|
||||
auto first = supported_implementations.begin();
|
||||
auto last = supported_implementations.end();
|
||||
|
||||
|
||||
const bool reference=utf8verify(*first);
|
||||
|
||||
bool failed=false;
|
||||
for(auto it=first+1; it != last; ++it) {
|
||||
const bool current=utf8verify(*it);
|
||||
if(current!=reference) {
|
||||
failed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if(failed) {
|
||||
std::cerr<<std::boolalpha<<"Mismatch between implementations of validate_utf8() found:\n";
|
||||
for(const auto& e: supported_implementations) {
|
||||
if(!e->supported_by_runtime_system()) { continue; }
|
||||
const bool current=utf8verify(e);
|
||||
std::cerr<<e->name()<<" returns "<<current<<std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
+1
-4
@@ -2,11 +2,8 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "FuzzUtils.h"
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size);
|
||||
|
||||
+6
-4
@@ -7,9 +7,7 @@
|
||||
# invoke it from the git root.
|
||||
|
||||
# make sure to exit on problems
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
set -eux
|
||||
|
||||
for prog in zip cmake ninja; do
|
||||
if ! which $prog >/dev/null; then
|
||||
@@ -32,13 +30,17 @@ cmake .. \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=Off \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_GIT=Off \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
cmake --build . --target all_fuzzers
|
||||
|
||||
cp fuzz/fuzz_* $OUT
|
||||
|
||||
# all corpora are equal, they all take json as input
|
||||
# all fuzzers but one (the tiny target for utf8 validation) takes json
|
||||
# as input, therefore use the same corpus of json files for all.
|
||||
for f in $(ls $OUT/fuzz* |grep -v '.zip$') ; do
|
||||
cp ../corpus.zip $OUT/$(basename $f).zip
|
||||
done
|
||||
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script is to make a quick check that the fuzzers work,
|
||||
# good when working locally developing the fuzzers or making
|
||||
# sure code changes still pass the fuzzers.
|
||||
#
|
||||
# It will download the corpus from bintray (kept up to date
|
||||
# by the crontab github actions) unless a local out/ directory
|
||||
# already exists.
|
||||
#
|
||||
# Run it standing in the root of the simdjson repository.
|
||||
#
|
||||
# By Paul Dreik 20201003
|
||||
|
||||
set -eu
|
||||
|
||||
for prog in wget tar cmake; do
|
||||
if ! which $prog >/dev/null; then
|
||||
echo please install $prog
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
#download the corpus if it does not already exist
|
||||
if [ ! -d out ] ; then
|
||||
wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
tar xf corpus.tar && rm corpus.tar
|
||||
fi
|
||||
|
||||
builddir=build-sanitizers
|
||||
|
||||
if [ ! -d $builddir ] ; then
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
else
|
||||
cmake --build $builddir --target all_fuzzers
|
||||
fi
|
||||
|
||||
fuzzernames=$(cmake --build $builddir --target print_all_fuzzernames |tail -n1)
|
||||
|
||||
for fuzzer in $fuzzernames ; do
|
||||
exe=$builddir/fuzz/$fuzzer
|
||||
shortname=$(echo $fuzzer |cut -f2- -d_)
|
||||
echo found fuzzer $shortname with executable $exe
|
||||
mkdir -p out/$shortname
|
||||
others=$(find out -type d -not -name $shortname -not -name out -not -name cmin)
|
||||
$exe -max_total_time=20 -max_len=4000 out/$shortname $others
|
||||
echo "*************************************************************************"
|
||||
done
|
||||
echo "all is good, no errors found in any of these fuzzers: $fuzzernames"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief get_runtime_supported_implementations
|
||||
* Returns a vector of implementations, which both
|
||||
* have been compiled *and* are dynamically checked to
|
||||
* be supported at runtime.
|
||||
*
|
||||
* Aborts if no implementations are available (should not happen, fallback
|
||||
* should always be there for us!)
|
||||
* @return
|
||||
*/
|
||||
std::vector<const simdjson::implementation*>
|
||||
get_runtime_supported_implementations() {
|
||||
std::vector<const simdjson::implementation*> ret;
|
||||
for(auto& e: simdjson::available_implementations) {
|
||||
if(e->supported_by_runtime_system()) {
|
||||
ret.emplace_back(e);
|
||||
}
|
||||
}
|
||||
if(ret.empty()) {
|
||||
// No implementations available, not even fallback, weird.
|
||||
std::abort();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
+56
-10
@@ -5,6 +5,33 @@
|
||||
* @mainpage
|
||||
*
|
||||
* Check the [README.md](https://github.com/lemire/simdjson/blob/master/README.md#simdjson--parsing-gigabytes-of-json-per-second).
|
||||
*
|
||||
* Sample code. See https://github.com/simdjson/simdjson/blob/master/doc/basics.md for more examples.
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
int main(void) {
|
||||
// load from `twitter.json` file:
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets = parser.load("twitter.json");
|
||||
std::cout << tweets["search_metadata"]["count"] << " results." << std::endl;
|
||||
|
||||
// Parse and iterate through an array of objects
|
||||
auto abstract_json = R"( [
|
||||
{ "12345" : {"a":12.34, "b":56.78, "c": 9998877} },
|
||||
{ "12545" : {"a":11.44, "b":12.78, "c": 11111111} }
|
||||
] )"_padded;
|
||||
|
||||
for (simdjson::dom::object obj : parser.parse(abstract_json)) {
|
||||
for(const auto& key_value : obj) {
|
||||
cout << "key: " << key_value.key << " : ";
|
||||
simdjson::dom::object innerobj = key_value.value;
|
||||
cout << "a: " << double(innerobj["a"]) << ", ";
|
||||
cout << "b: " << double(innerobj["b"]) << ", ";
|
||||
cout << "c: " << int64_t(innerobj["c"]) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#include "simdjson/compiler_check.h"
|
||||
@@ -16,6 +43,7 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
// Public API
|
||||
#include "simdjson/simdjson_version.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/dom/array.h"
|
||||
@@ -24,6 +52,7 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#include "simdjson/dom/element.h"
|
||||
#include "simdjson/dom/object.h"
|
||||
#include "simdjson/dom/parser.h"
|
||||
#include "simdjson/dom/serialization.h"
|
||||
|
||||
// Deprecated API
|
||||
#include "simdjson/dom/jsonparser.h"
|
||||
@@ -31,16 +60,33 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#include "simdjson/dom/parsedjson_iterator.h"
|
||||
|
||||
// Inline functions
|
||||
#include "simdjson/inline/array.h"
|
||||
#include "simdjson/inline/document_stream.h"
|
||||
#include "simdjson/inline/document.h"
|
||||
#include "simdjson/inline/element.h"
|
||||
#include "simdjson/inline/error.h"
|
||||
#include "simdjson/inline/object.h"
|
||||
#include "simdjson/inline/padded_string.h"
|
||||
#include "simdjson/inline/parsedjson_iterator.h"
|
||||
#include "simdjson/inline/parser.h"
|
||||
#include "simdjson/inline/tape_ref.h"
|
||||
#include "simdjson/dom/array-inl.h"
|
||||
#include "simdjson/dom/document_stream-inl.h"
|
||||
#include "simdjson/dom/document-inl.h"
|
||||
#include "simdjson/dom/element-inl.h"
|
||||
#include "simdjson/error-inl.h"
|
||||
#include "simdjson/dom/object-inl.h"
|
||||
#include "simdjson/padded_string-inl.h"
|
||||
#include "simdjson/dom/parsedjson_iterator-inl.h"
|
||||
#include "simdjson/dom/parser-inl.h"
|
||||
#include "simdjson/internal/tape_ref-inl.h"
|
||||
#include "simdjson/dom/serialization-inl.h"
|
||||
|
||||
// Implementation-internal files (must be included before the implementations themselves, to keep
|
||||
// amalgamation working--otherwise, the first time a file is included, it might be put inside the
|
||||
// #ifdef SIMDJSON_IMPLEMENTATION_ARM64/FALLBACK/etc., which means the other implementations can't
|
||||
// compile unless that implementation is turned on).
|
||||
#include "simdjson/internal/isadetection.h"
|
||||
#include "simdjson/internal/jsoncharutils_tables.h"
|
||||
#include "simdjson/internal/numberparsing_tables.h"
|
||||
#include "simdjson/internal/simdprune_tables.h"
|
||||
|
||||
// Implementations
|
||||
#include "simdjson/arm64.h"
|
||||
#include "simdjson/haswell.h"
|
||||
#include "simdjson/westmere.h"
|
||||
#include "simdjson/fallback.h"
|
||||
#include "simdjson/builtin.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef SIMDJSON_ARM64_H
|
||||
#define SIMDJSON_ARM64_H
|
||||
|
||||
#ifdef SIMDJSON_FALLBACK_H
|
||||
#error "arm64.h must be included before fallback.h"
|
||||
#endif
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#include "simdjson/internal/isadetection.h"
|
||||
#include "simdjson/internal/jsoncharutils_tables.h"
|
||||
#include "simdjson/internal/numberparsing_tables.h"
|
||||
#include "simdjson/internal/simdprune_tables.h"
|
||||
|
||||
#if SIMDJSON_IMPLEMENTATION_ARM64
|
||||
|
||||
namespace simdjson {
|
||||
/**
|
||||
* Implementation for NEON (ARMv8).
|
||||
*/
|
||||
namespace arm64 {
|
||||
} // namespace arm64
|
||||
} // namespace simdjson
|
||||
|
||||
#include "simdjson/arm64/implementation.h"
|
||||
|
||||
#include "simdjson/arm64/begin.h"
|
||||
|
||||
// Declarations
|
||||
#include "simdjson/generic/dom_parser_implementation.h"
|
||||
#include "simdjson/arm64/intrinsics.h"
|
||||
#include "simdjson/arm64/bitmanipulation.h"
|
||||
#include "simdjson/arm64/bitmask.h"
|
||||
#include "simdjson/arm64/simd.h"
|
||||
#include "simdjson/generic/jsoncharutils.h"
|
||||
#include "simdjson/generic/atomparsing.h"
|
||||
#include "simdjson/arm64/stringparsing.h"
|
||||
#include "simdjson/arm64/numberparsing.h"
|
||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||
#include "simdjson/generic/ondemand.h"
|
||||
|
||||
// Inline definitions
|
||||
#include "simdjson/generic/implementation_simdjson_result_base-inl.h"
|
||||
#include "simdjson/generic/ondemand-inl.h"
|
||||
#include "simdjson/arm64/end.h"
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_ARM64
|
||||
|
||||
#endif // SIMDJSON_ARM64_H
|
||||
@@ -0,0 +1 @@
|
||||
#define SIMDJSON_IMPLEMENTATION arm64
|
||||
@@ -1,17 +1,15 @@
|
||||
#ifndef SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
#define SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "arm64/intrinsics.h"
|
||||
|
||||
namespace simdjson {
|
||||
namespace arm64 {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
|
||||
// We sometimes call trailing_zero on inputs that are zero,
|
||||
// but the algorithms do not end up using the returned value.
|
||||
// Sadly, sanitizers are not smart enough to figure it out.
|
||||
NO_SANITIZE_UNDEFINED
|
||||
really_inline int trailing_zeroes(uint64_t input_num) {
|
||||
simdjson_really_inline int trailing_zeroes(uint64_t input_num) {
|
||||
#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
unsigned long ret;
|
||||
// Search the mask data from least significant bit (LSB)
|
||||
@@ -24,12 +22,12 @@ really_inline int trailing_zeroes(uint64_t input_num) {
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
really_inline uint64_t clear_lowest_bit(uint64_t input_num) {
|
||||
simdjson_really_inline uint64_t clear_lowest_bit(uint64_t input_num) {
|
||||
return input_num & (input_num-1);
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
really_inline int leading_zeroes(uint64_t input_num) {
|
||||
simdjson_really_inline int leading_zeroes(uint64_t input_num) {
|
||||
#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
unsigned long leading_zero = 0;
|
||||
// Search the mask data from most significant bit (MSB)
|
||||
@@ -44,11 +42,11 @@ really_inline int leading_zeroes(uint64_t input_num) {
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
really_inline int count_ones(uint64_t input_num) {
|
||||
simdjson_really_inline int count_ones(uint64_t input_num) {
|
||||
return vaddv_u8(vcnt_u8(vcreate_u8(input_num)));
|
||||
}
|
||||
|
||||
really_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) {
|
||||
simdjson_really_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) {
|
||||
#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
*result = value1 + value2;
|
||||
return *result < value1;
|
||||
@@ -58,16 +56,8 @@ really_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *resu
|
||||
#endif
|
||||
}
|
||||
|
||||
really_inline bool mul_overflow(uint64_t value1, uint64_t value2, uint64_t *result) {
|
||||
#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
*result = value1 * value2;
|
||||
return !!__umulh(value1, value2);
|
||||
#else
|
||||
return __builtin_umulll_overflow(value1, value2, (unsigned long long *)result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user