mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
397 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cb79e6977 | |||
| c5e21a2469 | |||
| 13aee51011 | |||
| 53fca1b5e6 | |||
| 7dad9fca0f | |||
| b249d7c76c | |||
| 4060f64232 | |||
| 5b2f7d3374 | |||
| 3116e29d16 | |||
| d2406f2a22 | |||
| a648318900 | |||
| 849f54e4f8 | |||
| 61009fea3f | |||
| 21dce6cca9 | |||
| 56bc8a778d | |||
| d93af1161d | |||
| 434776db1a | |||
| 6167e9cefc | |||
| dc918d764e | |||
| 9906887151 | |||
| b5a1017afa | |||
| 7badc230a4 | |||
| ab78482ee7 | |||
| 6369cf4dd9 | |||
| 7656bd50ee | |||
| 2115596ed3 | |||
| 0e3453f7c2 | |||
| 7ed65e42d7 | |||
| 835b640ebd | |||
| 6ee3318531 | |||
| ae24fe3850 | |||
| d4f4608dab | |||
| ea8a5020e2 | |||
| 622d9c9480 | |||
| bb0c9547be | |||
| 62da98aef6 | |||
| 03746b966b | |||
| de001da35b | |||
| 0da460ca13 | |||
| 450e19858b | |||
| 836e1fc330 | |||
| e836c28008 | |||
| fff4f921e4 | |||
| 9f265711a8 | |||
| 32afcd2e48 | |||
| 748df8d109 | |||
| 5ad405006c | |||
| 47859f3560 | |||
| 56d1b9a226 | |||
| 1b6a31b277 | |||
| 90a7503181 | |||
| e3efbcddc1 | |||
| c14b2fb36c | |||
| f0f111b387 | |||
| c95e45d283 | |||
| 52b8d50178 | |||
| f581627d10 | |||
| 6a8ec95a46 | |||
| b6c6680add | |||
| 5fb149f833 | |||
| abb0bf9247 | |||
| 8f3ddd3a73 | |||
| 8f34e6714a | |||
| 56841bcede | |||
| 006cc2ed60 | |||
| c79cf8d6bf | |||
| cf704cf81b | |||
| 265d474ec8 | |||
| 2e420169c3 | |||
| 5aec2671ea | |||
| ab0e22a316 | |||
| 06587824be | |||
| a0bce440a6 | |||
| 26b15251e2 | |||
| 1cf4fe405d | |||
| 6b8f5d3354 | |||
| d5af359365 | |||
| 8769e42a56 | |||
| 7cde65aa6e | |||
| e1b1500e3b | |||
| 2943a1c27f | |||
| b28cafc1d1 | |||
| 65f999b7b7 | |||
| 06d6636b97 | |||
| eb5a1ea113 | |||
| d84e70b6e5 | |||
| 7ff034504d | |||
| dedf0c6a8d | |||
| 5514ae3879 | |||
| 5af0dfb031 | |||
| 0bcda5e384 | |||
| d1eef242c6 | |||
| ceee00b276 | |||
| 6c2ab064cb | |||
| 772a5dc3d5 | |||
| 3e39a998ce | |||
| 2867dc50fa | |||
| c3c43769ae | |||
| 36ceaa4452 | |||
| c34b1a1b2a | |||
| e4df0ca368 | |||
| 8a91cecf41 | |||
| 04e8710cf5 | |||
| e8b3f9eaad | |||
| 23d6ec6cff | |||
| 0a6edae2dd | |||
| 0e45663ce8 | |||
| 80a9f4defd | |||
| 8f1e4018c0 | |||
| afe36d0b36 | |||
| 5d1e3efce8 | |||
| 293ec7aec5 | |||
| 6cefeb338b | |||
| f1744f5495 | |||
| 5750a173ff | |||
| e3a4fd9f93 | |||
| 5a071c1907 | |||
| 7cf3a7511b | |||
| af203aaf86 | |||
| 8e2c06cb0e | |||
| 1a5d8f1957 | |||
| 03c828c7ad | |||
| 758dc511fb | |||
| 0164723a8e | |||
| 032936a7b5 | |||
| da3e064fc7 | |||
| 317fc6ba0e | |||
| 1aaad223c0 | |||
| 81c86d7090 | |||
| d9a9fd387d | |||
| 0c190b165c | |||
| acc7bd79b0 | |||
| e4e89fe27a | |||
| 24551db0c8 | |||
| 12e6611ba4 | |||
| fb15886a1c | |||
| 06c1dc3a29 | |||
| 89d9de2353 | |||
| 12c85d3e23 | |||
| a5afec1f94 | |||
| ac0899c043 | |||
| 40c6213d7e | |||
| d140bc23f5 | |||
| 00f0859e1f | |||
| a0b2fab6fa | |||
| f669aafcf2 | |||
| 66a2807210 | |||
| c3009eb324 | |||
| 3bdfe167de | |||
| 31e8a12e88 | |||
| ebbfdcd35a | |||
| 9a7c8fb5be | |||
| cfef4ff2ad | |||
| b2220d6157 | |||
| 5ff941ae3d | |||
| 1c922d3b73 | |||
| b23dd28a06 | |||
| a55f41a24a | |||
| 5525c6f729 | |||
| eb147d9868 | |||
| f58a5d534e | |||
| b3ea8c406e | |||
| 99667f7c55 | |||
| 0b21203141 | |||
| 9a9ca974c2 | |||
| 140e4dde3d | |||
| 68670301e3 | |||
| a98d841983 | |||
| 332b764cc4 | |||
| 560f0742cc | |||
| 910f272467 | |||
| b6423a3426 | |||
| 4d2736ffa9 | |||
| 4dc2adf7f8 | |||
| da34f9a253 | |||
| 1f76737510 | |||
| bc8bc7d1a8 | |||
| 083569fca8 | |||
| a8903d9765 | |||
| 8e7d1a5f09 | |||
| c879b56f41 | |||
| 4518f1fba1 | |||
| 5c59b3a775 | |||
| 299dfcdd3c | |||
| 76c706644a | |||
| 0c8f2b9d85 | |||
| c924aaede9 | |||
| 28710f8ad5 | |||
| e695a19d11 | |||
| 6978a0b8d4 | |||
| 6784530b8b | |||
| 03d5fc33ca | |||
| ba14232628 | |||
| 1cdf5581f3 | |||
| 3488c49d0a | |||
| adaef43bc6 | |||
| ce8fe1bdf6 | |||
| fa04595d90 | |||
| aea79912ec | |||
| 80b4dd2e8a | |||
| 48530b89ea | |||
| a4025788ae | |||
| c6f2f60b03 | |||
| 33060738b6 | |||
| ab6d4871d8 | |||
| f87e64f988 | |||
| 27861f6358 | |||
| f611b65bc0 | |||
| 2dc61fbdc4 | |||
| 22be05400d | |||
| a9f501fe7d | |||
| e9077370ec | |||
| a804351a76 | |||
| f97b655f02 | |||
| 1498b78342 | |||
| 85e84fc1fa | |||
| 833e5d8bf1 | |||
| 773883c486 | |||
| 6e5e0278c2 | |||
| 951c4bedf8 | |||
| 9842e1f9d0 | |||
| 4c0c1c9830 | |||
| 6706d6053e | |||
| 0a874a5063 | |||
| 2caa6e3370 | |||
| a9e990251d | |||
| 7bde23590a | |||
| 5042dd52ce | |||
| 3b9e6bff3c | |||
| a2d05b21ff | |||
| f4f5f670a2 | |||
| 165e23773f | |||
| 8dbb598057 | |||
| ba9dc12164 | |||
| 399d08c86c | |||
| 6f799435b6 | |||
| 3d14154a29 | |||
| 7e331957c4 | |||
| 4da06830f1 | |||
| 27293cc1c1 | |||
| 2caac2b218 | |||
| 0dc80ccf21 | |||
| f2b48ede4c | |||
| 6cefdc2f5c | |||
| 29e78413fe | |||
| 8192e63a4b | |||
| b2ebdb0d07 | |||
| 9c3828fefe | |||
| 60916318f7 | |||
| d7c83397e4 | |||
| 1d621bba37 | |||
| e2f349e7bd | |||
| 102262c7ab | |||
| f02babe427 | |||
| fc6133b58f | |||
| 2bd65fa444 | |||
| d33208c7db | |||
| c9cd8e6211 | |||
| 74a96878bc | |||
| 7e28708e1d | |||
| 1211c01ca1 | |||
| f32b97733b | |||
| 4e1c90f76f | |||
| e63f258470 | |||
| ede9f9117f | |||
| 7c560fa137 | |||
| 178a0842fe | |||
| f345490cae | |||
| db141e82c9 | |||
| f163155929 | |||
| 9b6377fd80 | |||
| 7356b4532f | |||
| 7d7bec856d | |||
| c5504ef50b | |||
| 3658ff650d | |||
| 6d14afd80e | |||
| 6e5178efc4 | |||
| 29fc51522a | |||
| 6cd8fb7982 | |||
| ce824f8653 | |||
| 708f4a094d | |||
| 2704b73399 | |||
| 783ccd6c21 | |||
| 3fd1c3b64a | |||
| 58d249ca16 | |||
| bdc2b07339 | |||
| 6888ca709d | |||
| 8ae818e17c | |||
| c4f1baad31 | |||
| 3439ce19c9 | |||
| b7c18df540 | |||
| 74799134b1 | |||
| 3828e1e538 | |||
| d89046d515 | |||
| 4bc128f07e | |||
| e383b7a6ab | |||
| c89d6bf68b | |||
| 52640518d3 | |||
| cf493254b7 | |||
| c97eb41dc6 | |||
| b1224a77db | |||
| 17b777f751 | |||
| 9442c9e1f4 | |||
| 15740500af | |||
| 3484dda45e | |||
| 1ece6c0e2f | |||
| 59cad23aeb | |||
| da1c35d04b | |||
| c469aed047 | |||
| a065805b0f | |||
| f41a18b57d | |||
| 1257432df3 | |||
| d0c0e31220 | |||
| 9b7832c39a | |||
| e3e29b720d | |||
| 64872bddf4 | |||
| 81f2249575 | |||
| 9bbd6bd874 | |||
| 13e477ebfe | |||
| c284b54fc2 | |||
| 69caa477fb | |||
| 81f9aac13f | |||
| b2eff3c90c | |||
| aa45fe7359 | |||
| 253af0766c | |||
| cf9dbe583d | |||
| de8df0a05f | |||
| 53b6deaeae | |||
| 462858efa3 | |||
| f7e893667d | |||
| 5765c81f66 | |||
| 92334a8e28 | |||
| c4218c8e40 | |||
| c1f27fb848 | |||
| 6d0fd5bb93 | |||
| bd15d3ae24 | |||
| 7f249cd179 | |||
| aef3f4be99 | |||
| bf8083888d | |||
| f4fa5b7340 | |||
| 169568ca47 | |||
| 9cc4ddfc88 | |||
| 441963c84c | |||
| cf4ae61ac6 | |||
| da0f1cacea | |||
| 5e5592178d | |||
| b01222518d | |||
| 2060cf8a70 | |||
| a4bd87119b | |||
| 9f26355fe0 | |||
| f667d4965d | |||
| 585f84a734 | |||
| a1bff85263 | |||
| fb920bba62 | |||
| 58697f6f3b | |||
| 18c5b8d68a | |||
| 08cf140811 | |||
| 94673bcdf2 | |||
| aa15917c9d | |||
| 85fb37b6ea | |||
| ae3ae9a474 | |||
| e9be643db5 | |||
| b49eefbee6 | |||
| 640283fec6 | |||
| c8d50a6060 | |||
| 6a2728e730 | |||
| 1740d93420 | |||
| 0042d9b406 | |||
| 3bfa6097d5 | |||
| 237b8865f5 | |||
| 8f01cece3a | |||
| 2ca574d9e6 | |||
| 7f27e1e0e1 | |||
| 875e2f9d0d | |||
| 5c538dd9d6 | |||
| 3fb82502f7 | |||
| 7be2998cae | |||
| 9dfab9d9a4 | |||
| b63ae1f190 | |||
| 4fc796b387 | |||
| c3ff41dd84 | |||
| 0b927f059c | |||
| f3c3afd4cd | |||
| 1e26859bb7 | |||
| b1beacd1f3 | |||
| d9a0e2b8f4 | |||
| 4c7e95aac3 | |||
| c3310c6e8f | |||
| 2a24567370 | |||
| bd9628df93 | |||
| 99a153d9e8 | |||
| 04da71c3a1 | |||
| 0c86dd9d8d | |||
| 349068dcda | |||
| 144b10b35d | |||
| 44722bddcf | |||
| 2a240e3fe2 |
+12
-5
@@ -11,12 +11,19 @@ platform:
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- AVXFLAG: "OFF"
|
||||
- AVXFLAG: "ON"
|
||||
|
||||
- SIMDJSON_BUILD_STATIC: "OFF"
|
||||
THREADS: "ON"
|
||||
- SIMDJSON_BUILD_STATIC: "OFF"
|
||||
THREADS: "OFF"
|
||||
- SIMDJSON_BUILD_STATIC: "ON"
|
||||
THREADS: "ON"
|
||||
# - SIMDJSON_BUILD_STATIC: "ON"
|
||||
# THREADS: "OFF"
|
||||
|
||||
build_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- ps: cmake -DSIMDJSON_DISABLE_AVX="$env:AVXFLAG" -DCMAKE_GENERATOR_PLATFORM=x64 ..
|
||||
- ps: cmake -DSIMDJSON_BUILD_STATIC="$env:SIMDJSON_BUILD_STATIC" -DSIMDJSON_ENABLE_THREADS="$env:THREADS" -DCMAKE_BUILD_TYPE=Release -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_GOOGLE_BENCHMARKS=OFF ..
|
||||
- cmake --build .
|
||||
- ctest --verbose
|
||||
- ctest --verbose --output-on-failure
|
||||
|
||||
|
||||
+215
-151
@@ -1,162 +1,226 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
# Reusable image / compiler definitions
|
||||
executors:
|
||||
gcc7:
|
||||
docker:
|
||||
- image: gcc:7
|
||||
environment:
|
||||
CXX: g++
|
||||
gcc8:
|
||||
docker:
|
||||
- image: gcc:8
|
||||
environment:
|
||||
CXX: g++
|
||||
gcc9:
|
||||
docker:
|
||||
- image: gcc:9
|
||||
environment:
|
||||
CXX: g++
|
||||
clang6:
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
|
||||
# Reusable test commands (and initializer for clang 6)
|
||||
commands:
|
||||
init_clang6:
|
||||
steps:
|
||||
- run: apt-get update -qq
|
||||
- run: apt-get install -y clang build-essential git
|
||||
make_test:
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make amalgamate
|
||||
- run: ARCHFLAGS=-march=haswell make amalgamate # some users do this: https://github.com/lemire/simdjson/issues/444
|
||||
- run: make test
|
||||
- run: make checkperf
|
||||
- run: make clean
|
||||
- run: ARCHFLAGS=-march=haswell make test # this breaks runtime dispatch, but see https://github.com/lemire/simdjson/issues/444... this is a code robustness test
|
||||
- run: make clean
|
||||
- run: EXTRAFLAGS=-DSIMDJSON_NO_COMPUTED_GOTO=true make test # this should run tests with computed gotos disabled
|
||||
make_test_strict: # this version fails when a warning is detected.
|
||||
steps:
|
||||
- checkout
|
||||
- run: EXTRAFLAGS=-Werror make
|
||||
- run: EXTRAFLAGS=-Werror make amalgamate
|
||||
- run: ARCHFLAGS=-march=haswell make amalgamate # some users do this: https://github.com/lemire/simdjson/issues/444
|
||||
- run: EXTRAFLAGS=-Werror make quicktests
|
||||
- run: make clean
|
||||
|
||||
cmake_simple_test: # this version just builds and test
|
||||
steps:
|
||||
- run: apt-get update -qq
|
||||
- run: apt-get install -y cmake
|
||||
- checkout
|
||||
- run: cmake $CMAKE_TEST_FLAGS
|
||||
- run: make all
|
||||
- run: make test
|
||||
|
||||
cmake_test: # this version builds, install, test and then verifyi from the installation
|
||||
steps:
|
||||
- run: apt-get update -qq
|
||||
- run: apt-get install -y cmake
|
||||
- checkout
|
||||
- run: cmake $CMAKE_TEST_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination
|
||||
- run: make all install
|
||||
- run: make test
|
||||
- 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++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json # we not only want cmake to build and run tests, but we want also a succesful installation from which we can build, link and run programs
|
||||
|
||||
jobs:
|
||||
"gcc":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: g++-7
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
g++-7
|
||||
git
|
||||
gcc-avx-unthreaded:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2 *without* threads
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_ENABLE_THREADS=OFF }
|
||||
steps: [ cmake_test ]
|
||||
gcc-avx-threaded:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2 with threads
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_ENABLE_THREADS=ON }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
- run:
|
||||
name: Building (gcc)
|
||||
command: make
|
||||
clang-avx-unthreaded:
|
||||
description: Build, run tests and check performance on Clang 6 and AVX 2 *without* threads
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_ENABLE_THREADS=OFF }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-avx-threaded:
|
||||
description: Build, run tests and check performance on Clang 6 and AVX 2 with threads
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_ENABLE_THREADS=ON }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc)
|
||||
command: make quiettest amalgamate
|
||||
gcc9-avx:
|
||||
description: Build, run tests and check performance on GCC 9 and AVX 2
|
||||
executor: gcc9
|
||||
steps: [ make_test_strict ]
|
||||
gcc8-avx:
|
||||
description: Build, run tests and check performance on GCC 8 and AVX 2
|
||||
executor: gcc9
|
||||
steps: [ make_test_strict ]
|
||||
gcc-avx:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2
|
||||
executor: gcc7
|
||||
steps: [ make_test ]
|
||||
gcc-avx-dynamic:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2 with a cmake dynamic build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ cmake_test ]
|
||||
gcc-avx-static:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2 with a cmake static build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test ]
|
||||
gcc-avx-google-benchmarks:
|
||||
description: Build, run tests and check performance on GCC 7 with google benchmarks enabled
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON }
|
||||
steps: [ cmake_test ]
|
||||
gcc-avx-sanitize:
|
||||
description: Build, run tests and check performance on GCC 7 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON }
|
||||
steps: [ cmake_simple_test ]
|
||||
|
||||
- run:
|
||||
name: Building (gcc, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
gcc-sse:
|
||||
description: Build, run tests and check performance on GCC 7 and SSE 4.2
|
||||
executor: gcc7
|
||||
environment: { ARCHFLAGS: -march=nehalem }
|
||||
steps: [ make_test ]
|
||||
gcc-sse-dynamic:
|
||||
description: Build, run tests and check performance on GCC 7 and SSE 4.2 with a cmake dynamic build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ cmake_test ]
|
||||
gcc-sse-static:
|
||||
description: Build, run tests and check performance on GCC 7 and SSE 4.2 with a cmake static build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test ]
|
||||
gcc-sse-sanitize:
|
||||
description: Build, run tests and check performance on GCC 7 and SSE 4.2 with a cmake sanitize build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON }
|
||||
steps: [ cmake_simple_test ]
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
clang-avx:
|
||||
description: Build, run tests and check performance on clang 6 and AVX 2
|
||||
executor: clang6
|
||||
steps: [ init_clang6, make_test ]
|
||||
clang-avx-dynamic:
|
||||
description: Build, run tests and check performance on clang 6 and AVX 2 with a cmake dynamic build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-avx-static:
|
||||
description: Build, run tests and check performance on clang 6 and AVX 2 with a cmake static build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-avx-google-benchmarks:
|
||||
description: Build, run tests and check performance on clang 6 with google benchmarks enabled
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-avx-sanitize:
|
||||
description: Build, run tests and check performance on clang 6 and AVX 2 with a cmake sanitize build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON }
|
||||
steps: [ init_clang6, cmake_simple_test ]
|
||||
|
||||
"gccnoavx":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: g++-7
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
g++-7
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (gcc)
|
||||
command: ARCHFLAGS="-march=nehalem" make
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc)
|
||||
command: ARCHFLAGS="-march=nehalem" make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (gcc, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
|
||||
"clang":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
clang-6.0
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (clang)
|
||||
command: make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang)
|
||||
command: make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (clang, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
|
||||
"clangnoavx":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
clang-6.0
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (clang)
|
||||
command: ARCHFLAGS="-march=nehalem" make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang)
|
||||
command: ARCHFLAGS="-march=nehalem" make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (clang, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
clang-sse:
|
||||
description: Build, run tests and check performance on Clang 6 and SSE 4.2
|
||||
executor: clang6
|
||||
environment: { ARCHFLAGS: -march=nehalem }
|
||||
steps: [ init_clang6, make_test ]
|
||||
clang-sse-dynamic:
|
||||
description: Build, run tests and check performance on Clang 6 and SSE 4.2 with a cmake dynamic build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-sse-static:
|
||||
description: Build, run tests and check performance on Clang 6 and SSE 4.2 with a cmake static build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ init_clang6, cmake_test ]
|
||||
clang-sse-sanitize:
|
||||
description: Build, run tests and check performance on Clang 6 and SSE 4.2 with a cmake sanitize build
|
||||
executor: clang6
|
||||
environment: { CMAKE_TEST_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON }
|
||||
steps: [ init_clang6, cmake_simple_test ]
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
version: 2.1
|
||||
build_and_test:
|
||||
jobs:
|
||||
- "clang"
|
||||
- "gcc"
|
||||
- "clangnoavx"
|
||||
- "gccnoavx"
|
||||
- gcc9-avx
|
||||
- gcc8-avx
|
||||
- gcc-avx
|
||||
- gcc-avx-dynamic
|
||||
- gcc-avx-static
|
||||
- gcc-avx-google-benchmarks
|
||||
- gcc-avx-sanitize
|
||||
- gcc-sse
|
||||
- gcc-sse-dynamic
|
||||
- gcc-sse-static
|
||||
- gcc-sse-sanitize
|
||||
- clang-avx
|
||||
- clang-avx-dynamic
|
||||
- clang-avx-static
|
||||
- clang-avx-google-benchmarks
|
||||
- clang-avx-sanitize
|
||||
- clang-sse
|
||||
- clang-sse-dynamic
|
||||
- clang-sse-static
|
||||
- clang-sse-sanitize
|
||||
- gcc-avx-threaded
|
||||
- gcc-avx-unthreaded
|
||||
- clang-avx-threaded
|
||||
- clang-avx-unthreaded
|
||||
|
||||
# TODO add windows: https://circleci.com/docs/2.0/configuration-reference/#windows
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
*
|
||||
!.git
|
||||
!Makefile
|
||||
!amalgamation.sh
|
||||
!benchmark
|
||||
!dependencies
|
||||
!include
|
||||
!jsonchecker
|
||||
!jsonexamples
|
||||
!scripts
|
||||
!singleheader
|
||||
!src
|
||||
!style
|
||||
!tests
|
||||
!tools
|
||||
+459
-33
@@ -1,32 +1,199 @@
|
||||
kind: pipeline
|
||||
name: x64
|
||||
name: x64-quicktests-libc
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: gcc:8
|
||||
commands:
|
||||
- make
|
||||
- make quiettest
|
||||
- make amalgamate
|
||||
- name: quicktests
|
||||
image: conanio/clang8
|
||||
user: root
|
||||
commands: [ EXTRAFLAGS=-stdlib=libc++ make quicktests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64
|
||||
name: x64-quicktests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: quicktests
|
||||
image: gcc:8
|
||||
commands: [ make quicktests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-checkperf
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: checkperf
|
||||
image: gcc:8
|
||||
environment:
|
||||
CHECKPERF_REPOSITORY: https://github.com/lemire/simdjson
|
||||
commands: [ sed '/^$/Q' /proc/cpuinfo, make checkperf ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: gcc:8
|
||||
commands: [ make, make amalgamate ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-slowtests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: slowtests
|
||||
image: gcc:8
|
||||
commands: [ make slowtests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-noexceptions-quicktests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: quicktests
|
||||
image: gcc:8
|
||||
environment:
|
||||
EXTRA_FLAGS: -fno-exceptions
|
||||
commands: [ make quicktests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-noexceptions-build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: gcc:8
|
||||
environment:
|
||||
EXTRA_FLAGS: -fno-exceptions
|
||||
commands: [ make, make amalgamate ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-noexceptions-slowtests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: slowtests
|
||||
image: gcc:8
|
||||
environment:
|
||||
EXTRA_FLAGS: -fno-exceptions
|
||||
commands: [ make slowtests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-quicktests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
- name: quicktests
|
||||
image: gcc:8
|
||||
commands:
|
||||
- make
|
||||
- make quiettest
|
||||
- make amalgamate
|
||||
commands: [ make quicktests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-checkperf
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: checkperf
|
||||
image: gcc:8
|
||||
environment:
|
||||
CHECKPERF_REPOSITORY: https://github.com/lemire/simdjson
|
||||
commands: [ cat /proc/cpuinfo, make checkperf ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: gcc:8
|
||||
commands: [ make, make amalgamate ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-slowtests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: slowtests
|
||||
image: gcc:8
|
||||
commands: [ make slowtests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-amalgamated-build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: gcc:8
|
||||
environment:
|
||||
SIMDJSON_TEST_AMALGAMATED_HEADERS: 1
|
||||
commands: [ make amalgamate, make ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-amalgamated-quicktests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: quicktests
|
||||
image: gcc:8
|
||||
environment:
|
||||
SIMDJSON_TEST_AMALGAMATED_HEADERS: 1
|
||||
commands: [ make amalgamate, make quicktests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: x64-amalgamated-slowtests
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: slowtests
|
||||
image: gcc:8
|
||||
environment:
|
||||
SIMDJSON_TEST_AMALGAMATED_HEADERS: 1
|
||||
commands: [ make amalgamate, make slowtests ]
|
||||
---
|
||||
kind: pipeline
|
||||
name: stylecheck
|
||||
@@ -44,7 +211,7 @@ steps:
|
||||
- ./style/run-clang-format.py -r include/ benchmark/ src/ tests/
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake
|
||||
name: amd64_clang_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
@@ -56,17 +223,40 @@ steps:
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake_no_exceptions
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest --output-on-failure
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake
|
||||
name: amd64_clang_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
@@ -76,19 +266,66 @@ steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake
|
||||
name: amd64_gcc_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
@@ -100,17 +337,41 @@ steps:
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_gcc_cmake
|
||||
name: arm64_gcc_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
@@ -120,13 +381,178 @@ steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
- ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_gcc_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest --output-on-failure
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake_sanitize
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_gcc_cmake_sanitize
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake_sanitize
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake_sanitize
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake_sanitize_noavx
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION_HASWELL=OFF
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang make cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake_sanitize_noavx
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION_HASWELL=OFF
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest --output-on-failure
|
||||
|
||||
+113
-15
@@ -1,19 +1,117 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
# Uncomment next line to adjust line endings
|
||||
* text=auto
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.c text
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.java text
|
||||
*.xml text
|
||||
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.svg binary
|
||||
*.json binary
|
||||
# we don't want json files to be modified for this project
|
||||
*.json binary
|
||||
|
||||
|
||||
# Common settings that generally should always be used with your language specific settings
|
||||
|
||||
#
|
||||
# From generator:
|
||||
# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||
#
|
||||
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as an asset (binary) by default.
|
||||
*.svg text
|
||||
# If you want to treat it as binary,
|
||||
# use the following line instead.
|
||||
# *.svg binary
|
||||
*.eps binary
|
||||
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.sh text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
||||
# Serialisation
|
||||
#*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
*.zip binary
|
||||
|
||||
#
|
||||
# Exclude files from exporting
|
||||
#
|
||||
|
||||
.gitattributes export-ignore
|
||||
.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
|
||||
|
||||
# Compiled Object files
|
||||
*.slo binary
|
||||
*.lo binary
|
||||
*.o binary
|
||||
*.obj binary
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch binary
|
||||
*.pch binary
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so binary
|
||||
*.dylib binary
|
||||
*.dll binary
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai binary
|
||||
*.la binary
|
||||
*.a binary
|
||||
*.lib binary
|
||||
|
||||
# Executables
|
||||
*.exe binary
|
||||
*.out binary
|
||||
*.app binary
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
name: Run fuzzers on stored corpus and test it with 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:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: 23 */8 * * *
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
allfuzzers: parser dump dump_raw_tape print_json
|
||||
artifactsprefix: -artifact_prefix=fuzzfailure/
|
||||
steps:
|
||||
- name: Install packages necessary for building
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install --quiet ninja-build valgrind zip unzip
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 8
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
- name: Create and prepare the initial seed corpus
|
||||
run: |
|
||||
fuzz/build_corpus.sh
|
||||
mv corpus.zip 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
|
||||
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
|
||||
mkdir -p out/$fuzzer # in case this is a new fuzzer, or corpus.tar is broken
|
||||
build-ossfuzz-fast8/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
|
||||
done
|
||||
- name: Run the other fuzzer variants for $fuzzer, with sanitizers etc
|
||||
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-noavx8/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-noavx8/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
echo build-ossfuzz-msan-withavx8/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
- name: Minimize the corpus with the fast fuzzer
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
mkdir -p out/cmin/$fuzzer
|
||||
build-ossfuzz-fast8/fuzz/fuzz_$fuzzer -merge=1 out/cmin/$fuzzer out/$fuzzer
|
||||
rm -rf out/$fuzzer
|
||||
mv out/cmin/$fuzzer out/$fuzzer
|
||||
done
|
||||
- name: Package the corpus into an artifact
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
tar rf corpus.tar out/$fuzzer
|
||||
done
|
||||
- name: Save the corpus as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: corpus
|
||||
path: corpus.tar
|
||||
- name: Run the corpus through valgrind (normal build)
|
||||
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
|
||||
- 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
|
||||
with:
|
||||
name: valgrindresults
|
||||
path: valgrind.tar
|
||||
- name: Upload the corpus and results to bintray if we are on master
|
||||
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
|
||||
+189
-1
@@ -1 +1,189 @@
|
||||
build/
|
||||
visual_studio
|
||||
|
||||
/benchbranch/
|
||||
/submodules/
|
||||
|
||||
# eclipse project files
|
||||
.cproject
|
||||
.project
|
||||
.settings
|
||||
|
||||
# emacs temp files
|
||||
*~
|
||||
|
||||
# vim temp files
|
||||
.*.swp
|
||||
|
||||
# XCode
|
||||
^build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
*.DS_Store
|
||||
|
||||
# IDE specific folder for JetBrains IDEs
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Visual Studio Code artifacts
|
||||
.vscode/*
|
||||
.history/
|
||||
|
||||
# Visual Studio artifacts
|
||||
/VS/
|
||||
|
||||
# C/C++ build outputs
|
||||
.build/
|
||||
bins
|
||||
gens
|
||||
libs
|
||||
objs
|
||||
|
||||
# Build outputs (TODO build to a subdir so we can exclude that instead)
|
||||
/allparserscheckfile
|
||||
/allparsingcompetition
|
||||
/basictests
|
||||
/benchfeatures
|
||||
/benchmark/bench_dom_api
|
||||
/benchmark/bench_parse_call
|
||||
/benchmark/get_corpus_benchmark
|
||||
/benchmark/parse
|
||||
/benchmark/parse_stream
|
||||
/benchmark/perfdiff
|
||||
/benchmark/statisticalmodel
|
||||
/build/
|
||||
/build-ossfuzz-*/
|
||||
/build-plain-*/
|
||||
/corpus.zip
|
||||
/distinctuseridcompetition
|
||||
/errortests
|
||||
/examples/quickstart/quickstart
|
||||
/examples/quickstart/simdjson.cpp
|
||||
/examples/quickstart/simdjson.h
|
||||
/examples/quickstart/twitter.json
|
||||
/fuzz/fuzz_dump
|
||||
/fuzz/fuzz_dump_raw_tape
|
||||
/fuzz/fuzz_parser
|
||||
/fuzz/fuzz_print_json
|
||||
/get_corpus_benchmark
|
||||
/json2json
|
||||
/jsoncheck
|
||||
/jsoncheck_westmere
|
||||
/jsoncheck_fallback
|
||||
/jsonpointer
|
||||
/jsonstats
|
||||
/integer_tests
|
||||
/libsimdjson.so*
|
||||
/minifiercompetition
|
||||
/minify
|
||||
/numberparsingcheck
|
||||
/ossfuzz-out
|
||||
/out
|
||||
/parse
|
||||
/parse_many_test
|
||||
/parse_nonumberparsing
|
||||
/parse_nostringparsing
|
||||
/parse_noutf8validation
|
||||
/parse_stream
|
||||
/parseandstatcompetition
|
||||
/parsingcompetition
|
||||
/perfdiff
|
||||
/pointercheck
|
||||
/readme_examples
|
||||
/readme_examples_noexceptions
|
||||
/statisticalmodel
|
||||
/stringparsingcheck
|
||||
/submodules
|
||||
/ujdecode.o
|
||||
/amalgamation_demo.cpp
|
||||
/simdjson.cpp
|
||||
/simdjson.h
|
||||
/singleheader/amalgamation_demo
|
||||
/singleheader/demo
|
||||
/tests/basictests
|
||||
/tests/errortests
|
||||
/tests/jsoncheck
|
||||
/tests/pointercheck
|
||||
/tests/integer_tests
|
||||
/tests/parse_many_test
|
||||
/tests/readme_examples
|
||||
/tests/readme_examples_noexceptions
|
||||
/tools/json2json
|
||||
/tools/jsonstats
|
||||
/tools/minify
|
||||
|
||||
# Don't check in generated API docs
|
||||
/doc/api
|
||||
|
||||
# Don't check in generated examples
|
||||
/jsonexamples/generated
|
||||
|
||||
# C++ ignore from https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
||||
### https://raw.github.com/github/gitignore/e92f8db7a027af8cc25da2dc0758317e39697684/CMake.gitignore
|
||||
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
# CMake files that may be specific to our installation
|
||||
/CPackConfig.cmake
|
||||
/CPackSourceConfig.cmake
|
||||
# We check in a custom version of root Makefile that is not generated by CMake
|
||||
!/Makefile
|
||||
|
||||
|
||||
@@ -25,3 +25,9 @@
|
||||
[submodule "dependencies/jsoncpp"]
|
||||
path = dependencies/jsoncpp
|
||||
url = https://github.com/open-source-parsers/jsoncpp.git
|
||||
[submodule "dependencies/json"]
|
||||
path = dependencies/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "dependencies/benchmark"]
|
||||
path = dependencies/benchmark
|
||||
url = https://github.com/google/benchmark.git
|
||||
|
||||
+57
-17
@@ -1,52 +1,90 @@
|
||||
cmake_minimum_required(VERSION 3.9) # CMP0069 NEW
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ltoresult)
|
||||
if(ltoresult)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "No build type selected, default to Release")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
# usage: cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
option(SIMDJSON_DISABLE_AVX "Forcefully disable AVX even if hardware supports it" OFF)
|
||||
project(simdjson
|
||||
DESCRIPTION "Parsing gigabytes of JSON per second"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
# LTO seems to create all sorts of fun problems. Let us
|
||||
# disable temporarily.
|
||||
#include(CheckIPOSupported)
|
||||
#check_ipo_supported(RESULT ltoresult)
|
||||
#if(ltoresult)
|
||||
# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
#endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MACOSX_RPATH OFF)
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "No build type selected, default to Release")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD ON)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
|
||||
project(simdjson)
|
||||
set(SIMDJSON_LIB_NAME simdjson)
|
||||
set(PROJECT_VERSION_MAJOR 0)
|
||||
set(PROJECT_VERSION_MINOR 2)
|
||||
set(PROJECT_VERSION_PATCH 0)
|
||||
set(SIMDJSON_LIB_VERSION "0.2.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "0" CACHE STRING "simdjson library soversion")
|
||||
set(PROJECT_VERSION_MINOR 3)
|
||||
set(PROJECT_VERSION_PATCH 1)
|
||||
set(SIMDJSON_LIB_VERSION "0.3.1" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "1" CACHE STRING "simdjson library soversion")
|
||||
|
||||
option(SIMDJSON_IMPLEMENTATION_HASWELL "Include the haswell implementation" ON)
|
||||
option(SIMDJSON_IMPLEMENTATION_WESTMERE "Include the westmere implementation" ON)
|
||||
option(SIMDJSON_IMPLEMENTATION_ARM64 "Include the arm64 implementation" ON)
|
||||
option(SIMDJSON_IMPLEMENTATION_FALLBACK "Include the fallback implementation" ON)
|
||||
if(NOT MSVC)
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" OFF) # turning it on disables the production of a dynamic library
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" OFF) # turning it on disables the production of a dynamic library
|
||||
else()
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" ON) # turning it on disables the production of a dynamic library
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" ON) # turning it on disables the production of a dynamic library
|
||||
endif()
|
||||
option(SIMDJSON_SANITIZE "Sanitize addresses" OFF)
|
||||
option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark benchmarks" OFF)
|
||||
option(SIMDJSON_ENABLE_THREADS "enable threaded operation" ON)
|
||||
option(SIMDJSON_EXCEPTIONS "Enable simdjson's exception-throwing interface" ON)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake")
|
||||
|
||||
find_package(CTargets)
|
||||
find_package(Options)
|
||||
|
||||
# We used to have install(DIRECTORY include/${SIMDJSON_LIB_NAME} DESTINATION include)
|
||||
# alone.
|
||||
# However, this fails because we also need the root level simdjson.h file.
|
||||
install(DIRECTORY include/${SIMDJSON_LIB_NAME} DESTINATION include)
|
||||
install(FILES include/${SIMDJSON_LIB_NAME}.h DESTINATION include)
|
||||
set (TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/jsonchecker/")
|
||||
set (BENCHMARK_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/jsonexamples/")
|
||||
add_definitions(-DSIMDJSON_TEST_DATA_DIR="${TEST_DATA_DIR}")
|
||||
add_definitions(-DSIMDJSON_BENCHMARK_DATA_DIR="${TEST_DATA_DIR}")
|
||||
enable_testing()
|
||||
include(include/CMakeLists.txt)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(benchmark)
|
||||
|
||||
if (SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
if(NOT EXISTS dependencies/benchmark/CMakeLists.txt)
|
||||
# message(STATUS "Unable to find dependencies/benchmark/CMakeLists.txt")
|
||||
execute_process(COMMAND git submodule update --init -- dependencies/benchmark
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
||||
option(BENCHMARK_ENABLE_TESTING OFF)
|
||||
set(BENCHMARK_ENABLE_TESTING OFF)
|
||||
option(BENCHMARK_ENABLE_INSTALL OFF)
|
||||
set(BENCHMARK_ENABLE_INSTALL OFF)
|
||||
add_subdirectory(dependencies/benchmark)
|
||||
endif()
|
||||
|
||||
# for fuzzing, read the comments in the fuzz/CMakeLists.txt file
|
||||
option(ENABLE_FUZZING "enable building the fuzzers" ON)
|
||||
if(ENABLE_FUZZING)
|
||||
add_subdirectory(fuzz)
|
||||
endif()
|
||||
|
||||
set(CPACK_PACKAGE_VENDOR "Daniel Lemire")
|
||||
set(CPACK_PACKAGE_CONTACT "lemire@gmail.com")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Parsing gigabytes of JSON per second")
|
||||
@@ -61,3 +99,5 @@ set(CPACK_RPM_PACKAGE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
||||
|
||||
include(CPack)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
The simdjson library is an open project written in C++. Contributions are invited. Contributors
|
||||
agree to the project's license.
|
||||
|
||||
We have an extensive list of issues, and contributions toward any of these issues is invited.
|
||||
Contributions can take the form of code samples, better documentation or design ideas.
|
||||
|
||||
In particular, the following contributions are invited:
|
||||
|
||||
- The library is focused on performance. Well-documented performance optimization are invited.
|
||||
- Fixes to known or newly discovered bugs are always welcome. Typically, a bug fix should come with
|
||||
a test demonstrating that the bug has been fixed.
|
||||
- The simdjson library is advanced software and maintainability and flexibility are always a
|
||||
concern. Specific contributions to improve maintainability and flexibility are invited.
|
||||
|
||||
We discourage the following types of contributions:
|
||||
|
||||
- Code refactoring. We all have our preferences as to how code should be written, but unnecessary
|
||||
refactoring can waste time and introduce new bugs. If you believe that refactoring is needed, you
|
||||
first must explain how it helps in concrete terms. Does it improve the performance?
|
||||
- Applications of new language features for their own sake. Using advanced C++ language constructs
|
||||
is actually a negative as it may reduce portability (to old compilers, old standard libraries and
|
||||
systems) and reduce accessibility (to programmers that have not kept up), so it must be offsetted
|
||||
by clear gains like performance or maintainability. When in doubt, avoid advanced C++ features
|
||||
(beyond C++11).
|
||||
- Style formatting. In general, please abstain from reformatting code just to make it look prettier.
|
||||
Though code formatting is important, it can also be a waste of time if several contributors try to
|
||||
tweak the code base toward their own preference. Please do not introduce unneeded white-space
|
||||
changes.
|
||||
|
||||
In short, most code changes should either bring new features or better performance. We want to avoid unmotivated code changes.
|
||||
|
||||
Guidelines
|
||||
----------
|
||||
|
||||
Contributors are encouraged to :
|
||||
|
||||
- Document their changes. Though we do not enforce a rule regarding code comments, we prefer that non-trivial algorithms and techniques be somewhat documented in the code.
|
||||
- Follow as much as possible the existing code style. We do not enforce a specific code style, but we prefer consistency.
|
||||
- Modify as few lines of code as possible when working on an issue. The more lines you modify, the harder it is for your fellow human beings to understand what is going on.
|
||||
- Tools may report "problems" with the code, but we never delegate programming to tools: if there is a problem with the code, we need to understand it. Thus we will not "fix" code merely to please a static analyzer if we do not understand.
|
||||
- Provide tests for any new feature. We will not merge a new feature without tests.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Though we do not have a formal code of conduct, we will not tolerate bullying, bigotry or
|
||||
intimidation. Everyone is welcome to contribute.
|
||||
|
||||
Getting Started Hacking
|
||||
-----------------------
|
||||
|
||||
An overview of simdjson's directory structure, with pointers to architecture and design
|
||||
considerations and other helpful notes, can be found at [HACKING.md](HACKING.md).
|
||||
@@ -23,5 +23,13 @@ Chang Liu
|
||||
Sunny Gleason
|
||||
John Keiser
|
||||
Zach Bjornson
|
||||
Vitaly Baranov
|
||||
Juho Lauri
|
||||
Michael Eisel
|
||||
Io Daza Dillon
|
||||
Paul Dreik
|
||||
Jeremie Piotte
|
||||
Matthew Wilson
|
||||
Dušan Jovanović
|
||||
# if you have contributed to the project and your name does not
|
||||
# appear in this list, please let us know!
|
||||
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
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.
|
||||
|
||||
Directory Structure and Source
|
||||
------------------------------
|
||||
|
||||
simdjson's source structure, from the top level, looks like this:
|
||||
|
||||
* **Makefile:** The main Makefile for Linux. This is not the same as CMakeLists.txt.
|
||||
* **CMakeLists.txt:** A Makefile generator for non-default cases and options.
|
||||
* **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
|
||||
the distributed simdjson.h.
|
||||
* simdjson/*.h: Declarations for public simdjson classes and functions.
|
||||
* simdjson/inline/*.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
|
||||
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.
|
||||
* generic/: Generic implementations of the simdjson parser. These files may be included and
|
||||
compiled multiple times, from whichever architectures use them. They assume they are already
|
||||
enclosed in a namespace, e.g.:
|
||||
```c++
|
||||
namespace simdjson::haswell {
|
||||
#include "generic/stage1_find_marks.h"
|
||||
}
|
||||
```
|
||||
|
||||
Other important files and directories:
|
||||
* **.drone.yml:** Definitions for Drone CI.
|
||||
* **.appveyor.yml:** Definitions for Appveyor CI (Windows).
|
||||
* **.circleci:** Definitions for Circle CI.
|
||||
* **amalgamation.sh:** Generates singleheader/simdjson.h and singleheader/simdjson.cpp for release.
|
||||
* **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. If you're not sure what else to do to check your performance, this is always a good start:
|
||||
```bash
|
||||
make parse && ./parse jsonexamples/twitter.json
|
||||
```
|
||||
* **fuzz:** The source for fuzz testing. This lets us explore important edge and middle cases
|
||||
automatically, and is run in CI.
|
||||
* **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.
|
||||
* **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
|
||||
|
||||
> **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
|
||||
|
||||
Regenerating Single Headers From Master
|
||||
---------------------------------------
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
make amalgamate
|
||||
```
|
||||
|
||||
The amalgamator is at `amalgamation.sh` at the top level. It 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 (old-school Makefile on platforms like Linux or macOS)
|
||||
|
||||
Requirements: recent clang or gcc, and make. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. A 64-bit system like Linux or macOS is expected.
|
||||
|
||||
To test:
|
||||
|
||||
```
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
To run benchmarks:
|
||||
|
||||
```
|
||||
make parse
|
||||
./parse jsonexamples/twitter.json
|
||||
```
|
||||
|
||||
Under Linux, the `parse` command gives a detailed analysis of the performance counters.
|
||||
|
||||
To run comparative benchmarks (with other parsers):
|
||||
|
||||
```
|
||||
make benchmark
|
||||
```
|
||||
|
||||
### Usage (CMake on 64-bit platforms like Linux or macOS)
|
||||
|
||||
Requirements: We require a recent version of cmake. On macOS, the easiest way to install cmake might be to use [brew](https://brew.sh) and then type
|
||||
|
||||
```
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
There is an [equivalent brew on Linux which works the same way as well](https://linuxbrew.sh).
|
||||
|
||||
You need a recent compiler like clang or gcc. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. For example, you can install a recent compiler with brew:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
```
|
||||
|
||||
Optional: You need to tell cmake which compiler you wish to use by setting the CC and CXX variables. Under bash, you can do so with commands such as `export CC=gcc-7` and `export CXX=g++-7`.
|
||||
|
||||
Building: While in the project repository, do the following:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
CMake will build a library. By default, it builds a shared library (e.g., libsimdjson.so on Linux).
|
||||
|
||||
You can build a static library:
|
||||
|
||||
```
|
||||
mkdir buildstatic
|
||||
cd buildstatic
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
In some cases, you may want to specify your compiler, especially if the default compiler on your system is too old. You may proceed as follows:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
mkdir build
|
||||
cd build
|
||||
export CXX=g++-8 CC=gcc-8
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
### Usage (CMake on 64-bit Windows using Visual Studio)
|
||||
|
||||
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).
|
||||
|
||||
- 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 `VisualStudio`.
|
||||
- Using a shell, go to this newly created directory.
|
||||
- Type `cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..` in the shell while in the `VisualStudio` repository. (Alternatively, if you want to build a DLL, you may use the command line `cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF ..`.)
|
||||
- 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`.
|
||||
|
||||
### 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 }
|
||||
|
||||
## 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.
|
||||
|
||||
### In-depth comparisons
|
||||
|
||||
If you want to see how a wide range of parsers validate a given JSON file:
|
||||
|
||||
```
|
||||
make allparserscheckfile
|
||||
./allparserscheckfile myfile.json
|
||||
```
|
||||
|
||||
For performance comparisons:
|
||||
|
||||
```
|
||||
make parsingcompetition
|
||||
./parsingcompetition myfile.json
|
||||
```
|
||||
|
||||
For broader comparisons:
|
||||
|
||||
```
|
||||
make allparsingcompetition
|
||||
./allparsingcompetition myfile.json
|
||||
```
|
||||
|
||||
Both the `parsingcompetition` and `allparsingcompetition` tools take a `-t` flag which produces
|
||||
a table-oriented output that can be conveniently parsed by other tools.
|
||||
|
||||
### 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
|
||||
- GoogleGson https://github.com/google/gson
|
||||
- Jackson https://github.com/FasterXML/jackson
|
||||
- https://www.yelp.com/dataset_challenge
|
||||
- RapidJSON. http://rapidjson.org/
|
||||
|
||||
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,3 +1,4 @@
|
||||
REFERENCE_VERSION = master
|
||||
|
||||
.SUFFIXES:
|
||||
#
|
||||
@@ -5,7 +6,7 @@
|
||||
|
||||
|
||||
.PHONY: clean cleandist
|
||||
COREDEPSINCLUDE = -Idependencies/rapidjson/include -Idependencies/sajson/include -Idependencies/cJSON -Idependencies/jsmn
|
||||
COREDEPSINCLUDE = -Idependencies/json/single_include -Idependencies/rapidjson/include -Idependencies/sajson/include -Idependencies/cJSON -Idependencies/jsmn
|
||||
EXTRADEPSINCLUDE = -Idependencies/jsoncppdist -Idependencies/json11 -Idependencies/fastjson/src -Idependencies/fastjson/include -Idependencies/gason/src -Idependencies/ujson4c/3rdparty -Idependencies/ujson4c/src
|
||||
# users can provide their own additional flags with make EXTRAFLAGS=something
|
||||
architecture:=$(shell arch)
|
||||
@@ -13,17 +14,11 @@ architecture:=$(shell arch)
|
||||
####
|
||||
# If you want to specify your own target architecture,
|
||||
# then define ARCHFLAGS. Otherwise, we set good default.
|
||||
# E.g., type ' ARCHFLAGS="-march=nehalem" make parse '
|
||||
# E.g., type ' ARCHFLAGS="-march=westmere" make parse '
|
||||
###
|
||||
ifeq ($(architecture),aarch64)
|
||||
ARCHFLAGS ?= -march=armv8-a+crc+crypto
|
||||
else
|
||||
ARCHFLAGS ?= -msse4.2 -mpclmul # lowest supported feature set?
|
||||
endif
|
||||
|
||||
CXXFLAGS = $(ARCHFLAGS) -std=c++17 -Wall -Wextra -Wshadow -Iinclude -Ibenchmark/linux $(EXTRAFLAGS)
|
||||
CFLAGS = $(ARCHFLAGS) -Idependencies/ujson4c/3rdparty -Idependencies/ujson4c/src $(EXTRAFLAGS)
|
||||
|
||||
CXXFLAGS = $(ARCHFLAGS) -std=c++17 -pthread -Wall -Wextra -Wshadow -Ibenchmark/linux
|
||||
CFLAGS = $(ARCHFLAGS) -Idependencies/ujson4c/3rdparty -Idependencies/ujson4c/src $(EXTRAFLAGS)
|
||||
|
||||
# This is a convenience flag
|
||||
ifdef SANITIZEGOLD
|
||||
@@ -39,16 +34,16 @@ endif
|
||||
|
||||
# SANITIZE *implies* DEBUG
|
||||
ifeq ($(MEMSANITIZE),1)
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
else
|
||||
ifeq ($(SANITIZE),1)
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
else
|
||||
ifeq ($(DEBUG),1)
|
||||
CXXFLAGS += -g3 -O0
|
||||
CFLAGS += -g3 -O0
|
||||
CXXFLAGS += -g3 -O0
|
||||
CFLAGS += -g3 -O0
|
||||
else
|
||||
# we opt for -O3 for regular builds
|
||||
CXXFLAGS += -O3
|
||||
@@ -57,16 +52,31 @@ endif # ifeq ($(DEBUG),1)
|
||||
endif # ifeq ($(SANITIZE),1)
|
||||
endif # ifeq ($(MEMSANITIZE),1)
|
||||
|
||||
MAINEXECUTABLES=parse minify json2json jsonstats statisticalmodel jsonpointer
|
||||
TESTEXECUTABLES=jsoncheck numberparsingcheck stringparsingcheck pointercheck
|
||||
COMPARISONEXECUTABLES=minifiercompetition parsingcompetition parseandstatcompetition distinctuseridcompetition allparserscheckfile allparsingcompetition
|
||||
SUPPLEMENTARYEXECUTABLES=parse_noutf8validation parse_nonumberparsing parse_nostringparsing
|
||||
# Headers and sources
|
||||
SRCHEADERS_GENERIC=src/generic/atomparsing.h src/generic/numberparsing.h src/generic/json_scanner.h src/generic/json_string_scanner.h src/generic/json_structural_indexer.h src/generic/json_minifier.h src/generic/buf_block_reader.h src/generic/stage2_build_tape.h src/generic/stringparsing.h src/generic/stage2_streaming_build_tape.h src/generic/utf8_fastvalidate_algorithm.h src/generic/utf8_lookup_algorithm.h src/generic/utf8_lookup2_algorithm.h src/generic/utf8_range_algorithm.h src/generic/utf8_zwegner_algorithm.h
|
||||
SRCHEADERS_ARM64= src/arm64/bitmanipulation.h src/arm64/bitmask.h src/arm64/intrinsics.h src/arm64/numberparsing.h src/arm64/simd.h src/arm64/stage1_find_marks.h src/arm64/stage2_build_tape.h src/arm64/stringparsing.h
|
||||
SRCHEADERS_HASWELL= src/haswell/bitmanipulation.h src/haswell/bitmask.h src/haswell/intrinsics.h src/haswell/numberparsing.h src/haswell/simd.h src/haswell/stage1_find_marks.h src/haswell/stage2_build_tape.h src/haswell/stringparsing.h
|
||||
SRCHEADERS_FALLBACK= src/fallback/bitmanipulation.h src/fallback/implementation.h src/fallback/numberparsing.h src/fallback/stage1_find_marks.h src/fallback/stage2_build_tape.h src/fallback/stringparsing.h
|
||||
SRCHEADERS_WESTMERE=src/westmere/bitmanipulation.h src/westmere/bitmask.h src/westmere/intrinsics.h src/westmere/numberparsing.h src/westmere/simd.h src/westmere/stage1_find_marks.h src/westmere/stage2_build_tape.h src/westmere/stringparsing.h
|
||||
SRCHEADERS_SRC=src/isadetection.h src/jsoncharutils.h src/simdprune_tables.h src/implementation.cpp src/stage1_find_marks.cpp src/stage2_build_tape.cpp src/document_parser_callbacks.h
|
||||
SRCHEADERS=$(SRCHEADERS_SRC) $(SRCHEADERS_GENERIC) $(SRCHEADERS_ARM64) $(SRCHEADERS_HASWELL) $(SRCHEADERS_WESTMERE) $(SRCHEADERS_FALLBACK)
|
||||
|
||||
HEADERS= include/simdjson/simdutf8check_haswell.h include/simdjson/simdutf8check_westmere.h include/simdjson/simdutf8check_arm64.h include/simdjson/stringparsing.h include/simdjson/stringparsing_arm64.h include/simdjson/stringparsing_haswell.h include/simdjson/stringparsing_macros.h include/simdjson/stringparsing_westmere.h include/simdjson/numberparsing.h include/simdjson/jsonparser.h include/simdjson/common_defs.h include/simdjson/jsonioutil.h benchmark/benchmark.h benchmark/linux/linux-perf-events.h include/simdjson/parsedjson.h include/simdjson/stage1_find_marks.h include/simdjson/stage1_find_marks_arm64.h include/simdjson/stage1_find_marks_haswell.h include/simdjson/stage1_find_marks_westmere.h include/simdjson/stage1_find_marks_macros.h include/simdjson/stage2_build_tape.h include/simdjson/jsoncharutils.h include/simdjson/jsonformatutils.h include/simdjson/stage1_find_marks_flatten.h include/simdjson/stage1_find_marks_flatten_haswell.h
|
||||
LIBFILES=src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/stage2_build_tape.cpp src/parsedjson.cpp src/parsedjsoniterator.cpp
|
||||
MINIFIERHEADERS=include/simdjson/jsonminifier.h include/simdjson/simdprune_tables.h
|
||||
MINIFIERLIBFILES=src/jsonminifier.cpp
|
||||
INCLUDEHEADERS=include/simdjson.h include/simdjson/common_defs.h include/simdjson/internal/jsonformatutils.h include/simdjson/jsonioutil.h include/simdjson/jsonparser.h include/simdjson/padded_string.h include/simdjson/inline/padded_string.h include/simdjson/document.h include/simdjson/inline/document.h include/simdjson/parsedjson_iterator.h include/simdjson/inline/parsedjson_iterator.h include/simdjson/document_stream.h include/simdjson/inline/document_stream.h include/simdjson/implementation.h include/simdjson/parsedjson.h include/simdjson/portability.h include/simdjson/error.h include/simdjson/inline/error.h include/simdjson/simdjson.h include/simdjson/simdjson_version.h
|
||||
|
||||
ifeq ($(SIMDJSON_TEST_AMALGAMATED_HEADERS),1)
|
||||
HEADERS=singleheader/simdjson.h
|
||||
LIBFILES=singleheader/simdjson.cpp
|
||||
CXXFLAGS += -Isingleheader
|
||||
else
|
||||
HEADERS=$(INCLUDEHEADERS) $(SRCHEADERS)
|
||||
LIBFILES=src/simdjson.cpp
|
||||
CXXFLAGS += -Isrc -Iinclude
|
||||
endif
|
||||
|
||||
# We put EXTRAFLAGS after all other CXXFLAGS so they can override if necessary
|
||||
CXXFLAGS += $(EXTRAFLAGS)
|
||||
|
||||
FEATURE_JSON_FILES=jsonexamples/generated/0-structurals-full.json jsonexamples/generated/0-structurals-miss.json jsonexamples/generated/0-structurals.json jsonexamples/generated/15-structurals-full.json jsonexamples/generated/15-structurals-miss.json jsonexamples/generated/15-structurals.json jsonexamples/generated/23-structurals-full.json jsonexamples/generated/23-structurals-miss.json jsonexamples/generated/23-structurals.json jsonexamples/generated/7-structurals-full.json jsonexamples/generated/7-structurals-miss.json jsonexamples/generated/7-structurals.json jsonexamples/generated/escape-full.json jsonexamples/generated/escape-miss.json jsonexamples/generated/escape.json jsonexamples/generated/utf-8-full.json jsonexamples/generated/utf-8-miss.json jsonexamples/generated/utf-8.json
|
||||
|
||||
RAPIDJSON_INCLUDE:=dependencies/rapidjson/include
|
||||
SAJSON_INCLUDE:=dependencies/sajson/include
|
||||
@@ -76,11 +86,15 @@ GASON_INCLUDE:=dependencies/gason/src/gason.h
|
||||
UJSON4C_INCLUDE:=dependencies/ujson4c/src/ujdecode.c
|
||||
CJSON_INCLUDE:=dependencies/cJSON/cJSON.h
|
||||
JSMN_INCLUDE:=dependencies/jsmn/jsmn.h
|
||||
|
||||
|
||||
LIBS=$(RAPIDJSON_INCLUDE) $(SAJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE)
|
||||
JSON_INCLUDE:=dependencies/json/single_include/nlohmann/json.hpp
|
||||
|
||||
EXTRAOBJECTS=ujdecode.o
|
||||
|
||||
MAINEXECUTABLES=parse minify json2json jsonstats statisticalmodel jsonpointer get_corpus_benchmark
|
||||
TESTEXECUTABLES=jsoncheck jsoncheck_westmere jsoncheck_fallback integer_tests numberparsingcheck stringparsingcheck pointercheck parse_many_test basictests errortests readme_examples readme_examples_noexceptions
|
||||
COMPARISONEXECUTABLES=minifiercompetition parsingcompetition parseandstatcompetition distinctuseridcompetition allparserscheckfile allparsingcompetition
|
||||
SUPPLEMENTARYEXECUTABLES=parse_noutf8validation parse_nonumberparsing parse_nostringparsing
|
||||
|
||||
all: $(MAINEXECUTABLES)
|
||||
|
||||
competition: $(COMPARISONEXECUTABLES)
|
||||
@@ -91,112 +105,197 @@ benchmark:
|
||||
bash ./scripts/parser.sh
|
||||
bash ./scripts/parseandstat.sh
|
||||
|
||||
test: jsoncheck numberparsingcheck stringparsingcheck basictests allparserscheckfile minify json2json pointercheck
|
||||
run_basictests: basictests
|
||||
./basictests
|
||||
|
||||
run_errortests: errortests
|
||||
./errortests
|
||||
|
||||
run_numberparsingcheck: numberparsingcheck
|
||||
./numberparsingcheck
|
||||
|
||||
run_integer_tests: integer_tests
|
||||
./integer_tests
|
||||
|
||||
run_stringparsingcheck: stringparsingcheck
|
||||
./stringparsingcheck
|
||||
|
||||
run_jsoncheck: jsoncheck
|
||||
./jsoncheck
|
||||
|
||||
run_parse_many_test: parse_many_test
|
||||
./parse_many_test
|
||||
|
||||
run_jsoncheck_westmere: jsoncheck_westmere
|
||||
./jsoncheck_westmere
|
||||
|
||||
run_jsoncheck_fallback: jsoncheck_fallback
|
||||
./jsoncheck_fallback
|
||||
|
||||
run_pointercheck: pointercheck
|
||||
./pointercheck
|
||||
./scripts/testjson2json.sh
|
||||
|
||||
run_issue150_sh: allparserscheckfile
|
||||
./scripts/issue150.sh
|
||||
|
||||
quickstart:
|
||||
cd examples/quickstart && make quickstart
|
||||
|
||||
run_quickstart:
|
||||
cd examples/quickstart && make test
|
||||
|
||||
run_testjson2json_sh: minify json2json
|
||||
./scripts/testjson2json.sh
|
||||
|
||||
$(FEATURE_JSON_FILES): benchmark/genfeaturejson.rb
|
||||
ruby ./benchmark/genfeaturejson.rb
|
||||
|
||||
run_benchfeatures: benchfeatures $(FEATURE_JSON_FILES)
|
||||
./benchfeatures -n 1000
|
||||
|
||||
test: quicktests slowtests
|
||||
@echo "It looks like the code is good!"
|
||||
|
||||
quiettest: jsoncheck numberparsingcheck stringparsingcheck basictests allparserscheckfile minify json2json pointercheck
|
||||
./basictests
|
||||
./numberparsingcheck
|
||||
./stringparsingcheck
|
||||
./jsoncheck
|
||||
./pointercheck
|
||||
./scripts/testjson2json.sh
|
||||
./scripts/issue150.sh
|
||||
quiettest: quicktests slowtests
|
||||
|
||||
quicktests: run_basictests run_quickstart readme_examples readme_examples_noexceptions run_jsoncheck run_numberparsingcheck run_integer_tests run_stringparsingcheck run_jsoncheck run_parse_many_test run_pointercheck run_jsoncheck_westmere run_jsoncheck_fallback
|
||||
|
||||
slowtests: run_testjson2json_sh run_issue150_sh
|
||||
|
||||
amalgamate:
|
||||
./amalgamation.sh
|
||||
$(CXX) $(CXXFLAGS) -o singleheader/demo ./singleheader/amalgamation_demo.cpp -Isingleheader
|
||||
|
||||
submodules:
|
||||
singleheader/simdjson.h singleheader/simdjson.cpp singleheader/amalgamation_demo.cpp: amalgamation.sh src/simdjson.cpp $(SRCHEADERS) $(INCLUDEHEADERS)
|
||||
./amalgamation.sh
|
||||
|
||||
singleheader/demo: singleheader/simdjson.h singleheader/simdjson.cpp singleheader/amalgamation_demo.cpp
|
||||
$(CXX) $(CXXFLAGS) -o singleheader/demo singleheader/amalgamation_demo.cpp -Isingleheader
|
||||
|
||||
submodules:
|
||||
-git submodule update --init --recursive
|
||||
-touch submodules
|
||||
|
||||
$(SAJSON_INCLUDE) $(RAPIDJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE) : submodules
|
||||
$(JSON_INCLUDE) $(SAJSON_INCLUDE) $(RAPIDJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE) : submodules
|
||||
|
||||
parse: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
parse: benchmark/parse.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse benchmark/parse.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
get_corpus_benchmark: benchmark/get_corpus_benchmark.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o get_corpus_benchmark benchmark/get_corpus_benchmark.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
parse_stream: benchmark/parse_stream.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_stream benchmark/parse_stream.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
benchfeatures: benchmark/benchfeatures.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o benchfeatures benchmark/benchfeatures.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
perfdiff: benchmark/perfdiff.cpp
|
||||
$(CXX) $(CXXFLAGS) -o perfdiff benchmark/perfdiff.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
checkperf:
|
||||
bash ./scripts/checkperf.sh $(REFERENCE_VERSION)
|
||||
|
||||
statisticalmodel: benchmark/statisticalmodel.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o statisticalmodel $(LIBFILES) benchmark/statisticalmodel.cpp $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o statisticalmodel benchmark/statisticalmodel.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
|
||||
parse_noutf8validation: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_noutf8validation -DSIMDJSON_SKIPUTF8VALIDATION $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o parse_noutf8validation -DSIMDJSON_SKIPUTF8VALIDATION benchmark/parse.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
parse_nonumberparsing: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nonumberparsing -DSIMDJSON_SKIPNUMBERPARSING $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nonumberparsing -DSIMDJSON_SKIPNUMBERPARSING benchmark/parse.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
parse_nostringparsing: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nostringparsing -DSIMDJSON_SKIPSTRINGPARSING $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nostringparsing -DSIMDJSON_SKIPSTRINGPARSING benchmark/parse.cpp $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
|
||||
jsoncheck:tests/jsoncheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsoncheck $(LIBFILES) tests/jsoncheck.cpp -I. $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o jsoncheck tests/jsoncheck.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
parse_many_test:tests/parse_many_test.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_many_test tests/parse_many_test.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
|
||||
jsoncheck_westmere:tests/jsoncheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsoncheck_westmere tests/jsoncheck.cpp -I. $(LIBFILES) $(LIBFLAGS) -DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
jsoncheck_fallback:tests/jsoncheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsoncheck_fallback tests/jsoncheck.cpp -I. $(LIBFILES) $(LIBFLAGS) -DSIMDJSON_IMPLEMENTATION_HASWELL=0 -DSIMDJSON_IMPLEMENTATION_WESTMERE=0 -DSIMDJSON_IMPLEMENTATION_ARM64=0
|
||||
|
||||
basictests:tests/basictests.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o basictests $(LIBFILES) tests/basictests.cpp -I. $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o basictests tests/basictests.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
errortests:tests/errortests.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o errortests tests/errortests.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
readme_examples: tests/readme_examples.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o readme_examples tests/readme_examples.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
readme_examples_noexceptions: tests/readme_examples_noexceptions.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o readme_examples_noexceptions tests/readme_examples_noexceptions.cpp -I. $(LIBFILES) $(LIBFLAGS) -fno-exceptions
|
||||
|
||||
numberparsingcheck: tests/numberparsingcheck.cpp $(HEADERS) src/simdjson.cpp
|
||||
$(CXX) $(CXXFLAGS) -o numberparsingcheck tests/numberparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_NUMBERS
|
||||
|
||||
integer_tests:tests/integer_tests.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o integer_tests tests/integer_tests.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
|
||||
numberparsingcheck:tests/numberparsingcheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o numberparsingcheck tests/numberparsingcheck.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp -I. $(LIBFLAGS) -DJSON_TEST_NUMBERS
|
||||
|
||||
|
||||
stringparsingcheck:tests/stringparsingcheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o stringparsingcheck tests/stringparsingcheck.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp -I. $(LIBFLAGS) -DJSON_TEST_STRINGS
|
||||
stringparsingcheck: tests/stringparsingcheck.cpp $(HEADERS) src/simdjson.cpp
|
||||
$(CXX) $(CXXFLAGS) -o stringparsingcheck tests/stringparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_STRINGS
|
||||
|
||||
pointercheck:tests/pointercheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o pointercheck tests/pointercheck.cpp src/stage2_build_tape.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp src/parsedjsoniterator.cpp -I. $(LIBFLAGS)
|
||||
$(CXX) $(CXXFLAGS) -o pointercheck tests/pointercheck.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
minifiercompetition: benchmark/minifiercompetition.cpp $(HEADERS) submodules $(MINIFIERHEADERS) $(LIBFILES) $(MINIFIERLIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minifiercompetition $(LIBFILES) $(MINIFIERLIBFILES) benchmark/minifiercompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
minifiercompetition: benchmark/minifiercompetition.cpp submodules $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minifiercompetition benchmark/minifiercompetition.cpp -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
minify: tools/minify.cpp $(HEADERS) $(MINIFIERHEADERS) $(LIBFILES) $(MINIFIERLIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minify $(MINIFIERLIBFILES) $(LIBFILES) tools/minify.cpp -I.
|
||||
minify: tools/minify.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minify tools/minify.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
json2json: tools/json2json.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o json2json $ tools/json2json.cpp $(LIBFILES) -I.
|
||||
$(CXX) $(CXXFLAGS) -o json2json $ tools/json2json.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
jsonpointer: tools/jsonpointer.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsonpointer $ tools/jsonpointer.cpp $(LIBFILES) -I.
|
||||
$(CXX) $(CXXFLAGS) -o jsonpointer $ tools/jsonpointer.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
jsonstats: tools/jsonstats.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsonstats $ tools/jsonstats.cpp $(LIBFILES) -I.
|
||||
$(CXX) $(CXXFLAGS) -o jsonstats $ tools/jsonstats.cpp -I. $(LIBFILES) $(LIBFLAGS)
|
||||
|
||||
ujdecode.o: $(UJSON4C_INCLUDE)
|
||||
$(CC) $(CFLAGS) -c dependencies/ujson4c/src/ujdecode.c
|
||||
|
||||
parseandstatcompetition: benchmark/parseandstatcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
$(CXX) $(CXXFLAGS) -o parseandstatcompetition $(LIBFILES) benchmark/parseandstatcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
$(CXX) $(CXXFLAGS) -o parseandstatcompetition benchmark/parseandstatcompetition.cpp -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
distinctuseridcompetition: benchmark/distinctuseridcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
$(CXX) $(CXXFLAGS) -o distinctuseridcompetition $(LIBFILES) benchmark/distinctuseridcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
$(CXX) $(CXXFLAGS) -o distinctuseridcompetition benchmark/distinctuseridcompetition.cpp -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
parsingcompetition: benchmark/parsingcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
$(CXX) $(CXXFLAGS) -o parsingcompetition $(LIBFILES) benchmark/parsingcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
parsingcompetition: benchmark/parsingcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
@echo "In case of build error due to missing files, try 'make clean'"
|
||||
$(CXX) $(CXXFLAGS) -o parsingcompetition benchmark/parsingcompetition.cpp -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
allparsingcompetition: benchmark/parsingcompetition.cpp $(HEADERS) $(LIBFILES) $(EXTRAOBJECTS) submodules
|
||||
$(CXX) $(CXXFLAGS) -o allparsingcompetition $(LIBFILES) benchmark/parsingcompetition.cpp $(EXTRAOBJECTS) -I. $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE) -DALLPARSER
|
||||
$(CXX) $(CXXFLAGS) -o allparsingcompetition benchmark/parsingcompetition.cpp $(EXTRAOBJECTS) -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE) -DALLPARSER
|
||||
|
||||
|
||||
allparserscheckfile: tests/allparserscheckfile.cpp $(HEADERS) $(LIBFILES) $(EXTRAOBJECTS) submodules
|
||||
$(CXX) $(CXXFLAGS) -o allparserscheckfile $(LIBFILES) tests/allparserscheckfile.cpp $(EXTRAOBJECTS) -I. $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE)
|
||||
$(CXX) $(CXXFLAGS) -o allparserscheckfile tests/allparserscheckfile.cpp $(EXTRAOBJECTS) -I. $(LIBFILES) $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE)
|
||||
|
||||
.PHONY: clean cppcheck cleandist
|
||||
|
||||
cppcheck:
|
||||
cppcheck --enable=all src/*.cpp benchmarks/*.cpp tests/*.cpp -Iinclude -I. -Ibenchmark/linux
|
||||
|
||||
everything: $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
everything: $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES) quickstart
|
||||
|
||||
clean:
|
||||
rm -f submodules $(EXTRAOBJECTS) $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
cd examples/quickstart && make clean
|
||||
|
||||
cleandist:
|
||||
rm -f submodules $(EXTRAOBJECTS) $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
|
||||
doc/api: Doxyfile $(HEADERS)
|
||||
doxygen
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# Notes on simdjson
|
||||
|
||||
## Rationale:
|
||||
|
||||
The simdjson project serves two purposes:
|
||||
|
||||
1. It creates a useful library for parsing JSON data quickly.
|
||||
|
||||
2. It is a demonstration of the use of SIMD and pipelined programming techniques to perform a complex and irregular task.
|
||||
These techniques include the use of large registers and SIMD instructions to process large amounts of input data at once,
|
||||
to hold larger entities than can typically be held in a single General Purpose Register (GPR), and to perform operations
|
||||
that are not cheap to perform without use of a SIMD unit (for example table lookup using permute instructions).
|
||||
|
||||
The other key technique is that the system is designed to minimize the number of unpredictable branches that must be taken
|
||||
to perform the task. Modern architectures are both wide and deep (4-wide pipelines with ~14 stages are commonplace). A
|
||||
recent Intel Architecture processor, for example, can perform 3 256-bit SIMD operations or 2 512-bit SIMD operations per
|
||||
cycle as well as other operations on general purpose registers or with the load/store unit. An incorrectly predicted branch
|
||||
will clear this pipeline. While it is rare that a programmer can achieve the maximum throughput on a machine, a developer
|
||||
may be missing the opportunity to carry out 56 operations for each branch miss.
|
||||
|
||||
Many code-bases make use of SIMD and deeply pipelined, "non-branchy", processing for regular tasks. Numerical problems
|
||||
(e.g. "matrix multiply") or simple 'bulk search' tasks (e.g. "count all the occurrences of a given character in a text",
|
||||
"find the first occurrence of the string 'foo' in a text") frequently use this class of techniques. We are demonstrating
|
||||
that these techniques can be applied to much more complex and less regular tasks.
|
||||
|
||||
## Design:
|
||||
|
||||
### Stage 1: SIMD over bytes; bit vector processing over bytes.
|
||||
|
||||
The first stage of our processing must identify key points in our input: the 'structural characters' of JSON (curly and
|
||||
square braces, colon, and comma), the start and end of strings as delineated by double quote characters, other JSON 'atoms'
|
||||
that are not distinguishable by simple characters (constructs such as "true", "false", "null" and numbers), as well as
|
||||
discovering these characters and atoms in the presence of both quoting conventions and backslash escaping conventions.
|
||||
|
||||
As such we follow the broad outline of the construction of a structural index as set forth in the Mison paper [XXX]; first,
|
||||
the discovery of odd-length sequences of backslash characters (which will cause quote characters immediately following to
|
||||
be escaped and not serve their quoting role but instead be literal charaters), second, the discovery of quote pairs (which
|
||||
cause structural characters within the quote pairs to also be merely literal characters and have no function as structural
|
||||
characters), then finally the discovery of structural characters not contained without the quote pairs.
|
||||
|
||||
We depart from the Mison paper in terms of method and overall design. In terms of method, the Mison paper uses iteration
|
||||
over bit vectors to discover backslash sequences and quote pairs; we introduce branch-free techniques to discover both of
|
||||
these properties.
|
||||
|
||||
We also make use of our ability to quickly detect whitespace in this early stage. We can use another bit-vector based
|
||||
transformation to discover locations in our data that follow a structural character or quote or whitespace and are not whitespace. Excluding locations within strings, and the structural characters we have already discovered,
|
||||
these locations are the only place that we can expect to see the starts of the JSON 'atoms'. These locations are thus
|
||||
treated as 'structural' ('pseudo-structural characters').
|
||||
|
||||
This stage involves either SIMD processing over bytes or the manipulation of bit arrays that have 1 bit corresponding
|
||||
to 1 byte of input. As such, it can be quite inefficient for some inputs - it is possible to observe dozens of operations
|
||||
taking place to discover that there are in fact no odd-numbered sequences of backslashes or quotes in a given block of
|
||||
input. However, this inefficiency on such inputs is balanced by the fact that it costs no more to run this code over
|
||||
complex structured input, and the alternatives would generally involve running a number of unpredictable branches (for
|
||||
example, the loop branches in Mison that iterate over bit vectors).
|
||||
|
||||
### Stage 2: The transition from "SIMD over bytes" to "indices"
|
||||
|
||||
Our structural, pseudo-structural and other 'interesting' characters are relatively rare (TODO: quantify in detail -
|
||||
it's typically about 1 in 10). As such, continuing to process them as bit vectors will involve manipulating data structures
|
||||
that are relatively large as well as being fairly unpredictably spaced. We must transform these bitvectors of "interesting"
|
||||
locations into offsets.
|
||||
|
||||
Note that we can examine the character at the offset to discover what the original function of the item in the bitvector
|
||||
was. While the JSON structural characters and quotes are relatively self-explanatory (although working only with one offset
|
||||
at a time, we have lost the distinction between opening quotes and closing quotes, something that was available in Stage 1),
|
||||
it is a quirk of JSON that the legal atoms can all be distinguished from each other by their first character - 't' for
|
||||
'true', 'f' for 'false', 'n' for 'null' and the character class [0-9-] for numerical values.
|
||||
|
||||
Thus, the offset suffices, as long as we retain our original input.
|
||||
|
||||
Our current implementation involves a straightforward transformation of bitmaps to indices by use of the 'count trailing
|
||||
zeros' operation and the well-known operation to clear the lowest set bit. Note that this implementation introduces an
|
||||
unpredictable branch; unless there is a regular pattern in our bitmaps, we would expect to have at least one branch miss
|
||||
for each bitmap.
|
||||
|
||||
### Stage 3: Operation over indices
|
||||
|
||||
This now works over a dual structure.
|
||||
|
||||
1. The "state machine", whose role it is to validate the sequence of structural characters and ensure that the input is at least generally structured like valid JSON (after this stage, the only errors permissible should be malformed atoms and numbers). If and only if the "state machine" reached all accept states, then,
|
||||
|
||||
2. The "tape machine" will have produced valid output. The tape machine works blindly over characters writing records to tapes. These records create a lean but somewhat traversable linked structure that, for valid inputs, should represent what we need to know about the JSON input.
|
||||
|
||||
FIXME: a lot more detail is required on the operation of both these machines.
|
||||
@@ -1,576 +1,186 @@
|
||||
# simdjson : Parsing gigabytes of JSON per second
|
||||
[](https://cloud.drone.io/lemire/simdjson/)
|
||||
[](https://circleci.com/gh/lemire/simdjson)
|
||||
[](https://ci.appveyor.com/project/lemire/simdjson)
|
||||
[](https://cloud.drone.io/simdjson/simdjson)
|
||||
[](https://circleci.com/gh/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)
|
||||
[![][license img]][license]
|
||||
[](https://lgtm.com/projects/g/lemire/simdjson/context:cpp)
|
||||
|
||||
simdjson : Parsing gigabytes of JSON per second
|
||||
===============================================
|
||||
|
||||
## A C++ library to see how fast we can parse JSON with complete validation.
|
||||
<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.
|
||||
|
||||
JSON documents are everywhere on the Internet. Servers spend a lot of time parsing these documents. We want to accelerate the parsing of JSON per se using commonly available SIMD instructions as much as possible while doing full validation (including character encoding).
|
||||
* **Fast:** Over 2.5x faster than other production-grade JSON parsers.
|
||||
* **Easy:** First-class, easy to use API.
|
||||
* **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.
|
||||
|
||||
<img src="images/logo.png" width="10%">
|
||||
This library is part of the [Awesome Modern C++](https://awesomecpp.com) list.
|
||||
|
||||
Table of Contents
|
||||
-----------------
|
||||
|
||||
## Real-world usage
|
||||
* [Quick Start](#quick-start)
|
||||
* [Documentation](#documentation)
|
||||
* [Performance results](#performance-results)
|
||||
* [Real-world usage](#real-world-usage)
|
||||
* [Bindings and Ports of simdjson](#bindings-and-ports-of-simdjson)
|
||||
* [About simdjson](#about-simdjson)
|
||||
* [Funding](#funding)
|
||||
* [Contributing to simdjson](#contributing-to-simdjson)
|
||||
* [License](#license)
|
||||
|
||||
- [Microsoft FishStore](https://github.com/microsoft/FishStore)
|
||||
- [Yandex ClickHouse](https://github.com/yandex/ClickHouse)
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
## Paper
|
||||
|
||||
The simdjson library is easily consumable with a single .h and .cpp file.
|
||||
|
||||
A description of the design and implementation of simdjson appears at https://arxiv.org/abs/1902.08318 and an informal blog post providing some background and context is at https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/.
|
||||
0. Prerequisites: `g++` (version 7 or better) or `clang++` (version 6 or better), and a 64-bit system.
|
||||
1. Pull [simdjson.h](singleheader/simdjson.h) and [simdjson.cpp](singleheader/simdjson.cpp) into a directory, along with the sample file [twitter.json](jsonexamples/twitter.json).
|
||||
```
|
||||
wget https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.h https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.cpp https://raw.githubusercontent.com/simdjson/simdjson/master/jsonexamples/twitter.json
|
||||
```
|
||||
2. Create `quickstart.cpp`:
|
||||
|
||||
Some people [enjoy reading our paper](https://arxiv.org/abs/1902.08318):
|
||||
```c++
|
||||
#include "simdjson.h"
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets = parser.load("twitter.json");
|
||||
std::cout << tweets["search_metadata"]["count"] << " results." << std::endl;
|
||||
}
|
||||
```
|
||||
3. `c++ -o quickstart quickstart.cpp simdjson.cpp -std=c++17`
|
||||
4. `./quickstart`
|
||||
```
|
||||
100 results.
|
||||
```
|
||||
|
||||
[<img src="images/halvarflake.png" width="50%">](https://twitter.com/halvarflake/status/1118459536686362625)
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Usage documentation is available:
|
||||
|
||||
## Performance results
|
||||
* [Basics](doc/basics.md) is an overview of how to use simdjson and its APIs.
|
||||
* [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.0/annotated.html) contains the automatically generated API documentation.
|
||||
|
||||
simdjson uses three-quarters less instructions than state-of-the-art parser RapidJSON and fifty percent less than sajson. To our knowledge, simdjson is the first fully-validating JSON parser to run at gigabytes per second on commodity processors.
|
||||
Performance results
|
||||
-------------------
|
||||
|
||||
The simdjson library uses three-quarters less instructions than state-of-the-art parser RapidJSON and
|
||||
fifty percent less than sajson. To our knowledge, simdjson is the first fully-validating JSON parser
|
||||
to run at gigabytes per second on commodity processors. It can parse millions of JSON documents
|
||||
per second on a single core.
|
||||
|
||||
The following figure represents parsing speed in GB/s for parsing various files
|
||||
on an Intel Skylake processor (3.4 GHz) using the GNU GCC 9 compiler (with the -O3 flag).
|
||||
We compare against the best and fastest C++ libraries.
|
||||
The simdjson library offers full unicode (UTF-8) validation and exact
|
||||
number parsing. The RapidJSON library is tested in two modes: fast and
|
||||
exact number parsing. The sajson library offers fast (but not exact)
|
||||
number parsing and partial unicode validation. In this data set, the file
|
||||
sizes range from 65KB (github_events) all the way to 3.3GB (gsoc-2018).
|
||||
Many files are mostly made of numbers: canada, mesh.pretty, mesh, random
|
||||
and numbers: in such instances, we see lower JSON parsing speeds due to the
|
||||
high cost of number parsing. The simdjson library uses exact number parsing which
|
||||
is particular taxing.
|
||||
|
||||
<img src="doc/gbps.png" width="90%">
|
||||
|
||||
On a Skylake processor, the parsing speeds (in GB/s) of various processors on the twitter.json file are as follows.
|
||||
On a Skylake processor, the parsing speeds (in GB/s) of various processors on the twitter.json file are as follows, using again GNU GCC 9.1 (with the -O3 flag). The popular JSON for Modern C++ library is particularly slow: it obviously trades parsing speed for other desirable features.
|
||||
|
||||
| parser | GB/s |
|
||||
| ------------------------------------- | ---- |
|
||||
| simdjson | 2.2 |
|
||||
| RapidJSON encoding-validation | 0.51 |
|
||||
| RapidJSON encoding-validation, insitu | 0.71 |
|
||||
| sajson (insitu, dynamic) | 0.70 |
|
||||
| sajson (insitu, static) | 0.97 |
|
||||
| dropbox | 0.14 |
|
||||
| fastjson | 0.26 |
|
||||
| gason | 0.85 |
|
||||
| ultrajson | 0.42 |
|
||||
| jsmn | 0.28 |
|
||||
| cJSON | 0.34 |
|
||||
| simdjson | 2.5 |
|
||||
| RapidJSON UTF8-validation | 0.29 |
|
||||
| RapidJSON UTF8-valid., exact numbers | 0.28 |
|
||||
| RapidJSON insitu, UTF8-validation | 0.41 |
|
||||
| RapidJSON insitu, UTF8-valid., exact | 0.39 |
|
||||
| sajson (insitu, dynamic) | 0.62 |
|
||||
| sajson (insitu, static) | 0.88 |
|
||||
| dropbox | 0.13 |
|
||||
| fastjson | 0.27 |
|
||||
| gason | 0.59 |
|
||||
| ultrajson | 0.34 |
|
||||
| jsmn | 0.25 |
|
||||
| cJSON | 0.31 |
|
||||
| JSON for Modern C++ (nlohmann/json) | 0.11 |
|
||||
|
||||
## Requirements
|
||||
|
||||
- We support platforms like Linux or macOS, as well as Windows through Visual Studio 2017 or later.
|
||||
- A processor with
|
||||
- AVX2 (i.e., Intel processors starting with the Haswell microarchitecture released 2013 and AMD processors starting with the Zen microarchitecture released 2017),
|
||||
- or 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)
|
||||
- or a 64-bit ARM processor (ARMv8-A): this covers a wide range of mobile processors, including all Apple processors currently available for sale, going back as far back as the iPhone 5s (2013).
|
||||
- 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.
|
||||
The simdjson library offers high speed whether it processes tiny files (e.g., 300 bytes)
|
||||
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%">
|
||||
|
||||
## License
|
||||
|
||||
[All our experiments are reproducible](https://github.com/simdjson/simdjson_experiments_vldb2019).
|
||||
|
||||
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)
|
||||
|
||||
If you are planning to use simdjson in a product, please work from one of our releases.
|
||||
|
||||
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/simd-lite): 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.
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
|
||||
The simdjson library takes advantage of modern microarchitectures, parallelizing with SIMD vector
|
||||
instructions, reducing branch misprediction, and reducing data dependency to take advantage of each
|
||||
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.
|
||||
|
||||
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/).
|
||||
|
||||
For the video inclined, <br />
|
||||
[](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
(it was the best voted talk, we're kinda proud of it).
|
||||
|
||||
Funding
|
||||
-------
|
||||
|
||||
The work is supported by the Natural Sciences and Engineering Research Council of Canada under grant number RGPIN-2017-03910.
|
||||
|
||||
[license]: LICENSE
|
||||
[license img]: https://img.shields.io/badge/License-Apache%202-blue.svg
|
||||
|
||||
Contributing to simdjson
|
||||
------------------------
|
||||
|
||||
Head over to [CONTRIBUTING.md](CONTRIBUTING.md) for information on contributing to simdjson, and
|
||||
[HACKING.md](HACKING.md) for information on source, building, and architecture/design.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This code is made available under the Apache License 2.0.
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it under the liberal (business-friendly) MIT license.
|
||||
|
||||
## Code usage and example
|
||||
|
||||
The main API involves populating a `ParsedJson` object which hosts a fully navigable document-object-model (DOM) view of the JSON document. The DOM can be accessed using [JSON Pointer](https://tools.ietf.org/html/rfc6901) paths, for example. The main function is `json_parse` which takes a string containing the JSON document as well as a reference to pre-allocated `ParsedJson` object (which can be reused multiple time). Once you have populated the `ParsedJson` object you can navigate through the DOM with an iterator (e.g., created by `ParsedJson::Iterator pjh(pj)`, see 'Navigating the parsed document').
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
const char * filename = ... //
|
||||
|
||||
// use whatever means you want to get a string (UTF-8) of your JSON document
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj;
|
||||
pj.allocate_capacity(p.size()); // allocate memory for parsing up to p.size() bytes
|
||||
const int res = json_parse(p, pj); // do the parsing, return 0 on success
|
||||
// parsing is done!
|
||||
if (res != 0) {
|
||||
// You can use the "simdjson/simdjson.h" header to access the error message
|
||||
std::cout << "Error parsing:" << simdjson::error_message(res) << std::endl;
|
||||
}
|
||||
// the ParsedJson document can be used here
|
||||
// pj can be reused with other json_parse calls.
|
||||
```
|
||||
|
||||
It is also possible to use a simpler API if you do not mind having the overhead
|
||||
of memory allocation with each new JSON document:
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
const char * filename = ... //
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj = build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
// something went wrong
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Though the `padded_string` class is recommended for best performance, you can call `json_parse` and `build_parsed_json`, passing a standard `std::string` object.
|
||||
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
std::string mystring = ... //
|
||||
ParsedJson pj;
|
||||
pj.allocate_capacity(mystring.size()); // allocate memory for parsing up to p.size() bytes
|
||||
// std::string may not overallocate so a copy will be needed
|
||||
const int res = json_parse(mystring, pj); // do the parsing, return 0 on success
|
||||
// parsing is done!
|
||||
if (res != 0) {
|
||||
// You can use the "simdjson/simdjson.h" header to access the error message
|
||||
std::cout << "Error parsing:" << simdjson::error_message(res) << std::endl;
|
||||
}
|
||||
// pj can be reused with other json_parse calls.
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
std::string mystring = ... //
|
||||
// std::string may not overallocate so a copy will be needed
|
||||
ParsedJson pj = build_parsed_json(mystring); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
// something went wrong
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
As needed, the `json_parse` and `build_parsed_json` functions copy the input data to a temporary buffer readable up to SIMDJSON_PADDING bytes beyond the end of the data.
|
||||
|
||||
## Usage: easy single-header version
|
||||
|
||||
See the "singleheader" repository for a single header version. See the included
|
||||
file "amalgamation_demo.cpp" for usage. This requires no specific build system: just
|
||||
copy the files in your project in your include path. You can then include them quite simply:
|
||||
|
||||
```C
|
||||
#include <iostream>
|
||||
#include "simdjson.h"
|
||||
#include "simdjson.cpp"
|
||||
using namespace simdjson;
|
||||
int main(int argc, char *argv[]) {
|
||||
const char * filename = argv[1];
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj = build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
std::cout << "not valid" << std::endl;
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
} else {
|
||||
std::cout << "valid" << std::endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Note: In some settings, it might be desirable to precompile `simdjson.cpp` instead of including it.
|
||||
|
||||
## Runtime dispatch
|
||||
|
||||
On Intel and AMD processors, we get best performance by using the hardware support for AVX2 instructions. However, simdjson also
|
||||
runs on older Intel and AMD processors. We require a minimum feature support of SSE 4.2 and CLMUL (2010 Intel Westmere or better).
|
||||
The code automatically detects the feature set of your processor and switches to the right function at runtime (a technical
|
||||
sometimes called runtime dispatch).
|
||||
|
||||
|
||||
We also support 64-bit ARM. We assume NEON support, and if the cryptographic extension is available, we leverage it, at compile-time.
|
||||
There is no runtime dispatch on ARM.
|
||||
|
||||
|
||||
## Usage (old-school Makefile on platforms like Linux or macOS)
|
||||
|
||||
Requirements: recent clang or gcc, and make. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. A system like Linux or macOS is expected.
|
||||
|
||||
To test:
|
||||
|
||||
```
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
To run benchmarks:
|
||||
|
||||
```
|
||||
make parse
|
||||
./parse jsonexamples/twitter.json
|
||||
```
|
||||
|
||||
Under Linux, the `parse` command gives a detailed analysis of the performance counters.
|
||||
|
||||
To run comparative benchmarks (with other parsers):
|
||||
|
||||
```
|
||||
make benchmark
|
||||
```
|
||||
|
||||
## Usage (CMake on platforms like Linux or macOS)
|
||||
|
||||
Requirements: We require a recent version of cmake. On macOS, the easiest way to install cmake might be to use [brew](https://brew.sh) and then type
|
||||
|
||||
```
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
There is an [equivalent brew on Linux which works the same way as well](https://linuxbrew.sh).
|
||||
|
||||
You need a recent compiler like clang or gcc. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. For example, you can install a recent compiler with brew:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
```
|
||||
|
||||
Optional: You need to tell cmake which compiler you wish to use by setting the CC and CXX variables. Under bash, you can do so with commands such as `export CC=gcc-7` and `export CXX=g++-7`.
|
||||
|
||||
Building: While in the project repository, do the following:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
CMake will build a library. By default, it builds a shared library (e.g., libsimdjson.so on Linux).
|
||||
|
||||
You can build a static library:
|
||||
|
||||
```
|
||||
mkdir buildstatic
|
||||
cd buildstatic
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
In some cases, you may want to specify your compiler, especially if the default compiler on your system is too old. You may proceed as follows:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
mkdir build
|
||||
cd build
|
||||
export CXX=g++-8 CC=gcc-8
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
## Usage (CMake on Windows using Visual Studio)
|
||||
|
||||
We assume you have a common 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).
|
||||
|
||||
- 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 `VisualStudio`.
|
||||
- Using a shell, go to this newly created directory.
|
||||
- Type `cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..` in the shell while in the `VisualStudio` repository. (Alternatively, if you want to build a DLL, you may use the command line `cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF ..`.)
|
||||
- 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`.
|
||||
|
||||
|
||||
|
||||
## Usage (Using `vcpkg` on 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 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 submiting an issue or by creating a PR.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
## 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).
|
||||
- Performance is optimized for JSON documents spanning at least a tens kilobytes up to many megabytes: the performance issues with having to parse many tiny JSON documents or one truly enormous JSON document are different.
|
||||
|
||||
_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.
|
||||
|
||||
## 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 64-bit integers in [-9223372036854775808,9223372036854775808), like a Java `long` or a C/C++ `long long`. 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 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 bound on the [unit of least precision (ULP)](https://en.wikipedia.org/wiki/Unit_in_the_last_place) of one. Practically speaking, this implies 15 digits of accuracy or better.
|
||||
- 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.
|
||||
|
||||
## JSON Pointer
|
||||
|
||||
We can navigate the parsed JSON using JSON Pointers as per the [RFC6901 standard](https://tools.ietf.org/html/rfc6901).
|
||||
|
||||
You can build a tool (jsonpointer) to parse a JSON document and then issue an array of JSON Pointer queries:
|
||||
|
||||
```
|
||||
make jsonpointer
|
||||
./jsonpointer jsonexamples/small/demo.json /Image/Width /Image/Height /Image/IDs/2
|
||||
./jsonpointer jsonexamples/twitter.json /statuses/0/id /statuses/1/id /statuses/2/id /statuses/3/id /statuses/4/id /statuses/5/id
|
||||
```
|
||||
|
||||
In C++, given a `ParsedJson`, we can move to a node with the `move_to` method, passing a `std::string` representing the JSON Pointer query.
|
||||
|
||||
## Navigating the parsed document
|
||||
|
||||
Here is a code sample to dump back the parsed JSON to a string:
|
||||
|
||||
```c
|
||||
ParsedJson::Iterator pjh(pj);
|
||||
if (!pjh.is_ok()) {
|
||||
std::cerr << " Could not iterate parsed result. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
compute_dump(pj);
|
||||
//
|
||||
// where compute_dump is :
|
||||
|
||||
void compute_dump(ParsedJson::Iterator &pjh) {
|
||||
if (pjh.is_object()) {
|
||||
std::cout << "{";
|
||||
if (pjh.down()) {
|
||||
pjh.print(std::cout); // must be a string
|
||||
std::cout << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
std::cout << ",";
|
||||
pjh.print(std::cout);
|
||||
std::cout << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
std::cout << "}";
|
||||
} else if (pjh.is_array()) {
|
||||
std::cout << "[";
|
||||
if (pjh.down()) {
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
std::cout << ",";
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
std::cout << "]";
|
||||
} else {
|
||||
pjh.print(std::cout); // just print the lone value
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following function will find all user.id integers:
|
||||
|
||||
```C
|
||||
void simdjson_scan(std::vector<int64_t> &answer, ParsedJson::Iterator &i) {
|
||||
while(i.move_forward()) {
|
||||
if(i.get_scope_type() == '{') {
|
||||
bool found_user = (i.get_string_length() == 4) && (memcmp(i.get_string(), "user", 4) == 0);
|
||||
i.move_to_value();
|
||||
if(found_user) {
|
||||
if(i.is_object() && i.move_to_key("id",2)) {
|
||||
if (i.is_integer()) {
|
||||
answer.push_back(i.get_integer());
|
||||
}
|
||||
i.up();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## In-depth comparisons
|
||||
|
||||
If you want to see how a wide range of parsers validate a given JSON file:
|
||||
|
||||
```
|
||||
make allparserscheckfile
|
||||
./allparserscheckfile myfile.json
|
||||
```
|
||||
|
||||
For performance comparisons:
|
||||
|
||||
```
|
||||
make parsingcompetition
|
||||
./parsingcompetition myfile.json
|
||||
```
|
||||
|
||||
For broader comparisons:
|
||||
|
||||
```
|
||||
make allparsingcompetition
|
||||
./allparsingcompetition myfile.json
|
||||
```
|
||||
|
||||
Both the `parsingcompetition` and `allparsingcompetition` tools take a `-t` flag which produces
|
||||
a table-oriented output that can be conventiently parsed by other tools.
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
One can run tests and benchmarks using docker. It especially makes sense under Linux. A privileged access may be needed to get performance counters.
|
||||
|
||||
```
|
||||
git clone https://github.com/lemire/simdjson.git
|
||||
cd simdjson
|
||||
docker build -t simdjson .
|
||||
docker run --privileged -t simdjson
|
||||
```
|
||||
|
||||
## Other programming languages
|
||||
|
||||
We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything).
|
||||
|
||||
- [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.
|
||||
|
||||
## 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
|
||||
- GoogleGson https://github.com/google/gson
|
||||
- Jackson https://github.com/FasterXML/jackson
|
||||
- https://www.yelp.com/dataset_challenge
|
||||
- RapidJSON. http://rapidjson.org/
|
||||
|
||||
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/
|
||||
|
||||
## 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 }
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
## Funding
|
||||
|
||||
The work is supported by the Natural Sciences and Engineering Research Council of Canada under grant number RGPIN-2017-03910.
|
||||
|
||||
|
||||
[license]: LICENSE
|
||||
[license img]: https://img.shields.io/badge/License-Apache%202-blue.svg
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# 0.3
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Multi-Document Parsing:** Read a bundle of JSON documents (ndjson) 2-4x faster than doing it
|
||||
individually. [API docs](https://github.com/simdjson/simdjson/blob/master/doc/basics.md#newline-delimited-json-ndjson-and-json-lines) / [Design Details](https://github.com/simdjson/simdjson/blob/master/doc/parse_many.md)
|
||||
- **Simplified API:** The API has been completely revamped for ease of use, including a new JSON
|
||||
navigation API and fluent support for error code *and* exception styles of error handling with a
|
||||
single API. [Docs](https://github.com/simdjson/simdjson/blob/master/doc/basics.md#the-basics-loading-and-parsing-json-documents)
|
||||
- **Exact Float Parsing:** Now simdjson parses floats flawlessly *without* any performance loss,
|
||||
thanks to [great work by @michaeleisel and @lemire](https://github.com/simdjson/simdjson/pull/558).
|
||||
[Blog Post](https://lemire.me/blog/2020/03/10/fast-float-parsing-in-practice/)
|
||||
- **Even Faster:** The fastest parser got faster! With a [shiny new UTF-8 validator](https://github.com/simdjson/simdjson/pull/387)
|
||||
and meticulously refactored SIMD core, simdjson 0.3 is 15% faster than before, running at 2.5 GB/s
|
||||
(where 0.2 ran at 2.2 GB/s).
|
||||
|
||||
## Minor Highlights
|
||||
|
||||
- Fallback implementation: simdjson now has a non-SIMD fallback implementation, and can run even on
|
||||
very old 64-bit machines.
|
||||
- Automatic allocation: as part of API simplification, the parser no longer has to be preallocated--
|
||||
it will adjust automatically when it encounters larger files.
|
||||
- Runtime selection API: We've exposed simdjson's runtime CPU detection and implementation selection
|
||||
as an API, so you can tell what implementation we detected and test with other implementations.
|
||||
- Error handling your way: Whether you use exceptions or check error codes, simdjson lets you handle
|
||||
errors in your style. APIs that can fail return simdjson_result<T>, letting you check the error
|
||||
code before using the result. But if you are more comfortable with exceptions, skip the error code
|
||||
and cast straight to T, and exceptions will be thrown automatically if an error happens. Use the
|
||||
same API either way!
|
||||
- Error chaining: We also worked to keep non-exception error-handling short and sweet. Instead of
|
||||
having to check the error code after every single operation, now you can *chain* JSON navigation
|
||||
calls like looking up an object field or array element, or casting to a string, so that you only
|
||||
have to check the error code once at the very end.
|
||||
+86
-59
@@ -11,78 +11,88 @@ echo "See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/
|
||||
AMAL_H="simdjson.h"
|
||||
AMAL_C="simdjson.cpp"
|
||||
|
||||
# order does not matter
|
||||
SRCPATH="$SCRIPTPATH/src"
|
||||
INCLUDEPATH="$SCRIPTPATH/include"
|
||||
|
||||
# this list excludes the "src/generic headers"
|
||||
ALLCFILES="
|
||||
$SCRIPTPATH/src/simdjson.cpp
|
||||
$SCRIPTPATH/src/jsonioutil.cpp
|
||||
$SCRIPTPATH/src/jsonminifier.cpp
|
||||
$SCRIPTPATH/src/jsonparser.cpp
|
||||
$SCRIPTPATH/src/stage1_find_marks.cpp
|
||||
$SCRIPTPATH/src/stage2_build_tape.cpp
|
||||
$SCRIPTPATH/src/parsedjson.cpp
|
||||
$SCRIPTPATH/src/parsedjsoniterator.cpp
|
||||
simdjson.cpp
|
||||
"
|
||||
|
||||
# order matters
|
||||
ALLCHEADERS="
|
||||
$SCRIPTPATH/include/simdjson/simdjson_version.h
|
||||
$SCRIPTPATH/include/simdjson/portability.h
|
||||
$SCRIPTPATH/include/simdjson/isadetection.h
|
||||
$SCRIPTPATH/include/simdjson/simdjson.h
|
||||
$SCRIPTPATH/include/simdjson/common_defs.h
|
||||
$SCRIPTPATH/include/simdjson/padded_string.h
|
||||
$SCRIPTPATH/include/simdjson/jsoncharutils.h
|
||||
$SCRIPTPATH/include/simdjson/jsonformatutils.h
|
||||
$SCRIPTPATH/include/simdjson/jsonioutil.h
|
||||
$SCRIPTPATH/include/simdjson/simdprune_tables.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/jsonminifier.h
|
||||
$SCRIPTPATH/include/simdjson/parsedjson.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_flatten.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_flatten_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_macros.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_macros.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/numberparsing.h
|
||||
$SCRIPTPATH/include/simdjson/stage2_build_tape.h
|
||||
$SCRIPTPATH/include/simdjson/jsonparser.h
|
||||
simdjson.h
|
||||
"
|
||||
|
||||
for i in ${ALLCHEADERS} ${ALLCFILES}; do
|
||||
test -e $i && continue
|
||||
echo "FATAL: source file [$i] not found."
|
||||
found_includes=()
|
||||
|
||||
for file in ${ALLCFILES}; do
|
||||
test -e "$SRCPATH/$file" && continue
|
||||
echo "FATAL: source file [$SRCPATH/$file] not found."
|
||||
exit 127
|
||||
done
|
||||
|
||||
for file in ${ALLCHEADERS}; do
|
||||
test -e "$INCLUDEPATH/$file" && continue
|
||||
echo "FATAL: source file [$INCLUDEPATH/$file] not found."
|
||||
exit 127
|
||||
done
|
||||
|
||||
function stripinc()
|
||||
function doinclude()
|
||||
{
|
||||
sed -e '/# *include *"/d' -e '/# *include *<simdjson\//d'
|
||||
file=$1
|
||||
line="${@:2}"
|
||||
if [ -f $INCLUDEPATH/$file ]; then
|
||||
if [[ ! " ${found_includes[@]} " =~ " ${file} " ]]; then
|
||||
found_includes+=("$file")
|
||||
dofile $INCLUDEPATH/$file
|
||||
fi;
|
||||
elif [ -f $SRCPATH/$file ]; then
|
||||
# generic includes are included multiple times
|
||||
if [[ "${file}" == *'generic/'*'.h' ]]; then
|
||||
dofile $SRCPATH/$file
|
||||
elif [[ ! " ${found_includes[@]} " =~ " ${file} " ]]; then
|
||||
found_includes+=("$file")
|
||||
dofile $SRCPATH/$file
|
||||
else
|
||||
echo "/* $file already included: $line */"
|
||||
fi
|
||||
else
|
||||
# If we don't recognize it, just emit the #include
|
||||
echo "$line"
|
||||
fi
|
||||
}
|
||||
|
||||
function dofile()
|
||||
{
|
||||
# Last lines are always ignored. Files should end by an empty lines.
|
||||
RELFILE=${1#"$SCRIPTPATH/"}
|
||||
echo "/* begin file $RELFILE */"
|
||||
# echo "#line 8 \"$1\"" ## redefining the line/file is not nearly as useful as it sounds for debugging. It breaks IDEs.
|
||||
stripinc < $1
|
||||
while IFS= read -r line || [ -n "$line" ];
|
||||
do
|
||||
if [[ "${line}" == '#include "'*'"'* ]]; then
|
||||
file=$(echo $line| cut -d'"' -f 2)
|
||||
|
||||
if [[ "${file}" == '../'* ]]; then
|
||||
file=$(echo $file| cut -d'/' -f 2-)
|
||||
fi;
|
||||
|
||||
# we explicitly include simdjson headers, one time each (unless they are generic, in which case multiple times is fine)
|
||||
doinclude $file $line
|
||||
else
|
||||
# Otherwise we simply copy the line
|
||||
echo "$line"
|
||||
fi
|
||||
done < "$1"
|
||||
echo "/* end file $RELFILE */"
|
||||
}
|
||||
|
||||
timestamp=$(date)
|
||||
echo "Creating ${AMAL_H}..."
|
||||
echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${AMAL_H}"
|
||||
{
|
||||
for h in ${ALLCHEADERS}; do
|
||||
dofile $h
|
||||
doinclude $h "ERROR $h not found"
|
||||
done
|
||||
} >> "${AMAL_H}"
|
||||
|
||||
@@ -99,13 +109,12 @@ echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${AMAL_C}"
|
||||
echo "#endif"
|
||||
echo ""
|
||||
|
||||
for h in ${ALLCFILES}; do
|
||||
dofile $h
|
||||
for file in ${ALLCFILES}; do
|
||||
dofile "$SRCPATH/$file"
|
||||
done
|
||||
} >> "${AMAL_C}"
|
||||
|
||||
|
||||
|
||||
DEMOCPP="amalgamation_demo.cpp"
|
||||
echo "Creating ${DEMOCPP}..."
|
||||
echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${DEMOCPP}"
|
||||
@@ -115,15 +124,33 @@ cat <<< '
|
||||
#include "simdjson.cpp"
|
||||
int main(int argc, char *argv[]) {
|
||||
if(argc < 2) {
|
||||
std::cerr << "Please specify a filename " << std::endl;
|
||||
std::cerr << "Please specify at least one file name. " << std::endl;
|
||||
}
|
||||
const char * filename = argv[1];
|
||||
simdjson::padded_string p = simdjson::get_corpus(filename);
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
std::cout << "not valid" << std::endl;
|
||||
simdjson::dom::parser parser;
|
||||
auto [doc, error] = parser.load(filename); // do the parsing
|
||||
if (error) {
|
||||
std::cout << "parse failed" << std::endl;
|
||||
std::cout << "error code: " << error << std::endl;
|
||||
std::cout << error << std::endl;
|
||||
} else {
|
||||
std::cout << "valid" << std::endl;
|
||||
std::cout << "parse valid" << std::endl;
|
||||
}
|
||||
if(argc == 2) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// parse_many
|
||||
const char * filename2 = argv[2];
|
||||
for (auto result : parser.load_many(filename2)) {
|
||||
error = result.error();
|
||||
}
|
||||
if (error) {
|
||||
std::cout << "parse_many failed" << std::endl;
|
||||
std::cout << "error code: " << error << std::endl;
|
||||
std::cout << error << std::endl;
|
||||
} else {
|
||||
std::cout << "parse_many valid" << std::endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -140,16 +167,16 @@ echo "Giving final instructions:"
|
||||
CPPBIN=${DEMOCPP%%.*}
|
||||
|
||||
echo "Try :"
|
||||
echo "c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json "
|
||||
echo "c++ -O3 -std=c++17 -pthread -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json ../jsonexamples/amazon_cellphones.ndjson"
|
||||
|
||||
SINGLEHDR=$SCRIPTPATH/singleheader
|
||||
echo "Copying files to $SCRIPTPATH/singleheader "
|
||||
mkdir -p $SINGLEHDR
|
||||
echo "c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json " > $SINGLEHDR/README.md
|
||||
echo "c++ -O3 -std=c++17 -pthread -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json ../jsonexamples/amazon_cellphones.ndjson" > $SINGLEHDR/README.md
|
||||
cp ${AMAL_C} ${AMAL_H} ${DEMOCPP} $SINGLEHDR
|
||||
ls $SINGLEHDR
|
||||
|
||||
cd $SINGLEHDR && c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json
|
||||
cd $SINGLEHDR && c++ -O3 -std=c++17 -pthread -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json ../jsonexamples/amazon_cellphones.ndjson
|
||||
|
||||
lowercase(){
|
||||
echo "$1" | tr 'A-Z' 'a-z'
|
||||
|
||||
@@ -6,3 +6,16 @@ target_include_directories(${SIMDJSON_LIB_NAME}
|
||||
|
||||
add_cpp_benchmark(parse)
|
||||
add_cpp_benchmark(statisticalmodel)
|
||||
add_cpp_benchmark(parse_stream)
|
||||
add_cpp_benchmark(get_corpus_benchmark)
|
||||
add_executable(perfdiff perfdiff.cpp)
|
||||
|
||||
# Google Benchmarks
|
||||
if (SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
add_cpp_benchmark(bench_parse_call)
|
||||
target_link_libraries(bench_parse_call benchmark::benchmark)
|
||||
|
||||
add_cpp_benchmark(bench_dom_api)
|
||||
target_link_libraries(bench_dom_api benchmark::benchmark)
|
||||
target_compile_definitions(bench_dom_api PRIVATE JSON_TEST_PATH="${PROJECT_SOURCE_DIR}/jsonexamples/twitter.json")
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
# From the ROOT, run:
|
||||
# docker build -t simdjsonbench -f benchmark/Dockerfile . && docker run --privileged -t simdjsonbench
|
||||
FROM gcc:8.3
|
||||
|
||||
# # Build latest
|
||||
# ENV latest_release=v0.2.1
|
||||
# WORKDIR /usr/src/$latest_release/
|
||||
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ -b $latest_release .
|
||||
# RUN make parse
|
||||
|
||||
# # Build master
|
||||
# WORKDIR /usr/src/master/
|
||||
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ .
|
||||
# RUN make parse
|
||||
|
||||
# Build the current source
|
||||
COPY . /usr/src/current/
|
||||
WORKDIR /usr/src/current/
|
||||
RUN make checkperf
|
||||
@@ -0,0 +1,268 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include "simdjson.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace benchmark;
|
||||
using namespace std;
|
||||
|
||||
#ifndef JSON_TEST_PATH
|
||||
#define JSON_TEST_PATH "jsonexamples/twitter.json"
|
||||
#endif
|
||||
|
||||
const padded_string EMPTY_ARRAY("[]", 2);
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_count);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
padded_string json = padded_string::load(JSON_TEST_PATH);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (auto _ : state) {
|
||||
ParsedJson::Iterator iter(pj);
|
||||
// uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (!iter.move_to_key("search_metadata")) { return; }
|
||||
if (!iter.move_to_key("count")) { return; }
|
||||
if (!iter.is_integer()) { return; }
|
||||
int64_t result_count = iter.get_integer();
|
||||
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(iterator_twitter_count);
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
static void twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
for (dom::object tweet : doc["statuses"].get<dom::array>()) {
|
||||
dom::object user = tweet["user"];
|
||||
if (user["default_profile"]) {
|
||||
default_users.insert(user["screen_name"]);
|
||||
}
|
||||
}
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_default_profile);
|
||||
|
||||
static void twitter_image_sizes(State& state) {
|
||||
// Count unique image sizes
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
for (dom::object tweet : doc["statuses"].get<dom::array>()) {
|
||||
auto [media, not_found] = tweet["entities"]["media"];
|
||||
if (!not_found) {
|
||||
for (dom::object image : media.get<dom::array>()) {
|
||||
for (auto [key, size] : image["sizes"].get<dom::object>()) {
|
||||
image_sizes.insert({ size["w"], size["h"] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_image_sizes);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void error_code_twitter_count(State& state) noexcept {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
auto [value, error] = doc["search_metadata"]["count"].get<uint64_t>();
|
||||
if (error) { return; }
|
||||
if (value != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_count);
|
||||
|
||||
static void error_code_twitter_default_profile(State& state) noexcept {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
|
||||
auto [tweets, error] = doc["statuses"].get<dom::array>();
|
||||
if (error) { return; }
|
||||
for (dom::element tweet : tweets) {
|
||||
auto [user, error2] = tweet["user"].get<dom::object>();
|
||||
if (error2) { return; }
|
||||
auto [default_profile, error3] = user["default_profile"].get<bool>();
|
||||
if (error3) { return; }
|
||||
if (default_profile) {
|
||||
auto [screen_name, error4] = user["screen_name"].get<std::string_view>();
|
||||
if (error4) { return; }
|
||||
default_users.insert(screen_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_default_profile);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
padded_string json = padded_string::load(JSON_TEST_PATH);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
// for (dom::object tweet : doc["statuses"].get<dom::array>()) {
|
||||
if (!(iter.move_to_key("statuses") && iter.is_array())) { return; }
|
||||
if (iter.down()) { // first status
|
||||
do {
|
||||
|
||||
// dom::object user = tweet["user"];
|
||||
if (!(iter.move_to_key("user") && iter.is_object())) { return; }
|
||||
|
||||
// if (user["default_profile"]) {
|
||||
if (iter.move_to_key("default_profile")) {
|
||||
if (iter.is_true()) {
|
||||
if (!iter.up()) { return; } // back to user
|
||||
|
||||
// default_users.insert(user["screen_name"]);
|
||||
if (!(iter.move_to_key("screen_name") && iter.is_string())) { return; }
|
||||
default_users.insert(string_view(iter.get_string(), iter.get_string_length()));
|
||||
}
|
||||
if (!iter.up()) { return; } // back to user
|
||||
}
|
||||
|
||||
if (!iter.up()) { return; } // back to status
|
||||
|
||||
} while (iter.next()); // next status
|
||||
}
|
||||
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
BENCHMARK(iterator_twitter_default_profile);
|
||||
|
||||
static void error_code_twitter_image_sizes(State& state) noexcept {
|
||||
// Count unique image sizes
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(JSON_TEST_PATH);
|
||||
for (auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
auto [statuses, error] = doc["statuses"].get<dom::array>();
|
||||
if (error) { return; }
|
||||
for (dom::element tweet : statuses) {
|
||||
auto [images, not_found] = tweet["entities"]["media"].get<dom::array>();
|
||||
if (!not_found) {
|
||||
for (dom::element image : images) {
|
||||
auto [sizes, error2] = image["sizes"].get<dom::object>();
|
||||
if (error2) { return; }
|
||||
for (auto [key, size] : sizes) {
|
||||
auto [width, error3] = size["w"].get<uint64_t>();
|
||||
auto [height, error4] = size["h"].get<uint64_t>();
|
||||
if (error3 || error4) { return; }
|
||||
image_sizes.insert({ width, height });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_image_sizes);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_image_sizes(State& state) {
|
||||
// Count unique image sizes
|
||||
padded_string json = padded_string::load(JSON_TEST_PATH);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
// for (dom::object tweet : doc["statuses"].get<dom::array>()) {
|
||||
if (!(iter.move_to_key("statuses") && iter.is_array())) { return; }
|
||||
if (iter.down()) { // first status
|
||||
do {
|
||||
|
||||
// auto [media, not_found] = tweet["entities"]["media"];
|
||||
// if (!not_found) {
|
||||
if (iter.move_to_key("entities")) {
|
||||
if (!iter.is_object()) { return; }
|
||||
if (iter.move_to_key("media")) {
|
||||
if (!iter.is_array()) { return; }
|
||||
|
||||
// for (dom::object image : media.get<dom::array>()) {
|
||||
if (iter.down()) { // first media
|
||||
do {
|
||||
|
||||
// for (auto [key, size] : image["sizes"].get<dom::object>()) {
|
||||
if (!(iter.move_to_key("sizes") && iter.is_object())) { return; }
|
||||
if (iter.down()) { // first size
|
||||
do {
|
||||
iter.move_to_value();
|
||||
|
||||
// image_sizes.insert({ size["w"], size["h"] });
|
||||
if (!(iter.move_to_key("w")) && !iter.is_integer()) { return; }
|
||||
uint64_t width = iter.get_integer();
|
||||
if (!iter.up()) { return; } // back to size
|
||||
if (!(iter.move_to_key("h")) && !iter.is_integer()) { return; }
|
||||
uint64_t height = iter.get_integer();
|
||||
if (!iter.up()) { return; } // back to size
|
||||
image_sizes.insert({ width, height });
|
||||
|
||||
} while (iter.next()); // next size
|
||||
if (!iter.up()) { return; } // back to sizes
|
||||
}
|
||||
if (!iter.up()) { return; } // back to image
|
||||
} while (iter.next()); // next image
|
||||
if (!iter.up()) { return; } // back to media
|
||||
}
|
||||
if (!iter.up()) { return; } // back to entities
|
||||
}
|
||||
if (!iter.up()) { return; } // back to status
|
||||
}
|
||||
} while (iter.next()); // next status
|
||||
}
|
||||
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(iterator_twitter_image_sizes);
|
||||
|
||||
static void print_json(State& state) noexcept {
|
||||
// Prints the number of results in twitter.json
|
||||
padded_string json = get_corpus(JSON_TEST_PATH);
|
||||
dom::parser parser;
|
||||
if (int error = json_parse(json, parser); error != SUCCESS) { cerr << error_message(error) << endl; return; }
|
||||
for (auto _ : state) {
|
||||
std::stringstream s;
|
||||
if (!parser.print_json(s)) { cerr << "print_json failed" << endl; return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(print_json);
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
using namespace benchmark;
|
||||
using namespace std;
|
||||
|
||||
const padded_string EMPTY_ARRAY("[]", 2);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void json_parse(State& state) {
|
||||
ParsedJson pj;
|
||||
if (!pj.allocate_capacity(EMPTY_ARRAY.length())) { return; }
|
||||
for (auto _ : state) {
|
||||
auto error = json_parse(EMPTY_ARRAY, pj);
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
BENCHMARK(json_parse);
|
||||
static void parser_parse_error_code(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (auto _ : state) {
|
||||
auto [doc, error] = parser.parse(EMPTY_ARRAY);
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(parser_parse_error_code);
|
||||
static void parser_parse_exception(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (auto _ : state) {
|
||||
try {
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK(parser_parse_exception);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void build_parsed_json(State& state) {
|
||||
for (auto _ : state) {
|
||||
dom::parser parser = simdjson::build_parsed_json(EMPTY_ARRAY);
|
||||
if (!parser.valid) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
BENCHMARK(build_parsed_json);
|
||||
static void document_parse_error_code(State& state) {
|
||||
for (auto _ : state) {
|
||||
dom::parser parser;
|
||||
auto [doc, error] = parser.parse(EMPTY_ARRAY);
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(document_parse_error_code);
|
||||
static void document_parse_exception(State& state) {
|
||||
for (auto _ : state) {
|
||||
try {
|
||||
dom::parser parser;
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK(document_parse_exception);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,480 @@
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cinttypes>
|
||||
#include <initializer_list>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "benchmarker.h"
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
|
||||
// Stash the exe_name in main() for functions to use
|
||||
char* exe_name;
|
||||
|
||||
void print_usage(ostream& out) {
|
||||
out << "Usage: " << exe_name << " [-v] [-n #] [-s STAGE] [-a ARCH]" << endl;
|
||||
out << endl;
|
||||
out << "Runs the parser against jsonexamples/generated json files in a loop, measuring speed and other statistics." << endl;
|
||||
out << endl;
|
||||
out << "Options:" << endl;
|
||||
out << endl;
|
||||
out << "-n # - Number of iterations per file. Default: 400" << endl;
|
||||
out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl;
|
||||
out << "-v - Verbose output." << endl;
|
||||
out << "-s STAGE - Stop after the given stage." << endl;
|
||||
out << " -s stage1 - Stop after find_structural_bits." << endl;
|
||||
out << " -s all - Run all stages." << endl;
|
||||
out << "-a ARCH - Use the parser with the designated architecture (HASWELL, WESTMERE" << endl;
|
||||
out << " or ARM64). By default, detects best supported architecture." << endl;
|
||||
}
|
||||
|
||||
void exit_usage(string message) {
|
||||
cerr << message << endl;
|
||||
cerr << endl;
|
||||
print_usage(cerr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct option_struct {
|
||||
architecture arch = architecture::UNSUPPORTED;
|
||||
bool stage1_only = false;
|
||||
|
||||
int32_t iterations = 400;
|
||||
int32_t iteration_step = 50;
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
option_struct(int argc, char **argv) {
|
||||
#ifndef _MSC_VER
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "vtn:i:a:s:")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
iterations = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
iteration_step = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a':
|
||||
arch = parse_architecture(optarg);
|
||||
if (arch == architecture::UNSUPPORTED) {
|
||||
exit_usage(string("Unsupported option value -a ") + optarg + ": expected -a HASWELL, WESTMERE or ARM64");
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
stage1_only = true;
|
||||
} else if (!strcmp(optarg, "all")) {
|
||||
stage1_only = false;
|
||||
} else {
|
||||
exit_usage(string("Unsupported option value -s ") + optarg + ": expected -s stage1 or all");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
exit_error("Unexpected argument " + c);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int optind = 1;
|
||||
#endif
|
||||
|
||||
// If architecture is not specified, pick the best supported architecture by default
|
||||
if (arch == architecture::UNSUPPORTED) {
|
||||
arch = find_best_supported_architecture();
|
||||
}
|
||||
dom::parser::use_implementation(arch);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void each_stage(const F& f) const {
|
||||
f(BenchmarkStage::STAGE1);
|
||||
if (!this->stage1_only) {
|
||||
f(BenchmarkStage::STAGE2);
|
||||
f(BenchmarkStage::ALL);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct feature_benchmarker {
|
||||
benchmarker utf8;
|
||||
benchmarker utf8_miss;
|
||||
benchmarker escape;
|
||||
benchmarker escape_miss;
|
||||
benchmarker empty;
|
||||
benchmarker empty_miss;
|
||||
benchmarker struct7;
|
||||
benchmarker struct7_miss;
|
||||
benchmarker struct7_full;
|
||||
benchmarker struct15;
|
||||
benchmarker struct15_miss;
|
||||
benchmarker struct23;
|
||||
benchmarker struct23_miss;
|
||||
|
||||
feature_benchmarker(const simdjson::implementation &parser, event_collector& collector) :
|
||||
utf8 ("jsonexamples/generated/utf-8.json", parser, collector),
|
||||
utf8_miss ("jsonexamples/generated/utf-8-miss.json", parser, collector),
|
||||
escape ("jsonexamples/generated/escape.json", parser, collector),
|
||||
escape_miss ("jsonexamples/generated/escape-miss.json", parser, collector),
|
||||
empty ("jsonexamples/generated/0-structurals.json", parser, collector),
|
||||
empty_miss ("jsonexamples/generated/0-structurals-miss.json", parser, collector),
|
||||
struct7 ("jsonexamples/generated/7-structurals.json", parser, collector),
|
||||
struct7_miss ("jsonexamples/generated/7-structurals-miss.json", parser, collector),
|
||||
struct7_full ("jsonexamples/generated/7-structurals-full.json", parser, collector),
|
||||
struct15 ("jsonexamples/generated/15-structurals.json", parser, collector),
|
||||
struct15_miss("jsonexamples/generated/15-structurals-miss.json", parser, collector),
|
||||
struct23 ("jsonexamples/generated/23-structurals.json", parser, collector),
|
||||
struct23_miss("jsonexamples/generated/23-structurals-miss.json", parser, collector)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
utf8.run_iterations(iterations, stage1_only);
|
||||
utf8_miss.run_iterations(iterations, stage1_only);
|
||||
escape.run_iterations(iterations, stage1_only);
|
||||
escape_miss.run_iterations(iterations, stage1_only);
|
||||
empty.run_iterations(iterations, stage1_only);
|
||||
empty_miss.run_iterations(iterations, stage1_only);
|
||||
struct15.run_iterations(iterations, stage1_only);
|
||||
struct15_miss.run_iterations(iterations, stage1_only);
|
||||
struct23.run_iterations(iterations, stage1_only);
|
||||
struct23_miss.run_iterations(iterations, stage1_only);
|
||||
}
|
||||
|
||||
double cost_per_block(BenchmarkStage stage, const benchmarker& feature, size_t feature_blocks, const benchmarker& base) const {
|
||||
return (feature[stage].best.elapsed_ns() - base[stage].best.elapsed_ns()) / feature_blocks;
|
||||
}
|
||||
|
||||
// Whether we're recording cache miss and branch miss events
|
||||
bool has_events() const {
|
||||
return empty.collector.has_events();
|
||||
}
|
||||
|
||||
// Base cost of any block (including empty ones)
|
||||
double base_cost(BenchmarkStage stage) const {
|
||||
return (empty[stage].best.elapsed_ns() / empty.stats->blocks);
|
||||
}
|
||||
|
||||
// Extra cost of a 1-7 structural block over an empty block
|
||||
double struct1_7_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct7, struct7.stats->blocks_with_1_structural, empty);
|
||||
}
|
||||
// Extra cost of an 1-7-structural miss
|
||||
double struct1_7_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct7_miss, struct7_miss.stats->blocks_with_1_structural, struct7);
|
||||
}
|
||||
// Rate of 1-7-structural misses per 8-structural flip
|
||||
double struct1_7_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses()) / struct7_miss.stats->blocks_with_1_structural_flipped;
|
||||
}
|
||||
|
||||
// Extra cost of an 8-15 structural block over a 1-7 structural block
|
||||
double struct8_15_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct15, struct15.stats->blocks_with_8_structurals, struct7);
|
||||
}
|
||||
// Extra cost of an 8-15-structural miss over a 1-7 miss
|
||||
double struct8_15_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct15_miss, struct15_miss.stats->blocks_with_8_structurals_flipped, struct15);
|
||||
}
|
||||
// Rate of 8-15-structural misses per 8-structural flip
|
||||
double struct8_15_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / struct15_miss.stats->blocks_with_8_structurals_flipped;
|
||||
}
|
||||
|
||||
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
|
||||
double struct16_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct23, struct23.stats->blocks_with_16_structurals, struct15);
|
||||
}
|
||||
// Extra cost of a 16-structural miss over an 8-15 miss
|
||||
double struct16_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct23_miss, struct23_miss.stats->blocks_with_16_structurals_flipped, struct23);
|
||||
}
|
||||
// Rate of 16-structural misses per 16-structural flip
|
||||
double struct16_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / struct23_miss.stats->blocks_with_16_structurals_flipped;
|
||||
}
|
||||
|
||||
// Extra cost of having UTF-8 in a block
|
||||
double utf8_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, utf8, utf8.stats->blocks_with_utf8, struct7_full);
|
||||
}
|
||||
// Extra cost of a UTF-8 miss
|
||||
double utf8_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, utf8_miss, utf8_miss.stats->blocks_with_utf8_flipped, utf8);
|
||||
}
|
||||
// Rate of UTF-8 misses per UTF-8 flip
|
||||
double utf8_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / utf8_miss.stats->blocks_with_utf8_flipped;
|
||||
}
|
||||
|
||||
// Extra cost of having escapes in a block
|
||||
double escape_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, escape, escape.stats->blocks_with_escapes, struct7_full);
|
||||
}
|
||||
// Extra cost of an escape miss
|
||||
double escape_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, escape_miss, escape_miss.stats->blocks_with_escapes_flipped, escape);
|
||||
}
|
||||
// Rate of escape misses per escape flip
|
||||
double escape_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / escape_miss.stats->blocks_with_escapes_flipped;
|
||||
}
|
||||
|
||||
double calc_expected_feature_cost(BenchmarkStage stage, const benchmarker& file) const {
|
||||
// Expected base ns/block (empty)
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = base_cost(stage) * stats.blocks;
|
||||
expected += struct1_7_cost(stage) * stats.blocks_with_1_structural;
|
||||
expected += utf8_cost(stage) * stats.blocks_with_utf8;
|
||||
expected += escape_cost(stage) * stats.blocks_with_escapes;
|
||||
expected += struct8_15_cost(stage) * stats.blocks_with_8_structurals;
|
||||
expected += struct16_cost(stage) * stats.blocks_with_16_structurals;
|
||||
return expected / stats.blocks;
|
||||
}
|
||||
|
||||
double calc_expected_miss_cost(BenchmarkStage stage, const benchmarker& file) const {
|
||||
// Expected base ns/block (empty)
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = struct1_7_miss_cost(stage) * stats.blocks_with_1_structural_flipped * struct1_7_miss_rate(stage);
|
||||
expected += utf8_miss_cost(stage) * stats.blocks_with_utf8_flipped * utf8_miss_rate(stage);
|
||||
expected += escape_miss_cost(stage) * stats.blocks_with_escapes_flipped * escape_miss_rate(stage);
|
||||
expected += struct8_15_miss_cost(stage) * stats.blocks_with_8_structurals_flipped * struct8_15_miss_rate(stage);
|
||||
expected += struct16_miss_cost(stage) * stats.blocks_with_16_structurals_flipped * struct16_miss_rate(stage);
|
||||
return expected / stats.blocks;
|
||||
}
|
||||
|
||||
double calc_expected_misses(BenchmarkStage stage, const benchmarker& file) const {
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = stats.blocks_with_1_structural_flipped * struct1_7_miss_rate(stage);
|
||||
expected += stats.blocks_with_utf8_flipped * utf8_miss_rate(stage);
|
||||
expected += stats.blocks_with_escapes_flipped * escape_miss_rate(stage);
|
||||
expected += stats.blocks_with_8_structurals_flipped * struct8_15_miss_rate(stage);
|
||||
expected += stats.blocks_with_16_structurals_flipped * struct16_miss_rate(stage);
|
||||
return expected;
|
||||
}
|
||||
|
||||
double calc_expected(BenchmarkStage stage, const benchmarker& file) const {
|
||||
return calc_expected_feature_cost(stage, file) + calc_expected_miss_cost(stage, file);
|
||||
}
|
||||
|
||||
void print(const option_struct& options) const {
|
||||
printf("\n");
|
||||
printf("Features in ns/block (64 bytes):\n");
|
||||
printf("\n");
|
||||
printf("| %-8s ", "Stage");
|
||||
printf("| %8s ", "Base");
|
||||
printf("| %8s ", "7 Struct");
|
||||
printf("| %8s ", "UTF-8");
|
||||
printf("| %8s ", "Escape");
|
||||
printf("| %8s ", "15 Str.");
|
||||
printf("| %8s ", "16+ Str.");
|
||||
printf("| %15s ", "7 Struct Miss");
|
||||
printf("| %15s ", "UTF-8 Miss");
|
||||
printf("| %15s ", "Escape Miss");
|
||||
printf("| %15s ", "15 Str. Miss");
|
||||
printf("| %15s ", "16+ Str. Miss");
|
||||
printf("|\n");
|
||||
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|\n");
|
||||
|
||||
options.each_stage([&](auto stage) {
|
||||
printf("| %-8s ", benchmark_stage_name(stage));
|
||||
printf("| %8.3g ", base_cost(stage));
|
||||
printf("| %8.3g ", struct1_7_cost(stage));
|
||||
printf("| %8.3g ", utf8_cost(stage));
|
||||
printf("| %8.3g ", escape_cost(stage));
|
||||
printf("| %8.3g ", struct8_15_cost(stage));
|
||||
printf("| %8.3g ", struct16_cost(stage));
|
||||
if (has_events()) {
|
||||
printf("| %8.3g (%3d%%) ", struct1_7_miss_cost(stage), int(struct1_7_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", utf8_miss_cost(stage), int(utf8_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", escape_miss_cost(stage), int(escape_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", struct8_15_miss_cost(stage), int(struct8_15_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", struct16_miss_cost(stage), int(struct16_miss_rate(stage)*100));
|
||||
} else {
|
||||
printf("| %8.3g ", struct1_7_miss_cost(stage));
|
||||
printf("| %8.3g ", utf8_miss_cost(stage));
|
||||
printf("| %8.3g ", escape_miss_cost(stage));
|
||||
printf("| %8.3g ", struct8_15_miss_cost(stage));
|
||||
printf("| %8.3g ", struct16_miss_cost(stage));
|
||||
}
|
||||
printf("|\n");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
|
||||
double actual = results[stage].best.elapsed_ns() / results.stats->blocks;
|
||||
double calc = features.calc_expected(stage, results);
|
||||
uint64_t actual_misses = results[stage].best.branch_misses();
|
||||
uint64_t calc_misses = uint64_t(features.calc_expected_misses(stage, results));
|
||||
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
|
||||
printf(" | %-8s ", benchmark_stage_name(stage));
|
||||
printf("| %-15s ", filename);
|
||||
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
|
||||
printf("| %8.3g ", calc_miss_cost);
|
||||
printf("| %8.3g ", calc);
|
||||
printf("| %8.3g ", actual);
|
||||
printf("| %+8.3g ", actual - calc);
|
||||
printf("| %13lu ", calc_misses);
|
||||
if (features.has_events()) {
|
||||
printf("| %13lu ", actual_misses);
|
||||
printf("| %+13ld ", int64_t(actual_misses - calc_misses));
|
||||
double miss_adjustment = calc_miss_cost * (double(int64_t(actual_misses - calc_misses)) / calc_misses);
|
||||
printf("| %8.3g ", calc_miss_cost + miss_adjustment);
|
||||
printf("| %+8.3g ", actual - (calc + miss_adjustment));
|
||||
}
|
||||
printf("|\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Read options
|
||||
exe_name = argv[0];
|
||||
option_struct options(argc, argv);
|
||||
if (options.verbose) {
|
||||
verbose_stream = &cout;
|
||||
}
|
||||
|
||||
// Initialize the event collector. We put this early so if it prints an error message, it's the
|
||||
// first thing printed.
|
||||
event_collector collector;
|
||||
|
||||
// Set up benchmarkers by reading all files
|
||||
feature_benchmarker features(collector);
|
||||
benchmarker gsoc_2018("jsonexamples/gsoc-2018.json", collector);
|
||||
benchmarker twitter("jsonexamples/twitter.json", collector);
|
||||
benchmarker random("jsonexamples/random.json", collector);
|
||||
|
||||
// Run the benchmarks
|
||||
progress_bar progress(options.iterations, 100);
|
||||
// Put the if (options.stage1_only) *outside* the loop so that run_iterations will be optimized
|
||||
if (options.stage1_only) {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
features.run_iterations(options.iteration_step, true);
|
||||
gsoc_2018.run_iterations(options.iteration_step, true);
|
||||
twitter.run_iterations(options.iteration_step, true);
|
||||
random.run_iterations(options.iteration_step, true);
|
||||
}
|
||||
} else {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
features.run_iterations(options.iteration_step, false);
|
||||
gsoc_2018.run_iterations(options.iteration_step, false);
|
||||
twitter.run_iterations(options.iteration_step, false);
|
||||
random.run_iterations(options.iteration_step, false);
|
||||
}
|
||||
}
|
||||
if (!options.verbose) { progress.erase(); }
|
||||
|
||||
features.print(options);
|
||||
|
||||
// Gauge effectiveness
|
||||
if (options.verbose) {
|
||||
printf("\n");
|
||||
printf(" Effectiveness Check: Estimated vs. Actual ns/block for real files:\n");
|
||||
printf("\n");
|
||||
printf(" | %8s ", "Stage");
|
||||
printf("| %-15s ", "File");
|
||||
printf("| %11s ", "Est. (Base)");
|
||||
printf("| %11s ", "Est. (Miss)");
|
||||
printf("| %8s ", "Est.");
|
||||
printf("| %8s ", "Actual");
|
||||
printf("| %8s ", "Diff");
|
||||
printf("| %13s ", "Est. Misses");
|
||||
if (features.has_events()) {
|
||||
printf("| %13s ", "Actual Misses");
|
||||
printf("| %13s ", "Diff (Misses)");
|
||||
printf("| %13s ", "Adjusted Miss");
|
||||
printf("| %13s ", "Adjusted Diff");
|
||||
}
|
||||
printf("|\n");
|
||||
printf(" |%.10s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.13s", "---------------------------------------");
|
||||
printf("|%.13s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
if (features.has_events()) {
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
}
|
||||
printf("|\n");
|
||||
|
||||
options.each_stage([&](auto stage) {
|
||||
print_file_effectiveness(stage, "gsoc-2018.json", gsoc_2018, features);
|
||||
print_file_effectiveness(stage, "twitter.json", twitter, features);
|
||||
print_file_effectiveness(stage, "random.json", random, features);
|
||||
});
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -130,12 +130,16 @@ double diff(timespec start, timespec end) {
|
||||
if (verbose) \
|
||||
printf(" %7.3f %s per input byte (best) ", cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf(" %7.3f %s per input byte (avg) ", avg_cycle_per_op, unitname); \
|
||||
printf(" %7.3f %s (avg) ", avg_cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf(" %7.3f GB/s (error margin: %.3f GB/s)", max_gb_per_s, \
|
||||
printf(" %7.3f GB/s (error margin: %5.3f GB/s)", max_gb_per_s, \
|
||||
-avg_gb_per_s + max_gb_per_s); \
|
||||
if (verbose) \
|
||||
printf(" %13.0f documents/s (best)", 1.0/min_sumclockdiff); \
|
||||
if (verbose) \
|
||||
printf(" %13.0f documents/s (avg)", 1.0/(sumclockdiff/repeat)); \
|
||||
if (!verbose) \
|
||||
printf(" %20.3f %20.3f %20.3f %20.3f ", cycle_per_op, \
|
||||
printf(" %20.3f %20.3f %20.3f %20.3f", cycle_per_op, \
|
||||
avg_cycle_per_op - cycle_per_op, max_gb_per_s, \
|
||||
-avg_gb_per_s + max_gb_per_s); \
|
||||
printf("\n"); \
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
#ifndef __BENCHMARKER_H
|
||||
#define __BENCHMARKER_H
|
||||
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cinttypes>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
|
||||
// Initialize "verbose" to go nowhere. We'll read options in main() and set to cout if verbose is true.
|
||||
std::ofstream dev_null;
|
||||
ostream *verbose_stream = &dev_null;
|
||||
const size_t BYTES_PER_BLOCK = 64;
|
||||
|
||||
ostream& verbose() {
|
||||
return *verbose_stream;
|
||||
}
|
||||
|
||||
void exit_error(string message) {
|
||||
cerr << message << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct json_stats {
|
||||
size_t bytes = 0;
|
||||
size_t blocks = 0;
|
||||
size_t structurals = 0;
|
||||
size_t blocks_with_utf8 = 0;
|
||||
size_t blocks_with_utf8_flipped = 0;
|
||||
size_t blocks_with_escapes = 0;
|
||||
size_t blocks_with_escapes_flipped = 0;
|
||||
size_t blocks_with_0_structurals = 0;
|
||||
size_t blocks_with_0_structurals_flipped = 0;
|
||||
size_t blocks_with_1_structural = 0;
|
||||
size_t blocks_with_1_structural_flipped = 0;
|
||||
size_t blocks_with_8_structurals = 0;
|
||||
size_t blocks_with_8_structurals_flipped = 0;
|
||||
size_t blocks_with_16_structurals = 0;
|
||||
size_t blocks_with_16_structurals_flipped = 0;
|
||||
|
||||
json_stats(const padded_string& json, const dom::parser& parser) {
|
||||
bytes = json.size();
|
||||
blocks = bytes / BYTES_PER_BLOCK;
|
||||
if (bytes % BYTES_PER_BLOCK > 0) { blocks++; } // Account for remainder block
|
||||
structurals = parser.n_structural_indexes-1;
|
||||
|
||||
// Calculate stats on blocks that will trigger utf-8 if statements / mispredictions
|
||||
bool last_block_has_utf8 = false;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Find utf-8 in the block
|
||||
size_t block_start = block*BYTES_PER_BLOCK;
|
||||
size_t block_end = block_start+BYTES_PER_BLOCK;
|
||||
if (block_end > json.size()) { block_end = json.size(); }
|
||||
bool block_has_utf8 = false;
|
||||
for (size_t i=block_start; i<block_end; i++) {
|
||||
if (json.data()[i] & 0x80) {
|
||||
block_has_utf8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block_has_utf8) {
|
||||
blocks_with_utf8++;
|
||||
}
|
||||
if (block > 0 && last_block_has_utf8 != block_has_utf8) {
|
||||
blocks_with_utf8_flipped++;
|
||||
}
|
||||
last_block_has_utf8 = block_has_utf8;
|
||||
}
|
||||
|
||||
// Calculate stats on blocks that will trigger escape if statements / mispredictions
|
||||
bool last_block_has_escapes = false;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Find utf-8 in the block
|
||||
size_t block_start = block*BYTES_PER_BLOCK;
|
||||
size_t block_end = block_start+BYTES_PER_BLOCK;
|
||||
if (block_end > json.size()) { block_end = json.size(); }
|
||||
bool block_has_escapes = false;
|
||||
for (size_t i=block_start; i<block_end; i++) {
|
||||
if (json.data()[i] == '\\') {
|
||||
block_has_escapes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block_has_escapes) {
|
||||
blocks_with_escapes++;
|
||||
}
|
||||
if (block > 0 && last_block_has_escapes != block_has_escapes) {
|
||||
blocks_with_escapes_flipped++;
|
||||
}
|
||||
last_block_has_escapes = block_has_escapes;
|
||||
}
|
||||
|
||||
// Calculate stats on blocks that will trigger structural count if statements / mispredictions
|
||||
bool last_block_has_0_structurals = false;
|
||||
bool last_block_has_1_structural = false;
|
||||
bool last_block_has_8_structurals = false;
|
||||
bool last_block_has_16_structurals = false;
|
||||
size_t structural=0;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Count structurals in the block
|
||||
int block_structurals=0;
|
||||
while (structural < parser.n_structural_indexes && parser.structural_indexes[structural] < (block+1)*BYTES_PER_BLOCK) {
|
||||
block_structurals++;
|
||||
structural++;
|
||||
}
|
||||
|
||||
bool block_has_0_structurals = block_structurals == 0;
|
||||
if (block_has_0_structurals) {
|
||||
blocks_with_0_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_0_structurals != block_has_0_structurals) {
|
||||
blocks_with_0_structurals_flipped++;
|
||||
}
|
||||
last_block_has_0_structurals = block_has_0_structurals;
|
||||
|
||||
bool block_has_1_structural = block_structurals >= 1;
|
||||
if (block_has_1_structural) {
|
||||
blocks_with_1_structural++;
|
||||
}
|
||||
if (block > 0 && last_block_has_1_structural != block_has_1_structural) {
|
||||
blocks_with_1_structural_flipped++;
|
||||
}
|
||||
last_block_has_1_structural = block_has_1_structural;
|
||||
|
||||
bool block_has_8_structurals = block_structurals >= 8;
|
||||
if (block_has_8_structurals) {
|
||||
blocks_with_8_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_8_structurals != block_has_8_structurals) {
|
||||
blocks_with_8_structurals_flipped++;
|
||||
}
|
||||
last_block_has_8_structurals = block_has_8_structurals;
|
||||
|
||||
bool block_has_16_structurals = block_structurals >= 16;
|
||||
if (block_has_16_structurals) {
|
||||
blocks_with_16_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_16_structurals != block_has_16_structurals) {
|
||||
blocks_with_16_structurals_flipped++;
|
||||
}
|
||||
last_block_has_16_structurals = block_has_16_structurals;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct progress_bar {
|
||||
int max_value;
|
||||
int total_ticks;
|
||||
double ticks_per_value;
|
||||
int next_tick;
|
||||
progress_bar(int _max_value, int _total_ticks) : max_value(_max_value), total_ticks(_total_ticks), ticks_per_value(double(_total_ticks)/_max_value), next_tick(0) {
|
||||
fprintf(stderr, "[");
|
||||
for (int i=0;i<total_ticks;i++) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
fprintf(stderr, "]");
|
||||
for (int i=0;i<total_ticks+1;i++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
}
|
||||
|
||||
void print(int value) {
|
||||
double ticks = value*ticks_per_value;
|
||||
if (ticks >= total_ticks) {
|
||||
ticks = total_ticks-1;
|
||||
}
|
||||
int tick;
|
||||
for (tick=next_tick; tick <= ticks && tick <= total_ticks; tick++) {
|
||||
fprintf(stderr, "=");
|
||||
}
|
||||
next_tick = tick;
|
||||
}
|
||||
void erase() const {
|
||||
for (int i=0;i<next_tick+1;i++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
for (int tick=0; tick<=total_ticks+2; tick++) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
for (int tick=0; tick<=total_ticks+2; tick++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class BenchmarkStage {
|
||||
ALL,
|
||||
ALLOCATE,
|
||||
STAGE1,
|
||||
STAGE2
|
||||
};
|
||||
|
||||
const char* benchmark_stage_name(BenchmarkStage stage) {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return "All";
|
||||
case BenchmarkStage::ALLOCATE: return "Allocate";
|
||||
case BenchmarkStage::STAGE1: return "Stage 1";
|
||||
case BenchmarkStage::STAGE2: return "Stage 2";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct benchmarker {
|
||||
// JSON text from loading the file. Owns the memory.
|
||||
padded_string json;
|
||||
// JSON filename
|
||||
const char *filename;
|
||||
// Event collector that can be turned on to measure cycles, missed branches, etc.
|
||||
event_collector& collector;
|
||||
|
||||
// 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 (not including allocation)
|
||||
event_aggregate all_stages;
|
||||
// Speed and event summary for stage 1
|
||||
event_aggregate stage1;
|
||||
// Speed and event summary for stage 2
|
||||
event_aggregate stage2;
|
||||
// Speed and event summary for allocation
|
||||
event_aggregate allocate_stage;
|
||||
// Speed and event summary for the repeatly-parsing mode
|
||||
event_aggregate loop;
|
||||
|
||||
benchmarker(const char *_filename, event_collector& _collector)
|
||||
: filename(_filename), collector(_collector), stats(NULL) {
|
||||
verbose() << "[verbose] loading " << filename << endl;
|
||||
simdjson::error_code error;
|
||||
padded_string::load(filename).tie(this->json, error);
|
||||
if (error) {
|
||||
exit_error(string("Could not load the file ") + filename);
|
||||
}
|
||||
verbose() << "[verbose] loaded " << filename << endl;
|
||||
}
|
||||
|
||||
~benchmarker() {
|
||||
if (stats) {
|
||||
delete stats;
|
||||
}
|
||||
}
|
||||
|
||||
const event_aggregate& operator[](BenchmarkStage stage) const {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return this->all_stages;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int iterations() const {
|
||||
return all_stages.iterations;
|
||||
}
|
||||
|
||||
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());
|
||||
if (error) {
|
||||
exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(error));
|
||||
}
|
||||
event_count allocate_count = collector.end();
|
||||
allocate_stage << allocate_count;
|
||||
// Run it once to get hot buffers
|
||||
if(hotbuffers) {
|
||||
auto result = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (result.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(result.error()));
|
||||
}
|
||||
}
|
||||
|
||||
verbose() << "[verbose] allocated memory for parsed JSON " << endl;
|
||||
|
||||
// Stage 1 (find structurals)
|
||||
collector.start();
|
||||
error = active_implementation->stage1((const uint8_t *)json.data(), json.size(), parser, false);
|
||||
event_count stage1_count = collector.end();
|
||||
stage1 << stage1_count;
|
||||
if (error) {
|
||||
exit_error(string("Failed to parse ") + filename + " during stage 1: " + error_message(error));
|
||||
}
|
||||
|
||||
// Stage 2 (unified machine) and the rest
|
||||
|
||||
if (stage1_only) {
|
||||
all_stages << stage1_count;
|
||||
} else {
|
||||
event_count stage2_count;
|
||||
collector.start();
|
||||
error = active_implementation->stage2((const uint8_t *)json.data(), json.size(), parser);
|
||||
if (error) {
|
||||
exit_error(string("Failed to parse ") + filename + " during stage 2 parsing " + error_message(error));
|
||||
}
|
||||
stage2_count = collector.end();
|
||||
stage2 << stage2_count;
|
||||
all_stages << allocate_count + stage1_count + stage2_count;
|
||||
}
|
||||
// Calculate stats the first time we parse
|
||||
if (stats == NULL) {
|
||||
if (stage1_only) { // we need stage 2 once
|
||||
error = active_implementation->stage2((const uint8_t *)json.data(), json.size(), parser);
|
||||
if (error) {
|
||||
printf("Warning: failed to parse during stage 2. Unable to acquire statistics.\n");
|
||||
}
|
||||
}
|
||||
stats = new json_stats(json, parser);
|
||||
}
|
||||
}
|
||||
|
||||
void run_loop(size_t iterations) {
|
||||
dom::parser parser;
|
||||
auto firstresult = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (firstresult.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(firstresult.error()));
|
||||
}
|
||||
|
||||
collector.start();
|
||||
// some users want something closer to "number of documents per second"
|
||||
for(size_t i = 0; i < iterations; i++) {
|
||||
auto result = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (result.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(result.error()));
|
||||
}
|
||||
}
|
||||
event_count all_loop_count = collector.end();
|
||||
loop << all_loop_count;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
run_loop(iterations);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void print_aggregate(const char* prefix, const T& stage) const {
|
||||
printf("%s%-13s: %8.4f ns per block (%6.2f%%) - %8.4f ns per byte - %8.4f ns per structural - %8.3f GB/s\n",
|
||||
prefix,
|
||||
"Speed",
|
||||
stage.elapsed_ns() / stats->blocks, // per block
|
||||
100.0 * stage.elapsed_sec() / all_stages.elapsed_sec(), // %
|
||||
stage.elapsed_ns() / stats->bytes, // per byte
|
||||
stage.elapsed_ns() / stats->structurals, // per structural
|
||||
(json.size() / 1000000000.0) / stage.elapsed_sec() // GB/s
|
||||
);
|
||||
|
||||
if (collector.has_events()) {
|
||||
printf("%s%-13s: %8.4f per block (%6.2f%%) - %8.4f per byte - %8.4f per structural - %8.3f GHz est. frequency\n",
|
||||
prefix,
|
||||
"Cycles",
|
||||
stage.cycles() / stats->blocks,
|
||||
100.0 * stage.cycles() / all_stages.cycles(),
|
||||
stage.cycles() / stats->bytes,
|
||||
stage.cycles() / stats->structurals,
|
||||
(stage.cycles() / stage.elapsed_sec()) / 1000000000.0
|
||||
);
|
||||
printf("%s%-13s: %8.4f per block (%6.2f%%) - %8.4f per byte - %8.4f per structural - %8.3f per cycle\n",
|
||||
prefix,
|
||||
"Instructions",
|
||||
stage.instructions() / stats->blocks,
|
||||
100.0 * stage.instructions() / all_stages.instructions(),
|
||||
stage.instructions() / stats->bytes,
|
||||
stage.instructions() / stats->structurals,
|
||||
stage.instructions() / stage.cycles()
|
||||
);
|
||||
|
||||
// NOTE: removed cycles/miss because it is a somewhat misleading stat
|
||||
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
|
||||
prefix,
|
||||
"Misses",
|
||||
stage.branch_misses(),
|
||||
100.0 * stage.branch_misses() / all_stages.branch_misses(),
|
||||
stage.cache_misses(),
|
||||
100.0 * stage.cache_misses() / all_stages.cache_misses(),
|
||||
stage.cache_references()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void print(bool tabbed_output, size_t iterations) const {
|
||||
if (tabbed_output) {
|
||||
char* filename_copy = (char*)malloc(strlen(filename)+1);
|
||||
strcpy(filename_copy, filename);
|
||||
#if defined(__linux__)
|
||||
char* base = ::basename(filename_copy);
|
||||
#else
|
||||
char* base = filename_copy;
|
||||
#endif
|
||||
if (strlen(base) >= 5 && !strcmp(base+strlen(base)-5, ".json")) {
|
||||
base[strlen(base)-5] = '\0';
|
||||
}
|
||||
|
||||
double gb = json.size() / 1000000000.0;
|
||||
if (collector.has_events()) {
|
||||
printf("\"%s\"\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n",
|
||||
base,
|
||||
allocate_stage.best.cycles() / json.size(),
|
||||
stage1.best.cycles() / json.size(),
|
||||
stage2.best.cycles() / json.size(),
|
||||
all_stages.best.cycles() / json.size(),
|
||||
gb / all_stages.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 / stage1.best.elapsed_sec(),
|
||||
gb / stage2.best.elapsed_sec());
|
||||
}
|
||||
free(filename_copy);
|
||||
} else {
|
||||
printf("\n");
|
||||
printf("%s\n", filename);
|
||||
printf("%s\n", string(strlen(filename), '=').c_str());
|
||||
printf("%9zu blocks - %10zu bytes - %5zu structurals (%5.1f %%)\n", stats->bytes / BYTES_PER_BLOCK, stats->bytes, stats->structurals, 100.0 * stats->structurals / stats->bytes);
|
||||
if (stats) {
|
||||
printf("special blocks with: utf8 %9zu (%5.1f %%) - escape %9zu (%5.1f %%) - 0 structurals %9zu (%5.1f %%) - 1+ structurals %9zu (%5.1f %%) - 8+ structurals %9zu (%5.1f %%) - 16+ structurals %9zu (%5.1f %%)\n",
|
||||
stats->blocks_with_utf8, 100.0 * stats->blocks_with_utf8 / stats->blocks,
|
||||
stats->blocks_with_escapes, 100.0 * stats->blocks_with_escapes / stats->blocks,
|
||||
stats->blocks_with_0_structurals, 100.0 * stats->blocks_with_0_structurals / stats->blocks,
|
||||
stats->blocks_with_1_structural, 100.0 * stats->blocks_with_1_structural / stats->blocks,
|
||||
stats->blocks_with_8_structurals, 100.0 * stats->blocks_with_8_structurals / stats->blocks,
|
||||
stats->blocks_with_16_structurals, 100.0 * stats->blocks_with_16_structurals / stats->blocks);
|
||||
printf("special block flips: utf8 %9zu (%5.1f %%) - escape %9zu (%5.1f %%) - 0 structurals %9zu (%5.1f %%) - 1+ structurals %9zu (%5.1f %%) - 8+ structurals %9zu (%5.1f %%) - 16+ structurals %9zu (%5.1f %%)\n",
|
||||
stats->blocks_with_utf8_flipped, 100.0 * stats->blocks_with_utf8_flipped / stats->blocks,
|
||||
stats->blocks_with_escapes_flipped, 100.0 * stats->blocks_with_escapes_flipped / stats->blocks,
|
||||
stats->blocks_with_0_structurals_flipped, 100.0 * stats->blocks_with_0_structurals_flipped / stats->blocks,
|
||||
stats->blocks_with_1_structural_flipped, 100.0 * stats->blocks_with_1_structural_flipped / stats->blocks,
|
||||
stats->blocks_with_8_structurals_flipped, 100.0 * stats->blocks_with_8_structurals_flipped / stats->blocks,
|
||||
stats->blocks_with_16_structurals_flipped, 100.0 * stats->blocks_with_16_structurals_flipped / stats->blocks);
|
||||
}
|
||||
printf("\n");
|
||||
printf("All Stages\n");
|
||||
print_aggregate("| " , all_stages.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()) {
|
||||
printf("|- Allocation\n");
|
||||
print_aggregate("| ", allocate_stage.best);
|
||||
}
|
||||
printf("|- Stage 1\n");
|
||||
print_aggregate("| ", stage1.best);
|
||||
printf("|- Stage 2\n");
|
||||
print_aggregate("| ", stage2.best);
|
||||
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 freqmin = std::min(freq1, freq2);
|
||||
double freqmax = std::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\n", iterations/loop.best.elapsed_sec());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#include <algorithm>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -13,6 +16,8 @@
|
||||
|
||||
#include "sajson.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
bool equals(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
|
||||
@@ -30,30 +35,74 @@ void print_vec(const std::vector<int64_t> &v) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void simdjson_scan(std::vector<int64_t> &answer,
|
||||
simdjson::ParsedJson::Iterator &i) {
|
||||
while (i.move_forward()) {
|
||||
if (i.get_scope_type() == '{') {
|
||||
bool found_user = (i.get_string_length() == 4) &&
|
||||
(memcmp(i.get_string(), "user", 4) == 0);
|
||||
i.move_to_value();
|
||||
if (found_user) {
|
||||
if (i.is_object() && i.move_to_key("id", 2)) {
|
||||
if (i.is_integer()) {
|
||||
answer.push_back(i.get_integer());
|
||||
// clang-format off
|
||||
|
||||
// simdjson_recurse below come be implemented like so but it is slow:
|
||||
/*void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
auto [array, array_error] = element.get<simdjson::dom::array>();
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
auto [object, error] = element.get<simdjson::dom::object>();
|
||||
int64_t id;
|
||||
object["user"]["id"].get<int64_t>().tie(id,error);
|
||||
if(!error) {
|
||||
v.push_back(id);
|
||||
}
|
||||
for (auto [key, value] : object) {
|
||||
if (value.is<simdjson::dom::array>() || value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// clang-format on
|
||||
|
||||
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
auto array = element.get<simdjson::dom::array>();
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
auto object = element.get<simdjson::dom::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"
|
||||
if(value.is<simdjson::dom::object>()) {
|
||||
auto child_object = value.get<simdjson::dom::object>();
|
||||
for (auto [child_key, child_value] : child_object) {
|
||||
if((child_key.size() == 2) && (memcmp(child_key.data(), "id", 2) == 0)) {
|
||||
if(child_value.is<int64_t>()) {
|
||||
v.push_back(child_value.get<int64_t>());
|
||||
}
|
||||
}
|
||||
if (child_value.is<simdjson::dom::array>() || child_value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, child_value);
|
||||
}
|
||||
}
|
||||
i.up();
|
||||
} else if (value.is<simdjson::dom::array>()) {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
// end of: we are in an object under the key "user"
|
||||
} else if (value.is<simdjson::dom::array>() || value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
simdjson_just_dom(simdjson::ParsedJson &pj) {
|
||||
simdjson_just_dom(simdjson::dom::element doc) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson::ParsedJson::Iterator i(pj);
|
||||
simdjson_scan(answer, i);
|
||||
simdjson_recurse(answer, doc);
|
||||
remove_duplicates(answer);
|
||||
return answer;
|
||||
}
|
||||
@@ -61,21 +110,17 @@ simdjson_just_dom(simdjson::ParsedJson &pj) {
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
if (!pj.is_valid()) {
|
||||
return answer;
|
||||
}
|
||||
simdjson::ParsedJson::Iterator i(pj);
|
||||
simdjson_scan(answer, i);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc = parser.parse(p);
|
||||
simdjson_recurse(answer, doc);
|
||||
remove_duplicates(answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool
|
||||
__attribute__((noinline)) simdjson::error_code
|
||||
simdjson_just_parse(const simdjson::padded_string &p) {
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
bool answer = !pj.is_valid();
|
||||
return answer;
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(p).error();
|
||||
}
|
||||
|
||||
void sajson_traverse(std::vector<int64_t> &answer, const sajson::value &node) {
|
||||
@@ -274,11 +319,9 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto [p, error] = simdjson::padded_string::load(filename);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -317,22 +360,24 @@ int main(int argc, char *argv[]) {
|
||||
printf(
|
||||
"name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
|
||||
}
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).size(), size, , repeat,
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).size(), size, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("rapid ", rapid_compute_stats(p).size(), size, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).size(), size, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson (just parse) ", simdjson_just_parse(p), false, , repeat,
|
||||
BEST_TIME("simdjson (just parse) ", simdjson_just_parse(p), simdjson::error_code::SUCCESS, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("rapid (just parse) ", rapid_just_parse(p), false, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("sasjon (just parse) ", sasjon_just_parse(p), false, , repeat,
|
||||
volume, !just_data);
|
||||
simdjson::ParsedJson dsimdjson = simdjson::build_parsed_json(p);
|
||||
BEST_TIME("simdjson (just dom) ", simdjson_just_dom(dsimdjson).size(), size,
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc = parser.parse(p);
|
||||
BEST_TIME("simdjson (just dom) ", simdjson_just_dom(doc).size(), size,
|
||||
, repeat, volume, !just_data);
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
buffer[p.size()] = '\0';
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
rapidjson::Document drapid;
|
||||
drapid.ParseInsitu<kParseValidateEncodingFlag>(buffer);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#ifndef __EVENT_COUNTER_H
|
||||
#define __EVENT_COUNTER_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cinttypes>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::chrono::steady_clock;
|
||||
using std::chrono::time_point;
|
||||
using std::chrono::duration;
|
||||
|
||||
struct event_count {
|
||||
duration<double> elapsed;
|
||||
vector<unsigned long long> event_counts;
|
||||
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
|
||||
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
|
||||
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
|
||||
|
||||
// The types of counters (so we can read the getter more easily)
|
||||
enum event_counter_types {
|
||||
CPU_CYCLES,
|
||||
INSTRUCTIONS,
|
||||
BRANCH_MISSES,
|
||||
CACHE_REFERENCES,
|
||||
CACHE_MISSES
|
||||
};
|
||||
|
||||
double elapsed_sec() const { return duration<double>(elapsed).count(); }
|
||||
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
|
||||
double cycles() const { return event_counts[CPU_CYCLES]; }
|
||||
double instructions() const { return event_counts[INSTRUCTIONS]; }
|
||||
double branch_misses() const { return event_counts[BRANCH_MISSES]; }
|
||||
double cache_references() const { return event_counts[CACHE_REFERENCES]; }
|
||||
double cache_misses() const { return event_counts[CACHE_MISSES]; }
|
||||
|
||||
event_count& operator=(const event_count& other) {
|
||||
this->elapsed = other.elapsed;
|
||||
this->event_counts = other.event_counts;
|
||||
return *this;
|
||||
}
|
||||
event_count operator+(const event_count& other) const {
|
||||
return event_count(elapsed+other.elapsed, {
|
||||
event_counts[0]+other.event_counts[0],
|
||||
event_counts[1]+other.event_counts[1],
|
||||
event_counts[2]+other.event_counts[2],
|
||||
event_counts[3]+other.event_counts[3],
|
||||
event_counts[4]+other.event_counts[4],
|
||||
});
|
||||
}
|
||||
|
||||
void operator+=(const event_count& other) {
|
||||
*this = *this + other;
|
||||
}
|
||||
};
|
||||
|
||||
struct event_aggregate {
|
||||
int iterations = 0;
|
||||
event_count total;
|
||||
event_count best;
|
||||
event_count worst;
|
||||
|
||||
event_aggregate() {}
|
||||
|
||||
void operator<<(const event_count& other) {
|
||||
if (iterations == 0 || other.elapsed < best.elapsed) {
|
||||
best = other;
|
||||
}
|
||||
if (iterations == 0 || other.elapsed > worst.elapsed) {
|
||||
worst = other;
|
||||
}
|
||||
iterations++;
|
||||
total += other;
|
||||
}
|
||||
|
||||
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
|
||||
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
|
||||
double cycles() const { return total.cycles() / iterations; }
|
||||
double instructions() const { return total.instructions() / iterations; }
|
||||
double branch_misses() const { return total.branch_misses() / iterations; }
|
||||
double cache_references() const { return total.cache_references() / iterations; }
|
||||
double cache_misses() const { return total.cache_misses() / iterations; }
|
||||
};
|
||||
|
||||
struct event_collector {
|
||||
event_count count;
|
||||
time_point<steady_clock> start_clock;
|
||||
|
||||
#if defined(__linux__)
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
|
||||
event_collector() : 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
|
||||
}) {}
|
||||
bool has_events() {
|
||||
return linux_events.is_working();
|
||||
}
|
||||
#else
|
||||
event_collector() {}
|
||||
bool has_events() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
really_inline void start() {
|
||||
#if defined(__linux)
|
||||
linux_events.start();
|
||||
#endif
|
||||
start_clock = steady_clock::now();
|
||||
}
|
||||
really_inline event_count& end() {
|
||||
time_point<steady_clock> end_clock = steady_clock::now();
|
||||
#if defined(__linux)
|
||||
linux_events.end(count.event_counts);
|
||||
#endif
|
||||
count.elapsed = end_clock - start_clock;
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
class ChunkWriter
|
||||
def initialize(output_dir, miss_templates, file_size=640*1000, block_size=64)
|
||||
@@output_dir = output_dir
|
||||
@@miss_templates = miss_templates
|
||||
@@file_size = file_size
|
||||
@@block_size = block_size
|
||||
end
|
||||
|
||||
def prepare_chunk(chunks, include_newline)
|
||||
Array(chunks).map do |chunk|
|
||||
"#{chunk}#{' '*(@@block_size-chunk.bytesize-1)}#{include_newline ? "\n" : " "}"
|
||||
end.join("")
|
||||
end
|
||||
|
||||
def write_files(filename, start1, repeat1, end1, repeat2: '', include_newline: true)
|
||||
start1 = prepare_chunk(start1, include_newline)
|
||||
repeat1 = prepare_chunk(repeat1, include_newline)
|
||||
end1 = prepare_chunk(end1, include_newline)
|
||||
write_full(File.join(@@output_dir, "#{filename}-full.json"), start1, repeat1, end1)
|
||||
|
||||
repeat2 = prepare_chunk(repeat2, include_newline)
|
||||
repeat2 = repeat2 * (repeat1.bytesize/repeat2.bytesize)
|
||||
write_half(File.join(@@output_dir, "#{filename}.json"), start1, repeat1, end1, repeat2)
|
||||
write_half_miss(File.join(@@output_dir, "#{filename}-miss.json"), start1, repeat1, end1, repeat2)
|
||||
end
|
||||
|
||||
def write_full(filename, start1, repeat1, end1)
|
||||
puts "Writing #{filename} ..."
|
||||
File.open(filename, "w") do |file|
|
||||
write_chunks(file, start1, repeat1, end1, @@file_size)
|
||||
end
|
||||
raise "OMG wrong file size #{File.size(filename)} (should be #{@@file_size})" if File.size(filename) != @@file_size
|
||||
end
|
||||
|
||||
def write_half(filename, start1, repeat1, end1, repeat2)
|
||||
# repeat1 is already represented in start1 and end1, so it doesn't need quite
|
||||
# half the iterations.
|
||||
repeat1_len = (@@file_size/2) - start1.bytesize - end1.bytesize
|
||||
halfway_point = start1.bytesize + repeat1_len + repeat2.bytesize
|
||||
|
||||
puts "Writing #{filename} ..."
|
||||
File.open(filename, "w") do |file|
|
||||
write_chunks(file, start1, repeat1, repeat2, halfway_point)
|
||||
write_chunks(file, repeat2, repeat2, end1, @@file_size-halfway_point)
|
||||
end
|
||||
raise "OMG wrong file size #{File.size(filename)} (should be #{@@file_size})" if File.size(filename) != @@file_size
|
||||
end
|
||||
|
||||
def write_half_miss(filename, start1, repeat1, end1, repeat2)
|
||||
miss_template = Array(File.read(File.join(@@miss_templates, "#{repeat1.bytesize}.txt")).chomp.split("\n"))
|
||||
# Take the start and end out of the template
|
||||
repeat_template = miss_template[(start1.bytesize/64)..(-end1.bytesize/64-1)]
|
||||
# If repeat is 128 bytes, each *pair* of elements is set. Use that.
|
||||
repeat_chunks = repeat1.bytesize/64
|
||||
repeat_template = (repeat_chunks - 1).step(repeat_template.size - 1, repeat_chunks).map { |i| repeat_template[i] }
|
||||
|
||||
puts "Writing #{filename} ..."
|
||||
File.open(filename, "w") do |file|
|
||||
file.write(start1)
|
||||
repeat_template.each do |should_repeat|
|
||||
file.write(should_repeat == "1" ? repeat1 : repeat2)
|
||||
end
|
||||
file.write(end1)
|
||||
end
|
||||
raise "OMG wrong file size #{File.size(filename)} (should be #{@@file_size})" if File.size(filename) != @@file_size
|
||||
end
|
||||
|
||||
def write_chunks(file, start1, repeat1, end1, size)
|
||||
pos = 0
|
||||
file.write(start1)
|
||||
pos += start1.bytesize
|
||||
|
||||
repeat_end = size-end1.bytesize
|
||||
loop do
|
||||
file.write(repeat1)
|
||||
pos += repeat1.bytesize
|
||||
break if pos >= repeat_end
|
||||
end
|
||||
|
||||
file.write(end1)
|
||||
pos += end1.bytesize
|
||||
return pos
|
||||
end
|
||||
end
|
||||
|
||||
output_dir = File.expand_path("../jsonexamples/generated", File.dirname(__FILE__))
|
||||
miss_templates = File.expand_path("miss-templates", File.dirname(__FILE__))
|
||||
Dir.mkdir(output_dir) unless File.directory?(output_dir)
|
||||
w = ChunkWriter.new(output_dir, miss_templates)
|
||||
w.write_files "utf-8", '["֏","֏",{}', ',"֏","֏",{}', ',"֏","֏","֏"]', repeat2: ',"ab","ab",{}'
|
||||
w.write_files "escape", '["\\"","\\"",{}', ',"\\"","\\"",{}', ',"\\"","\\"","\\""]', repeat2: ',"ab","ab",{}'
|
||||
w.write_files "0-structurals", '"ab"', '', ''
|
||||
# w.write_files "1-structurals", [ '[', '"ab"' ], [ ',', '"ab"' ], [ ',', '{', '}', ']' ]
|
||||
# w.write_files "2-structurals", '["ab"', ',"ab"', [',{', '}]']
|
||||
# w.write_files "3-structurals", '[{}', ',{}', ',"ab"]'
|
||||
# w.write_files "4-structurals", '["ab","ab"', ',"ab","ab"', ',{}]'
|
||||
# w.write_files "5-structurals", '["ab",{}', ',"ab",{}', ',"ab","ab"]'
|
||||
# w.write_files "6-structurals", '["ab","ab","ab"', ',"ab","ab","ab"', ',"ab",{}]'
|
||||
w.write_files "7-structurals", '["ab","ab",{}', ',"ab","ab",{}', ',"ab","ab","ab"]'
|
||||
# w.write_files "8-structurals", '["ab","ab","ab","ab"', ',"ab","ab","ab","ab"', ',"ab","ab",{}]'
|
||||
# w.write_files "9-structurals", '["ab","ab","ab",{}', ',"ab","ab","ab",{}', ',"ab","ab","ab","ab"]'
|
||||
# w.write_files "10-structurals", '["ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab"', ',"ab","ab","ab",{}]'
|
||||
# w.write_files "11-structurals", '["ab","ab","ab","ab",{}', ',"ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "12-structurals", '["ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab",{}]'
|
||||
# w.write_files "13-structurals", '["ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "14-structurals", '["ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab",{}]'
|
||||
w.write_files "15-structurals", '["ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "16-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab",{}]'
|
||||
# w.write_files "17-structurals", '["ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "18-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab",{}]'
|
||||
# w.write_files "19-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "20-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab",{}]'
|
||||
# w.write_files "21-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"]'
|
||||
# w.write_files "22-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab",{}]'
|
||||
w.write_files "23-structurals", '["ab","ab","ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab","ab",{}', ',"ab","ab","ab","ab","ab","ab","ab","ab","ab","ab","ab"]'
|
||||
@@ -0,0 +1,49 @@
|
||||
def gen_seeds(start_blocks, repeat_blocks, end_blocks)
|
||||
total_size = 640*1000
|
||||
total_blocks = total_size/64
|
||||
seed_space = 1..1000000
|
||||
target_blocks = total_blocks*0.5
|
||||
target_flips = total_blocks*0.25
|
||||
percent_flips = 0.25*repeat_blocks
|
||||
|
||||
puts "Seeds for #{start_blocks} start blocks, #{end_blocks} end blocks and #{repeat_blocks} repeat blocks: #{percent_flips*100}% flips"
|
||||
closest_flips = nil
|
||||
closest_seeds = []
|
||||
seed_space.each do |seed|
|
||||
r = Random.new(seed)
|
||||
# First block is always type 1
|
||||
flips = 0
|
||||
type1 = true
|
||||
type1_blocks = start_blocks
|
||||
finished_blocks = start_blocks
|
||||
last_repeat = total_blocks-end_blocks
|
||||
while finished_blocks < last_repeat
|
||||
if r.rand < percent_flips
|
||||
flips += 1
|
||||
type1 = !type1
|
||||
end
|
||||
type1_blocks += repeat_blocks if type1
|
||||
finished_blocks += repeat_blocks
|
||||
end
|
||||
|
||||
# Last one is always type 1
|
||||
flips += 1 if !type1
|
||||
type1 = true
|
||||
type1_blocks += end_blocks
|
||||
finished_blocks += end_blocks
|
||||
|
||||
raise "simulated the wrong number of blocks #{finished_blocks}" if finished_blocks != total_blocks
|
||||
|
||||
if type1_blocks == target_blocks
|
||||
if flips == target_flips
|
||||
puts seed
|
||||
closest_seeds << seed
|
||||
end
|
||||
end
|
||||
end
|
||||
puts closest_seeds
|
||||
end
|
||||
|
||||
gen_seeds(1,1,1)
|
||||
gen_seeds(1,1,2)
|
||||
gen_seeds(2,2,4)
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
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();
|
||||
simdjson::padded_string::load(filename).first.swap(p);
|
||||
std::chrono::time_point<std::chrono::steady_clock> end_clock =
|
||||
std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> elapsed = end_clock - start_clock;
|
||||
return (p.size() / (1024. * 1024 * 1024.)) / elapsed.count();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int optind = 1;
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Reads document as far as possible. " << std::endl;
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
bench(filename, p);
|
||||
double meanval = 0;
|
||||
double maxval = 0;
|
||||
double minval = 10000;
|
||||
std::cout << "file size: "<< (p.size() / (1024. * 1024 * 1024.)) << " GB" <<std::endl;
|
||||
size_t times = p.size() > 1024*1024*1024 ? 5 : 50;
|
||||
#if __cpp_exceptions
|
||||
try {
|
||||
#endif
|
||||
for(size_t i = 0; i < times; i++) {
|
||||
double tval = bench(filename, p);
|
||||
if(maxval < tval) maxval = tval;
|
||||
if(minval > tval) minval = tval;
|
||||
meanval += tval;
|
||||
}
|
||||
#if __cpp_exceptions
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
std::cout << "average speed: " << meanval / times << " GB/s"<< std::endl;
|
||||
std::cout << "min speed : " << minval << " GB/s" << std::endl;
|
||||
std::cout << "max speed : " << maxval << " GB/s" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -56,25 +56,29 @@ public:
|
||||
temp_result_vec.resize(num_events * 2 + 1);
|
||||
}
|
||||
|
||||
~LinuxEvents() { close(fd); }
|
||||
~LinuxEvents() { if (fd != -1) { close(fd); } }
|
||||
|
||||
inline void start() {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_RESET)");
|
||||
}
|
||||
if (fd != -1) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_RESET)");
|
||||
}
|
||||
|
||||
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
|
||||
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void end(std::vector<unsigned long long> &results) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
|
||||
}
|
||||
if (fd != -1) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
|
||||
}
|
||||
|
||||
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
|
||||
report_error("read");
|
||||
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
|
||||
report_error("read");
|
||||
}
|
||||
}
|
||||
// our actual results are in slots 1,3,5, ... of this structure
|
||||
// we really should be checking our ids obtained earlier to be safe
|
||||
@@ -83,6 +87,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool is_working() {
|
||||
return working;
|
||||
}
|
||||
|
||||
private:
|
||||
void report_error(const std::string &context) {
|
||||
if (working)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonminifier.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad
|
||||
// #define RAPIDJSON_SSE42 // bad
|
||||
@@ -14,8 +14,10 @@
|
||||
#include "rapidjson/writer.h"
|
||||
#include "sajson.h"
|
||||
|
||||
using namespace simdjson;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
using namespace simdjson;
|
||||
|
||||
std::string rapid_stringme_insitu(char *json) {
|
||||
Document d;
|
||||
@@ -43,6 +45,7 @@ std::string rapid_stringme(char *json) {
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int c;
|
||||
bool verbose = false;
|
||||
@@ -64,11 +67,9 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto [p, error] = simdjson::padded_string::load(filename);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (verbose) {
|
||||
@@ -81,7 +82,7 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
char *buffer = simdjson::allocate_padded_buffer(p.size() + 1);
|
||||
char *buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
|
||||
@@ -102,16 +103,14 @@ int main(int argc, char *argv[]) {
|
||||
"despacing with RapidJSON Insitu", rapid_stringme_insitu((char *)buffer),
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
|
||||
size_t outlength = simdjson::json_minify((const uint8_t *)buffer, p.size(),
|
||||
(uint8_t *)buffer);
|
||||
if (verbose)
|
||||
std::cout << "json_minify length is " << outlength << std::endl;
|
||||
|
||||
size_t outlength;
|
||||
uint8_t *cbuffer = (uint8_t *)buffer;
|
||||
BEST_TIME("json_minify", simdjson::json_minify(cbuffer, p.size(), cbuffer),
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) ? outlength : -1),
|
||||
outlength, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
|
||||
printf("minisize = %zu, original size = %zu (minified down to %.2f percent "
|
||||
"of original) \n",
|
||||
outlength, p.size(), outlength * 100.0 / p.size());
|
||||
@@ -124,9 +123,11 @@ int main(int argc, char *argv[]) {
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
char *mini_buffer = simdjson::allocate_padded_buffer(p.size() + 1);
|
||||
size_t minisize = simdjson::json_minify((const uint8_t *)p.data(), p.size(),
|
||||
(uint8_t *)mini_buffer);
|
||||
char *mini_buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
size_t minisize;
|
||||
auto minierror = simdjson::active_implementation->minify((const uint8_t *)p.data(), p.size(),
|
||||
(uint8_t *)mini_buffer, minisize);
|
||||
if (!minierror) { std::cerr << minierror << std::endl; exit(1); }
|
||||
mini_buffer[minisize] = '\0';
|
||||
|
||||
BEST_TIME("RapidJSON Insitu despaced", d.ParseInsitu(buffer).HasParseError(),
|
||||
@@ -150,31 +151,19 @@ int main(int argc, char *argv[]) {
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, mini_buffer, p.size()), repeat, volume, !just_data);
|
||||
|
||||
simdjson::ParsedJson pj;
|
||||
bool is_alloc_ok = pj.allocate_capacity(p.size(), 1024);
|
||||
if (!is_alloc_ok) {
|
||||
fprintf(stderr, "failed to allocate memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
simdjson::dom::parser parser;
|
||||
bool automated_reallocation = false;
|
||||
BEST_TIME("simdjson orig",
|
||||
simdjson::json_parse((const uint8_t *)buffer, p.size(), pj,
|
||||
automated_reallocation),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
parser.parse((const uint8_t *)buffer, p.size(),
|
||||
automated_reallocation).error(),
|
||||
simdjson::SUCCESS, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson despaced",
|
||||
parser.parse((const uint8_t *)buffer, minisize,
|
||||
automated_reallocation).error(),
|
||||
simdjson::SUCCESS, memcpy(buffer, mini_buffer, p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
simdjson::ParsedJson pj2;
|
||||
bool is_alloc_ok2 = pj2.allocate_capacity(p.size(), 1024);
|
||||
if (!is_alloc_ok2) {
|
||||
fprintf(stderr, "failed to allocate memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
automated_reallocation = false;
|
||||
BEST_TIME("simdjson despaced",
|
||||
simdjson::json_parse((const uint8_t *)buffer, minisize, pj2,
|
||||
automated_reallocation),
|
||||
true, memcpy(buffer, mini_buffer, p.size()), repeat, volume,
|
||||
!just_data);
|
||||
free(buffer);
|
||||
free(ast_buffer);
|
||||
free(mini_buffer);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+173
-346
@@ -1,3 +1,5 @@
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
@@ -26,368 +28,193 @@
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
//#define DEBUG
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/isadetection.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/stage1_find_marks.h"
|
||||
#include "simdjson/stage2_build_tape.h"
|
||||
namespace simdjson {
|
||||
Architecture _find_best_supported_implementation() {
|
||||
constexpr uint32_t haswell_flags =
|
||||
instruction_set::AVX2 | instruction_set::PCLMULQDQ |
|
||||
instruction_set::BMI1 | instruction_set::BMI2;
|
||||
constexpr uint32_t westmere_flags =
|
||||
instruction_set::SSE42 | instruction_set::PCLMULQDQ;
|
||||
uint32_t supports = detect_supported_architectures();
|
||||
// Order from best to worst (within architecture)
|
||||
if ((haswell_flags & supports) == haswell_flags) {
|
||||
return Architecture::HASWELL;
|
||||
}
|
||||
if ((westmere_flags & supports) == westmere_flags) {
|
||||
return Architecture::WESTMERE;
|
||||
}
|
||||
if (instruction_set::NEON)
|
||||
return Architecture::ARM64;
|
||||
|
||||
return Architecture::NONE;
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "benchmarker.h"
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
|
||||
// Stash the exe_name in main() for functions to use
|
||||
char* exe_name;
|
||||
|
||||
void print_usage(ostream& out) {
|
||||
out << "Usage: " << exe_name << " [-vt] [-n #] [-s STAGE] [-a ARCH] <jsonfile> ..." << endl;
|
||||
out << endl;
|
||||
out << "Runs the parser against the given json files in a loop, measuring speed and other statistics." << endl;
|
||||
out << endl;
|
||||
out << "Options:" << endl;
|
||||
out << endl;
|
||||
out << "-n # - Number of iterations per file. Default: 200" << endl;
|
||||
out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl;
|
||||
out << "-t - Tabbed data output" << endl;
|
||||
out << "-v - Verbose output." << endl;
|
||||
out << "-s stage1 - Stop after find_structural_bits." << endl;
|
||||
out << "-s all - Run all stages." << endl;
|
||||
out << "-H - Make the buffers hot (reduce page allocation during parsing)" << endl;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
using unified_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
|
||||
using stage1_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
|
||||
|
||||
extern unified_functype *unified_ptr;
|
||||
|
||||
extern stage1_functype *stage1_ptr;
|
||||
|
||||
int unified_machine_dispatch(const uint8_t *buf, size_t len, ParsedJson &pj) {
|
||||
Architecture best_implementation = _find_best_supported_implementation();
|
||||
// Selecting the best implementation
|
||||
switch (best_implementation) {
|
||||
#ifdef IS_X86_64
|
||||
case Architecture::HASWELL:
|
||||
unified_ptr = &unified_machine<Architecture::HASWELL>;
|
||||
break;
|
||||
case Architecture::WESTMERE:
|
||||
unified_ptr = &unified_machine<Architecture::WESTMERE>;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IS_ARM64
|
||||
case Architecture::ARM64:
|
||||
unified_ptr = &unified_machine<Architecture::ARM64>;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
std::cerr << "The processor is not supported by simdjson." << std::endl;
|
||||
return simdjson::UNEXPECTED_ERROR;
|
||||
}
|
||||
|
||||
return unified_ptr(buf, len, pj);
|
||||
void exit_usage(string message) {
|
||||
cerr << message << endl;
|
||||
cerr << endl;
|
||||
print_usage(cerr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Responsible to select the best json_parse implementation
|
||||
int find_structural_bits_dispatch(const uint8_t *buf, size_t len,
|
||||
ParsedJson &pj) {
|
||||
Architecture best_implementation = _find_best_supported_implementation();
|
||||
// Selecting the best implementation
|
||||
switch (best_implementation) {
|
||||
#ifdef IS_X86_64
|
||||
case Architecture::HASWELL:
|
||||
stage1_ptr = &find_structural_bits<Architecture::HASWELL>;
|
||||
break;
|
||||
case Architecture::WESTMERE:
|
||||
stage1_ptr = &find_structural_bits<Architecture::WESTMERE>;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IS_ARM64
|
||||
case Architecture::ARM64:
|
||||
stage1_ptr = &find_structural_bits<Architecture::ARM64>;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
std::cerr << "The processor is not supported by simdjson." << std::endl;
|
||||
return simdjson::UNEXPECTED_ERROR;
|
||||
struct option_struct {
|
||||
vector<char*> files;
|
||||
bool stage1_only = false;
|
||||
|
||||
int32_t iterations = 200;
|
||||
int32_t iteration_step = 50;
|
||||
|
||||
bool verbose = false;
|
||||
bool tabbed_output = false;
|
||||
bool hotbuffers = false;
|
||||
|
||||
option_struct(int argc, char **argv) {
|
||||
#ifndef _MSC_VER
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "vtn:i:a:s:H")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
iterations = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
iteration_step = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
tabbed_output = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a': {
|
||||
const implementation *impl = simdjson::available_implementations[optarg];
|
||||
if (!impl) {
|
||||
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 += " ";
|
||||
}
|
||||
exit_usage(exit_message);
|
||||
}
|
||||
simdjson::active_implementation = impl;
|
||||
break;
|
||||
}
|
||||
case 'H':
|
||||
hotbuffers = true;
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
stage1_only = true;
|
||||
} else if (!strcmp(optarg, "all")) {
|
||||
stage1_only = false;
|
||||
} else {
|
||||
exit_usage(string("Unsupported option value -s ") + optarg + ": expected -s stage1 or all");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// reaching here means an argument was given to getopt() which did not have a case label
|
||||
exit_usage("Unexpected argument - missing case for option "+
|
||||
std::string(1,static_cast<char>(c))+
|
||||
" (programming error)");
|
||||
}
|
||||
}
|
||||
#else
|
||||
int optind = 1;
|
||||
#endif
|
||||
|
||||
// All remaining arguments are considered to be files
|
||||
for (int i=optind; i<argc; i++) {
|
||||
files.push_back(argv[i]);
|
||||
}
|
||||
if (files.empty()) {
|
||||
exit_usage("No files specified");
|
||||
}
|
||||
|
||||
// Keeps the numbers the same for CI (old ./parse didn't have a two-stage loop)
|
||||
if (files.size() == 1) {
|
||||
iteration_step = iterations;
|
||||
}
|
||||
|
||||
#if !defined(__linux__)
|
||||
if (tabbed_output) {
|
||||
exit_error("tabbed_output (-t) flag only works under linux.\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return stage1_ptr(buf, len, pj);
|
||||
}
|
||||
|
||||
stage1_functype *stage1_ptr = &find_structural_bits_dispatch;
|
||||
unified_functype *unified_ptr = &unified_machine_dispatch;
|
||||
} // namespace simdjson
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool dump = false;
|
||||
bool json_output = false;
|
||||
bool force_one_iteration = false;
|
||||
bool just_data = false;
|
||||
#ifndef _MSC_VER
|
||||
int c;
|
||||
// Read options
|
||||
exe_name = argv[0];
|
||||
option_struct options(argc, argv);
|
||||
if (options.verbose) {
|
||||
verbose_stream = &cout;
|
||||
verbose() << "Implementation: " << simdjson::active_implementation->name() << endl;
|
||||
}
|
||||
|
||||
while ((c = getopt(argc, argv, "1vdt")) != -1) {
|
||||
switch (c) {
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'd':
|
||||
dump = true;
|
||||
break;
|
||||
case 'j':
|
||||
json_output = true;
|
||||
break;
|
||||
case '1':
|
||||
force_one_iteration = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#else
|
||||
int optind = 1;
|
||||
#endif
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] loading " << filename << std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] loaded " << filename << " (" << p.size()
|
||||
<< " bytes)" << std::endl;
|
||||
}
|
||||
#if defined(DEBUG)
|
||||
const uint32_t iterations = 1;
|
||||
#else
|
||||
const uint32_t iterations =
|
||||
force_one_iteration ? 1 : (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
||||
#endif
|
||||
std::vector<double> res;
|
||||
res.resize(iterations);
|
||||
if (!just_data)
|
||||
printf("number of iterations %u \n", iterations);
|
||||
#if !defined(__linux__)
|
||||
#define SQUASH_COUNTERS
|
||||
if (just_data) {
|
||||
printf("just_data (-t) flag only works under linux.\n");
|
||||
}
|
||||
#endif
|
||||
{ // practice run
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (allocok) {
|
||||
simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj);
|
||||
simdjson::unified_ptr(
|
||||
(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t *)
|
||||
p.data(),
|
||||
p.size(), pj);
|
||||
}
|
||||
}
|
||||
#ifndef SQUASH_COUNTERS
|
||||
std::vector<int> evts;
|
||||
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
|
||||
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
|
||||
evts.push_back(PERF_COUNT_HW_BRANCH_MISSES);
|
||||
evts.push_back(PERF_COUNT_HW_CACHE_REFERENCES);
|
||||
evts.push_back(PERF_COUNT_HW_CACHE_MISSES);
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
|
||||
std::vector<unsigned long long> results;
|
||||
results.resize(evts.size());
|
||||
unsigned long cy0 = 0, cy1 = 0, cy2 = 0;
|
||||
unsigned long cl0 = 0, cl1 = 0, cl2 = 0;
|
||||
unsigned long mis0 = 0, mis1 = 0, mis2 = 0;
|
||||
unsigned long cref0 = 0, cref1 = 0, cref2 = 0;
|
||||
unsigned long cmis0 = 0, cmis1 = 0, cmis2 = 0;
|
||||
#endif
|
||||
bool isok = true;
|
||||
#ifndef SQUASH_COUNTERS
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||
}
|
||||
unified.start();
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
unified.end(results);
|
||||
cy0 += results[0];
|
||||
cl0 += results[1];
|
||||
mis0 += results[2];
|
||||
cref0 += results[3];
|
||||
cmis0 += results[4];
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] allocated memory for parsed JSON " << std::endl;
|
||||
}
|
||||
unified.start();
|
||||
isok = (simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
cy1 += results[0];
|
||||
cl1 += results[1];
|
||||
mis1 += results[2];
|
||||
cref1 += results[3];
|
||||
cmis1 += results[4];
|
||||
if (!isok) {
|
||||
std::cout << "Failed during stage 1" << std::endl;
|
||||
break;
|
||||
}
|
||||
unified.start();
|
||||
isok = isok &&
|
||||
(simdjson::SUCCESS ==
|
||||
simdjson::unified_ptr((const uint8_t *)p.data(), p.size(), pj));
|
||||
unified.end(results);
|
||||
cy2 += results[0];
|
||||
cl2 += results[1];
|
||||
mis2 += results[2];
|
||||
cref2 += results[3];
|
||||
cmis2 += results[4];
|
||||
if (!isok) {
|
||||
std::cout << "Failed during stage 2" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// we do it again, this time just measuring the elapsed time
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||
}
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] allocated memory for parsed JSON " << std::endl;
|
||||
}
|
||||
// Start collecting events. We put this early so if it prints an error message, it's the
|
||||
// first thing printed.
|
||||
event_collector collector;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
isok = (simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
isok = isok &&
|
||||
(simdjson::SUCCESS ==
|
||||
simdjson::unified_ptr((const uint8_t *)p.data(), p.size(), pj));
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
res[i] = secs.count();
|
||||
if (!isok) {
|
||||
std::cerr << pj.get_error_message() << std::endl;
|
||||
std::cerr << "Could not parse. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// Print preamble
|
||||
if (!options.tabbed_output) {
|
||||
printf("number of iterations %u \n", options.iterations);
|
||||
}
|
||||
simdjson::ParsedJson pj =
|
||||
build_parsed_json(p); // do the parsing again to get the stats
|
||||
if (!pj.is_valid()) {
|
||||
std::cerr << pj.get_error_message() << std::endl;
|
||||
std::cerr << "Could not parse. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
|
||||
// Set up benchmarkers by reading all files
|
||||
vector<benchmarker*> benchmarkers;
|
||||
for (size_t i=0; i<options.files.size(); i++) {
|
||||
benchmarkers.push_back(new benchmarker(options.files[i], collector));
|
||||
}
|
||||
double min_result = *min_element(res.begin(), res.end());
|
||||
double speedinGBs = (p.size()) / (min_result * 1000000000.0);
|
||||
#ifndef SQUASH_COUNTERS
|
||||
unsigned long total = cy0 + cy1 + cy2;
|
||||
if (just_data) {
|
||||
float cpb0 = (double)cy0 / (iterations * p.size());
|
||||
float cpb1 = (double)cy1 / (iterations * p.size());
|
||||
float cpb2 = (double)cy2 / (iterations * p.size());
|
||||
float cpbtotal = (double)total / (iterations * p.size());
|
||||
char *newfile = (char *)malloc(strlen(filename) + 1);
|
||||
if (newfile == NULL) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
::strcpy(newfile, filename);
|
||||
char *snewfile = ::basename(newfile);
|
||||
size_t nl = strlen(snewfile);
|
||||
for (size_t j = nl - 1; j > 0; j--) {
|
||||
if (snewfile[j] == '.') {
|
||||
snewfile[j] = '\0';
|
||||
break;
|
||||
|
||||
// Run the benchmarks
|
||||
progress_bar progress(options.iterations, 50);
|
||||
// Put the if (options.stage1_only) *outside* the loop so that run_iterations will be optimized
|
||||
if (options.stage1_only) {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
// Benchmark each file once per iteration
|
||||
for (size_t f=0; f<options.files.size(); f++) {
|
||||
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
|
||||
benchmarkers[f]->run_iterations(options.iteration_step, true, options.hotbuffers);
|
||||
}
|
||||
}
|
||||
printf("\"%s\"\t%f\t%f\t%f\t%f\t%f\n", snewfile, cpb0, cpb1, cpb2, cpbtotal,
|
||||
speedinGBs);
|
||||
free(newfile);
|
||||
} else {
|
||||
printf("number of bytes %ld number of structural chars %u ratio %.3f\n",
|
||||
p.size(), pj.n_structural_indexes,
|
||||
(double)pj.n_structural_indexes / p.size());
|
||||
printf("mem alloc instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache accesses: "
|
||||
"%10lu (failure %10lu)\n",
|
||||
cl0 / iterations, cy0 / iterations, 100. * cy0 / total,
|
||||
(double)cl0 / cy0, mis0 / iterations, (double)cy0 / mis0,
|
||||
cref1 / iterations, cmis0 / iterations);
|
||||
printf(" mem alloc runs at %.2f cycles per input byte.\n",
|
||||
(double)cy0 / (iterations * p.size()));
|
||||
printf("stage 1 instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache accesses: "
|
||||
"%10lu (failure %10lu)\n",
|
||||
cl1 / iterations, cy1 / iterations, 100. * cy1 / total,
|
||||
(double)cl1 / cy1, mis1 / iterations, (double)cy1 / mis1,
|
||||
cref1 / iterations, cmis1 / iterations);
|
||||
printf(" stage 1 runs at %.2f cycles per input byte.\n",
|
||||
(double)cy1 / (iterations * p.size()));
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
// Benchmark each file once per iteration
|
||||
for (size_t f=0; f<options.files.size(); f++) {
|
||||
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
|
||||
benchmarkers[f]->run_iterations(options.iteration_step, false, options.hotbuffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!options.verbose) { progress.erase(); }
|
||||
|
||||
printf("stage 2 instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache "
|
||||
"accesses: %10lu (failure %10lu)\n",
|
||||
cl2 / iterations, cy2 / iterations, 100. * cy2 / total,
|
||||
(double)cl2 / cy2, mis2 / iterations, (double)cy2 / mis2,
|
||||
cref2 / iterations, cmis2 / iterations);
|
||||
printf(" stage 2 runs at %.2f cycles per input byte and ",
|
||||
(double)cy2 / (iterations * p.size()));
|
||||
printf("%.2f cycles per structural character.\n",
|
||||
(double)cy2 / (iterations * pj.n_structural_indexes));
|
||||
for (size_t i=0; i<options.files.size(); i++) {
|
||||
benchmarkers[i]->print(options.tabbed_output, options.iterations);
|
||||
delete benchmarkers[i];
|
||||
}
|
||||
|
||||
printf(" all stages: %.2f cycles per input byte.\n",
|
||||
(double)total / (iterations * p.size()));
|
||||
printf("Estimated average frequency: %.3f GHz.\n",
|
||||
(double)total / (iterations * min_result * 1000000000.0));
|
||||
}
|
||||
#endif
|
||||
if (!just_data) {
|
||||
std::cout << "Min: " << min_result << " bytes read: " << p.size()
|
||||
<< " Gigabytes/second: " << speedinGBs << std::endl;
|
||||
}
|
||||
if (json_output) {
|
||||
isok = isok && pj.print_json(std::cout);
|
||||
}
|
||||
if (dump) {
|
||||
isok = isok && pj.dump_raw_tape(std::cout);
|
||||
}
|
||||
if (!isok) {
|
||||
fprintf(stderr, " Parsing failed. \n ");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#define NB_ITERATION 5
|
||||
#define MIN_BATCH_SIZE 200000
|
||||
#define MAX_BATCH_SIZE 10000000
|
||||
|
||||
bool test_baseline = false;
|
||||
bool test_per_batch = true;
|
||||
bool test_best_batch = true;
|
||||
|
||||
bool compare(std::pair<size_t, double> i, std::pair<size_t, double> j){
|
||||
return i.second > j.second;
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]){
|
||||
|
||||
if (argc <= 1) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[1];
|
||||
auto [p, err] = simdjson::padded_string::load(filename);
|
||||
if (err) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (test_baseline) {
|
||||
std::wclog << "Baseline: Getline + normal parse... " << std::endl;
|
||||
std::cout << "Gigabytes/second\t" << "Nb of documents parsed" << std::endl;
|
||||
for (auto i = 0; i < 3; i++) {
|
||||
//Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code alloc_error = parser.allocate(p.size());
|
||||
if (alloc_error) {
|
||||
std::cerr << alloc_error << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::istringstream ss(std::string(p.data(), p.size()));
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int count = 0;
|
||||
std::string line;
|
||||
int parse_res = simdjson::SUCCESS;
|
||||
while (getline(ss, line)) {
|
||||
// TODO we're likely triggering simdjson's padding reallocation here. Is that intentional?
|
||||
parser.parse(line);
|
||||
count++;
|
||||
}
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
double speedinGBs = (p.size()) / (secs.count() * 1000000000.0);
|
||||
std::cout << speedinGBs << "\t\t\t\t" << count << std::endl;
|
||||
|
||||
if (parse_res != simdjson::SUCCESS) {
|
||||
std::cerr << "Parsing failed" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<size_t, double> batch_size_res;
|
||||
if(test_per_batch) {
|
||||
std::wclog << "parse_many: Speed per batch_size... from " << MIN_BATCH_SIZE
|
||||
<< " bytes to " << MAX_BATCH_SIZE << " bytes..." << std::endl;
|
||||
std::cout << "Batch Size\t" << "Gigabytes/second\t" << "Nb of documents parsed" << std::endl;
|
||||
for (size_t i = MIN_BATCH_SIZE; i <= MAX_BATCH_SIZE; i += (MAX_BATCH_SIZE - MIN_BATCH_SIZE) / 50) {
|
||||
batch_size_res.insert(std::pair<size_t, double>(i, 0));
|
||||
int count;
|
||||
for (size_t j = 0; j < 5; j++) {
|
||||
//Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
count = 0;
|
||||
for (auto result : parser.parse_many(p, 4000000)) {
|
||||
error = result.error();
|
||||
count++;
|
||||
}
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
double speedinGBs = (p.size()) / (secs.count() * 1000000000.0);
|
||||
if (speedinGBs > batch_size_res.at(i))
|
||||
batch_size_res[i] = speedinGBs;
|
||||
|
||||
if (error != simdjson::SUCCESS) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
std::cout << i << "\t\t" << std::fixed << std::setprecision(3) << batch_size_res.at(i) << "\t\t\t\t" << count << std::endl;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (test_best_batch) {
|
||||
size_t optimal_batch_size;
|
||||
if (test_per_batch) {
|
||||
optimal_batch_size = (*min_element(batch_size_res.begin(), batch_size_res.end(), compare)).first;
|
||||
} else {
|
||||
optimal_batch_size = MIN_BATCH_SIZE;
|
||||
}
|
||||
std::wclog << "Starting speed test... Best of " << NB_ITERATION << " iterations..." << std::endl;
|
||||
std::wclog << "Seemingly optimal batch_size: " << optimal_batch_size << "..." << std::endl;
|
||||
std::vector<double> res;
|
||||
for (int i = 0; i < NB_ITERATION; i++) {
|
||||
|
||||
// Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
// TODO this includes allocation of the parser; is that intentional?
|
||||
for (auto result : parser.parse_many(p, 4000000)) {
|
||||
error = result.error();
|
||||
}
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
res.push_back(secs.count());
|
||||
|
||||
if (error != simdjson::SUCCESS) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::min(res.begin(), res.end());
|
||||
|
||||
double min_result = *min_element(res.begin(), res.end());
|
||||
double speedinGBs = (p.size()) / (min_result * 1000000000.0);
|
||||
|
||||
|
||||
std::cout << "Min: " << min_result << " bytes read: " << p.size()
|
||||
<< " Gigabytes/second: " << speedinGBs << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#include <unistd.h>
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -11,6 +14,8 @@
|
||||
|
||||
#include "sajson.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
using namespace simdjson;
|
||||
struct stat_s {
|
||||
@@ -43,63 +48,60 @@ void print_stat(const stat_t &s) {
|
||||
s.true_count, s.false_count);
|
||||
}
|
||||
|
||||
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<double>()) {
|
||||
s.number_count++;
|
||||
} else if (element.is<bool>()) {
|
||||
if (element.get<bool>()) {
|
||||
s.true_count++;
|
||||
} else {
|
||||
s.false_count++;
|
||||
}
|
||||
} else if (element.is_null()) {
|
||||
s.null_count++;
|
||||
}
|
||||
}
|
||||
|
||||
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
s.array_count++;
|
||||
auto [array, array_error] = element.get<simdjson::dom::array>();
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, child);
|
||||
} else {
|
||||
simdjson_process_atom(s, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
s.object_count++;
|
||||
auto [object, object_error] = element.get<simdjson::dom::object>();
|
||||
for (auto [key, value] : object) {
|
||||
if (value.is<simdjson::dom::array>() || value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, value);
|
||||
} else {
|
||||
simdjson_process_atom(s, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
simdjson_process_atom(s, element);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) stat_t
|
||||
simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
simdjson::ParsedJson pj = build_parsed_json(p);
|
||||
answer.valid = pj.is_valid();
|
||||
if (!answer.valid) {
|
||||
return answer;
|
||||
stat_t s{};
|
||||
simdjson::dom::parser parser;
|
||||
auto [doc, error] = parser.parse(p);
|
||||
if (error) {
|
||||
s.valid = false;
|
||||
return s;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
size_t tape_idx = 0;
|
||||
uint64_t tape_val = pj.tape[tape_idx++];
|
||||
uint8_t type = (tape_val >> 56);
|
||||
size_t how_many = 0;
|
||||
assert(type == 'r');
|
||||
how_many = tape_val & JSON_VALUE_MASK;
|
||||
for (; tape_idx < how_many; tape_idx++) {
|
||||
tape_val = pj.tape[tape_idx];
|
||||
// uint64_t payload = tape_val & JSON_VALUE_MASK;
|
||||
type = (tape_val >> 56);
|
||||
switch (type) {
|
||||
case 'l': // we have a long int
|
||||
answer.number_count++;
|
||||
tape_idx++; // skipping the integer
|
||||
break;
|
||||
case 'd': // we have a double
|
||||
answer.number_count++;
|
||||
tape_idx++; // skipping the double
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
answer.null_count++;
|
||||
break;
|
||||
case 't': // we have a true
|
||||
answer.true_count++;
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
answer.false_count++;
|
||||
break;
|
||||
case '{': // we have an object
|
||||
answer.object_count++;
|
||||
break;
|
||||
case '}': // we end an object
|
||||
break;
|
||||
case '[': // we start an array
|
||||
answer.array_count++;
|
||||
break;
|
||||
case ']': // we end an array
|
||||
break;
|
||||
default:
|
||||
break; // ignore
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
s.valid = true;
|
||||
simdjson_recurse(s, doc);
|
||||
return s;
|
||||
}
|
||||
|
||||
// see
|
||||
@@ -149,11 +151,15 @@ __attribute__((noinline)) stat_t
|
||||
sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
auto d = sajson::parse(sajson::dynamic_allocation(),
|
||||
sajson::mutable_string_view(p.size(), buffer));
|
||||
answer.valid = d.is_valid();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
@@ -207,12 +213,16 @@ __attribute__((noinline)) stat_t
|
||||
rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
rapidjson::Document d;
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
|
||||
answer.valid = !d.HasParseError();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
@@ -226,6 +236,32 @@ rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) stat_t
|
||||
rapid_accurate_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
rapidjson::Document d;
|
||||
d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(buffer);
|
||||
answer.valid = !d.HasParseError();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
rapid_traverse(answer, d);
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
@@ -256,11 +292,9 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto [p, error] = simdjson::padded_string::load(filename);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -284,6 +318,11 @@ int main(int argc, char *argv[]) {
|
||||
printf("rapid: ");
|
||||
print_stat(s2);
|
||||
}
|
||||
stat_t s2a = rapid_accurate_compute_stats(p);
|
||||
if (verbose) {
|
||||
printf("rapid full: ");
|
||||
print_stat(s2a);
|
||||
}
|
||||
stat_t s3 = sasjon_compute_stats(p);
|
||||
if (verbose) {
|
||||
printf("sasjon: ");
|
||||
@@ -296,10 +335,12 @@ int main(int argc, char *argv[]) {
|
||||
if (just_data) {
|
||||
printf("name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
|
||||
}
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).valid, true, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("RapidJSON ", rapid_compute_stats(p).valid, true, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).valid, true, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON ", rapid_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON (precise) ", rapid_accurate_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include "linux-perf-events.h"
|
||||
#include <unistd.h>
|
||||
@@ -11,6 +12,8 @@
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -20,6 +23,9 @@
|
||||
|
||||
#include "sajson.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#ifdef ALLPARSER
|
||||
|
||||
#include "fastjson.cpp"
|
||||
@@ -41,11 +47,13 @@ extern "C" {
|
||||
|
||||
#endif
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
#ifdef ALLPARSER
|
||||
// fastjson has a tricky interface
|
||||
void on_json_error(void *, const fastjson::ErrorContext &ec) {
|
||||
void on_json_error(void *, UNUSED const fastjson::ErrorContext &ec) {
|
||||
// std::cerr<<"ERROR: "<<ec.mesg<<std::endl;
|
||||
}
|
||||
bool fastjson_parse(const char *input) {
|
||||
@@ -57,70 +65,57 @@ bool fastjson_parse(const char *input) {
|
||||
// end of fastjson stuff
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "vt")) != -1)
|
||||
switch (c) {
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
if (optind >= argc) {
|
||||
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;
|
||||
exit(1);
|
||||
never_inline size_t sum_line_lengths(char * data, size_t length) {
|
||||
std::stringstream is;
|
||||
is.rdbuf()->pubsetbuf(data, length);
|
||||
std::string line;
|
||||
size_t sumofalllinelengths{0};
|
||||
while(getline(is, line)) {
|
||||
sumofalllinelengths += line.size();
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
return sumofalllinelengths;
|
||||
}
|
||||
|
||||
|
||||
bool bench(const char *filename, bool verbose, bool just_data, int repeat_multiplier) {
|
||||
auto [p, err] = simdjson::padded_string::load(filename);
|
||||
if (err) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int repeat = (50000000 * repeat_multiplier) / p.size();
|
||||
if (repeat < 10) { repeat = 10; }
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Input has ";
|
||||
std::cout << "Input " << filename << " has ";
|
||||
if (p.size() > 1024 * 1024)
|
||||
std::cout << p.size() / (1024 * 1024) << " MB ";
|
||||
std::cout << p.size() / (1024 * 1024) << " MB";
|
||||
else if (p.size() > 1024)
|
||||
std::cout << p.size() / 1024 << " KB ";
|
||||
std::cout << p.size() / 1024 << " KB";
|
||||
else
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
std::cout << p.size() << " B";
|
||||
std::cout << ": will run " << repeat << " iterations." << std::endl;
|
||||
}
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size(), 1024);
|
||||
|
||||
if (!allocok) {
|
||||
std::cerr << "can't allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
int repeat = (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
||||
int 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");
|
||||
}
|
||||
if (!just_data) {
|
||||
size_t lc = sum_line_lengths(p.data(), p.size());
|
||||
BEST_TIME("getline ",sum_line_lengths(p.data(), p.size()) , lc, ,
|
||||
repeat, volume, !just_data);
|
||||
}
|
||||
|
||||
if (!just_data)
|
||||
BEST_TIME("simdjson (dynamic mem) ", build_parsed_json(p).is_valid(), true,
|
||||
BEST_TIME("simdjson (dynamic mem) ", simdjson::dom::parser().parse(p).error(), simdjson::SUCCESS,
|
||||
, repeat, volume, !just_data);
|
||||
// (static alloc)
|
||||
BEST_TIME("simdjson ", json_parse(p, pj), simdjson::SUCCESS, , repeat, volume,
|
||||
simdjson::dom::parser parser;
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat, volume,
|
||||
!just_data);
|
||||
|
||||
|
||||
rapidjson::Document d;
|
||||
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
@@ -129,16 +124,35 @@ int main(int argc, char *argv[]) {
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON ",
|
||||
d.Parse<kParseValidateEncodingFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON (accurate number parsing) ",
|
||||
d.Parse<kParseValidateEncodingFlag|kParseFullPrecisionFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
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, accurate number parsing)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag|kParseFullPrecisionFlag>(buffer).HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
@@ -158,6 +172,13 @@ int main(int argc, char *argv[]) {
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
|
||||
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);
|
||||
|
||||
#ifdef ALLPARSER
|
||||
std::string json11err;
|
||||
BEST_TIME("dropbox (json11) ",
|
||||
@@ -182,14 +203,14 @@ int main(int argc, char *argv[]) {
|
||||
{
|
||||
std::unique_ptr<jsmntok_t[]> tokens =
|
||||
std::make_unique<jsmntok_t[]>(p.size());
|
||||
jsmn_parser parser;
|
||||
jsmn_init(&parser);
|
||||
jsmn_parser jparser;
|
||||
jsmn_init(&jparser);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
BEST_TIME(
|
||||
"jsmn ",
|
||||
(jsmn_parse(&parser, buffer, p.size(), tokens.get(), p.size()) > 0),
|
||||
true, jsmn_init(&parser), repeat, volume, !just_data);
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(), p.size()) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
@@ -229,7 +250,8 @@ int main(int argc, char *argv[]) {
|
||||
std::fill(stats.begin(), stats.end(), 0); // unnecessary
|
||||
for (int i = 0; i < repeat; i++) {
|
||||
unified.start();
|
||||
if (json_parse(p, pj) != simdjson::SUCCESS)
|
||||
auto [doc, parse_error] = parser.parse(p);
|
||||
if (parse_error)
|
||||
printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
@@ -282,9 +304,45 @@ int main(int argc, char *argv[]) {
|
||||
stats[2] * 1.0 / repeat, stats[3] * 1.0 / repeat,
|
||||
stats[4] * 1.0 / repeat, volume * repeat * 1.0 / stats[2],
|
||||
stats[1] * 1.0 / stats[0], stats[1] * 1.0 / (volume * repeat));
|
||||
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
free(ast_buffer);
|
||||
free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
double repeat_multiplier = 1;
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "r:vt")) != -1)
|
||||
switch (c) {
|
||||
case 'r':
|
||||
repeat_multiplier = atof(optarg);
|
||||
break;
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
if (optind >= argc) {
|
||||
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;
|
||||
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");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
|
||||
int closepipe(FILE *pipe) {
|
||||
int exit_code = pclose(pipe);
|
||||
if (exit_code != EXIT_SUCCESS) {
|
||||
std::cerr << "Error " << exit_code << " running benchmark command!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
std::string exec(const char* cmd) {
|
||||
std::cerr << cmd << std::endl;
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
std::unique_ptr<FILE, decltype(&closepipe)> pipe(popen(cmd, "r"), closepipe);
|
||||
if (!pipe) {
|
||||
throw std::runtime_error("popen() failed!");
|
||||
}
|
||||
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||
result += buffer.data();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double readThroughput(std::string parseOutput) {
|
||||
std::istringstream output(parseOutput);
|
||||
std::string line;
|
||||
double result = 0;
|
||||
int numResults = 0;
|
||||
while (std::getline(output, line)) {
|
||||
int pos = 0;
|
||||
for (int i=0; i<5; i++) {
|
||||
pos = line.find('\t', pos);
|
||||
if (pos < 0) {
|
||||
std::cerr << "Command printed out a line with less than 5 fields in it:\n" << line << std::endl;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
result += std::stod(line.substr(pos));
|
||||
numResults++;
|
||||
}
|
||||
if (numResults == 0) {
|
||||
std::cerr << "No results returned from benchmark command!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return result / numResults;
|
||||
}
|
||||
|
||||
const double INTERLEAVED_ATTEMPTS = 7;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <new parse cmd> <reference parse cmd>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::vector<double> ref;
|
||||
std::vector<double> newcode;
|
||||
for (int attempt=0; attempt < INTERLEAVED_ATTEMPTS; attempt++) {
|
||||
std::cout << "Attempt #" << (attempt+1) << " of " << INTERLEAVED_ATTEMPTS << std::endl;
|
||||
|
||||
// Read new throughput
|
||||
double newThroughput = readThroughput(exec(argv[1]));
|
||||
std::cout << "New throughput: " << newThroughput << std::endl;
|
||||
newcode.push_back(newThroughput);
|
||||
|
||||
// Read reference throughput
|
||||
double referenceThroughput = readThroughput(exec(argv[2]));
|
||||
std::cout << "Ref throughput: " << referenceThroughput << std::endl;
|
||||
ref.push_back(referenceThroughput);
|
||||
}
|
||||
// we check if the maximum of newcode is lower than minimum of ref, if so we have a problem so fail!
|
||||
double worseref = *std::min_element(ref.begin(), ref.end());
|
||||
double bestnewcode = *std::max_element(newcode.begin(), newcode.end());
|
||||
double bestref = *std::max_element(ref.begin(), ref.end());
|
||||
double worsenewcode = *std::min_element(newcode.begin(), newcode.end());
|
||||
std::cout << "The new code has a throughput in " << worsenewcode << " -- " << bestnewcode << std::endl;
|
||||
std::cout << "The reference code has a throughput in " << worseref << " -- " << bestref << std::endl;
|
||||
if(bestnewcode < worseref) {
|
||||
std::cerr << "You probably have a performance degradation." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(bestnewcode < worseref) {
|
||||
std::cout << "You probably have a performance gain." << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
std::cout << "There is no obvious performance difference. A manual check might be needed." << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#ifdef __linux__
|
||||
#include "linux-perf-events.h"
|
||||
#endif
|
||||
@@ -42,72 +41,70 @@ struct stat_s {
|
||||
|
||||
using stat_t = struct stat_s;
|
||||
|
||||
|
||||
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<int64_t>()) {
|
||||
s.integer_count++;
|
||||
} else if(element.is<std::string_view>()) {
|
||||
s.string_count++;
|
||||
} else if(element.is<double>()) {
|
||||
s.float_count++;
|
||||
} else if (element.is<bool>()) {
|
||||
if (element.get<bool>()) {
|
||||
s.true_count++;
|
||||
} else {
|
||||
s.false_count++;
|
||||
}
|
||||
} else if (element.is_null()) {
|
||||
s.null_count++;
|
||||
}
|
||||
}
|
||||
|
||||
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
s.array_count++;
|
||||
auto [array, array_error] = element.get<simdjson::dom::array>();
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, child);
|
||||
} else {
|
||||
simdjson_process_atom(s, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
s.object_count++;
|
||||
auto [object, object_error] = element.get<simdjson::dom::object>();
|
||||
for (auto [key, value] : object) {
|
||||
s.string_count++; // for key
|
||||
if (value.is<simdjson::dom::array>() || value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, value);
|
||||
} else {
|
||||
simdjson_process_atom(s, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
simdjson_process_atom(s, element);
|
||||
}
|
||||
}
|
||||
|
||||
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
answer.valid = pj.is_valid();
|
||||
if (!answer.valid) {
|
||||
stat_t answer{};
|
||||
simdjson::dom::parser parser;
|
||||
auto [doc, error] = parser.parse(p);
|
||||
if (error) {
|
||||
answer.valid = false;
|
||||
return answer;
|
||||
}
|
||||
answer.valid = true;
|
||||
answer.backslash_count =
|
||||
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
|
||||
answer.non_ascii_byte_count = count_nonasciibytes(
|
||||
reinterpret_cast<const uint8_t *>(p.data()), p.size());
|
||||
answer.byte_count = p.size();
|
||||
answer.integer_count = 0;
|
||||
answer.float_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
answer.string_count = 0;
|
||||
answer.structural_indexes_count = pj.n_structural_indexes;
|
||||
size_t tape_idx = 0;
|
||||
uint64_t tape_val = pj.tape[tape_idx++];
|
||||
uint8_t type = (tape_val >> 56);
|
||||
size_t how_many = 0;
|
||||
assert(type == 'r');
|
||||
how_many = tape_val & JSON_VALUE_MASK;
|
||||
for (; tape_idx < how_many; tape_idx++) {
|
||||
tape_val = pj.tape[tape_idx];
|
||||
// uint64_t payload = tape_val & JSON_VALUE_MASK;
|
||||
type = (tape_val >> 56);
|
||||
switch (type) {
|
||||
case 'l': // we have a long int
|
||||
answer.integer_count++;
|
||||
tape_idx++; // skipping the integer
|
||||
break;
|
||||
case 'd': // we have a double
|
||||
answer.float_count++;
|
||||
tape_idx++; // skipping the double
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
answer.null_count++;
|
||||
break;
|
||||
case 't': // we have a true
|
||||
answer.true_count++;
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
answer.false_count++;
|
||||
break;
|
||||
case '{': // we have an object
|
||||
answer.object_count++;
|
||||
break;
|
||||
case '}': // we end an object
|
||||
break;
|
||||
case '[': // we start an array
|
||||
answer.array_count++;
|
||||
break;
|
||||
case ']': // we end an array
|
||||
break;
|
||||
case '"': // we have a string
|
||||
answer.string_count++;
|
||||
break;
|
||||
default:
|
||||
break; // ignore
|
||||
}
|
||||
}
|
||||
answer.structural_indexes_count = parser.n_structural_indexes;
|
||||
simdjson_recurse(answer, doc);
|
||||
return answer;
|
||||
}
|
||||
|
||||
@@ -135,10 +132,8 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
auto [p, error] = simdjson::padded_string::load(filename);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -164,10 +159,11 @@ int main(int argc, char *argv[]) {
|
||||
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
|
||||
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
|
||||
#ifdef __linux__
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
simdjson::dom::parser parser;
|
||||
const simdjson::implementation &stage_parser = *simdjson::active_implementation;
|
||||
simdjson::error_code alloc_error = parser.allocate(p.size());
|
||||
if (alloc_error) {
|
||||
std::cerr << alloc_error << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
|
||||
@@ -181,17 +177,15 @@ int main(int argc, char *argv[]) {
|
||||
results.resize(evts.size());
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
unified.start();
|
||||
// The default template is simdjson::Architecture::NATIVE.
|
||||
bool isok = (simdjson::find_structural_bits<>(p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
// The default template is simdjson::architecture::NATIVE.
|
||||
bool isok = (stage_parser.stage1((const uint8_t *)p.data(), p.size(), parser, false) == simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
|
||||
cy1 += results[0];
|
||||
cl1 += results[1];
|
||||
|
||||
unified.start();
|
||||
isok =
|
||||
isok && (simdjson::SUCCESS == unified_machine(p.data(), p.size(), pj));
|
||||
isok = isok && (stage_parser.stage2((const uint8_t *)p.data(), p.size(), parser) == simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
|
||||
cy2 += results[0];
|
||||
|
||||
+1
Submodule dependencies/benchmark added at 8982e1ee6a
+1
Submodule dependencies/json added at a015b78e81
Vendored
+8
@@ -2652,10 +2652,18 @@ char const* Exception::what() const JSONCPP_NOEXCEPT { return msg_.c_str(); }
|
||||
RuntimeError::RuntimeError(String const& msg) : Exception(msg) {}
|
||||
LogicError::LogicError(String const& msg) : Exception(msg) {}
|
||||
JSONCPP_NORETURN void throwRuntimeError(String const& msg) {
|
||||
#if __cpp_exceptions
|
||||
throw RuntimeError(msg);
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
JSONCPP_NORETURN void throwLogicError(String const& msg) {
|
||||
#if __cpp_exceptions
|
||||
throw LogicError(msg);
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
+309
@@ -0,0 +1,309 @@
|
||||
The Basics
|
||||
==========
|
||||
|
||||
An overview of what you need to know to use simdjson, with examples.
|
||||
|
||||
* [Including simdjson](#including-simdjson)
|
||||
* [The Basics: Loading and Parsing JSON Documents](#the-basics-loading-and-parsing-json-documents)
|
||||
* [Using the Parsed JSON](#using-the-parsed-json)
|
||||
* [JSON Pointer](#json-pointer)
|
||||
* [Error Handling](#error-handling)
|
||||
* [Error Handling Example](#error-handling-example)
|
||||
* [Exceptions](#exceptions)
|
||||
* [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)
|
||||
|
||||
Including simdjson
|
||||
------------------
|
||||
|
||||
To include simdjson, copy [simdjson.h](/singleheader/simdjson.h) and [simdjson.cpp](/singleheader/simdjson.cpp)
|
||||
into your project. Then include it in your project with:
|
||||
|
||||
```c++
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson; // optional
|
||||
```
|
||||
|
||||
You can compile with:
|
||||
|
||||
```
|
||||
c++ myproject.cpp simdjson.cpp --std=c++17
|
||||
```
|
||||
|
||||
The Basics: Loading and Parsing JSON Documents
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library offers a simple DOM tree API, which you can access by creating a
|
||||
`dom::parser` and calling the `load()` method:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(filename); // load and parse a file
|
||||
```
|
||||
|
||||
Or by creating a padded string (for efficiency reasons, simdjson requires a string with
|
||||
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
|
||||
```
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
Once you have an element, you can navigate it with idiomatic C++ iterators, operators and casts.
|
||||
|
||||
* **Extracting Values:** 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. You can also use is_*typename*()` to test if it is a
|
||||
given type, and as_*typename*() to do the cast and return an error code on failure instead of an
|
||||
exception.
|
||||
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
|
||||
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
|
||||
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
|
||||
* **Object Iteration:** You can iterate through an object's fields, too: `for (auto [key, value] : object)`
|
||||
* **Array Index:** To get at an array value by index, use the at() method: `array.at(0)` gets the
|
||||
first element.
|
||||
> Note that array[0] does not compile, because implementing [] gives the impression indexing is a
|
||||
> O(1) operation, which it is not presently in simdjson.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type`.
|
||||
|
||||
Here are some examples of all of the above:
|
||||
|
||||
```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::array cars = parser.parse(cars_json).get<dom::array>();
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::object car : cars) {
|
||||
// Accessing a field by name
|
||||
cout << "Make/Model: " << car["make"] << "/" << car["model"] << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year = car["year"];
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
for (double tire_pressure : car["tire_pressure"]) {
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto [key, value] : car) {
|
||||
cout << "- " << key << ": " << value << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```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("0/tire_pressure/1") << endl; // Prints 39.9
|
||||
```
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
All simdjson APIs that can fail return `simdjson_result<T>`, which is a <value, error_code>
|
||||
pair. The error codes and values can be accessed directly, reading the error like so:
|
||||
|
||||
```c++
|
||||
auto [doc, error] = parser.parse(json); // doc is a dom::element
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
// Use document here now that we've checked for the error
|
||||
```
|
||||
|
||||
When you use the code this way, it is your responsibility to check for error before using the
|
||||
result: if there is an error, the result value will not be valid and using it will caused undefined
|
||||
behavior.
|
||||
|
||||
> Note: because of the way `auto [x, y]` works in C++, you have to define new variables each time you
|
||||
> use it. If your project treats aliased, this means you can't use the same names in `auto [x, error]`
|
||||
> without triggering warnings or error (and particularly can't use the word "error" every time). To
|
||||
> circumvent this, you can use this instead:
|
||||
>
|
||||
> ```c++
|
||||
> dom::element doc;
|
||||
> simdjson::error_code error;
|
||||
> parser.parse(json).tie(doc, error); // <-- Assigns to doc and error just like "auto [doc, error]"
|
||||
> ```
|
||||
|
||||
### Error Handling Example
|
||||
|
||||
This is how the example in "Using the Parsed JSON" could be written using only error code checking:
|
||||
|
||||
```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;
|
||||
auto [cars, error] = parser.parse(cars_json).get<dom::array>();
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::element car_element : cars) {
|
||||
dom::object car;
|
||||
car_element.get<dom::object>().tie(car, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Accessing a field by name
|
||||
dom::element make, model;
|
||||
car["make"].tie(make, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
car["model"].tie(model, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
cout << "Make/Model: " << make << "/" << model << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year;
|
||||
car["year"].get<uint64_t>().tie(year, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
dom::array tire_pressure_array;
|
||||
car["tire_pressure"].get<dom::array>().tie(tire_pressure_array, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
for (dom::element tire_pressure_element : tire_pressure_array) {
|
||||
double tire_pressure;
|
||||
tire_pressure_element.get<double>().tie(tire_pressure, error);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto [key, value] : car) {
|
||||
cout << "- " << key << ": " << value << endl;
|
||||
}
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
Users more comfortable with an exception flow may choose to directly cast the `simdjson_result<T>` to the desired type:
|
||||
|
||||
```c++
|
||||
dom::element doc = parser.parse(json); // Throws an exception if there was an error!
|
||||
```
|
||||
|
||||
When used this way, a `simdjson_error` exception will be thrown if an error occurs, preventing the
|
||||
program from continuing if there was an error.
|
||||
|
||||
Tree Walking and JSON Element Types
|
||||
-----------------------------------
|
||||
|
||||
Sometimes you don't necessarily have a document with a known type, and are trying to generically
|
||||
inspect or walk over JSON elements. To do that, you can use iterators and the type() method. For
|
||||
example, here's a quick and dirty recursive function that verbosely prints the JSON document as JSON
|
||||
(* ignoring nuances like trailing commas and escaping strings, for brevity's sake):
|
||||
|
||||
```c++
|
||||
void print_json(dom::element element) {
|
||||
switch (element.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
cout << "[";
|
||||
for (dom::element child : dom::array(element)) {
|
||||
print_json(child);
|
||||
cout << ",";
|
||||
}
|
||||
cout << "]";
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
cout << "{";
|
||||
for (dom::key_value_pair field : dom::object(element)) {
|
||||
cout << "\"" << field.key << "\": ";
|
||||
print_json(field.value);
|
||||
}
|
||||
cout << "}";
|
||||
break;
|
||||
case dom::element_type::INT64:
|
||||
cout << int64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::UINT64:
|
||||
cout << uint64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::DOUBLE:
|
||||
cout << double(element) << endl;
|
||||
break;
|
||||
case dom::element_type::STRING:
|
||||
cout << std::string_view(element) << endl;
|
||||
break;
|
||||
case dom::element_type::BOOL:
|
||||
cout << bool(element) << endl;
|
||||
break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
cout << "null" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void basics_treewalk_1() {
|
||||
dom::parser parser;
|
||||
print_json(parser.load("twitter.json"));
|
||||
}
|
||||
```
|
||||
|
||||
Newline-Delimited JSON (ndjson) and JSON lines
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library also support multithreaded JSON streaming through a large file containing many
|
||||
smaller JSON documents in either [ndjson](http://ndjson.org) or [JSON lines](http://jsonlines.org)
|
||||
format. If your JSON documents all contain arrays or objects, we even support direct file
|
||||
concatenation without whitespace. The concatenated file has no size restrictions (including larger
|
||||
than 4GB), though each individual document must be less than 4GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```json
|
||||
{ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 }
|
||||
```
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
for (dom::element doc : parser.load_many(filename)) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`.
|
||||
|
||||
See [parse_many.md](parse_many.md) for detailed information and design.
|
||||
|
||||
Thread Safety
|
||||
-------------
|
||||
|
||||
The simdjson library is mostly single-threaded. Thread safety is the responsibility of the caller:
|
||||
it is unsafe to reuse a dom::parser object between different threads.
|
||||
|
||||
simdjson's CPU detection, which runs the first time parsing is attempted and switches to the fastest
|
||||
parser for your CPU, is transparent and thread-safe.
|
||||
|
||||
The json stream parser is threaded, using a second thread under its own control. Like the single
|
||||
document parser
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,83 @@
|
||||
CPU Architecture-Specific Implementations
|
||||
=========================================
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Runtime CPU Detection](#runtime-cpu-detection)
|
||||
* [Inspecting the Detected Implementation](#inspecting-the-detected-implementation)
|
||||
* [Querying Available Implementations](#querying-available-implementations)
|
||||
* [Manually Selecting the Implementation](#manually-selecting-the-implementation)
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The simdjson library takes advantage of SIMD instruction sets such as NEON, SSE and AVX to achieve
|
||||
much of its speed. Because these instruction sets work differently, simdjson has to compile a
|
||||
different version of the JSON parser for different CPU architectures, often with different
|
||||
algorithms to take better advantage of a given CPU!
|
||||
|
||||
The current implementations are:
|
||||
* haswell: AVX2 (2013 Intel Haswell or later)
|
||||
* westmere: SSE4.2 (2010 Westmere or later).
|
||||
* arm64: 64-bit ARMv8-A NEON
|
||||
* fallback: A generic implementation that runs on any 64-bit processor.
|
||||
|
||||
In many cases, you don't know where your compiled binary is going to run, so simdjson automatically
|
||||
compiles *all* the implementations into the executable. On Intel, it will include 3 implementations
|
||||
(haswell, westmere and fallback), and on ARM it will include 2 (arm64 and fallback).
|
||||
|
||||
If you know more about where you're going to run and want to save the space, you can disable any of
|
||||
these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is HASWELL,
|
||||
WESTMERE, ARM64 and FALLBACK).
|
||||
|
||||
The simdjson library automatically sets header flags for each implementation as it compiles; there
|
||||
is no need to set architecture-specific flags yourself (e.g., `-mavx2`, `/AVX2` or
|
||||
`-march=haswell`), and it may even break runtime dispatch and your binaries will fail to run on
|
||||
older processors.
|
||||
|
||||
Runtime CPU Detection
|
||||
---------------------
|
||||
|
||||
When you first use simdjson, it will detect the CPU you're running on, and swap over to the fastest
|
||||
implementation for it. This is a small, one-time cost and for many people will be paid the first
|
||||
time they call `parse()` or `load()`.
|
||||
|
||||
Inspecting the Detected Implementation
|
||||
--------------------------------------
|
||||
|
||||
You can check what implementation is running with `active_implementation`:
|
||||
|
||||
```c++
|
||||
cout << "simdjson v" << #SIMDJSON_VERSION << endl;
|
||||
cout << "Detected the best implementation for your machine: " << simdjson::active_implementation->name();
|
||||
cout << "(" << simdjson::active_implementation->description() << ")" << endl;
|
||||
```
|
||||
|
||||
Implementation detection will happen in this case when you first call `name()`.
|
||||
|
||||
Querying Available Implementations
|
||||
----------------------------------
|
||||
|
||||
You can list all available implementations, regardless of which one was selected:
|
||||
|
||||
```c++
|
||||
for (auto implementation : simdjson::available_implementations) {
|
||||
cout << implementation->name() << ": " << implementation->description() << endl;
|
||||
}
|
||||
```
|
||||
|
||||
And look them up by name:
|
||||
|
||||
```c++
|
||||
cout << simdjson::available_implementations["fallback"]->description() << endl;
|
||||
```
|
||||
|
||||
Manually Selecting the Implementation
|
||||
-------------------------------------
|
||||
|
||||
If you're trying to do performance tests or see how different implementations of simdjson run, you
|
||||
can select the CPU architecture yourself:
|
||||
|
||||
```c++
|
||||
// Use the fallback implementation, even though my machine is fast enough for anything
|
||||
simdjson::active_implementation = simdjson::available_implementations["fallback"];
|
||||
```
|
||||
@@ -0,0 +1,155 @@
|
||||
parse_many
|
||||
==========
|
||||
|
||||
An interface providing features to work with files or streams containing multiple JSON documents.
|
||||
As fast and convenient as possible.
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
- [Motivations](#motivations)
|
||||
- [Performance](#performance)
|
||||
- [How it works](#how-it-works)
|
||||
- [Support](#support)
|
||||
- [API](#api)
|
||||
- [Concurrency mode](#concurrency-mode)
|
||||
- [Example](#example)
|
||||
- [Use cases](#use-cases)
|
||||
|
||||
Motivations
|
||||
-----------
|
||||
|
||||
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.
|
||||
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a very 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.
|
||||
|
||||
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
|
||||
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!
|
||||
[](/doc/Multiline_JSON_Parse_Competition.png)
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
### Context
|
||||
|
||||
The parsing in simdjson is divided into 2 stages. First, in stage 1, we parse the document and find
|
||||
all the structural indexes (`{`, `}`, `]`, `[`, `,`, `"`, ...) and validate UTF8. Then, in stage 2,
|
||||
we go through the document again and build the tape using structural indexes found during stage 1.
|
||||
Although stage 1 finds the structural indexes, it has no knowledge of the structure of the document
|
||||
nor does it know whether it parsed a valid document, multiple documents, or even if the document is
|
||||
complete.
|
||||
|
||||
Prior to parse_many, most people who had to parse a multiline JSON file would proceed by reading the
|
||||
file line by line, using a utility function like `std::getline` or equivalent, and would then use
|
||||
the `parse` on each of those lines. From a performance point of view, this process is highly
|
||||
inefficient, in that it requires a lot of unnecessary memory allocation and makes use of the
|
||||
`getline` function, which is fundamentally slow, slower than the act of parsing with simdjson
|
||||
[(more on this here)](https://lemire.me/blog/2019/06/18/how-fast-is-getline-in-c/).
|
||||
|
||||
Unlike the popular parser RapidJson, our DOM does not require the buffer once the parsing job is
|
||||
completed, the DOM and the buffer are completely independent. The drawback of this architecture is
|
||||
that we need to allocate some additional memory to store our ParsedJson data, for every document
|
||||
inside a given file. Memory allocation can be slow and become a bottleneck, therefore, we want to
|
||||
minimize it as much as possible.
|
||||
|
||||
### Design
|
||||
|
||||
To achieve a minimum amount of allocations, we opted for a design where we create only one
|
||||
parser object and therefore allocate its memory once, and then recycle it for every document in a
|
||||
given file. But, knowing that they often have largely varying size, we need to make sure that we
|
||||
allocate enough memory so that all the documents can fit. This value is what we call the batch size.
|
||||
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.
|
||||
|
||||
1. When the user calls `parse_many`, we return a `document_stream` which the user can iterate over
|
||||
to receive parsed documents.
|
||||
2. We call stage 1 on the first batch_size bytes of JSON in the buffer, detecting structural
|
||||
indexes for all documents in that batch.
|
||||
3. We call stage 2 on the indexes, reading tokens until we reach the end of a valid document (i.e.
|
||||
a single array, object, string, boolean, number or null).
|
||||
4. Each time the user calls `++` to read the next document, we call stage 2 to parse the next
|
||||
document where we left off.
|
||||
5. When we reach the end of the batch, we call stage 1 on the next batch, starting from the end of
|
||||
the last document, and go to step 3.
|
||||
|
||||
### Threads
|
||||
|
||||
But how can we make use of threads? We found a pretty cool algorithm that allows us to quickly
|
||||
identify the position of the last JSON document in a given batch. Knowing exactly where the end of
|
||||
the batch is, we no longer need for stage 2 to finish in order to load a new batch. We already know
|
||||
where to start the next batch. Therefore, we can run stage 1 on the next batch concurrently while
|
||||
the main thread is going through stage 2. Now, running stage 1 in a different thread can, in best
|
||||
cases, remove almost entirely it's cost and replaces it by the overhead of a thread, which is orders
|
||||
of magnitude cheaper. Ain't that awesome!
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
Since we want to offer flexibility and not restrict ourselves to a specific file
|
||||
format, we support any file that contains any amount of valid JSON document, **separated by one
|
||||
or more character that is considered whitespace** by the JSON spec. Anything that is
|
||||
not whitespace will be parsed as a JSON document and could lead to failure.
|
||||
|
||||
Whitespace Characters:
|
||||
- **Space**
|
||||
- **Linefeed**
|
||||
- **Carriage return**
|
||||
- **Horizontal tab**
|
||||
- **Nothing**
|
||||
|
||||
Some official formats **(non-exhaustive list)**:
|
||||
- [Newline-Delimited JSON (NDJSON)](http://ndjson.org/)
|
||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by JsonStream!
|
||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
|
||||
|
||||
## Use cases
|
||||
|
||||
From [jsonlines.org](http://jsonlines.org/examples/):
|
||||
|
||||
- **Better than CSV**
|
||||
```json
|
||||
["Name", "Session", "Score", "Completed"]
|
||||
["Gilbert", "2013", 24, true]
|
||||
["Alexa", "2013", 29, true]
|
||||
["May", "2012B", 14, false]
|
||||
["Deloise", "2012A", 19, true]
|
||||
```
|
||||
CSV seems so easy that many programmers have written code to generate it themselves, and almost every implementation is
|
||||
different. Handling broken CSV files is a common and frustrating task. CSV has no standard encoding, no standard column
|
||||
separator and multiple character escaping standards. String is the only type supported for cell values, so some programs
|
||||
attempt to guess the correct types.
|
||||
|
||||
JSON Lines handles tabular data cleanly and without ambiguity. Cells may use the standard JSON types.
|
||||
|
||||
The biggest missing piece is an import/export filter for popular spreadsheet programs so that non-programmers can use
|
||||
this format.
|
||||
|
||||
- **Easy Nested Data**
|
||||
```json
|
||||
{"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
|
||||
{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
|
||||
{"name": "May", "wins": []}
|
||||
{"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
|
||||
```
|
||||
JSON Lines' biggest strength is in handling lots of similar nested data structures. One .jsonl file is easier to
|
||||
work with than a directory full of XML files.
|
||||
@@ -0,0 +1,142 @@
|
||||
Performance Notes
|
||||
=================
|
||||
|
||||
simdjson strives to be at its fastest *without tuning*, and generally achieves this. However, there
|
||||
are still some scenarios where tuning can enhance performance.
|
||||
|
||||
* [Reusing the parser for maximum efficiency](#reusing-the-parser-for-maximum-efficiency)
|
||||
* [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)
|
||||
|
||||
Reusing the parser for maximum efficiency
|
||||
-----------------------------------------
|
||||
|
||||
If you're using simdjson to parse multiple documents, or in a loop, you should make a parser once
|
||||
and reuse it. The simdjson library will allocate and retain internal buffers between parses, keeping
|
||||
buffers hot in cache and keeping memory allocation and initialization to a minimum.
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
|
||||
// This initializes buffers and a document big enough to handle this JSON.
|
||||
dom::element doc = parser.parse("[ true, false ]"_padded);
|
||||
cout << doc << endl;
|
||||
|
||||
// This reuses the existing buffers, and reuses and *overwrites* the old document
|
||||
doc = parser.parse("[1, 2, 3]"_padded);
|
||||
cout << doc << endl;
|
||||
|
||||
// This also reuses the existing buffers, and reuses and *overwrites* the old document
|
||||
dom::element doc2 = parser.parse("true"_padded);
|
||||
// Even if you keep the old reference around, doc and doc2 refer to the same document.
|
||||
cout << doc << endl;
|
||||
cout << doc2 << endl;
|
||||
```
|
||||
|
||||
It's not just internal buffers though. The simdjson library reuses the document itself. dom::element, dom::object and dom::array are *references* to the internal document.
|
||||
You are only *borrowing* the document from simdjson, which purposely reuses and overwrites it each
|
||||
time you call parse. This prevent wasteful and unnecessary memory allocation in 99% of cases where
|
||||
JSON is just read, used, and converted to native values or thrown away.
|
||||
|
||||
> **You are only borrowing the document from the simdjson parser. Don't keep it long term!**
|
||||
|
||||
This is key: don't keep the `document&`, `dom::element`, `dom::array`, `dom::object`
|
||||
or `string_view` objects you get back from the API. Convert them to C++ native values, structs and
|
||||
arrays that you own.
|
||||
|
||||
Server Loops: Long-Running Processes and Memory Capacity
|
||||
--------------------------------------------------------
|
||||
|
||||
The simdjson library automatically expands its memory capacity when larger documents are parsed, so
|
||||
that you don't unexpectedly fail. In a short process that reads a bunch of files and then exits,
|
||||
this works pretty flawlessly.
|
||||
|
||||
Server loops, though, are long-running processes that will keep the parser around forever. This
|
||||
means that if you encounter a really, really large document, simdjson will not resize back down.
|
||||
The simdjson library lets you adjust your allocation strategy to prevent your server from growing
|
||||
without bound:
|
||||
|
||||
* You can set a *max capacity* when constructing a parser:
|
||||
|
||||
```c++
|
||||
dom::parser parser(1024*1024); // Never grow past documents > 1MB
|
||||
for (web_request request : listen()) {
|
||||
auto [doc, error] = parser.parse(request.body);
|
||||
// If the document was above our limit, emit 413 = payload too large
|
||||
if (error == CAPACITY) { request.respond(413); continue; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This parser will grow normally as it encounters larger documents, but will never pass 1MB.
|
||||
|
||||
* You can set a *fixed capacity* that never grows, as well, which can be excellent for
|
||||
predictability and reliability, since simdjson will never call malloc after startup!
|
||||
|
||||
```c++
|
||||
dom::parser parser(0); // This parser will refuse to automatically grow capacity
|
||||
simdjson::error_code allocate_error = parser.allocate(1024*1024); // This allocates enough capacity to handle documents <= 1MB
|
||||
if (allocate_error) { cerr << allocate_error << endl; exit(1); }
|
||||
|
||||
for (web_request request : listen()) {
|
||||
auto [doc, error] = parser.parse(request.body);
|
||||
// If the document was above our limit, emit 413 = payload too large
|
||||
if (error == CAPACITY) { request.respond(413); continue; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Large files and huge page support
|
||||
---------------------------------
|
||||
|
||||
There is a memory allocation performance cost the first time you process a large file (e.g. 100MB).
|
||||
Between the cost of allocation, the fact that the memory is not in cache, and the initial zeroing of
|
||||
memory, [on some systems, allocation runs far slower than parsing (e.g., 1.4GB/s)](https://lemire.me/blog/2020/01/14/how-fast-can-you-allocate-a-large-block-of-memory-in-c/). Reusing the parser mitigates this by
|
||||
paying the cost once, but does not eliminate it.
|
||||
|
||||
In large file use cases, enabling transparent huge page allocation on the OS can help a lot. We
|
||||
haven't found the right way to do this on Windows or OS/X, but on Linux, you can enable transparent
|
||||
huge page allocation with a command like:
|
||||
|
||||
```bash
|
||||
echo always > /sys/kernel/mm/transparent_hugepage/enabled
|
||||
```
|
||||
|
||||
In general, when running benchmarks over large files, we recommend that you report performance
|
||||
numbers with and without huge pages if possible. Furthermore, you should amortize the parsing (e.g.,
|
||||
by parsing several large files) to distinguish the time spent parsing from the time spent allocating
|
||||
memory. If you are using the `parse` benchmarking tool provided with the simdjson library, you can
|
||||
use the `-H` flag to omit the memory allocation cost from the benchmark results.
|
||||
|
||||
```
|
||||
./parse largefile # includes memory allocation cost
|
||||
./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
|
||||
--------------
|
||||
|
||||
Some JSON files contain many floating-point values. It is the case with many GeoJSON files. Accurately
|
||||
parsing decimal strings into binary floating-point values with proper rounding is challenging. To
|
||||
our knowledge, it is not possible, in general, to parse streams of numbers at gigabytes per second
|
||||
using a single core. While using the simdjson library, it is possible that you might be limited to a
|
||||
few hundred megabytes per second if your JSON documents are densely packed with floating-point values.
|
||||
|
||||
|
||||
- When possible, you should favor integer values written without a decimal point, as it simpler and faster to parse decimal integer values.
|
||||
- When serializing numbers, you should not use more digits than necessary: 17 digits is all that is needed to exactly represent double-precision floating-point numbers. Using many more digits than necessary will make your files larger and slower to parse.
|
||||
- When benchmarking parsing speeds, always report whether your JSON documents are made mostly of floating-point numbers when it is the case, since number parsing can then dominate the parsing time.
|
||||
+40
-37
@@ -28,45 +28,46 @@ It is sometimes useful to start with an example. Consider the following JSON doc
|
||||
|
||||
The following is a dump of the content of the tape, with the first number of each line representing the index of a tape element.
|
||||
|
||||
```bash
|
||||
$ ./json2json -d jsonexamples/small/demo.json
|
||||
0 : r // pointing to 38 (right after last node)
|
||||
1 : { // pointing to next tape location 38 (first node after the scope)
|
||||
2 : string "Image"
|
||||
3 : { // pointing to next tape location 37 (first node after the scope)
|
||||
4 : string "Width"
|
||||
5 : integer 800
|
||||
7 : string "Height"
|
||||
8 : integer 600
|
||||
10 : string "Title"
|
||||
11 : string "View from 15th Floor"
|
||||
12 : string "Thumbnail"
|
||||
13 : { // pointing to next tape location 23 (first node after the scope)
|
||||
14 : string "Url"
|
||||
15 : string "http://www.example.com/image/481989943"
|
||||
16 : string "Height"
|
||||
17 : integer 125
|
||||
19 : string "Width"
|
||||
20 : integer 100
|
||||
22 : } // pointing to previous tape location 13 (start of the scope)
|
||||
23 : string "Animated"
|
||||
24 : false
|
||||
25 : string "IDs"
|
||||
26 : [ // pointing to next tape location 36 (first node after the scope)
|
||||
27 : integer 116
|
||||
29 : integer 943
|
||||
31 : integer 234
|
||||
33 : integer 38793
|
||||
35 : ] // pointing to previous tape location 26 (start of the scope)
|
||||
36 : } // pointing to previous tape location 3 (start of the scope)
|
||||
37 : } // pointing to previous tape location 1 (start of the scope)
|
||||
38 : r // pointing to 0 (start root)
|
||||
### The Tape
|
||||
| index | element (64 bit word) |
|
||||
| ----- | ------------------------------------------------------------------- |
|
||||
| 0 | r // pointing to 38 (right after last node) |
|
||||
| 1 | { // pointing to next tape location 38 (first node after the scope) |
|
||||
| 2 | string "Image" |
|
||||
| 3 | { // pointing to next tape location 37 (first node after the scope) |
|
||||
| 4 | string "Width" |
|
||||
| 5 | integer 800 |
|
||||
| 7 | string "Height" |
|
||||
| 8 | integer 600 |
|
||||
| 10 | string "Title" |
|
||||
| 11 | string "View from 15th Floor" |
|
||||
| 12 | string "Thumbnail" |
|
||||
| 13 | { // pointing to next tape location 23 (first node after the scope) |
|
||||
| 14 | string "Url" |
|
||||
| 15 | string "http://www.example.com/image/481989943" |
|
||||
| 16 | string "Height" |
|
||||
| 17 | integer 125 |
|
||||
| 19 | string "Width" |
|
||||
| 20 | integer 100 |
|
||||
| 22 | } // pointing to previous tape location 13 (start of the scope) |
|
||||
| 23 | string "Animated" |
|
||||
| 24 | false |
|
||||
| 25 | string "IDs" |
|
||||
| 26 | [ // pointing to next tape location 36 (first node after the scope) |
|
||||
| 27 | integer 116 |
|
||||
| 29 | integer 943 |
|
||||
| 31 | integer 234 |
|
||||
| 33 | integer 38793 |
|
||||
| 35 | ] // pointing to previous tape location 26 (start of the scope) |
|
||||
| 36 | } // pointing to previous tape location 3 (start of the scope) |
|
||||
| 37 | } // pointing to previous tape location 1 (start of the scope) |
|
||||
| 38 | r // pointing to 0 (start root) |
|
||||
|
||||
|
||||
```
|
||||
|
||||
## General formal of the tape elements
|
||||
|
||||
Most tape elements are written as `('c' << 56) + x` where `'c'` is some ASCII character determining the type of the element (out of 't', 'f', 'n', 'l', 'd', '"', '{', '}', '[', ']' ,'r') and where `x` is a 56-bit value called the payload. The payload is normally interpreted as an unsigned 56-bit integer. Note that 56-bit integers can be quite large.
|
||||
Most tape elements are written as `('c' << 56) + x` where `'c'` is some ASCII character determining the type of the element (out of 't', 'f', 'n', 'l', 'u', 'd', '"', '{', '}', '[', ']' ,'r') and where `x` is a 56-bit value called the payload. The payload is normally interpreted as an unsigned 56-bit integer. Note that 56-bit integers can be quite large.
|
||||
|
||||
|
||||
Performance consideration: We believe that accessing the tape in regular units of 64 bits is more important for performance than saving memory.
|
||||
@@ -83,10 +84,12 @@ Simple JSON nodes are represented with one tape element:
|
||||
## Integer and Double values
|
||||
|
||||
Integer values are represented as two 64-bit tape elements:
|
||||
- The 64-bit value `('l' << 56)` followed by the 64-bit integer value litterally. Integer values are assumed to be signed 64-bit values, using two's complement notation.
|
||||
- The 64-bit value `('l' << 56)` followed by the 64-bit integer value literally. Integer values are assumed to be signed 64-bit values, using two's complement notation.
|
||||
- The 64-bit value `('u' << 56)` followed by the 64-bit integer value literally. Integer values are assumed to be unsigned 64-bit values.
|
||||
|
||||
|
||||
Float values are represented as two 64-bit tape elements:
|
||||
- The 64-bit value `('d' << 56)` followed by the 64-bit double value litterally in standard IEEE 754 notation.
|
||||
- The 64-bit value `('d' << 56)` followed by the 64-bit double value literally in standard IEEE 754 notation.
|
||||
|
||||
Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
ROOT=../..
|
||||
SINGLEHEADER=$(ROOT)/singleheader
|
||||
JSONEXAMPLES=$(ROOT)/jsonexamples
|
||||
|
||||
test: quickstart twitter.json
|
||||
./quickstart
|
||||
simdjson.cpp: $(SINGLEHEADER)/simdjson.cpp
|
||||
cp $(SINGLEHEADER)/simdjson.cpp .
|
||||
simdjson.h: $(SINGLEHEADER)/simdjson.h
|
||||
cp $(SINGLEHEADER)/simdjson.h .
|
||||
twitter.json: $(JSONEXAMPLES)/twitter.json
|
||||
cp $(JSONEXAMPLES)/twitter.json .
|
||||
quickstart: quickstart.cpp simdjson.cpp simdjson.h
|
||||
c++ -o ./quickstart quickstart.cpp simdjson.cpp -std=c++17
|
||||
clean:
|
||||
rm -f simdjson.cpp simdjson.h twitter.json quickstart
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "simdjson.h"
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets = parser.load("twitter.json");
|
||||
std::cout << tweets["search_metadata"]["count"] << " results." << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef DUMPBITS_H
|
||||
#define DUMPBITS_H
|
||||
#include <iostream>
|
||||
|
||||
// dump bits low to high
|
||||
inline void dumpbits_always(uint64_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
std::cout << (((v >> static_cast<uint64_t>(i)) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
|
||||
inline void dumpbits32_always(uint32_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 32; i++) {
|
||||
std::cout << (((v >> i) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
# First attempt at a fuzzer, using libFuzzer.
|
||||
#
|
||||
# compile like this:
|
||||
# mkdir build-fuzzer
|
||||
# cd build-fuzzer
|
||||
# export LDFLAGS="-fsanitize=address,undefined"
|
||||
# export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined"
|
||||
# export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined"
|
||||
# export CXX=clang++
|
||||
# export CC=clang++
|
||||
# cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_FUZZING=On -DSIMDJSON_FUZZ_LINKMAIN=Off -DSIMDJSON_FUZZ_LDFLAGS=-fsanitize=fuzzer
|
||||
# ninja
|
||||
|
||||
|
||||
# settings this links in a main. useful for reproducing,
|
||||
# kcov, gdb, afl, valgrind.
|
||||
# (note that libFuzzer can also reproduce, just pass it the files)
|
||||
#
|
||||
# Using this by default, means the fuzzers will be built as a part of the normal
|
||||
# workflow, meaning they wont bitrot and will participate in refactoring etc.
|
||||
#
|
||||
option(SIMDJSON_FUZZ_LINKMAIN "links a main into fuzz targets for building reproducers" On)
|
||||
|
||||
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
||||
set(SIMDJSON_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||
|
||||
set(SOURCES
|
||||
fuzz_parser.cpp
|
||||
# fuzz_minify.cpp # <--- does not pass the build check test on oss-fuzz, says "partially instrumented". help needed!
|
||||
fuzz_dump.cpp
|
||||
fuzz_print_json.cpp
|
||||
fuzz_dump_raw_tape.cpp
|
||||
)
|
||||
|
||||
add_custom_target(print_all_fuzz_targets
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ${SOURCES}
|
||||
)
|
||||
|
||||
macro(implement_fuzzer sourcefile)
|
||||
get_filename_component(basename ${sourcefile} NAME_WE)
|
||||
set(name ${basename})
|
||||
add_executable(${name} ${sourcefile})
|
||||
if (SIMDJSON_FUZZ_LINKMAIN)
|
||||
target_sources(${name} PRIVATE main.cpp)
|
||||
endif ()
|
||||
target_link_libraries(${name} PRIVATE ${SIMDJSON_LIB_NAME})
|
||||
if (SIMDJSON_FUZZ_LDFLAGS)
|
||||
target_link_libraries(${name} PRIVATE ${SIMDJSON_FUZZ_LDFLAGS})
|
||||
endif ()
|
||||
endmacro ()
|
||||
|
||||
foreach (X IN ITEMS ${SOURCES})
|
||||
implement_fuzzer(${X})
|
||||
endforeach ()
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Fuzzing
|
||||
|
||||
[Fuzzing](https://en.wikipedia.org/wiki/Fuzzing) is efficient for finding bugs. Here are a few bugs in simdjson found by fuzzing:
|
||||
|
||||
- https://github.com/lemire/simdjson/issues/353
|
||||
- https://github.com/lemire/simdjson/issues/351
|
||||
- https://github.com/lemire/simdjson/issues/345
|
||||
|
||||
The simdjson library tries to follow [fuzzing best practises](https://google.github.io/oss-fuzz/advanced-topics/ideal-integration/#summary).
|
||||
|
||||
The simdjson library is continuously fuzzed on [oss-fuzz](https://github.com/google/oss-fuzz).
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
## 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/lemire/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 here: https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
|
||||
One can also grab the corpus as an artifact from the github actions job. Pick a run, then go to artifacts and download.
|
||||
|
||||
## Fuzzing coverage
|
||||
|
||||
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 publicly accessible. Substitute the date in the URL to get a more recent link:
|
||||
https://storage.googleapis.com/oss-fuzz-coverage/simdjson/reports/20191112/linux/src/simdjson/report.html
|
||||
|
||||
|
||||
## Running the fuzzers locally
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// from https://stackoverflow.com/a/8244052
|
||||
class NulStreambuf : public std::streambuf {
|
||||
char dummyBuffer[64];
|
||||
|
||||
protected:
|
||||
virtual int overflow(int c) override final{
|
||||
setp(dummyBuffer, dummyBuffer + sizeof(dummyBuffer));
|
||||
return (c == traits_type::eof()) ? '\0' : c;
|
||||
}
|
||||
};
|
||||
|
||||
class NulOStream final : private NulStreambuf, public std::ostream {
|
||||
public:
|
||||
NulOStream() : std::ostream(this) {}
|
||||
NulStreambuf *rdbuf() { return this; }
|
||||
};
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Builds a corpus from all 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
|
||||
|
||||
set -eu
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
|
||||
root=$(readlink -f "$(dirname "$0")/..")
|
||||
|
||||
find $root -type f -name "*.json" | while read -r json; do
|
||||
cp "$json" "$tmp"/$(sha1sum < "$json" |cut -f1 -d' ').json
|
||||
done
|
||||
|
||||
zip --junk-paths -r corpus.zip "$tmp"
|
||||
rm -rf "$tmp"
|
||||
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This file builds multiple variants of the fuzzers
|
||||
# - different sanitizers
|
||||
# - different build options
|
||||
# - reproduce build, for running through valgrind
|
||||
|
||||
# fail on error
|
||||
set -eu
|
||||
|
||||
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_FUZZ_LINKMAIN=On \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# A reproduce build as plain as it gets. Everythings tunable is
|
||||
# using the defaults.
|
||||
variant=plain-normal
|
||||
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_FUZZ_LINKMAIN=On
|
||||
|
||||
ninja
|
||||
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_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja
|
||||
cd ..
|
||||
fi
|
||||
|
||||
|
||||
# a fuzzer with sanitizers, built with avx disabled.
|
||||
variant=ossfuzz-noavx8
|
||||
if which clang++-8 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang-8
|
||||
export CXX="clang++-8"
|
||||
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_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-8 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_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
ninja
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# a fast fuzzer, for fast exploration
|
||||
variant=ossfuzz-fast8
|
||||
if which clang++-8 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
export CC=clang-8
|
||||
export CXX="clang++-8"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE= \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
ninja
|
||||
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-8 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script emulates how oss fuzz invokes the build
|
||||
# process, handy for trouble shooting cmake issues and possibly
|
||||
# recreating testcases. For proper debugging of the oss fuzz
|
||||
# build, follow the procedure at https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally
|
||||
|
||||
set -eu
|
||||
|
||||
ossfuzz=$(readlink -f $(dirname $0))/ossfuzz.sh
|
||||
|
||||
mkdir -p ossfuzz-out
|
||||
export OUT=$(pwd)/ossfuzz-out
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
$ossfuzz
|
||||
|
||||
echo "look at the results in $OUT"
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
// from the README on the front page
|
||||
void compute_dump(simdjson::ParsedJson::Iterator &pjh) {
|
||||
NulOStream os;
|
||||
|
||||
if (pjh.is_object()) {
|
||||
os << "{";
|
||||
if (pjh.down()) {
|
||||
pjh.print(os); // must be a string
|
||||
os << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
os << ",";
|
||||
pjh.print(os);
|
||||
os << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
os << "}";
|
||||
} else if (pjh.is_array()) {
|
||||
os << "[";
|
||||
if (pjh.down()) {
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
os << ",";
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
os << "]";
|
||||
} else {
|
||||
pjh.print(os); // just print the lone value
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
try {
|
||||
auto pj = simdjson::build_parsed_json(Data, Size);
|
||||
if (!pj.is_valid()) {
|
||||
throw 1;
|
||||
}
|
||||
simdjson::ParsedJson::Iterator pjh(pj);
|
||||
if (pjh.is_ok()) {
|
||||
compute_dump(pjh);
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
try {
|
||||
auto pj = simdjson::build_parsed_json(Data, Size);
|
||||
NulOStream os;
|
||||
UNUSED bool ignored=pj.dump_raw_tape(os);
|
||||
} catch (...) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
auto begin = (const char *)Data;
|
||||
auto end = begin + Size;
|
||||
|
||||
std::string str(begin, end);
|
||||
|
||||
simdjson::json_minify(str.data(), str.size(), str.data());
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
auto ignored = simdjson::build_parsed_json(Data, Size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
try {
|
||||
auto pj = simdjson::build_parsed_json(Data, Size);
|
||||
NulOStream os;
|
||||
bool ignored=pj.print_json(os);
|
||||
(void)ignored;
|
||||
} catch (...) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
// 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);
|
||||
int main(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream in(argv[i]);
|
||||
assert(in);
|
||||
in.seekg(0, std::ios_base::end);
|
||||
const auto pos = in.tellg();
|
||||
assert(pos >= 0);
|
||||
in.seekg(0, std::ios_base::beg);
|
||||
std::vector<char> buf(static_cast<std::size_t>(pos));
|
||||
in.read(buf.data(), static_cast<long>(buf.size()));
|
||||
assert(in.gcount() == pos);
|
||||
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
|
||||
}
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# makes a coverage build.
|
||||
#
|
||||
# To measure and display the coverage:
|
||||
#
|
||||
#cd build-coverage
|
||||
#fuzz/fuzz_parser path/to/corpus/* # repeat with other fuzzers
|
||||
#gcovr -r . --html --html-details --sort-uncovered -o out.html
|
||||
# and view the results in out.html
|
||||
|
||||
bdir=build-coverage
|
||||
if [ ! -d $bdir ] ; then
|
||||
mkdir -p $bdir
|
||||
cd $bdir
|
||||
|
||||
export CC=gcc
|
||||
export CXX="g++"
|
||||
export CFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
export CXXFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
export LDFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On
|
||||
ninja
|
||||
cd ..
|
||||
fi
|
||||
|
||||
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# entry point for oss-fuzz, so that fuzzers
|
||||
# and build invocation can be changed without having
|
||||
# to modify the oss-fuzz repo.
|
||||
#
|
||||
# invoke it from the git root.
|
||||
|
||||
# make sure to exit on problems
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
|
||||
for prog in zip cmake ninja; do
|
||||
if ! which $prog >/dev/null; then
|
||||
echo please install $prog
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# build the corpus (all inputs are json, the same corpus can be used for everyone)
|
||||
fuzz/build_corpus.sh
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
cmake --build .
|
||||
|
||||
cp fuzz/fuzz_* $OUT
|
||||
|
||||
# all corpora are equal, they all take json as input
|
||||
for f in $OUT/fuzz* ; do
|
||||
cp ../corpus.zip $OUT/$(basename $f).zip
|
||||
done
|
||||
@@ -0,0 +1,24 @@
|
||||
set(SIMDJSON_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
|
||||
set(SIMDJSON_INCLUDE
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/common_defs.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/compiler_check.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/document_stream.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/document.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/error.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/implementation.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/inline/document_stream.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/inline/document.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/inline/error.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/inline/padded_string.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/inline/parsedjson_iterator.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/internal/jsonformatutils.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/jsonioutil.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/jsonparser.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/padded_string.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/parsedjson.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/parsedjson_iterator.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/portability.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/simdjson_version.h
|
||||
${SIMDJSON_INCLUDE_DIR}/simdjson/simdjson.h
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef SIMDJSON_H
|
||||
#define SIMDJSON_H
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
*
|
||||
* Check the [README.md](https://github.com/lemire/simdjson/blob/master/README.md#simdjson--parsing-gigabytes-of-json-per-second).
|
||||
*/
|
||||
|
||||
#include "simdjson/compiler_check.h"
|
||||
|
||||
// Public API
|
||||
#include "simdjson/simdjson_version.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/document.h"
|
||||
#include "simdjson/document_stream.h"
|
||||
|
||||
// Deprecated API
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/parsedjson_iterator.h"
|
||||
|
||||
// Inline functions
|
||||
#include "simdjson/inline/document.h"
|
||||
#include "simdjson/inline/document_stream.h"
|
||||
#include "simdjson/inline/error.h"
|
||||
#include "simdjson/inline/padded_string.h"
|
||||
#include "simdjson/inline/parsedjson_iterator.h"
|
||||
|
||||
#endif // SIMDJSON_H
|
||||
@@ -1,23 +1,53 @@
|
||||
#ifndef SIMDJSON_COMMON_DEFS_H
|
||||
#define SIMDJSON_COMMON_DEFS_H
|
||||
|
||||
#include <cassert>
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#include <cassert>
|
||||
namespace simdjson {
|
||||
|
||||
// we support documents up to 4GB
|
||||
#define SIMDJSON_MAXSIZE_BYTES 0xFFFFFFFF
|
||||
|
||||
// the input buf should be readable up to buf + SIMDJSON_PADDING
|
||||
#ifdef __AVX2__
|
||||
#define SIMDJSON_PADDING sizeof(__m256i)
|
||||
#ifndef SIMDJSON_EXCEPTIONS
|
||||
#if __cpp_exceptions
|
||||
#define SIMDJSON_EXCEPTIONS 1
|
||||
#else
|
||||
// this is a stopgap; there should be a better description of the
|
||||
// main loop and its behavior that abstracts over this
|
||||
#define SIMDJSON_PADDING 32
|
||||
#define SIMDJSON_EXCEPTIONS 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
/** The maximum document size supported by simdjson. */
|
||||
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
|
||||
|
||||
/**
|
||||
* The amount of padding needed in a buffer to parse JSON.
|
||||
*
|
||||
* the input buf should be readable up to buf + SIMDJSON_PADDING
|
||||
* this is a stopgap; there should be a better description of the
|
||||
* main loop and its behavior that abstracts over this
|
||||
* See https://github.com/lemire/simdjson/issues/174
|
||||
*/
|
||||
constexpr size_t SIMDJSON_PADDING = 32;
|
||||
|
||||
/**
|
||||
* By default, simdjson supports this many nested objects and arrays.
|
||||
*
|
||||
* This is the default for parser::max_depth().
|
||||
*/
|
||||
constexpr size_t DEFAULT_MAX_DEPTH = 1024;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#if defined(__GNUC__)
|
||||
// Marks a block with a name so that MCA analysis can see it.
|
||||
#define BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name);
|
||||
#define END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name);
|
||||
#define DEBUG_BLOCK(name, block) BEGIN_DEBUG_BLOCK(name); block; END_DEBUG_BLOCK(name);
|
||||
#else
|
||||
#define BEGIN_DEBUG_BLOCK(name)
|
||||
#define END_DEBUG_BLOCK(name)
|
||||
#define DEBUG_BLOCK(name, block)
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(SIMDJSON_NO_COMPUTED_GOTO)
|
||||
// Implemented using Labels as Values which works in GCC and CLANG (and maybe
|
||||
// also in Intel's compiler), but won't work in MSVC.
|
||||
#define SIMDJSON_USE_COMPUTED_GOTO
|
||||
@@ -43,38 +73,14 @@
|
||||
#define unlikely(x) x
|
||||
#endif
|
||||
|
||||
// For Visual Studio compilers, same-page buffer overrun is not fine.
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
#define SIMDJSON_PUSH_DISABLE_WARNINGS __pragma(warning( push ))
|
||||
#define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS __pragma(warning( push, 0 ))
|
||||
#define SIMDJSON_DISABLE_VS_WARNING(WARNING_NUMBER) __pragma(warning( disable : WARNING_NUMBER ))
|
||||
#define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_VS_WARNING(4996)
|
||||
#define SIMDJSON_POP_DISABLE_WARNINGS __pragma(warning( pop ))
|
||||
|
||||
#else
|
||||
#else // MSC_VER
|
||||
|
||||
// For non-Visual Studio compilers, we may assume that same-page buffer overrun
|
||||
// is fine. However, it will make it difficult to be "valgrind clean".
|
||||
//#ifndef ALLOW_SAME_PAGE_BUFFER_OVERRUN
|
||||
//#define ALLOW_SAME_PAGE_BUFFER_OVERRUN true
|
||||
//#else
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
//#endif
|
||||
|
||||
// The following is likely unnecessarily complex.
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
// we have GCC, stuck with https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67368
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
#elif defined(__has_feature)
|
||||
// we have CLANG?
|
||||
// todo: if we're setting ALLOW_SAME_PAGE_BUFFER_OVERRUN to false, why do we
|
||||
// have a non-empty qualifier?
|
||||
#if (__has_feature(address_sanitizer))
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER \
|
||||
__attribute__((no_sanitize("address")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__has_feature)
|
||||
#if (__has_feature(memory_sanitizer))
|
||||
#define LENIENT_MEM_SANITIZER __attribute__((no_sanitize("memory")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define really_inline inline __attribute__((always_inline, unused))
|
||||
#define never_inline inline __attribute__((noinline, unused))
|
||||
@@ -89,14 +95,19 @@
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
#define SIMDJSON_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push")
|
||||
// gcc doesn't seem to disable all warnings with all and extra, add warnings here as necessary
|
||||
#define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wall) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough)
|
||||
#define SIMDJSON_PRAGMA(P) _Pragma(#P)
|
||||
#define SIMDJSON_DISABLE_GCC_WARNING(WARNING) SIMDJSON_PRAGMA(GCC diagnostic ignored #WARNING)
|
||||
#define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wdeprecated-declarations)
|
||||
#define SIMDJSON_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop")
|
||||
|
||||
#endif // MSC_VER
|
||||
|
||||
// if it does not apply, make it an empty macro
|
||||
#ifndef ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER
|
||||
#endif
|
||||
#ifndef LENIENT_MEM_SANITIZER
|
||||
#define LENIENT_MEM_SANITIZER
|
||||
#endif
|
||||
|
||||
#endif // SIMDJSON_COMMON_DEFS_H
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef SIMDJSON_COMPILER_CHECK_H
|
||||
#define SIMDJSON_COMPILER_CHECK_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error simdjson requires a C++ compiler
|
||||
#endif
|
||||
|
||||
#ifndef SIMDJSON_CPLUSPLUS
|
||||
#if defined(_MSVC_LANG) && !defined(__clang__)
|
||||
#define SIMDJSON_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)
|
||||
#else
|
||||
#define SIMDJSON_CPLUSPLUS __cplusplus
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (SIMDJSON_CPLUSPLUS < 201703L)
|
||||
#error simdjson requires a compiler compliant with the C++17 standard
|
||||
#endif
|
||||
|
||||
#endif // SIMDJSON_COMPILER_CHECK_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,143 @@
|
||||
#ifndef SIMDJSON_DOCUMENT_STREAM_H
|
||||
#define SIMDJSON_DOCUMENT_STREAM_H
|
||||
|
||||
#include <thread>
|
||||
#include "simdjson/document.h"
|
||||
|
||||
namespace simdjson::dom {
|
||||
|
||||
/**
|
||||
* A forward-only stream of documents.
|
||||
*
|
||||
* Produced by parser::parse_many.
|
||||
*
|
||||
*/
|
||||
class document_stream {
|
||||
public:
|
||||
really_inline ~document_stream() noexcept;
|
||||
|
||||
/**
|
||||
* An iterator through a forward-only stream of documents.
|
||||
*/
|
||||
class iterator {
|
||||
public:
|
||||
/**
|
||||
* Get the current document (or error).
|
||||
*/
|
||||
really_inline simdjson_result<element> operator*() noexcept;
|
||||
/**
|
||||
* Advance to the next document.
|
||||
*/
|
||||
inline iterator& operator++() noexcept;
|
||||
/**
|
||||
* Check if we're at the end yet.
|
||||
* @param other the end iterator to compare to.
|
||||
*/
|
||||
really_inline bool operator!=(const iterator &other) const noexcept;
|
||||
|
||||
private:
|
||||
iterator(document_stream& stream, bool finished) noexcept;
|
||||
/** The document_stream we're iterating through. */
|
||||
document_stream& stream;
|
||||
/** Whether we're finished or not. */
|
||||
bool finished;
|
||||
friend class document_stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start iterating the documents in the stream.
|
||||
*/
|
||||
really_inline iterator begin() noexcept;
|
||||
/**
|
||||
* The end of the stream, for iterator comparison purposes.
|
||||
*/
|
||||
really_inline iterator end() noexcept;
|
||||
|
||||
private:
|
||||
|
||||
document_stream &operator=(const document_stream &) = delete; // Disallow copying
|
||||
|
||||
document_stream(document_stream &other) = delete; // Disallow copying
|
||||
|
||||
really_inline document_stream(dom::parser &parser, const uint8_t *buf, size_t len, size_t batch_size, error_code error = SUCCESS) noexcept;
|
||||
|
||||
/**
|
||||
* Parse the next document found in the buffer previously given to document_stream.
|
||||
*
|
||||
* The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
* UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
* discouraged.
|
||||
*
|
||||
* You do NOT need to pre-allocate a parser. This function takes care of
|
||||
* pre-allocating a capacity defined by the batch_size defined when creating the
|
||||
* document_stream object.
|
||||
*
|
||||
* The function returns simdjson::SUCCESS_AND_HAS_MORE (an integer = 1) in case
|
||||
* of success and indicates that the buffer still contains more data to be parsed,
|
||||
* meaning this function can be called again to return the next JSON document
|
||||
* after this one.
|
||||
*
|
||||
* The function returns simdjson::SUCCESS (as integer = 0) in case of success
|
||||
* and indicates that the buffer has successfully been parsed to the end.
|
||||
* Every document it contained has been parsed without error.
|
||||
*
|
||||
* The function returns an error code from simdjson/simdjson.h in case of failure
|
||||
* such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
* the simdjson::error_message function converts these error codes into a string).
|
||||
*
|
||||
* You can also check validity by calling parser.is_valid(). The same parser can
|
||||
* and should be reused for the other documents in the buffer. */
|
||||
inline error_code json_parse() noexcept;
|
||||
|
||||
/**
|
||||
* Returns the location (index) of where the next document should be in the
|
||||
* buffer.
|
||||
* Can be used for debugging, it tells the user the position of the end of the
|
||||
* last
|
||||
* valid JSON document parsed
|
||||
*/
|
||||
inline size_t get_current_buffer_loc() const { return current_buffer_loc; }
|
||||
|
||||
/**
|
||||
* Returns the total amount of complete documents parsed by the document_stream,
|
||||
* in the current buffer, at the given time.
|
||||
*/
|
||||
inline size_t get_n_parsed_docs() const { return n_parsed_docs; }
|
||||
|
||||
/**
|
||||
* Returns the total amount of data (in bytes) parsed by the document_stream,
|
||||
* in the current buffer, at the given time.
|
||||
*/
|
||||
inline size_t get_n_bytes_parsed() const { return n_bytes_parsed; }
|
||||
|
||||
inline const uint8_t *buf() const { return _buf + buf_start; }
|
||||
|
||||
inline void advance(size_t offset) { buf_start += offset; }
|
||||
|
||||
inline size_t remaining() const { return _len - buf_start; }
|
||||
|
||||
dom::parser &parser;
|
||||
const uint8_t *_buf;
|
||||
const size_t _len;
|
||||
size_t _batch_size; // this is actually variable!
|
||||
size_t buf_start{0};
|
||||
size_t next_json{0};
|
||||
bool load_next_batch{true};
|
||||
size_t current_buffer_loc{0};
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
size_t last_json_buffer_loc{0};
|
||||
#endif
|
||||
size_t n_parsed_docs{0};
|
||||
size_t n_bytes_parsed{0};
|
||||
error_code error{SUCCESS_AND_HAS_MORE};
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
error_code stage1_is_ok_thread{SUCCESS};
|
||||
std::thread stage_1_thread;
|
||||
dom::parser parser_thread;
|
||||
#endif
|
||||
friend class dom::parser;
|
||||
}; // class document_stream
|
||||
|
||||
} // end of namespace simdjson::dom
|
||||
|
||||
#endif // SIMDJSON_DOCUMENT_STREAM_H
|
||||
@@ -0,0 +1,233 @@
|
||||
#ifndef SIMDJSON_ERROR_H
|
||||
#define SIMDJSON_ERROR_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/**
|
||||
* All possible errors returned by simdjson.
|
||||
*/
|
||||
enum error_code {
|
||||
SUCCESS = 0, ///< No error
|
||||
SUCCESS_AND_HAS_MORE, ///< @private No error and buffer still has more data
|
||||
CAPACITY, ///< This parser can't support a document that big
|
||||
MEMALLOC, ///< Error allocating memory, most likely out of memory
|
||||
TAPE_ERROR, ///< Something went wrong while writing to the tape (stage 2), this is a generic error
|
||||
DEPTH_ERROR, ///< Your document exceeds the user-specified depth limitation
|
||||
STRING_ERROR, ///< Problem while parsing a string
|
||||
T_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 't'
|
||||
F_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'f'
|
||||
N_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'n'
|
||||
NUMBER_ERROR, ///< Problem while parsing a number
|
||||
UTF8_ERROR, ///< the input is not valid UTF-8
|
||||
UNINITIALIZED, ///< unknown error, or uninitialized document
|
||||
EMPTY, ///< no structural element found
|
||||
UNESCAPED_CHARS, ///< found unescaped characters in a string.
|
||||
UNCLOSED_STRING, ///< missing quote at the end
|
||||
UNSUPPORTED_ARCHITECTURE, ///< unsupported architecture
|
||||
INCORRECT_TYPE, ///< JSON element has a different type than user expected
|
||||
NUMBER_OUT_OF_RANGE, ///< JSON number does not fit in 64 bits
|
||||
INDEX_OUT_OF_BOUNDS, ///< JSON array index too large
|
||||
NO_SUCH_FIELD, ///< JSON field not found in object
|
||||
IO_ERROR, ///< Error reading a file
|
||||
INVALID_JSON_POINTER, ///< Invalid JSON pointer reference
|
||||
INVALID_URI_FRAGMENT, ///< Invalid URI fragment
|
||||
UNEXPECTED_ERROR, ///< indicative of a bug in simdjson
|
||||
/** @private Number of error codes */
|
||||
NUM_ERROR_CODES
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the error message for the given error code.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* auto [doc, error] = parser.parse("foo");
|
||||
* if (error) { printf("Error: %s\n", error_message(error)); }
|
||||
*
|
||||
* @return The error message.
|
||||
*/
|
||||
inline const char *error_message(error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Write the error message to the output stream
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Exception thrown when an exception-supporting simdjson method is called
|
||||
*/
|
||||
struct simdjson_error : public std::exception {
|
||||
/**
|
||||
* Create an exception from a simdjson error code.
|
||||
* @param error The error code
|
||||
*/
|
||||
simdjson_error(error_code error) noexcept : _error{error} { }
|
||||
/** The error message */
|
||||
const char *what() const noexcept { return error_message(error()); }
|
||||
/** The error code */
|
||||
error_code error() const noexcept { return _error; }
|
||||
private:
|
||||
/** The error code that was used */
|
||||
error_code _error;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* The result of a simdjson operation that could fail.
|
||||
*
|
||||
* Gives the option of reading error codes, or throwing an exception by casting to the desired result.
|
||||
*
|
||||
* This is a base class for implementations that want to add functions to the result type for
|
||||
* chaining.
|
||||
*
|
||||
* Override like:
|
||||
*
|
||||
* struct simdjson_result<T> : public internal::simdjson_result_base<T> {
|
||||
* simdjson_result() noexcept : internal::simdjson_result_base<T>() {}
|
||||
* simdjson_result(error_code error) noexcept : internal::simdjson_result_base<T>(error) {}
|
||||
* simdjson_result(T &&value) noexcept : internal::simdjson_result_base<T>(std::forward(value)) {}
|
||||
* simdjson_result(T &&value, error_code error) noexcept : internal::simdjson_result_base<T>(value, error) {}
|
||||
* // Your extra methods here
|
||||
* }
|
||||
*
|
||||
* Then any method returning simdjson_result<T> will be chainable with your methods.
|
||||
*/
|
||||
template<typename T>
|
||||
struct simdjson_result_base : public std::pair<T, error_code> {
|
||||
|
||||
/**
|
||||
* Create a new empty result with error = UNINITIALIZED.
|
||||
*/
|
||||
really_inline simdjson_result_base() noexcept;
|
||||
|
||||
/**
|
||||
* Create a new error result.
|
||||
*/
|
||||
really_inline simdjson_result_base(error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Create a new successful result.
|
||||
*/
|
||||
really_inline simdjson_result_base(T &&value) noexcept;
|
||||
|
||||
/**
|
||||
* Create a new result with both things (use if you don't want to branch when creating the result).
|
||||
*/
|
||||
really_inline simdjson_result_base(T &&value, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Move the value and the error to the provided variables.
|
||||
*/
|
||||
really_inline void tie(T &value, error_code &error) && noexcept;
|
||||
|
||||
/**
|
||||
* The error.
|
||||
*/
|
||||
really_inline error_code error() const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
/**
|
||||
* Get the result value.
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T& value() noexcept(false);
|
||||
|
||||
/**
|
||||
* Take the result value (move it).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T&& take_value() && noexcept(false);
|
||||
|
||||
/**
|
||||
* Cast to the value (will throw on error).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline operator T&&() && noexcept(false);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
}; // struct simdjson_result_base
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* The result of a simdjson operation that could fail.
|
||||
*
|
||||
* Gives the option of reading error codes, or throwing an exception by casting to the desired result.
|
||||
*/
|
||||
template<typename T>
|
||||
struct simdjson_result : public internal::simdjson_result_base<T> {
|
||||
/**
|
||||
* @private Create a new empty result with error = UNINITIALIZED.
|
||||
*/
|
||||
really_inline simdjson_result() noexcept;
|
||||
/**
|
||||
* @private Create a new error result.
|
||||
*/
|
||||
really_inline simdjson_result(T &&value) noexcept;
|
||||
/**
|
||||
* @private Create a new successful result.
|
||||
*/
|
||||
really_inline simdjson_result(error_code error_code) noexcept;
|
||||
/**
|
||||
* @private Create a new result with both things (use if you don't want to branch when creating the result).
|
||||
*/
|
||||
really_inline simdjson_result(T &&value, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Move the value and the error to the provided variables.
|
||||
*/
|
||||
really_inline void tie(T& t, error_code & e) && noexcept;
|
||||
|
||||
/**
|
||||
* The error.
|
||||
*/
|
||||
really_inline error_code error() const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
/**
|
||||
* Get the result value.
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T& value() noexcept(false);
|
||||
|
||||
/**
|
||||
* Take the result value (move it).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T&& take_value() && noexcept(false);
|
||||
|
||||
/**
|
||||
* Cast to the value (will throw on error).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline operator T&&() && noexcept(false);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
}; // struct simdjson_result
|
||||
|
||||
/**
|
||||
* @deprecated This is an alias and will be removed, use error_code instead
|
||||
*/
|
||||
using ErrorValues [[deprecated("This is an alias and will be removed, use error_code instead")]] = error_code;
|
||||
|
||||
/**
|
||||
* @deprecated Error codes should be stored and returned as `error_code`, use `error_message()` instead.
|
||||
*/
|
||||
[[deprecated("Error codes should be stored and returned as `error_code`, use `error_message()` instead.")]]
|
||||
inline const std::string &error_message(int error) noexcept;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ERROR_H
|
||||
@@ -0,0 +1,270 @@
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_H
|
||||
#define SIMDJSON_IMPLEMENTATION_H
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include "simdjson/document.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/**
|
||||
* An implementation of simdjson for a particular CPU architecture.
|
||||
*
|
||||
* Also used to maintain the currently active implementation. The active implementation is
|
||||
* automatically initialized on first use to the most advanced implementation supported by the host.
|
||||
*/
|
||||
class implementation {
|
||||
public:
|
||||
/**
|
||||
* The name of this implementation.
|
||||
*
|
||||
* const implementation *impl = simdjson::active_implementation;
|
||||
* cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl;
|
||||
*
|
||||
* @return the name of the implementation, e.g. "haswell", "westmere", "arm64"
|
||||
*/
|
||||
virtual const std::string &name() const { return _name; }
|
||||
|
||||
/**
|
||||
* The description of this implementation.
|
||||
*
|
||||
* const implementation *impl = simdjson::active_implementation;
|
||||
* cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl;
|
||||
*
|
||||
* @return the name of the implementation, e.g. "haswell", "westmere", "arm64"
|
||||
*/
|
||||
virtual const std::string &description() const { return _description; }
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* The instruction sets this implementation is compiled against.
|
||||
*
|
||||
* @return a mask of all required `instruction_set` values
|
||||
*/
|
||||
virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; };
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Run a full document parse (ensure_capacity, stage1 and stage2).
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param len the length of the json document.
|
||||
* @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity.
|
||||
* @return the error code, or SUCCESS if there was no error.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code parse(const uint8_t *buf, size_t len, dom::parser &parser) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Run a full document parse (ensure_capacity, stage1 and stage2).
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param len the length of the json document.
|
||||
* @param dst the buffer to write the minified document to. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param dst_len the number of bytes written. Output only.
|
||||
* @return the error code, or SUCCESS if there was no error.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Stage 1 of the document parser.
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param len the length of the json document.
|
||||
* @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity.
|
||||
* @param streaming whether this is being called by parser::parse_many.
|
||||
* @return the error code, or SUCCESS if there was no error.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code stage1(const uint8_t *buf, size_t len, dom::parser &parser, bool streaming) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Stage 2 of the document parser.
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param len the length of the json document.
|
||||
* @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity.
|
||||
* @return the error code, or SUCCESS if there was no error.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code stage2(const uint8_t *buf, size_t len, dom::parser &parser) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Stage 2 of the document parser for parser::parse_many.
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param len the length of the json document.
|
||||
* @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity.
|
||||
* @param next_json the next structural index. Start this at 0 the first time, and it will be updated to the next value to pass each time.
|
||||
* @return the error code, SUCCESS if there was no error, or SUCCESS_AND_HAS_MORE if there was no error and stage2 can be called again.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code stage2(const uint8_t *buf, size_t len, dom::parser &parser, size_t &next_json) const noexcept = 0;
|
||||
|
||||
protected:
|
||||
/** @private Construct an implementation with the given name and description. For subclasses. */
|
||||
really_inline implementation(
|
||||
std::string_view name,
|
||||
std::string_view description,
|
||||
uint32_t required_instruction_sets
|
||||
) :
|
||||
_name(name),
|
||||
_description(description),
|
||||
_required_instruction_sets(required_instruction_sets)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The name of this implementation.
|
||||
*/
|
||||
const std::string _name;
|
||||
|
||||
/**
|
||||
* The description of this implementation.
|
||||
*/
|
||||
const std::string _description;
|
||||
|
||||
/**
|
||||
* Instruction sets required for this implementation.
|
||||
*/
|
||||
const uint32_t _required_instruction_sets;
|
||||
};
|
||||
|
||||
/** @private */
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* The list of available implementations compiled into simdjson.
|
||||
*/
|
||||
class available_implementation_list {
|
||||
public:
|
||||
/** Get the list of available implementations compiled into simdjson */
|
||||
really_inline available_implementation_list() {}
|
||||
/** Number of implementations */
|
||||
size_t size() const noexcept;
|
||||
/** STL const begin() iterator */
|
||||
const implementation * const *begin() const noexcept;
|
||||
/** STL const end() iterator */
|
||||
const implementation * const *end() const noexcept;
|
||||
|
||||
/**
|
||||
* Get the implementation with the given name.
|
||||
*
|
||||
* Case sensitive.
|
||||
*
|
||||
* const implementation *impl = simdjson::available_implementations["westmere"];
|
||||
* if (!impl) { exit(1); }
|
||||
* simdjson::active_implementation = impl;
|
||||
*
|
||||
* @param name the implementation to find, e.g. "westmere", "haswell", "arm64"
|
||||
* @return the implementation, or nullptr if the parse failed.
|
||||
*/
|
||||
const implementation * operator[](const std::string_view &name) const noexcept {
|
||||
for (const implementation * impl : *this) {
|
||||
if (impl->name() == name) { return impl; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the most advanced implementation supported by the current host.
|
||||
*
|
||||
* This is used to initialize the implementation on startup.
|
||||
*
|
||||
* const implementation *impl = simdjson::available_implementation::detect_best_supported();
|
||||
* simdjson::active_implementation = impl;
|
||||
*
|
||||
* @return the most advanced supported implementation for the current host, or an
|
||||
* implementation that returns UNSUPPORTED_ARCHITECTURE if there is no supported
|
||||
* implementation. Will never return nullptr.
|
||||
*/
|
||||
const implementation *detect_best_supported() const noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private Detects best supported implementation on first use, and sets it
|
||||
*/
|
||||
class detect_best_supported_implementation_on_first_use final : public implementation {
|
||||
public:
|
||||
const std::string &name() const noexcept final { return set_best()->name(); }
|
||||
const std::string &description() const noexcept final { return set_best()->description(); }
|
||||
uint32_t required_instruction_sets() const noexcept final { return set_best()->required_instruction_sets(); }
|
||||
WARN_UNUSED error_code parse(const uint8_t *buf, size_t len, dom::parser &parser) const noexcept final {
|
||||
return set_best()->parse(buf, len, parser);
|
||||
}
|
||||
WARN_UNUSED error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final {
|
||||
return set_best()->minify(buf, len, dst, dst_len);
|
||||
}
|
||||
WARN_UNUSED error_code stage1(const uint8_t *buf, size_t len, dom::parser &parser, bool streaming) const noexcept final {
|
||||
return set_best()->stage1(buf, len, parser, streaming);
|
||||
}
|
||||
WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, dom::parser &parser) const noexcept final {
|
||||
return set_best()->stage2(buf, len, parser);
|
||||
}
|
||||
WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, dom::parser &parser, size_t &next_json) const noexcept final {
|
||||
return set_best()->stage2(buf, len, parser, next_json);
|
||||
}
|
||||
|
||||
really_inline detect_best_supported_implementation_on_first_use() noexcept : implementation("best_supported_detector", "Detects the best supported implementation and sets it", 0) {}
|
||||
private:
|
||||
const implementation *set_best() const noexcept;
|
||||
};
|
||||
|
||||
inline const detect_best_supported_implementation_on_first_use detect_best_supported_implementation_on_first_use_singleton;
|
||||
|
||||
template<typename T>
|
||||
class atomic_ptr {
|
||||
public:
|
||||
atomic_ptr(T *_ptr) : ptr{_ptr} {}
|
||||
|
||||
operator const T*() const { return ptr.load(); }
|
||||
const T& operator*() const { return *ptr; }
|
||||
const T* operator->() const { return ptr.load(); }
|
||||
|
||||
operator T*() { return ptr.load(); }
|
||||
T& operator*() { return *ptr; }
|
||||
T* operator->() { return ptr.load(); }
|
||||
T* operator=(T *_ptr) { return ptr = _ptr; }
|
||||
|
||||
private:
|
||||
std::atomic<T*> ptr;
|
||||
};
|
||||
|
||||
} // namespace [simdjson::]internal
|
||||
|
||||
/**
|
||||
* The list of available implementations compiled into simdjson.
|
||||
*/
|
||||
inline const internal::available_implementation_list available_implementations;
|
||||
|
||||
/**
|
||||
* The active implementation.
|
||||
*
|
||||
* Automatically initialized on first use to the most advanced implementation supported by this hardware.
|
||||
*
|
||||
* @hideinitializer
|
||||
*/
|
||||
inline internal::atomic_ptr<const implementation> active_implementation = &internal::detect_best_supported_implementation_on_first_use_singleton;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,276 @@
|
||||
#ifndef SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
#define SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
|
||||
#include "simdjson/document_stream.h"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
namespace simdjson::internal {
|
||||
|
||||
/**
|
||||
* This algorithm is used to quickly identify the buffer position of
|
||||
* the last JSON document inside the current batch.
|
||||
*
|
||||
* It does its work by finding the last pair of structural characters
|
||||
* that represent the end followed by the start of a document.
|
||||
*
|
||||
* Simply put, we iterate over the structural characters, starting from
|
||||
* the end. We consider that we found the end of a JSON document when the
|
||||
* first element of the pair is NOT one of these characters: '{' '[' ';' ','
|
||||
* and when the second element is NOT one of these characters: '}' '}' ';' ','.
|
||||
*
|
||||
* This simple comparison works most of the time, but it does not cover cases
|
||||
* where the batch's structural indexes contain a perfect amount of documents.
|
||||
* In such a case, we do not have access to the structural index which follows
|
||||
* the last document, therefore, we do not have access to the second element in
|
||||
* the pair, and means that we cannot identify the last document. To fix this
|
||||
* issue, we keep a count of the open and closed curly/square braces we found
|
||||
* while searching for the pair. When we find a pair AND the count of open and
|
||||
* closed curly/square braces is the same, we know that we just passed a
|
||||
* complete
|
||||
* document, therefore the last json buffer location is the end of the batch
|
||||
* */
|
||||
inline size_t find_last_json_buf_idx(const uint8_t *buf, size_t size, const dom::parser &parser) {
|
||||
// this function can be generally useful
|
||||
if (parser.n_structural_indexes == 0)
|
||||
return 0;
|
||||
auto last_i = parser.n_structural_indexes - 1;
|
||||
if (parser.structural_indexes[last_i] == size) {
|
||||
if (last_i == 0)
|
||||
return 0;
|
||||
last_i = parser.n_structural_indexes - 2;
|
||||
}
|
||||
auto arr_cnt = 0;
|
||||
auto obj_cnt = 0;
|
||||
for (auto i = last_i; i > 0; i--) {
|
||||
auto idxb = parser.structural_indexes[i];
|
||||
switch (buf[idxb]) {
|
||||
case ':':
|
||||
case ',':
|
||||
continue;
|
||||
case '}':
|
||||
obj_cnt--;
|
||||
continue;
|
||||
case ']':
|
||||
arr_cnt--;
|
||||
continue;
|
||||
case '{':
|
||||
obj_cnt++;
|
||||
break;
|
||||
case '[':
|
||||
arr_cnt++;
|
||||
break;
|
||||
}
|
||||
auto idxa = parser.structural_indexes[i - 1];
|
||||
switch (buf[idxa]) {
|
||||
case '{':
|
||||
case '[':
|
||||
case ':':
|
||||
case ',':
|
||||
continue;
|
||||
}
|
||||
if (!arr_cnt && !obj_cnt) {
|
||||
return last_i + 1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// returns true if the provided byte value is an ASCII character
|
||||
static inline bool is_ascii(char c) {
|
||||
return ((unsigned char)c) <= 127;
|
||||
}
|
||||
|
||||
// if the string ends with UTF-8 values, backtrack
|
||||
// up to the first ASCII character. May return 0.
|
||||
static inline size_t trimmed_length_safe_utf8(const char * c, size_t len) {
|
||||
while ((len > 0) and (not is_ascii(c[len - 1]))) {
|
||||
len--;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
} // namespace simdjson::internal
|
||||
|
||||
namespace simdjson::dom {
|
||||
|
||||
really_inline document_stream::document_stream(
|
||||
dom::parser &_parser,
|
||||
const uint8_t *buf,
|
||||
size_t len,
|
||||
size_t batch_size,
|
||||
error_code _error
|
||||
) noexcept : parser{_parser}, _buf{buf}, _len{len}, _batch_size(batch_size), error{_error} {
|
||||
if (!error) { error = json_parse(); }
|
||||
}
|
||||
|
||||
inline document_stream::~document_stream() noexcept {
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
if (stage_1_thread.joinable()) {
|
||||
stage_1_thread.join();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator document_stream::begin() noexcept {
|
||||
return iterator(*this, false);
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator document_stream::end() noexcept {
|
||||
return iterator(*this, true);
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator::iterator(document_stream& _stream, bool is_end) noexcept
|
||||
: stream{_stream}, finished{is_end} {
|
||||
}
|
||||
|
||||
really_inline simdjson_result<element> document_stream::iterator::operator*() noexcept {
|
||||
error_code err = stream.error == SUCCESS_AND_HAS_MORE ? SUCCESS : stream.error;
|
||||
if (err) { return err; }
|
||||
return stream.parser.doc.root();
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator& document_stream::iterator::operator++() noexcept {
|
||||
if (stream.error == SUCCESS_AND_HAS_MORE) {
|
||||
stream.error = stream.json_parse();
|
||||
} else {
|
||||
finished = true;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
really_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept {
|
||||
return finished != other.finished;
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
|
||||
// threaded version of json_parse
|
||||
// todo: simplify this code further
|
||||
inline error_code document_stream::json_parse() noexcept {
|
||||
error = parser.ensure_capacity(_batch_size);
|
||||
if (error) { return error; }
|
||||
error = parser_thread.ensure_capacity(_batch_size);
|
||||
if (error) { return error; }
|
||||
|
||||
if (unlikely(load_next_batch)) {
|
||||
// First time loading
|
||||
if (!stage_1_thread.joinable()) {
|
||||
_batch_size = (std::min)(_batch_size, remaining());
|
||||
_batch_size = internal::trimmed_length_safe_utf8((const char *)buf(), _batch_size);
|
||||
if (_batch_size == 0) {
|
||||
return simdjson::UTF8_ERROR;
|
||||
}
|
||||
auto stage1_is_ok = error_code(simdjson::active_implementation->stage1(buf(), _batch_size, parser, true));
|
||||
if (stage1_is_ok != simdjson::SUCCESS) {
|
||||
return stage1_is_ok;
|
||||
}
|
||||
size_t last_index = internal::find_last_json_buf_idx(buf(), _batch_size, parser);
|
||||
if (last_index == 0) {
|
||||
if (parser.n_structural_indexes == 0) {
|
||||
return simdjson::EMPTY;
|
||||
}
|
||||
} else {
|
||||
parser.n_structural_indexes = last_index + 1;
|
||||
}
|
||||
}
|
||||
// the second thread is running or done.
|
||||
else {
|
||||
stage_1_thread.join();
|
||||
if (stage1_is_ok_thread != simdjson::SUCCESS) {
|
||||
return stage1_is_ok_thread;
|
||||
}
|
||||
std::swap(parser.structural_indexes, parser_thread.structural_indexes);
|
||||
parser.n_structural_indexes = parser_thread.n_structural_indexes;
|
||||
advance(last_json_buffer_loc);
|
||||
n_bytes_parsed += last_json_buffer_loc;
|
||||
}
|
||||
// let us decide whether we will start a new thread
|
||||
if (remaining() - _batch_size > 0) {
|
||||
last_json_buffer_loc =
|
||||
parser.structural_indexes[internal::find_last_json_buf_idx(buf(), _batch_size, parser)];
|
||||
_batch_size = (std::min)(_batch_size, remaining() - last_json_buffer_loc);
|
||||
if (_batch_size > 0) {
|
||||
_batch_size = internal::trimmed_length_safe_utf8(
|
||||
(const char *)(buf() + last_json_buffer_loc), _batch_size);
|
||||
if (_batch_size == 0) {
|
||||
return simdjson::UTF8_ERROR;
|
||||
}
|
||||
// let us capture read-only variables
|
||||
const uint8_t *const b = buf() + last_json_buffer_loc;
|
||||
const size_t bs = _batch_size;
|
||||
// we call the thread on a lambda that will update
|
||||
// this->stage1_is_ok_thread
|
||||
// there is only one thread that may write to this value
|
||||
stage_1_thread = std::thread([this, b, bs] {
|
||||
this->stage1_is_ok_thread = error_code(simdjson::active_implementation->stage1(b, bs, this->parser_thread, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
next_json = 0;
|
||||
load_next_batch = false;
|
||||
} // load_next_batch
|
||||
error_code res = simdjson::active_implementation->stage2(buf(), remaining(), parser, next_json);
|
||||
if (res == simdjson::SUCCESS_AND_HAS_MORE) {
|
||||
n_parsed_docs++;
|
||||
current_buffer_loc = parser.structural_indexes[next_json];
|
||||
load_next_batch = (current_buffer_loc == last_json_buffer_loc);
|
||||
} else if (res == simdjson::SUCCESS) {
|
||||
n_parsed_docs++;
|
||||
if (remaining() > _batch_size) {
|
||||
current_buffer_loc = parser.structural_indexes[next_json - 1];
|
||||
load_next_batch = true;
|
||||
res = simdjson::SUCCESS_AND_HAS_MORE;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#else // SIMDJSON_THREADS_ENABLED
|
||||
|
||||
// single-threaded version of json_parse
|
||||
inline error_code document_stream::json_parse() noexcept {
|
||||
error = parser.ensure_capacity(_batch_size);
|
||||
if (error) { return error; }
|
||||
|
||||
if (unlikely(load_next_batch)) {
|
||||
advance(current_buffer_loc);
|
||||
n_bytes_parsed += current_buffer_loc;
|
||||
_batch_size = (std::min)(_batch_size, remaining());
|
||||
_batch_size = internal::trimmed_length_safe_utf8((const char *)buf(), _batch_size);
|
||||
auto stage1_is_ok = (error_code)simdjson::active_implementation->stage1(buf(), _batch_size, parser, true);
|
||||
if (stage1_is_ok != simdjson::SUCCESS) {
|
||||
return stage1_is_ok;
|
||||
}
|
||||
size_t last_index = internal::find_last_json_buf_idx(buf(), _batch_size, parser);
|
||||
if (last_index == 0) {
|
||||
if (parser.n_structural_indexes == 0) {
|
||||
return EMPTY;
|
||||
}
|
||||
} else {
|
||||
parser.n_structural_indexes = last_index + 1;
|
||||
}
|
||||
load_next_batch = false;
|
||||
} // load_next_batch
|
||||
error_code res = simdjson::active_implementation->stage2(buf(), remaining(), parser, next_json);
|
||||
if (likely(res == simdjson::SUCCESS_AND_HAS_MORE)) {
|
||||
n_parsed_docs++;
|
||||
current_buffer_loc = parser.structural_indexes[next_json];
|
||||
} else if (res == simdjson::SUCCESS) {
|
||||
n_parsed_docs++;
|
||||
if (remaining() > _batch_size) {
|
||||
current_buffer_loc = parser.structural_indexes[next_json - 1];
|
||||
next_json = 1;
|
||||
load_next_batch = true;
|
||||
res = simdjson::SUCCESS_AND_HAS_MORE;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif // SIMDJSON_THREADS_ENABLED
|
||||
|
||||
} // namespace simdjson::dom
|
||||
#endif // SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef SIMDJSON_INLINE_ERROR_H
|
||||
#define SIMDJSON_INLINE_ERROR_H
|
||||
|
||||
#include "simdjson/error.h"
|
||||
#include <string>
|
||||
|
||||
namespace simdjson::internal {
|
||||
// We store the error code so we can validate the error message is associated with the right code
|
||||
struct error_code_info {
|
||||
error_code code;
|
||||
std::string message;
|
||||
};
|
||||
// These MUST match the codes in error_code. We check this constraint in basictests.
|
||||
inline const error_code_info error_codes[] {
|
||||
{ SUCCESS, "No error" },
|
||||
{ SUCCESS_AND_HAS_MORE, "No error and buffer still has more data" },
|
||||
{ CAPACITY, "This parser can't support a document that big" },
|
||||
{ MEMALLOC, "Error allocating memory, we're most likely out of memory" },
|
||||
{ TAPE_ERROR, "Something went wrong while writing to the tape" },
|
||||
{ DEPTH_ERROR, "The JSON document was too deep (too many nested objects and arrays)" },
|
||||
{ STRING_ERROR, "Problem while parsing a string" },
|
||||
{ T_ATOM_ERROR, "Problem while parsing an atom starting with the letter 't'" },
|
||||
{ F_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'f'" },
|
||||
{ N_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'n'" },
|
||||
{ NUMBER_ERROR, "Problem while parsing a number" },
|
||||
{ UTF8_ERROR, "The input is not valid UTF-8" },
|
||||
{ UNINITIALIZED, "Uninitialized" },
|
||||
{ EMPTY, "Empty: no JSON found" },
|
||||
{ UNESCAPED_CHARS, "Within strings, some characters must be escaped, we found unescaped characters" },
|
||||
{ UNCLOSED_STRING, "A string is opened, but never closed." },
|
||||
{ UNSUPPORTED_ARCHITECTURE, "simdjson does not have an implementation supported by this CPU architecture (perhaps it's a non-SIMD CPU?)." },
|
||||
{ INCORRECT_TYPE, "The JSON element does not have the requested type." },
|
||||
{ NUMBER_OUT_OF_RANGE, "The JSON number is too large or too small to fit within the requested type." },
|
||||
{ INDEX_OUT_OF_BOUNDS, "Attempted to access an element of a JSON array that is beyond its length." },
|
||||
{ NO_SUCH_FIELD, "The JSON field referenced does not exist in this object." },
|
||||
{ IO_ERROR, "Error reading the file." },
|
||||
{ INVALID_JSON_POINTER, "Invalid JSON pointer syntax." },
|
||||
{ INVALID_URI_FRAGMENT, "Invalid URI fragment syntax." },
|
||||
{ UNEXPECTED_ERROR, "Unexpected error, consider reporting this problem as you may have found a bug in simdjson" }
|
||||
}; // error_messages[]
|
||||
} // namespace simdjson::internal
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
inline const char *error_message(error_code error) noexcept {
|
||||
// If you're using error_code, we're trusting you got it from the enum.
|
||||
return internal::error_codes[int(error)].message.c_str();
|
||||
}
|
||||
|
||||
inline const std::string &error_message(int error) noexcept {
|
||||
if (error < 0 || error >= error_code::NUM_ERROR_CODES) {
|
||||
return internal::error_codes[UNEXPECTED_ERROR].message;
|
||||
}
|
||||
return internal::error_codes[error].message;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept {
|
||||
return out << error_message(error);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
//
|
||||
// internal::simdjson_result_base<T> inline implementation
|
||||
//
|
||||
|
||||
template<typename T>
|
||||
really_inline void simdjson_result_base<T>::tie(T &value, error_code &error) && noexcept {
|
||||
// on the clang compiler that comes with current macOS (Apple clang version 11.0.0),
|
||||
// tie(width, error) = size["w"].get<uint64_t>();
|
||||
// fails with "error: no viable overloaded '='""
|
||||
value = std::forward<simdjson_result_base<T>>(*this).first;
|
||||
error = this->second;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
really_inline error_code simdjson_result_base<T>::error() const noexcept {
|
||||
return this->second;
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
template<typename T>
|
||||
really_inline T& simdjson_result_base<T>::value() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return this->first;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
really_inline T&& simdjson_result_base<T>::take_value() && noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return std::forward<T>(this->first);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
really_inline simdjson_result_base<T>::operator T&&() && noexcept(false) {
|
||||
return std::forward<simdjson_result_base<T>>(*this).take_value();
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
template<typename T>
|
||||
really_inline simdjson_result_base<T>::simdjson_result_base(T &&value, error_code error) noexcept
|
||||
: std::pair<T, error_code>(std::forward<T>(value), error) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result_base<T>::simdjson_result_base(error_code error) noexcept
|
||||
: simdjson_result_base(T{}, error) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result_base<T>::simdjson_result_base(T &&value) noexcept
|
||||
: simdjson_result_base(std::forward<T>(value), SUCCESS) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result_base<T>::simdjson_result_base() noexcept
|
||||
: simdjson_result_base(T{}, UNINITIALIZED) {}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
///
|
||||
/// simdjson_result<T> inline implementation
|
||||
///
|
||||
|
||||
template<typename T>
|
||||
really_inline void simdjson_result<T>::tie(T &value, error_code &error) && noexcept {
|
||||
std::forward<internal::simdjson_result_base<T>>(*this).tie(value, error);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
really_inline error_code simdjson_result<T>::error() const noexcept {
|
||||
return internal::simdjson_result_base<T>::error();
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
template<typename T>
|
||||
really_inline T& simdjson_result<T>::value() noexcept(false) {
|
||||
return internal::simdjson_result_base<T>::value();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
really_inline T&& simdjson_result<T>::take_value() && noexcept(false) {
|
||||
return std::forward<internal::simdjson_result_base<T>>(*this).take_value();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T>::operator T&&() && noexcept(false) {
|
||||
return std::forward<internal::simdjson_result_base<T>>(*this).take_value();
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T>::simdjson_result(T &&value, error_code error) noexcept
|
||||
: internal::simdjson_result_base<T>(std::forward<T>(value), error) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T>::simdjson_result(error_code error) noexcept
|
||||
: internal::simdjson_result_base<T>(error) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T>::simdjson_result(T &&value) noexcept
|
||||
: internal::simdjson_result_base<T>(std::forward<T>(value)) {}
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T>::simdjson_result() noexcept
|
||||
: internal::simdjson_result_base<T>() {}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_INLINE_ERROR_H
|
||||
@@ -0,0 +1,141 @@
|
||||
#ifndef SIMDJSON_INLINE_PADDED_STRING_H
|
||||
#define SIMDJSON_INLINE_PADDED_STRING_H
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
#include "simdjson/common_defs.h" // for SIMDJSON_PADDING
|
||||
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace simdjson::internal {
|
||||
|
||||
// low-level function to allocate memory with padding so we can read past the
|
||||
// "length" bytes safely. if you must provide a pointer to some data, create it
|
||||
// with this function: length is the max. size in bytes of the string caller is
|
||||
// responsible to free the memory (free(...))
|
||||
inline char *allocate_padded_buffer(size_t length) noexcept {
|
||||
// we could do a simple malloc
|
||||
// return (char *) malloc(length + SIMDJSON_PADDING);
|
||||
// However, we might as well align to cache lines...
|
||||
size_t totalpaddedlength = length + SIMDJSON_PADDING;
|
||||
char *padded_buffer = aligned_malloc_char(64, totalpaddedlength);
|
||||
#ifndef NDEBUG
|
||||
if (padded_buffer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
memset(padded_buffer + length, 0, totalpaddedlength - length);
|
||||
return padded_buffer;
|
||||
} // allocate_padded_buffer()
|
||||
|
||||
} // namespace simdjson::internal
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
inline padded_string::padded_string() noexcept : viable_size(0), data_ptr(nullptr) {}
|
||||
inline padded_string::padded_string(size_t length) noexcept
|
||||
: viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) {
|
||||
if (data_ptr != nullptr)
|
||||
data_ptr[length] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
inline padded_string::padded_string(const char *data, size_t length) noexcept
|
||||
: viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) {
|
||||
if ((data != nullptr) and (data_ptr != nullptr)) {
|
||||
memcpy(data_ptr, data, length);
|
||||
data_ptr[length] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
}
|
||||
// note: do not pass std::string arguments by value
|
||||
inline padded_string::padded_string(const std::string & str_ ) noexcept
|
||||
: viable_size(str_.size()), data_ptr(internal::allocate_padded_buffer(str_.size())) {
|
||||
if (data_ptr != nullptr) {
|
||||
memcpy(data_ptr, str_.data(), str_.size());
|
||||
data_ptr[str_.size()] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
}
|
||||
// note: do pass std::string_view arguments by value
|
||||
inline padded_string::padded_string(std::string_view sv_) noexcept
|
||||
: viable_size(sv_.size()), data_ptr(internal::allocate_padded_buffer(sv_.size())) {
|
||||
if (data_ptr != nullptr) {
|
||||
memcpy(data_ptr, sv_.data(), sv_.size());
|
||||
data_ptr[sv_.size()] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
}
|
||||
inline padded_string::padded_string(padded_string &&o) noexcept
|
||||
: viable_size(o.viable_size), data_ptr(o.data_ptr) {
|
||||
o.data_ptr = nullptr; // we take ownership
|
||||
}
|
||||
|
||||
inline padded_string &padded_string::operator=(padded_string &&o) noexcept {
|
||||
aligned_free_char(data_ptr);
|
||||
data_ptr = o.data_ptr;
|
||||
viable_size = o.viable_size;
|
||||
o.data_ptr = nullptr; // we take ownership
|
||||
o.viable_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void padded_string::swap(padded_string &o) noexcept {
|
||||
size_t tmp_viable_size = viable_size;
|
||||
char *tmp_data_ptr = data_ptr;
|
||||
viable_size = o.viable_size;
|
||||
data_ptr = o.data_ptr;
|
||||
o.data_ptr = tmp_data_ptr;
|
||||
o.viable_size = tmp_viable_size;
|
||||
}
|
||||
|
||||
inline padded_string::~padded_string() noexcept {
|
||||
aligned_free_char(data_ptr);
|
||||
}
|
||||
|
||||
inline size_t padded_string::size() const noexcept { return viable_size; }
|
||||
|
||||
inline size_t padded_string::length() const noexcept { return viable_size; }
|
||||
|
||||
inline const char *padded_string::data() const noexcept { return data_ptr; }
|
||||
|
||||
inline char *padded_string::data() noexcept { return data_ptr; }
|
||||
|
||||
inline padded_string::operator std::string_view() const { return std::string_view(data(), length()); }
|
||||
|
||||
inline simdjson_result<padded_string> padded_string::load(const std::string &filename) noexcept {
|
||||
// Open the file
|
||||
std::FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
if (fp == nullptr) {
|
||||
return IO_ERROR;
|
||||
}
|
||||
|
||||
// Get the file size
|
||||
if(std::fseek(fp, 0, SEEK_END) < 0) {
|
||||
std::fclose(fp);
|
||||
return IO_ERROR;
|
||||
}
|
||||
long llen = std::ftell(fp);
|
||||
if((llen < 0) || (llen == LONG_MAX)) {
|
||||
std::fclose(fp);
|
||||
return IO_ERROR;
|
||||
}
|
||||
|
||||
// Allocate the padded_string
|
||||
size_t len = (size_t) llen;
|
||||
padded_string s(len);
|
||||
if (s.data() == nullptr) {
|
||||
std::fclose(fp);
|
||||
return MEMALLOC;
|
||||
}
|
||||
|
||||
// Read the padded_string
|
||||
std::rewind(fp);
|
||||
size_t bytes_read = std::fread(s.data(), 1, len, fp);
|
||||
if (std::fclose(fp) != 0 || bytes_read != len) {
|
||||
return IO_ERROR;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_INLINE_PADDED_STRING_H
|
||||
@@ -0,0 +1,474 @@
|
||||
#ifndef SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H
|
||||
#define SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H
|
||||
|
||||
#include "simdjson/parsedjson_iterator.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Because of template weirdness, the actual class definition is inline in the document class
|
||||
|
||||
WARN_UNUSED bool dom::parser::Iterator::is_ok() const {
|
||||
return location < tape_length;
|
||||
}
|
||||
|
||||
// useful for debugging purposes
|
||||
size_t dom::parser::Iterator::get_tape_location() const {
|
||||
return location;
|
||||
}
|
||||
|
||||
// useful for debugging purposes
|
||||
size_t dom::parser::Iterator::get_tape_length() const {
|
||||
return tape_length;
|
||||
}
|
||||
|
||||
// returns the current depth (start at 1 with 0 reserved for the fictitious root
|
||||
// node)
|
||||
size_t dom::parser::Iterator::get_depth() const {
|
||||
return depth;
|
||||
}
|
||||
|
||||
// A scope is a series of nodes at the same depth, typically it is either an
|
||||
// object ({) or an array ([). The root node has type 'r'.
|
||||
uint8_t dom::parser::Iterator::get_scope_type() const {
|
||||
return depth_index[depth].scope_type;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_forward() {
|
||||
if (location + 1 >= tape_length) {
|
||||
return false; // we are at the end!
|
||||
}
|
||||
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// We are entering a new scope
|
||||
depth++;
|
||||
assert(depth < max_depth);
|
||||
depth_index[depth].start_of_scope = location;
|
||||
depth_index[depth].scope_type = current_type;
|
||||
} else if ((current_type == ']') || (current_type == '}')) {
|
||||
// Leaving a scope.
|
||||
depth--;
|
||||
} else if (is_number()) {
|
||||
// these types use 2 locations on the tape, not just one.
|
||||
location += 1;
|
||||
}
|
||||
|
||||
location += 1;
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
|
||||
void dom::parser::Iterator::move_to_value() {
|
||||
// assume that we are on a key, so move by 1.
|
||||
location += 1;
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_to_key(const char *key) {
|
||||
if (down()) {
|
||||
do {
|
||||
const bool right_key = (strcmp(get_string(), key) == 0);
|
||||
move_to_value();
|
||||
if (right_key) {
|
||||
return true;
|
||||
}
|
||||
} while (next());
|
||||
up();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_to_key_insensitive(
|
||||
const char *key) {
|
||||
if (down()) {
|
||||
do {
|
||||
const bool right_key = (simdjson_strcasecmp(get_string(), key) == 0);
|
||||
move_to_value();
|
||||
if (right_key) {
|
||||
return true;
|
||||
}
|
||||
} while (next());
|
||||
up();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_to_key(const char *key,
|
||||
uint32_t length) {
|
||||
if (down()) {
|
||||
do {
|
||||
bool right_key = ((get_string_length() == length) &&
|
||||
(memcmp(get_string(), key, length) == 0));
|
||||
move_to_value();
|
||||
if (right_key) {
|
||||
return true;
|
||||
}
|
||||
} while (next());
|
||||
up();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_to_index(uint32_t index) {
|
||||
if (down()) {
|
||||
uint32_t i = 0;
|
||||
for (; i < index; i++) {
|
||||
if (!next()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == index) {
|
||||
return true;
|
||||
}
|
||||
up();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::prev() {
|
||||
size_t target_location = location;
|
||||
to_start_scope();
|
||||
size_t npos = location;
|
||||
if (target_location == npos) {
|
||||
return false; // we were already at the start
|
||||
}
|
||||
size_t oldnpos;
|
||||
// we have that npos < target_location here
|
||||
do {
|
||||
oldnpos = npos;
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// we need to jump
|
||||
npos = (current_val & internal::JSON_VALUE_MASK);
|
||||
} else {
|
||||
npos = npos + ((current_type == 'd' || current_type == 'l') ? 2 : 1);
|
||||
}
|
||||
} while (npos < target_location);
|
||||
location = oldnpos;
|
||||
current_val = doc.tape[location];
|
||||
current_type = current_val >> 56;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::up() {
|
||||
if (depth == 1) {
|
||||
return false; // don't allow moving back to root
|
||||
}
|
||||
to_start_scope();
|
||||
// next we just move to the previous value
|
||||
depth--;
|
||||
location -= 1;
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::down() {
|
||||
if (location + 1 >= tape_length) {
|
||||
return false;
|
||||
}
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
size_t npos = (current_val & internal::JSON_VALUE_MASK);
|
||||
if (npos == location + 2) {
|
||||
return false; // we have an empty scope
|
||||
}
|
||||
depth++;
|
||||
assert(depth < max_depth);
|
||||
location = location + 1;
|
||||
depth_index[depth].start_of_scope = location;
|
||||
depth_index[depth].scope_type = current_type;
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void dom::parser::Iterator::to_start_scope() {
|
||||
location = depth_index[depth].start_of_scope;
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::next() {
|
||||
size_t npos;
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// we need to jump
|
||||
npos = (current_val & internal::JSON_VALUE_MASK);
|
||||
} else {
|
||||
npos = location + (is_number() ? 2 : 1);
|
||||
}
|
||||
uint64_t next_val = doc.tape[npos];
|
||||
uint8_t next_type = (next_val >> 56);
|
||||
if ((next_type == ']') || (next_type == '}')) {
|
||||
return false; // we reached the end of the scope
|
||||
}
|
||||
location = npos;
|
||||
current_val = next_val;
|
||||
current_type = next_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
dom::parser::Iterator::Iterator(const dom::parser &pj) noexcept(false)
|
||||
: doc(pj.doc), depth(0), location(0), tape_length(0) {
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
if (!pj.valid) { throw simdjson_error(pj.error); }
|
||||
#else
|
||||
if (!pj.valid) { abort(); }
|
||||
#endif
|
||||
|
||||
max_depth = pj.max_depth();
|
||||
depth_index = new scopeindex_t[max_depth + 1];
|
||||
depth_index[0].start_of_scope = location;
|
||||
current_val = doc.tape[location++];
|
||||
current_type = (current_val >> 56);
|
||||
depth_index[0].scope_type = current_type;
|
||||
tape_length = current_val & internal::JSON_VALUE_MASK;
|
||||
if (location < tape_length) {
|
||||
// If we make it here, then depth_capacity must >=2, but the compiler
|
||||
// may not know this.
|
||||
current_val = doc.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
depth++;
|
||||
assert(depth < max_depth);
|
||||
depth_index[depth].start_of_scope = location;
|
||||
depth_index[depth].scope_type = current_type;
|
||||
}
|
||||
}
|
||||
|
||||
dom::parser::Iterator::Iterator(
|
||||
const dom::parser::Iterator &o) noexcept
|
||||
: doc(o.doc), max_depth(o.depth), depth(o.depth), location(o.location),
|
||||
tape_length(o.tape_length), current_type(o.current_type),
|
||||
current_val(o.current_val) {
|
||||
depth_index = new scopeindex_t[max_depth+1];
|
||||
memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0]));
|
||||
}
|
||||
|
||||
dom::parser::Iterator::~Iterator() noexcept {
|
||||
if (depth_index) { delete[] depth_index; }
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::print(std::ostream &os, bool escape_strings) const {
|
||||
if (!is_ok()) {
|
||||
return false;
|
||||
}
|
||||
switch (current_type) {
|
||||
case '"': // we have a string
|
||||
os << '"';
|
||||
if (escape_strings) {
|
||||
os << internal::escape_json_string(std::string_view(get_string(), get_string_length()));
|
||||
} else {
|
||||
// was: os << get_string();, but given that we can include null chars, we
|
||||
// have to do something crazier:
|
||||
std::copy(get_string(), get_string() + get_string_length(), std::ostream_iterator<char>(os));
|
||||
}
|
||||
os << '"';
|
||||
break;
|
||||
case 'l': // we have a long int
|
||||
os << get_integer();
|
||||
break;
|
||||
case 'u':
|
||||
os << get_unsigned_integer();
|
||||
break;
|
||||
case 'd':
|
||||
os << get_double();
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
os << "null";
|
||||
break;
|
||||
case 't': // we have a true
|
||||
os << "true";
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
os << "false";
|
||||
break;
|
||||
case '{': // we have an object
|
||||
case '}': // we end an object
|
||||
case '[': // we start an array
|
||||
case ']': // we end an array
|
||||
os << static_cast<char>(current_type);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::move_to(const char *pointer,
|
||||
uint32_t length) {
|
||||
char *new_pointer = nullptr;
|
||||
if (pointer[0] == '#') {
|
||||
// Converting fragment representation to string representation
|
||||
new_pointer = new char[length];
|
||||
uint32_t new_length = 0;
|
||||
for (uint32_t i = 1; i < length; i++) {
|
||||
if (pointer[i] == '%' && pointer[i + 1] == 'x') {
|
||||
#if __cpp_exceptions
|
||||
try {
|
||||
#endif
|
||||
int fragment =
|
||||
std::stoi(std::string(&pointer[i + 2], 2), nullptr, 16);
|
||||
if (fragment == '\\' || fragment == '"' || (fragment <= 0x1F)) {
|
||||
// escaping the character
|
||||
new_pointer[new_length] = '\\';
|
||||
new_length++;
|
||||
}
|
||||
new_pointer[new_length] = fragment;
|
||||
i += 3;
|
||||
#if __cpp_exceptions
|
||||
} catch (std::invalid_argument &) {
|
||||
delete[] new_pointer;
|
||||
return false; // the fragment is invalid
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
new_pointer[new_length] = pointer[i];
|
||||
}
|
||||
new_length++;
|
||||
}
|
||||
length = new_length;
|
||||
pointer = new_pointer;
|
||||
}
|
||||
|
||||
// saving the current state
|
||||
size_t depth_s = depth;
|
||||
size_t location_s = location;
|
||||
uint8_t current_type_s = current_type;
|
||||
uint64_t current_val_s = current_val;
|
||||
|
||||
rewind(); // The json pointer is used from the root of the document.
|
||||
|
||||
bool found = relative_move_to(pointer, length);
|
||||
delete[] new_pointer;
|
||||
|
||||
if (!found) {
|
||||
// since the pointer has found nothing, we get back to the original
|
||||
// position.
|
||||
depth = depth_s;
|
||||
location = location_s;
|
||||
current_type = current_type_s;
|
||||
current_val = current_val_s;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool dom::parser::Iterator::relative_move_to(const char *pointer,
|
||||
uint32_t length) {
|
||||
if (length == 0) {
|
||||
// returns the whole document
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pointer[0] != '/') {
|
||||
// '/' must be the first character
|
||||
return false;
|
||||
}
|
||||
|
||||
// finding the key in an object or the index in an array
|
||||
std::string key_or_index;
|
||||
uint32_t offset = 1;
|
||||
|
||||
// checking for the "-" case
|
||||
if (is_array() && pointer[1] == '-') {
|
||||
if (length != 2) {
|
||||
// the pointer must be exactly "/-"
|
||||
// there can't be anything more after '-' as an index
|
||||
return false;
|
||||
}
|
||||
key_or_index = '-';
|
||||
offset = length; // will skip the loop coming right after
|
||||
}
|
||||
|
||||
// We either transform the first reference token to a valid json key
|
||||
// or we make sure it is a valid index in an array.
|
||||
for (; offset < length; offset++) {
|
||||
if (pointer[offset] == '/') {
|
||||
// beginning of the next key or index
|
||||
break;
|
||||
}
|
||||
if (is_array() && (pointer[offset] < '0' || pointer[offset] > '9')) {
|
||||
// the index of an array must be an integer
|
||||
// we also make sure std::stoi won't discard whitespaces later
|
||||
return false;
|
||||
}
|
||||
if (pointer[offset] == '~') {
|
||||
// "~1" represents "/"
|
||||
if (pointer[offset + 1] == '1') {
|
||||
key_or_index += '/';
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
// "~0" represents "~"
|
||||
if (pointer[offset + 1] == '0') {
|
||||
key_or_index += '~';
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (pointer[offset] == '\\') {
|
||||
if (pointer[offset + 1] == '\\' || pointer[offset + 1] == '"' ||
|
||||
(pointer[offset + 1] <= 0x1F)) {
|
||||
key_or_index += pointer[offset + 1];
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
return false; // invalid escaped character
|
||||
}
|
||||
if (pointer[offset] == '\"') {
|
||||
// unescaped quote character. this is an invalid case.
|
||||
// lets do nothing and assume most pointers will be valid.
|
||||
// it won't find any corresponding json key anyway.
|
||||
// return false;
|
||||
}
|
||||
key_or_index += pointer[offset];
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
if (is_object()) {
|
||||
if (move_to_key(key_or_index.c_str(), key_or_index.length())) {
|
||||
found = relative_move_to(pointer + offset, length - offset);
|
||||
}
|
||||
} else if (is_array()) {
|
||||
if (key_or_index == "-") { // handling "-" case first
|
||||
if (down()) {
|
||||
while (next())
|
||||
; // moving to the end of the array
|
||||
// moving to the nonexistent value right after...
|
||||
size_t npos;
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// we need to jump
|
||||
npos = (current_val & internal::JSON_VALUE_MASK);
|
||||
} else {
|
||||
npos =
|
||||
location + ((current_type == 'd' || current_type == 'l') ? 2 : 1);
|
||||
}
|
||||
location = npos;
|
||||
current_val = doc.tape[npos];
|
||||
current_type = (current_val >> 56);
|
||||
return true; // how could it fail ?
|
||||
}
|
||||
} else { // regular numeric index
|
||||
// The index can't have a leading '0'
|
||||
if (key_or_index[0] == '0' && key_or_index.length() > 1) {
|
||||
return false;
|
||||
}
|
||||
// it cannot be empty
|
||||
if (key_or_index.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
// we already checked the index contains only valid digits
|
||||
uint32_t index = std::stoi(key_or_index);
|
||||
if (move_to_index(index)) {
|
||||
found = relative_move_to(pointer + offset, length - offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef SIMDJSON_INTERNAL_JSONFORMATUTILS_H
|
||||
#define SIMDJSON_INTERNAL_JSONFORMATUTILS_H
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace simdjson::internal {
|
||||
|
||||
class escape_json_string;
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const escape_json_string &str);
|
||||
|
||||
class escape_json_string {
|
||||
public:
|
||||
escape_json_string(std::string_view _str) noexcept : str{_str} {}
|
||||
operator std::string() const noexcept { std::stringstream s; s << *this; return s.str(); }
|
||||
private:
|
||||
std::string_view str;
|
||||
friend std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped);
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped) {
|
||||
for (size_t i=0; i<unescaped.str.length(); i++) {
|
||||
switch (unescaped.str[i]) {
|
||||
case '\b':
|
||||
out << "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
out << "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
out << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
out << "\\r";
|
||||
break;
|
||||
case '\"':
|
||||
out << "\\\"";
|
||||
break;
|
||||
case '\t':
|
||||
out << "\\t";
|
||||
break;
|
||||
case '\\':
|
||||
out << "\\\\";
|
||||
break;
|
||||
default:
|
||||
if ((unsigned char)unescaped.str[i] <= 0x1F) {
|
||||
// TODO can this be done once at the beginning, or will it mess up << char?
|
||||
std::ios::fmtflags f(out.flags());
|
||||
out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(unescaped.str[i]);
|
||||
out.flags(f);
|
||||
} else {
|
||||
out << unescaped.str[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace simdjson::internal
|
||||
|
||||
#endif // SIMDJSON_INTERNAL_JSONFORMATUTILS_H
|
||||
@@ -1,301 +0,0 @@
|
||||
#ifndef SIMDJSON_JSONCHARUTILS_H
|
||||
#define SIMDJSON_JSONCHARUTILS_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
|
||||
namespace simdjson {
|
||||
// structural chars here are
|
||||
// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c (and NULL)
|
||||
// we are also interested in the four whitespace characters
|
||||
// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d
|
||||
|
||||
// these are the chars that can follow a true/false/null or number atom
|
||||
// and nothing else
|
||||
const uint32_t structural_or_whitespace_or_null_negated[256] = {
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
// return non-zero if not a structural or whitespace char
|
||||
// zero otherwise
|
||||
really_inline uint32_t is_not_structural_or_whitespace_or_null(uint8_t c) {
|
||||
return structural_or_whitespace_or_null_negated[c];
|
||||
}
|
||||
|
||||
const uint32_t structural_or_whitespace_negated[256] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
// return non-zero if not a structural or whitespace char
|
||||
// zero otherwise
|
||||
really_inline uint32_t is_not_structural_or_whitespace(uint8_t c) {
|
||||
return structural_or_whitespace_negated[c];
|
||||
}
|
||||
|
||||
const uint32_t structural_or_whitespace_or_null[256] = {
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
really_inline uint32_t is_structural_or_whitespace_or_null(uint8_t c) {
|
||||
return structural_or_whitespace_or_null[c];
|
||||
}
|
||||
|
||||
const uint32_t structural_or_whitespace[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
really_inline uint32_t is_structural_or_whitespace(uint8_t c) {
|
||||
return structural_or_whitespace[c];
|
||||
}
|
||||
|
||||
const uint32_t digit_to_val32[886] = {
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
|
||||
0x6, 0x7, 0x8, 0x9, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa,
|
||||
0xb, 0xc, 0xd, 0xe, 0xf, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xa, 0xb, 0xc, 0xd, 0xe,
|
||||
0xf, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x0, 0x10, 0x20, 0x30, 0x40, 0x50,
|
||||
0x60, 0x70, 0x80, 0x90, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa0,
|
||||
0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0,
|
||||
0xf0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x0, 0x100, 0x200, 0x300, 0x400, 0x500,
|
||||
0x600, 0x700, 0x800, 0x900, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa00,
|
||||
0xb00, 0xc00, 0xd00, 0xe00, 0xf00, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00,
|
||||
0xf00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x0, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000,
|
||||
0x6000, 0x7000, 0x8000, 0x9000, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa000,
|
||||
0xb000, 0xc000, 0xd000, 0xe000, 0xf000, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000,
|
||||
0xf000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
|
||||
// returns a value with the high 16 bits set if not valid
|
||||
// otherwise returns the conversion of the 4 hex digits at src into the bottom
|
||||
// 16 bits of the 32-bit return register
|
||||
//
|
||||
// see
|
||||
// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/
|
||||
static inline uint32_t hex_to_u32_nocheck(
|
||||
const uint8_t *src) { // strictly speaking, static inline is a C-ism
|
||||
uint32_t v1 = digit_to_val32[630 + src[0]];
|
||||
uint32_t v2 = digit_to_val32[420 + src[1]];
|
||||
uint32_t v3 = digit_to_val32[210 + src[2]];
|
||||
uint32_t v4 = digit_to_val32[0 + src[3]];
|
||||
return v1 | v2 | v3 | v4;
|
||||
}
|
||||
|
||||
// given a code point cp, writes to c
|
||||
// the utf-8 code, outputting the length in
|
||||
// bytes, if the length is zero, the code point
|
||||
// is invalid
|
||||
//
|
||||
// This can possibly be made faster using pdep
|
||||
// and clz and table lookups, but JSON documents
|
||||
// have few escaped code points, and the following
|
||||
// function looks cheap.
|
||||
//
|
||||
// Note: we assume that surrogates are treated separately
|
||||
//
|
||||
inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) {
|
||||
if (cp <= 0x7F) {
|
||||
c[0] = cp;
|
||||
return 1; // ascii
|
||||
}
|
||||
if (cp <= 0x7FF) {
|
||||
c[0] = (cp >> 6) + 192;
|
||||
c[1] = (cp & 63) + 128;
|
||||
return 2; // universal plane
|
||||
// Surrogates are treated elsewhere...
|
||||
//} //else if (0xd800 <= cp && cp <= 0xdfff) {
|
||||
// return 0; // surrogates // could put assert here
|
||||
} else if (cp <= 0xFFFF) {
|
||||
c[0] = (cp >> 12) + 224;
|
||||
c[1] = ((cp >> 6) & 63) + 128;
|
||||
c[2] = (cp & 63) + 128;
|
||||
return 3;
|
||||
} else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this
|
||||
// is not needed
|
||||
c[0] = (cp >> 18) + 240;
|
||||
c[1] = ((cp >> 12) & 63) + 128;
|
||||
c[2] = ((cp >> 6) & 63) + 128;
|
||||
c[3] = (cp & 63) + 128;
|
||||
return 4;
|
||||
}
|
||||
// will return 0 when the code point was too large.
|
||||
return 0; // bad r
|
||||
}
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
@@ -1,202 +0,0 @@
|
||||
#ifndef SIMDJSON_JSONFORMATUTILS_H
|
||||
#define SIMDJSON_JSONFORMATUTILS_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
namespace simdjson {
|
||||
// ends with zero char
|
||||
static inline void print_with_escapes(const unsigned char *src) {
|
||||
while (*src) {
|
||||
switch (*src) {
|
||||
case '\b':
|
||||
putchar('\\');
|
||||
putchar('b');
|
||||
break;
|
||||
case '\f':
|
||||
putchar('\\');
|
||||
putchar('f');
|
||||
break;
|
||||
case '\n':
|
||||
putchar('\\');
|
||||
putchar('n');
|
||||
break;
|
||||
case '\r':
|
||||
putchar('\\');
|
||||
putchar('r');
|
||||
break;
|
||||
case '\"':
|
||||
putchar('\\');
|
||||
putchar('"');
|
||||
break;
|
||||
case '\t':
|
||||
putchar('\\');
|
||||
putchar('t');
|
||||
break;
|
||||
case '\\':
|
||||
putchar('\\');
|
||||
putchar('\\');
|
||||
break;
|
||||
default:
|
||||
if (*src <= 0x1F) {
|
||||
printf("\\u%04x", *src);
|
||||
} else {
|
||||
putchar(*src);
|
||||
}
|
||||
}
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
// ends with zero char
|
||||
static inline void print_with_escapes(const unsigned char *src,
|
||||
std::ostream &os) {
|
||||
while (*src) {
|
||||
switch (*src) {
|
||||
case '\b':
|
||||
os << '\\';
|
||||
os << 'b';
|
||||
break;
|
||||
case '\f':
|
||||
os << '\\';
|
||||
os << 'f';
|
||||
break;
|
||||
case '\n':
|
||||
os << '\\';
|
||||
os << 'n';
|
||||
break;
|
||||
case '\r':
|
||||
os << '\\';
|
||||
os << 'r';
|
||||
break;
|
||||
case '\"':
|
||||
os << '\\';
|
||||
os << '"';
|
||||
break;
|
||||
case '\t':
|
||||
os << '\\';
|
||||
os << 't';
|
||||
break;
|
||||
case '\\':
|
||||
os << '\\';
|
||||
os << '\\';
|
||||
break;
|
||||
default:
|
||||
if (*src <= 0x1F) {
|
||||
std::ios::fmtflags f(os.flags());
|
||||
os << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<int>(*src);
|
||||
os.flags(f);
|
||||
} else {
|
||||
os << *src;
|
||||
}
|
||||
}
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
// print len chars
|
||||
static inline void print_with_escapes(const unsigned char *src, size_t len) {
|
||||
const unsigned char *finalsrc = src + len;
|
||||
while (src < finalsrc) {
|
||||
switch (*src) {
|
||||
case '\b':
|
||||
putchar('\\');
|
||||
putchar('b');
|
||||
break;
|
||||
case '\f':
|
||||
putchar('\\');
|
||||
putchar('f');
|
||||
break;
|
||||
case '\n':
|
||||
putchar('\\');
|
||||
putchar('n');
|
||||
break;
|
||||
case '\r':
|
||||
putchar('\\');
|
||||
putchar('r');
|
||||
break;
|
||||
case '\"':
|
||||
putchar('\\');
|
||||
putchar('"');
|
||||
break;
|
||||
case '\t':
|
||||
putchar('\\');
|
||||
putchar('t');
|
||||
break;
|
||||
case '\\':
|
||||
putchar('\\');
|
||||
putchar('\\');
|
||||
break;
|
||||
default:
|
||||
if (*src <= 0x1F) {
|
||||
printf("\\u%04x", *src);
|
||||
} else {
|
||||
putchar(*src);
|
||||
}
|
||||
}
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
// print len chars
|
||||
static inline void print_with_escapes(const unsigned char *src,
|
||||
std::ostream &os, size_t len) {
|
||||
const unsigned char *finalsrc = src + len;
|
||||
while (src < finalsrc) {
|
||||
switch (*src) {
|
||||
case '\b':
|
||||
os << '\\';
|
||||
os << 'b';
|
||||
break;
|
||||
case '\f':
|
||||
os << '\\';
|
||||
os << 'f';
|
||||
break;
|
||||
case '\n':
|
||||
os << '\\';
|
||||
os << 'n';
|
||||
break;
|
||||
case '\r':
|
||||
os << '\\';
|
||||
os << 'r';
|
||||
break;
|
||||
case '\"':
|
||||
os << '\\';
|
||||
os << '"';
|
||||
break;
|
||||
case '\t':
|
||||
os << '\\';
|
||||
os << 't';
|
||||
break;
|
||||
case '\\':
|
||||
os << '\\';
|
||||
os << '\\';
|
||||
break;
|
||||
default:
|
||||
if (*src <= 0x1F) {
|
||||
std::ios::fmtflags f(os.flags());
|
||||
os << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<int>(*src);
|
||||
os.flags(f);
|
||||
} else {
|
||||
os << *src;
|
||||
}
|
||||
}
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void print_with_escapes(const char *src, std::ostream &os) {
|
||||
print_with_escapes(reinterpret_cast<const unsigned char *>(src), os);
|
||||
}
|
||||
|
||||
static inline void print_with_escapes(const char *src, std::ostream &os,
|
||||
size_t len) {
|
||||
print_with_escapes(reinterpret_cast<const unsigned char *>(src), os, len);
|
||||
}
|
||||
} // namespace simdjson
|
||||
|
||||
#
|
||||
#endif
|
||||
@@ -1,32 +1,27 @@
|
||||
#ifndef SIMDJSON_JSONIOUTIL_H
|
||||
#define SIMDJSON_JSONIOUTIL_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// load a file in memory...
|
||||
// get a corpus; pad out to cache line so we can always use SIMD
|
||||
// throws exceptions in case of failure
|
||||
// first element of the pair is a string (null terminated)
|
||||
// whereas the second element is the length.
|
||||
// caller is responsible to free (aligned_free((void*)result.data())))
|
||||
//
|
||||
// throws an exception if the file cannot be opened, use try/catch
|
||||
// try {
|
||||
// p = get_corpus(filename);
|
||||
// } catch (const std::exception& e) {
|
||||
// aligned_free((void*)p.data());
|
||||
// std::cout << "Could not load the file " << filename << std::endl;
|
||||
// }
|
||||
padded_string get_corpus(const std::string &filename);
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
[[deprecated("Use padded_string::load() instead")]]
|
||||
inline padded_string get_corpus(const char *path) {
|
||||
return padded_string::load(path);
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
#endif // SIMDJSON_JSONIOUTIL_H
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef SIMDJSON_JSONMINIFIER_H
|
||||
#define SIMDJSON_JSONMINIFIER_H
|
||||
|
||||
#include "simdjson/padded_string.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Take input from buf and remove useless whitespace, write it to out; buf and
|
||||
// out can be the same pointer. Result is null terminated,
|
||||
// return the string length (minus the null termination).
|
||||
// The accelerated version of this function only runs on AVX2 hardware.
|
||||
size_t json_minify(const uint8_t *buf, size_t len, uint8_t *out);
|
||||
|
||||
static inline size_t json_minify(const char *buf, size_t len, char *out) {
|
||||
return json_minify(reinterpret_cast<const uint8_t *>(buf), len,
|
||||
reinterpret_cast<uint8_t *>(out));
|
||||
}
|
||||
|
||||
static inline size_t json_minify(const std::string_view &p, char *out) {
|
||||
return json_minify(p.data(), p.size(), out);
|
||||
}
|
||||
|
||||
static inline size_t json_minify(const padded_string &p, char *out) {
|
||||
return json_minify(p.data(), p.size(), out);
|
||||
}
|
||||
} // namespace simdjson
|
||||
#endif
|
||||
+103
-253
@@ -1,267 +1,117 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_JSONPARSER_H
|
||||
#define SIMDJSON_JSONPARSER_H
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
|
||||
#include "simdjson/document.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/simdjson.h"
|
||||
#include "simdjson/stage1_find_marks.h"
|
||||
#include "simdjson/stage2_build_tape.h"
|
||||
#include <string>
|
||||
#ifdef _MSC_VER
|
||||
#include <windows.h>
|
||||
// must be included after windows.h
|
||||
#include <sysinfoapi.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "simdjson/jsonioutil.h"
|
||||
|
||||
namespace simdjson {
|
||||
// The function that users are expected to call is json_parse.
|
||||
// We have more than one such function because we want to support several
|
||||
// instruction sets.
|
||||
|
||||
// function pointer type for json_parse
|
||||
using json_parse_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj,
|
||||
bool realloc_if_needed);
|
||||
//
|
||||
// C API (json_parse and build_parsed_json) declarations
|
||||
//
|
||||
|
||||
// Pointer that holds the json_parse implementation corresponding to the
|
||||
// available SIMD instruction set
|
||||
extern json_parse_functype *json_parse_ptr;
|
||||
|
||||
// json_parse_implementation is the generic function, it is specialized for
|
||||
// various architectures, e.g., as
|
||||
// json_parse_implementation<Architecture::HASWELL> or
|
||||
// json_parse_implementation<Architecture::ARM64>
|
||||
template <Architecture T>
|
||||
int json_parse_implementation(const uint8_t *buf, size_t len, ParsedJson &pj,
|
||||
bool realloc_if_needed = true) {
|
||||
if (pj.byte_capacity < len) {
|
||||
return simdjson::CAPACITY;
|
||||
}
|
||||
bool reallocated = false;
|
||||
if (realloc_if_needed) {
|
||||
#if ALLOW_SAME_PAGE_BUFFER_OVERRUN
|
||||
// realloc is needed if the end of the memory crosses a page
|
||||
#ifdef _MSC_VER
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetSystemInfo(&sysInfo);
|
||||
long page_size = sysInfo.dwPageSize;
|
||||
#else
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
//////////////
|
||||
// We want to check that buf + len - 1 and buf + len - 1 + SIMDJSON_PADDING
|
||||
// are in the same page.
|
||||
// That is, we want to check that
|
||||
// (buf + len - 1) / page_size == (buf + len - 1 + SIMDJSON_PADDING) /
|
||||
// page_size That's true if (buf + len - 1) % page_size + SIMDJSON_PADDING <
|
||||
// page_size.
|
||||
///////////
|
||||
if ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size) +
|
||||
SIMDJSON_PADDING <
|
||||
static_cast<uintptr_t>(page_size)) {
|
||||
#else // SIMDJSON_SAFE_SAME_PAGE_READ_OVERRUN
|
||||
if (true) { // if not SIMDJSON_SAFE_SAME_PAGE_READ_OVERRUN, we always
|
||||
// reallocate
|
||||
#endif
|
||||
const uint8_t *tmp_buf = buf;
|
||||
buf = (uint8_t *)allocate_padded_buffer(len);
|
||||
if (buf == NULL)
|
||||
return simdjson::MEMALLOC;
|
||||
memcpy((void *)buf, tmp_buf, len);
|
||||
reallocated = true;
|
||||
} // if (true) OR if ( (reinterpret_cast<uintptr_t>(buf + len - 1) %
|
||||
// page_size ) + SIMDJSON_PADDING < static_cast<uintptr_t>(page_size) ) {
|
||||
} // if(realloc_if_needed) {
|
||||
int stage1_is_ok = simdjson::find_structural_bits<T>(buf, len, pj);
|
||||
if (stage1_is_ok != simdjson::SUCCESS) {
|
||||
pj.error_code = stage1_is_ok;
|
||||
return pj.error_code;
|
||||
}
|
||||
int res = unified_machine<T>(buf, len, pj);
|
||||
if (reallocated) {
|
||||
aligned_free((void *)buf);
|
||||
}
|
||||
return res;
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const uint8_t *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const char *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const std::string &s, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const padded_string &s, dom::parser &parser) noexcept {
|
||||
error_code code = parser.parse(s).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
|
||||
// Parse a document found in buf.
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)).
|
||||
//
|
||||
// The function returns simdjson::SUCCESS (an integer = 0) in case of a success
|
||||
// or an error code from simdjson/simdjson.h in case of failure such as
|
||||
// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
// the simdjson::error_message function converts these error codes into a
|
||||
// string).
|
||||
//
|
||||
// You can also check validity by calling pj.is_valid(). The same ParsedJson can
|
||||
// be reused for other documents.
|
||||
//
|
||||
// If realloc_if_needed is true (default) then a temporary buffer is created
|
||||
// when needed during processing (a copy of the input string is made). The input
|
||||
// buf should be readable up to buf + len + SIMDJSON_PADDING if
|
||||
// realloc_if_needed is false, all bytes at and after buf + len are ignored
|
||||
// (can be garbage). The ParsedJson object can be reused.
|
||||
|
||||
inline int json_parse(const uint8_t *buf, size_t len, ParsedJson &pj,
|
||||
bool realloc_if_needed = true) {
|
||||
return json_parse_ptr(buf, len, pj, realloc_if_needed);
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const char *buf, size_t len, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const std::string &s, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const padded_string &s) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(s).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
|
||||
// Parse a document found in buf.
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)).
|
||||
//
|
||||
// The function returns simdjson::SUCCESS (an integer = 0) in case of a success
|
||||
// or an error code from simdjson/simdjson.h in case of failure such as
|
||||
// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
// the simdjson::error_message function converts these error codes into a
|
||||
// string).
|
||||
//
|
||||
// You can also check validity
|
||||
// by calling pj.is_valid(). The same ParsedJson can be reused for other
|
||||
// documents.
|
||||
//
|
||||
// If realloc_if_needed is true (default) then a temporary buffer is created
|
||||
// when needed during processing (a copy of the input string is made). The input
|
||||
// buf should be readable up to buf + len + SIMDJSON_PADDING if
|
||||
// realloc_if_needed is false, all bytes at and after buf + len are ignored
|
||||
// (can be garbage). The ParsedJson object can be reused.
|
||||
inline int json_parse(const char *buf, size_t len, ParsedJson &pj,
|
||||
bool realloc_if_needed = true) {
|
||||
return json_parse_ptr(reinterpret_cast<const uint8_t *>(buf), len, pj,
|
||||
realloc_if_needed);
|
||||
}
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
int json_parse(const char *buf, dom::parser &parser) noexcept = delete;
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
dom::parser build_parsed_json(const char *buf) noexcept = delete;
|
||||
|
||||
// We do not want to allow implicit conversion from C string to std::string.
|
||||
int json_parse(const char *buf, ParsedJson &pj) = delete;
|
||||
|
||||
// Parse a document found in in string s.
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)).
|
||||
//
|
||||
// The function returns simdjson::SUCCESS (an integer = 0) in case of a success
|
||||
// or an error code from simdjson/simdjson.h in case of failure such as
|
||||
// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
// the simdjson::error_message function converts these error codes into a
|
||||
// string).
|
||||
//
|
||||
// A temporary buffer is created when needed during processing
|
||||
// (a copy of the input string is made).
|
||||
inline int json_parse(const std::string &s, ParsedJson &pj) {
|
||||
return json_parse(s.data(), s.length(), pj, true);
|
||||
}
|
||||
|
||||
// Parse a document found in in string s.
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)).
|
||||
//
|
||||
// The function returns simdjson::SUCCESS (an integer = 0) in case of a success
|
||||
// or an error code from simdjson/simdjson.h in case of failure such as
|
||||
// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
// the simdjson::error_message function converts these error codes into a
|
||||
// string).
|
||||
//
|
||||
// You can also check validity
|
||||
// by calling pj.is_valid(). The same ParsedJson can be reused for other
|
||||
// documents.
|
||||
inline int json_parse(const padded_string &s, ParsedJson &pj) {
|
||||
return json_parse(s.data(), s.length(), pj, false);
|
||||
}
|
||||
|
||||
// Build a ParsedJson object. You can check validity
|
||||
// by calling pj.is_valid(). This does the memory allocation needed for
|
||||
// ParsedJson. If realloc_if_needed is true (default) then a temporary buffer is
|
||||
// created when needed during processing (a copy of the input string is made).
|
||||
//
|
||||
// The input buf should be readable up to buf + len + SIMDJSON_PADDING if
|
||||
// realloc_if_needed is false, all bytes at and after buf + len are ignored
|
||||
// (can be garbage).
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// This is a convenience function which calls json_parse.
|
||||
WARN_UNUSED
|
||||
ParsedJson build_parsed_json(const uint8_t *buf, size_t len,
|
||||
bool realloc_if_needed = true);
|
||||
|
||||
WARN_UNUSED
|
||||
// Build a ParsedJson object. You can check validity
|
||||
// by calling pj.is_valid(). This does the memory allocation needed for
|
||||
// ParsedJson. If realloc_if_needed is true (default) then a temporary buffer is
|
||||
// created when needed during processing (a copy of the input string is made).
|
||||
//
|
||||
// The input buf should be readable up to buf + len + SIMDJSON_PADDING if
|
||||
// realloc_if_needed is false, all bytes at and after buf + len are ignored
|
||||
// (can be garbage).
|
||||
//
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// This is a convenience function which calls json_parse.
|
||||
inline ParsedJson build_parsed_json(const char *buf, size_t len,
|
||||
bool realloc_if_needed = true) {
|
||||
return build_parsed_json(reinterpret_cast<const uint8_t *>(buf), len,
|
||||
realloc_if_needed);
|
||||
}
|
||||
|
||||
// We do not want to allow implicit conversion from C string to std::string.
|
||||
ParsedJson build_parsed_json(const char *buf) = delete;
|
||||
|
||||
// Parse a document found in in string s.
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)). Return SUCCESS (an integer = 0) in case of a
|
||||
// success. You can also check validity by calling pj.is_valid(). The same
|
||||
// ParsedJson can be reused for other documents.
|
||||
//
|
||||
// A temporary buffer is created when needed during processing
|
||||
// (a copy of the input string is made).
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// This is a convenience function which calls json_parse.
|
||||
WARN_UNUSED
|
||||
inline ParsedJson build_parsed_json(const std::string &s) {
|
||||
return build_parsed_json(s.data(), s.length(), true);
|
||||
}
|
||||
|
||||
// Parse a document found in in string s.
|
||||
// You need to preallocate ParsedJson with a capacity of len (e.g.,
|
||||
// pj.allocate_capacity(len)). Return SUCCESS (an integer = 0) in case of a
|
||||
// success. You can also check validity by calling pj.is_valid(). The same
|
||||
// ParsedJson can be reused for other documents.
|
||||
//
|
||||
// The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
// discouraged.
|
||||
//
|
||||
// This is a convenience function which calls json_parse.
|
||||
WARN_UNUSED
|
||||
inline ParsedJson build_parsed_json(const padded_string &s) {
|
||||
return build_parsed_json(s.data(), s.length(), false);
|
||||
}
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,587 +0,0 @@
|
||||
#ifndef SIMDJSON_NUMBERPARSING_H
|
||||
#define SIMDJSON_NUMBERPARSING_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/jsoncharutils.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
void found_invalid_number(const uint8_t *buf);
|
||||
void found_integer(int64_t result, const uint8_t *buf);
|
||||
void found_float(double result, const uint8_t *buf);
|
||||
#endif
|
||||
|
||||
namespace simdjson {
|
||||
// Allowable floating-point values range from
|
||||
// std::numeric_limits<double>::lowest() to std::numeric_limits<double>::max(),
|
||||
// so from -1.7976e308 all the way to 1.7975e308 in binary64. The lowest
|
||||
// non-zero normal values is std::numeric_limits<double>::min() or
|
||||
// about 2.225074e-308.
|
||||
static const double power_of_ten[] = {
|
||||
1e-308, 1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300,
|
||||
1e-299, 1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291,
|
||||
1e-290, 1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282,
|
||||
1e-281, 1e-280, 1e-279, 1e-278, 1e-277, 1e-276, 1e-275, 1e-274, 1e-273,
|
||||
1e-272, 1e-271, 1e-270, 1e-269, 1e-268, 1e-267, 1e-266, 1e-265, 1e-264,
|
||||
1e-263, 1e-262, 1e-261, 1e-260, 1e-259, 1e-258, 1e-257, 1e-256, 1e-255,
|
||||
1e-254, 1e-253, 1e-252, 1e-251, 1e-250, 1e-249, 1e-248, 1e-247, 1e-246,
|
||||
1e-245, 1e-244, 1e-243, 1e-242, 1e-241, 1e-240, 1e-239, 1e-238, 1e-237,
|
||||
1e-236, 1e-235, 1e-234, 1e-233, 1e-232, 1e-231, 1e-230, 1e-229, 1e-228,
|
||||
1e-227, 1e-226, 1e-225, 1e-224, 1e-223, 1e-222, 1e-221, 1e-220, 1e-219,
|
||||
1e-218, 1e-217, 1e-216, 1e-215, 1e-214, 1e-213, 1e-212, 1e-211, 1e-210,
|
||||
1e-209, 1e-208, 1e-207, 1e-206, 1e-205, 1e-204, 1e-203, 1e-202, 1e-201,
|
||||
1e-200, 1e-199, 1e-198, 1e-197, 1e-196, 1e-195, 1e-194, 1e-193, 1e-192,
|
||||
1e-191, 1e-190, 1e-189, 1e-188, 1e-187, 1e-186, 1e-185, 1e-184, 1e-183,
|
||||
1e-182, 1e-181, 1e-180, 1e-179, 1e-178, 1e-177, 1e-176, 1e-175, 1e-174,
|
||||
1e-173, 1e-172, 1e-171, 1e-170, 1e-169, 1e-168, 1e-167, 1e-166, 1e-165,
|
||||
1e-164, 1e-163, 1e-162, 1e-161, 1e-160, 1e-159, 1e-158, 1e-157, 1e-156,
|
||||
1e-155, 1e-154, 1e-153, 1e-152, 1e-151, 1e-150, 1e-149, 1e-148, 1e-147,
|
||||
1e-146, 1e-145, 1e-144, 1e-143, 1e-142, 1e-141, 1e-140, 1e-139, 1e-138,
|
||||
1e-137, 1e-136, 1e-135, 1e-134, 1e-133, 1e-132, 1e-131, 1e-130, 1e-129,
|
||||
1e-128, 1e-127, 1e-126, 1e-125, 1e-124, 1e-123, 1e-122, 1e-121, 1e-120,
|
||||
1e-119, 1e-118, 1e-117, 1e-116, 1e-115, 1e-114, 1e-113, 1e-112, 1e-111,
|
||||
1e-110, 1e-109, 1e-108, 1e-107, 1e-106, 1e-105, 1e-104, 1e-103, 1e-102,
|
||||
1e-101, 1e-100, 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93,
|
||||
1e-92, 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84,
|
||||
1e-83, 1e-82, 1e-81, 1e-80, 1e-79, 1e-78, 1e-77, 1e-76, 1e-75,
|
||||
1e-74, 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66,
|
||||
1e-65, 1e-64, 1e-63, 1e-62, 1e-61, 1e-60, 1e-59, 1e-58, 1e-57,
|
||||
1e-56, 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48,
|
||||
1e-47, 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, 1e-39,
|
||||
1e-38, 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30,
|
||||
1e-29, 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21,
|
||||
1e-20, 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12,
|
||||
1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3,
|
||||
1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6,
|
||||
1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
|
||||
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24,
|
||||
1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33,
|
||||
1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40, 1e41, 1e42,
|
||||
1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50, 1e51,
|
||||
1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, 1e60,
|
||||
1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
|
||||
1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78,
|
||||
1e79, 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87,
|
||||
1e88, 1e89, 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96,
|
||||
1e97, 1e98, 1e99, 1e100, 1e101, 1e102, 1e103, 1e104, 1e105,
|
||||
1e106, 1e107, 1e108, 1e109, 1e110, 1e111, 1e112, 1e113, 1e114,
|
||||
1e115, 1e116, 1e117, 1e118, 1e119, 1e120, 1e121, 1e122, 1e123,
|
||||
1e124, 1e125, 1e126, 1e127, 1e128, 1e129, 1e130, 1e131, 1e132,
|
||||
1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 1e140, 1e141,
|
||||
1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 1e150,
|
||||
1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
|
||||
1e160, 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168,
|
||||
1e169, 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177,
|
||||
1e178, 1e179, 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186,
|
||||
1e187, 1e188, 1e189, 1e190, 1e191, 1e192, 1e193, 1e194, 1e195,
|
||||
1e196, 1e197, 1e198, 1e199, 1e200, 1e201, 1e202, 1e203, 1e204,
|
||||
1e205, 1e206, 1e207, 1e208, 1e209, 1e210, 1e211, 1e212, 1e213,
|
||||
1e214, 1e215, 1e216, 1e217, 1e218, 1e219, 1e220, 1e221, 1e222,
|
||||
1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229, 1e230, 1e231,
|
||||
1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239, 1e240,
|
||||
1e241, 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249,
|
||||
1e250, 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258,
|
||||
1e259, 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267,
|
||||
1e268, 1e269, 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276,
|
||||
1e277, 1e278, 1e279, 1e280, 1e281, 1e282, 1e283, 1e284, 1e285,
|
||||
1e286, 1e287, 1e288, 1e289, 1e290, 1e291, 1e292, 1e293, 1e294,
|
||||
1e295, 1e296, 1e297, 1e298, 1e299, 1e300, 1e301, 1e302, 1e303,
|
||||
1e304, 1e305, 1e306, 1e307, 1e308};
|
||||
|
||||
static inline bool is_integer(char c) {
|
||||
return (c >= '0' && c <= '9');
|
||||
// this gets compiled to (uint8_t)(c - '0') <= 9 on all decent compilers
|
||||
}
|
||||
|
||||
// We need to check that the character following a zero is valid. This is
|
||||
// probably frequent and it is hard than it looks. We are building all of this
|
||||
// just to differentiate between 0x1 (invalid), 0,1 (valid) 0e1 (valid)...
|
||||
const bool structural_or_whitespace_or_exponent_or_decimal_negated[256] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
|
||||
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
really_inline bool
|
||||
is_not_structural_or_whitespace_or_exponent_or_decimal(unsigned char c) {
|
||||
return structural_or_whitespace_or_exponent_or_decimal_negated[c];
|
||||
}
|
||||
} // namespace simdjson
|
||||
#ifndef SIMDJSON_DISABLE_SWAR_NUMBER_PARSING
|
||||
#define SWAR_NUMBER_PARSING
|
||||
#endif
|
||||
|
||||
#ifdef SWAR_NUMBER_PARSING
|
||||
|
||||
namespace simdjson {
|
||||
// check quickly whether the next 8 chars are made of digits
|
||||
// at a glance, it looks better than Mula's
|
||||
// http://0x80.pl/articles/swar-digits-validate.html
|
||||
static inline bool is_made_of_eight_digits_fast(const char *chars) {
|
||||
uint64_t val;
|
||||
// this can read up to 7 bytes beyond the buffer size, but we require
|
||||
// SIMDJSON_PADDING of padding
|
||||
static_assert(7 <= SIMDJSON_PADDING);
|
||||
memcpy(&val, chars, 8);
|
||||
// a branchy method might be faster:
|
||||
// return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030)
|
||||
// && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) ==
|
||||
// 0x3030303030303030);
|
||||
return (((val & 0xF0F0F0F0F0F0F0F0) |
|
||||
(((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) ==
|
||||
0x3333333333333333);
|
||||
}
|
||||
} // namespace simdjson
|
||||
#ifdef IS_X86_64
|
||||
TARGET_WESTMERE
|
||||
namespace simdjson {
|
||||
static inline uint32_t parse_eight_digits_unrolled(const char *chars) {
|
||||
// this actually computes *16* values so we are being wasteful.
|
||||
const __m128i ascii0 = _mm_set1_epi8('0');
|
||||
const __m128i mul_1_10 =
|
||||
_mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1);
|
||||
const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1);
|
||||
const __m128i mul_1_10000 =
|
||||
_mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1);
|
||||
const __m128i input = _mm_sub_epi8(
|
||||
_mm_loadu_si128(reinterpret_cast<const __m128i *>(chars)), ascii0);
|
||||
const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10);
|
||||
const __m128i t2 = _mm_madd_epi16(t1, mul_1_100);
|
||||
const __m128i t3 = _mm_packus_epi32(t2, t2);
|
||||
const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000);
|
||||
return _mm_cvtsi128_si32(
|
||||
t4); // only captures the sum of the first 8 digits, drop the rest
|
||||
}
|
||||
} // namespace simdjson
|
||||
UNTARGET_REGION
|
||||
#endif
|
||||
|
||||
namespace simdjson {
|
||||
#ifdef IS_ARM64
|
||||
// we don't have SSE, so let us use a scalar function
|
||||
// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
|
||||
static inline uint32_t parse_eight_digits_unrolled(const char *chars) {
|
||||
uint64_t val;
|
||||
memcpy(&val, chars, sizeof(uint64_t));
|
||||
val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8;
|
||||
val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16;
|
||||
return (val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
// This function computes base * 10 ^ (- negative_exponent ).
|
||||
// It is only even going to be used when negative_exponent is tiny.
|
||||
static double subnormal_power10(double base, int negative_exponent) {
|
||||
// this is probably not going to be fast
|
||||
return base * 1e-308 * pow(10, negative_exponent + 308);
|
||||
}
|
||||
|
||||
// called by parse_number when we know that the output is a float,
|
||||
// but where there might be some integer overflow. The trick here is to
|
||||
// parse using floats from the start.
|
||||
// Do not call this function directly as it skips some of the checks from
|
||||
// parse_number
|
||||
//
|
||||
// This function will almost never be called!!!
|
||||
//
|
||||
// Note: a redesign could avoid this function entirely.
|
||||
//
|
||||
static never_inline bool parse_float(const uint8_t *const buf, ParsedJson &pj,
|
||||
const uint32_t offset, bool found_minus) {
|
||||
const char *p = reinterpret_cast<const char *>(buf + offset);
|
||||
bool negative = false;
|
||||
if (found_minus) {
|
||||
++p;
|
||||
negative = true;
|
||||
}
|
||||
long double i;
|
||||
if (*p == '0') { // 0 cannot be followed by an integer
|
||||
++p;
|
||||
i = 0;
|
||||
} else {
|
||||
unsigned char digit = *p - '0';
|
||||
i = digit;
|
||||
p++;
|
||||
while (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
i = 10 * i + digit;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
if ('.' == *p) {
|
||||
++p;
|
||||
int fractional_weight = 308;
|
||||
if (is_integer(*p)) {
|
||||
unsigned char digit = *p - '0';
|
||||
++p;
|
||||
|
||||
fractional_weight--;
|
||||
i = i + digit * (fractional_weight >= 0 ? power_of_ten[fractional_weight]
|
||||
: 0);
|
||||
} else {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
while (is_integer(*p)) {
|
||||
unsigned char digit = *p - '0';
|
||||
++p;
|
||||
fractional_weight--;
|
||||
i = i + digit * (fractional_weight >= 0 ? power_of_ten[fractional_weight]
|
||||
: 0);
|
||||
}
|
||||
}
|
||||
if (('e' == *p) || ('E' == *p)) {
|
||||
++p;
|
||||
bool neg_exp = false;
|
||||
if ('-' == *p) {
|
||||
neg_exp = true;
|
||||
++p;
|
||||
} else if ('+' == *p) {
|
||||
++p;
|
||||
}
|
||||
if (!is_integer(*p)) {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
unsigned char digit = *p - '0';
|
||||
int64_t exp_number = digit; // exponential part
|
||||
p++;
|
||||
if (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
if (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
if (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
while (is_integer(*p)) {
|
||||
if (exp_number > 0x100000000) { // we need to check for overflows
|
||||
// we refuse to parse this
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
if (unlikely(exp_number > 308)) {
|
||||
// this path is unlikely
|
||||
if (neg_exp) {
|
||||
// We either have zero or a subnormal.
|
||||
// We expect this to be uncommon so we go through a slow path.
|
||||
i = subnormal_power10(i, -exp_number);
|
||||
} else {
|
||||
// We know for sure that we have a number that is too large,
|
||||
// we refuse to parse this
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int exponent = (neg_exp ? -exp_number : exp_number);
|
||||
// we have that exp_number is [0,308] so that
|
||||
// exponent is [-308,308] so that
|
||||
// 308 + exponent is in [0, 2 * 308]
|
||||
i *= power_of_ten[308 + exponent];
|
||||
}
|
||||
}
|
||||
if (is_not_structural_or_whitespace(*p)) {
|
||||
return false;
|
||||
}
|
||||
double d = negative ? -i : i;
|
||||
pj.write_tape_double(d);
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_float(d, buf + offset);
|
||||
#endif
|
||||
return is_structural_or_whitespace(*p);
|
||||
}
|
||||
|
||||
// called by parse_number when we know that the output is an integer,
|
||||
// but where there might be some integer overflow.
|
||||
// we want to catch overflows!
|
||||
// Do not call this function directly as it skips some of the checks from
|
||||
// parse_number
|
||||
//
|
||||
// This function will almost never be called!!!
|
||||
//
|
||||
static never_inline bool parse_large_integer(const uint8_t *const buf,
|
||||
ParsedJson &pj,
|
||||
const uint32_t offset,
|
||||
bool found_minus) {
|
||||
const char *p = reinterpret_cast<const char *>(buf + offset);
|
||||
|
||||
bool negative = false;
|
||||
if (found_minus) {
|
||||
++p;
|
||||
negative = true;
|
||||
}
|
||||
uint64_t i;
|
||||
if (*p == '0') { // 0 cannot be followed by an integer
|
||||
++p;
|
||||
i = 0;
|
||||
} else {
|
||||
unsigned char digit = *p - '0';
|
||||
i = digit;
|
||||
p++;
|
||||
// the is_made_of_eight_digits_fast routine is unlikely to help here because
|
||||
// we rarely see large integer parts like 123456789
|
||||
while (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
if (mul_overflow(i, 10, &i)) {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false; // overflow
|
||||
}
|
||||
if (add_overflow(i, digit, &i)) {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false; // overflow
|
||||
}
|
||||
++p;
|
||||
}
|
||||
}
|
||||
if (negative) {
|
||||
if (i > 0x8000000000000000) {
|
||||
// overflows!
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false; // overflow
|
||||
}
|
||||
} else {
|
||||
if (i >= 0x8000000000000000) {
|
||||
// overflows!
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false; // overflow
|
||||
}
|
||||
}
|
||||
int64_t signed_answer =
|
||||
negative ? -static_cast<int64_t>(i) : static_cast<int64_t>(i);
|
||||
pj.write_tape_s64(signed_answer);
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_integer(signed_answer, buf + offset);
|
||||
#endif
|
||||
return is_structural_or_whitespace(*p);
|
||||
}
|
||||
|
||||
// parse the number at buf + offset
|
||||
// define JSON_TEST_NUMBERS for unit testing
|
||||
//
|
||||
// It is assumed that the number is followed by a structural ({,},],[) character
|
||||
// or a white space character. If that is not the case (e.g., when the JSON
|
||||
// document is made of a single number), then it is necessary to copy the
|
||||
// content and append a space before calling this function.
|
||||
//
|
||||
// Our objective is accurate parsing (ULP of 0 or 1) at high speed.
|
||||
static really_inline bool parse_number(const uint8_t *const buf, ParsedJson &pj,
|
||||
const uint32_t offset,
|
||||
bool found_minus) {
|
||||
#ifdef SIMDJSON_SKIPNUMBERPARSING // for performance analysis, it is sometimes
|
||||
// useful to skip parsing
|
||||
pj.write_tape_s64(0); // always write zero
|
||||
return true; // always succeeds
|
||||
#else
|
||||
const char *p = reinterpret_cast<const char *>(buf + offset);
|
||||
bool negative = false;
|
||||
if (found_minus) {
|
||||
++p;
|
||||
negative = true;
|
||||
if (!is_integer(*p)) { // a negative sign must be followed by an integer
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const char *const start_digits = p;
|
||||
|
||||
uint64_t i; // an unsigned int avoids signed overflows (which are bad)
|
||||
if (*p == '0') { // 0 cannot be followed by an integer
|
||||
++p;
|
||||
if (is_not_structural_or_whitespace_or_exponent_or_decimal(*p)) {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
i = 0;
|
||||
} else {
|
||||
if (!(is_integer(*p))) { // must start with an integer
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
unsigned char digit = *p - '0';
|
||||
i = digit;
|
||||
p++;
|
||||
// the is_made_of_eight_digits_fast routine is unlikely to help here because
|
||||
// we rarely see large integer parts like 123456789
|
||||
while (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
// a multiplication by 10 is cheaper than an arbitrary integer
|
||||
// multiplication
|
||||
i = 10 * i + digit; // might overflow, we will handle the overflow later
|
||||
++p;
|
||||
}
|
||||
}
|
||||
int64_t exponent = 0;
|
||||
bool is_float = false;
|
||||
if ('.' == *p) {
|
||||
is_float = true; // At this point we know that we have a float
|
||||
// we continue with the fiction that we have an integer. If the
|
||||
// floating point number is representable as x * 10^z for some integer
|
||||
// z that fits in 53 bits, then we will be able to convert back the
|
||||
// the integer into a float in a lossless manner.
|
||||
++p;
|
||||
const char *const first_after_period = p;
|
||||
if (is_integer(*p)) {
|
||||
unsigned char digit = *p - '0';
|
||||
++p;
|
||||
i = i * 10 + digit; // might overflow + multiplication by 10 is likely
|
||||
// cheaper than arbitrary mult.
|
||||
// we will handle the overflow later
|
||||
} else {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#ifdef SWAR_NUMBER_PARSING
|
||||
// this helps if we have lots of decimals!
|
||||
// this turns out to be frequent enough.
|
||||
if (is_made_of_eight_digits_fast(p)) {
|
||||
i = i * 100000000 + parse_eight_digits_unrolled(p);
|
||||
p += 8;
|
||||
}
|
||||
#endif
|
||||
while (is_integer(*p)) {
|
||||
unsigned char digit = *p - '0';
|
||||
++p;
|
||||
i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
|
||||
// because we have parse_highprecision_float later.
|
||||
}
|
||||
exponent = first_after_period - p;
|
||||
}
|
||||
int digit_count =
|
||||
p - start_digits - 1; // used later to guard against overflows
|
||||
int64_t exp_number = 0; // exponential part
|
||||
if (('e' == *p) || ('E' == *p)) {
|
||||
is_float = true;
|
||||
++p;
|
||||
bool neg_exp = false;
|
||||
if ('-' == *p) {
|
||||
neg_exp = true;
|
||||
++p;
|
||||
} else if ('+' == *p) {
|
||||
++p;
|
||||
}
|
||||
if (!is_integer(*p)) {
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
unsigned char digit = *p - '0';
|
||||
exp_number = digit;
|
||||
p++;
|
||||
if (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
if (is_integer(*p)) {
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
while (is_integer(*p)) {
|
||||
if (exp_number > 0x100000000) { // we need to check for overflows
|
||||
// we refuse to parse this
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_invalid_number(buf + offset);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
digit = *p - '0';
|
||||
exp_number = 10 * exp_number + digit;
|
||||
++p;
|
||||
}
|
||||
exponent += (neg_exp ? -exp_number : exp_number);
|
||||
}
|
||||
if (is_float) {
|
||||
uint64_t power_index = 308 + exponent;
|
||||
if (unlikely((digit_count >= 19))) { // this is uncommon
|
||||
// It is possible that the integer had an overflow.
|
||||
// We have to handle the case where we have 0.0000somenumber.
|
||||
const char *start = start_digits;
|
||||
while ((*start == '0') || (*start == '.')) {
|
||||
start++;
|
||||
}
|
||||
// we over-decrement by one when there is a '.'
|
||||
digit_count -= (start - start_digits);
|
||||
if (digit_count >= 19) {
|
||||
// Ok, chances are good that we had an overflow!
|
||||
// this is almost never going to get called!!!
|
||||
// we start anew, going slowly!!!
|
||||
return parse_float(buf, pj, offset, found_minus);
|
||||
}
|
||||
}
|
||||
if (unlikely((power_index > 2 * 308))) { // this is uncommon!!!
|
||||
// this is almost never going to get called!!!
|
||||
// we start anew, going slowly!!!
|
||||
return parse_float(buf, pj, offset, found_minus);
|
||||
}
|
||||
double factor = power_of_ten[power_index];
|
||||
factor = negative ? -factor : factor;
|
||||
double d = i * factor;
|
||||
pj.write_tape_double(d);
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_float(d, buf + offset);
|
||||
#endif
|
||||
} else {
|
||||
if (unlikely(digit_count >= 18)) { // this is uncommon!!!
|
||||
// there is a good chance that we had an overflow, so we need
|
||||
// need to recover: we parse the whole thing again.
|
||||
return parse_large_integer(buf, pj, offset, found_minus);
|
||||
}
|
||||
i = negative ? 0 - i : i;
|
||||
pj.write_tape_s64(i);
|
||||
#ifdef JSON_TEST_NUMBERS // for unit testing
|
||||
found_integer(i, buf + offset);
|
||||
#endif
|
||||
}
|
||||
return is_structural_or_whitespace(*p);
|
||||
#endif // SIMDJSON_SKIPNUMBERPARSING
|
||||
}
|
||||
} // simdjson
|
||||
#endif
|
||||
@@ -1,70 +1,130 @@
|
||||
#ifndef SIMDJSON_PADDING_STRING_H
|
||||
#define SIMDJSON_PADDING_STRING_H
|
||||
#ifndef SIMDJSON_PADDED_STRING_H
|
||||
#define SIMDJSON_PADDED_STRING_H
|
||||
#include "simdjson/portability.h"
|
||||
#include "simdjson/common_defs.h" // for SIMDJSON_PADDING
|
||||
#include "simdjson/error.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace simdjson {
|
||||
// low-level function to allocate memory with padding so we can read passed the
|
||||
// "length" bytes safely. if you must provide a pointer to some data, create it
|
||||
// with this function: length is the max. size in bytes of the string caller is
|
||||
// responsible to free the memory (free(...))
|
||||
char *allocate_padded_buffer(size_t length);
|
||||
|
||||
// Simple string with padded allocation.
|
||||
// We deliberately forbid copies, users should rely on swap or move
|
||||
// constructors.
|
||||
class padded_string {
|
||||
public:
|
||||
explicit padded_string() noexcept : viable_size(0), data_ptr(nullptr) {}
|
||||
explicit padded_string(size_t length) noexcept
|
||||
: viable_size(length), data_ptr(allocate_padded_buffer(length)) {
|
||||
/**
|
||||
* String with extra allocation for ease of use with parser::parse()
|
||||
*
|
||||
* This is a move-only class, it cannot be copied.
|
||||
*/
|
||||
struct padded_string final {
|
||||
|
||||
if (data_ptr != nullptr)
|
||||
data_ptr[length] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
explicit padded_string(char *data, size_t length) noexcept
|
||||
: viable_size(length), data_ptr(allocate_padded_buffer(length)) {
|
||||
if (data_ptr != nullptr) {
|
||||
memcpy(data_ptr, data, length);
|
||||
data_ptr[length] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
}
|
||||
padded_string(std::string s) noexcept
|
||||
: viable_size(s.size()), data_ptr(allocate_padded_buffer(s.size())) {
|
||||
if (data_ptr != nullptr) {
|
||||
memcpy(data_ptr, s.data(), s.size());
|
||||
data_ptr[s.size()] = '\0'; // easier when you need a c_str
|
||||
}
|
||||
}
|
||||
padded_string(padded_string &&o) noexcept
|
||||
: viable_size(o.viable_size), data_ptr(o.data_ptr) {
|
||||
o.data_ptr = nullptr; // we take ownership
|
||||
}
|
||||
void swap(padded_string &o) {
|
||||
size_t tmp_viable_size = viable_size;
|
||||
char *tmp_data_ptr = data_ptr;
|
||||
viable_size = o.viable_size;
|
||||
data_ptr = o.data_ptr;
|
||||
o.data_ptr = tmp_data_ptr;
|
||||
o.viable_size = tmp_viable_size;
|
||||
}
|
||||
/**
|
||||
* Create a new, empty padded string.
|
||||
*/
|
||||
explicit inline padded_string() noexcept;
|
||||
/**
|
||||
* Create a new padded string buffer.
|
||||
*
|
||||
* @param length the size of the string.
|
||||
*/
|
||||
explicit inline padded_string(size_t length) noexcept;
|
||||
/**
|
||||
* Create a new padded string by copying the given input.
|
||||
*
|
||||
* @param data the buffer to copy
|
||||
* @param length the number of bytes to copy
|
||||
*/
|
||||
explicit inline padded_string(const char *data, size_t length) noexcept;
|
||||
/**
|
||||
* Create a new padded string by copying the given input.
|
||||
*
|
||||
* @param str_ the string to copy
|
||||
*/
|
||||
inline padded_string(const std::string & str_ ) noexcept;
|
||||
/**
|
||||
* Create a new padded string by copying the given input.
|
||||
*
|
||||
* @param str_ the string to copy
|
||||
*/
|
||||
inline padded_string(std::string_view sv_) noexcept;
|
||||
/**
|
||||
* Move one padded string into another.
|
||||
*
|
||||
* The original padded string will be reduced to zero capacity.
|
||||
*
|
||||
* @param o the string to move.
|
||||
*/
|
||||
inline padded_string(padded_string &&o) noexcept;
|
||||
/**
|
||||
* Move one padded string into another.
|
||||
*
|
||||
* The original padded string will be reduced to zero capacity.
|
||||
*
|
||||
* @param o the string to move.
|
||||
*/
|
||||
inline padded_string &operator=(padded_string &&o) noexcept;
|
||||
inline void swap(padded_string &o) noexcept;
|
||||
~padded_string() noexcept;
|
||||
|
||||
~padded_string() { aligned_free_char(data_ptr); }
|
||||
/**
|
||||
* The length of the string.
|
||||
*
|
||||
* Does not include padding.
|
||||
*/
|
||||
size_t size() const noexcept;
|
||||
|
||||
size_t size() const { return viable_size; }
|
||||
/**
|
||||
* The length of the string.
|
||||
*
|
||||
* Does not include padding.
|
||||
*/
|
||||
size_t length() const noexcept;
|
||||
|
||||
size_t length() const { return viable_size; }
|
||||
/**
|
||||
* The string data.
|
||||
**/
|
||||
const char *data() const noexcept;
|
||||
|
||||
char *data() const { return data_ptr; }
|
||||
/**
|
||||
* The string data.
|
||||
**/
|
||||
char *data() noexcept;
|
||||
|
||||
/**
|
||||
* Create a std::string_view with the same content.
|
||||
*/
|
||||
operator std::string_view() const;
|
||||
|
||||
/**
|
||||
* Load this padded string from a file.
|
||||
*
|
||||
* @param path the path to the file.
|
||||
**/
|
||||
inline static simdjson_result<padded_string> load(const std::string &path) noexcept;
|
||||
|
||||
private:
|
||||
padded_string &operator=(const padded_string &o) = delete;
|
||||
padded_string(const padded_string &o) = delete;
|
||||
|
||||
size_t viable_size;
|
||||
char *data_ptr;
|
||||
};
|
||||
char *data_ptr{nullptr};
|
||||
|
||||
}; // padded_string
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
// This is deliberately outside of simdjson so that people get it without having to use the namespace
|
||||
inline simdjson::padded_string operator "" _padded(const char *str, size_t len) {
|
||||
return simdjson::padded_string(str, len);
|
||||
}
|
||||
|
||||
namespace simdjson::internal {
|
||||
|
||||
// low-level function to allocate memory with padding so we can read past the
|
||||
// "length" bytes safely. if you must provide a pointer to some data, create it
|
||||
// with this function: length is the max. size in bytes of the string caller is
|
||||
// responsible to free the memory (free(...))
|
||||
inline char *allocate_padded_buffer(size_t length) noexcept;
|
||||
|
||||
} // namespace simdjson::internal;
|
||||
|
||||
#endif // SIMDJSON_PADDED_STRING_H
|
||||
|
||||
@@ -1,544 +1,16 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_PARSEDJSON_H
|
||||
#define SIMDJSON_PARSEDJSON_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/jsonformatutils.h"
|
||||
#include "simdjson/portability.h"
|
||||
#include "simdjson/simdjson.h"
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
#define JSON_VALUE_MASK 0xFFFFFFFFFFFFFF
|
||||
|
||||
#define DEFAULT_MAX_DEPTH \
|
||||
1024 // a JSON document with a depth exceeding 1024 is probably de facto
|
||||
// invalid
|
||||
#include "simdjson/document.h"
|
||||
|
||||
namespace simdjson {
|
||||
/************
|
||||
* The JSON is parsed to a tape, see the accompanying tape.md file
|
||||
* for documentation.
|
||||
***********/
|
||||
class ParsedJson {
|
||||
public:
|
||||
// create a ParsedJson container with zero capacity, call allocate_capacity to
|
||||
// allocate memory
|
||||
ParsedJson();
|
||||
~ParsedJson();
|
||||
ParsedJson(ParsedJson &&p);
|
||||
|
||||
// if needed, allocate memory so that the object is able to process JSON
|
||||
// documents having up to len bytes and max_depth "depth"
|
||||
WARN_UNUSED
|
||||
bool allocate_capacity(size_t len, size_t max_depth = DEFAULT_MAX_DEPTH);
|
||||
/**
|
||||
* @deprecated Use `dom::parser` instead.
|
||||
*/
|
||||
using ParsedJson [[deprecated("Use dom::parser instead")]] = dom::parser;
|
||||
|
||||
// returns true if the document parsed was valid
|
||||
bool is_valid() const;
|
||||
|
||||
// return an error code corresponding to the last parsing attempt, see
|
||||
// simdjson.h will return simdjson::UNITIALIZED if no parsing was attempted
|
||||
int get_error_code() const;
|
||||
|
||||
// return the string equivalent of "get_error_code"
|
||||
std::string get_error_message() const;
|
||||
|
||||
// deallocate memory and set capacity to zero, called automatically by the
|
||||
// destructor
|
||||
void deallocate();
|
||||
|
||||
// this should be called when parsing (right before writing the tapes)
|
||||
void init();
|
||||
|
||||
// print the json to stdout (should be valid)
|
||||
// return false if the tape is likely wrong (e.g., you did not parse a valid
|
||||
// JSON).
|
||||
WARN_UNUSED
|
||||
bool print_json(std::ostream &os);
|
||||
WARN_UNUSED
|
||||
bool dump_raw_tape(std::ostream &os);
|
||||
|
||||
// all nodes are stored on the tape using a 64-bit word.
|
||||
//
|
||||
// strings, double and ints are stored as
|
||||
// a 64-bit word with a pointer to the actual value
|
||||
//
|
||||
//
|
||||
//
|
||||
// for objects or arrays, store [ or { at the beginning and } and ] at the
|
||||
// end. For the openings ([ or {), we annotate them with a reference to the
|
||||
// location on the tape of the end, and for then closings (} and ]), we
|
||||
// annotate them with a reference to the location of the opening
|
||||
//
|
||||
//
|
||||
|
||||
// this should be considered a private function
|
||||
really_inline void write_tape(uint64_t val, uint8_t c) {
|
||||
tape[current_loc++] = val | ((static_cast<uint64_t>(c)) << 56);
|
||||
}
|
||||
|
||||
really_inline void write_tape_s64(int64_t i) {
|
||||
write_tape(0, 'l');
|
||||
tape[current_loc++] = *(reinterpret_cast<uint64_t *>(&i));
|
||||
}
|
||||
|
||||
really_inline void write_tape_double(double d) {
|
||||
write_tape(0, 'd');
|
||||
static_assert(sizeof(d) == sizeof(tape[current_loc]), "mismatch size");
|
||||
memcpy(&tape[current_loc++], &d, sizeof(double));
|
||||
// tape[current_loc++] = *((uint64_t *)&d);
|
||||
}
|
||||
|
||||
really_inline uint32_t get_current_loc() { return current_loc; }
|
||||
|
||||
really_inline void annotate_previous_loc(uint32_t saved_loc, uint64_t val) {
|
||||
tape[saved_loc] |= val;
|
||||
}
|
||||
|
||||
class InvalidJSON : public std::exception {
|
||||
const char *what() const throw() { return "JSON document is invalid"; }
|
||||
};
|
||||
|
||||
class Iterator {
|
||||
// might throw InvalidJSON if ParsedJson is invalid
|
||||
public:
|
||||
explicit Iterator(ParsedJson &pj_);
|
||||
~Iterator();
|
||||
|
||||
Iterator(const Iterator &o) noexcept;
|
||||
|
||||
Iterator(Iterator &&o) noexcept;
|
||||
|
||||
inline bool is_ok() const;
|
||||
|
||||
// useful for debuging purposes
|
||||
inline size_t get_tape_location() const;
|
||||
|
||||
// useful for debuging purposes
|
||||
inline size_t get_tape_length() const;
|
||||
|
||||
// returns the current depth (start at 1 with 0 reserved for the fictitious
|
||||
// root node)
|
||||
inline size_t get_depth() const;
|
||||
|
||||
// A scope is a series of nodes at the same depth, typically it is either an
|
||||
// object ({) or an array ([). The root node has type 'r'.
|
||||
inline uint8_t get_scope_type() const;
|
||||
|
||||
// move forward in document order
|
||||
inline bool move_forward();
|
||||
|
||||
// retrieve the character code of what we're looking at:
|
||||
// [{"sltfn are the possibilities
|
||||
inline uint8_t get_type() const {
|
||||
return current_type; // short functions should be inlined!
|
||||
}
|
||||
|
||||
// get the int64_t value at this node; valid only if we're at "l"
|
||||
inline int64_t get_integer() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return 0; // default value in case of error
|
||||
}
|
||||
return static_cast<int64_t>(pj.tape[location + 1]);
|
||||
}
|
||||
|
||||
// get the string value at this node (NULL ended); valid only if we're at "
|
||||
// note that tabs, and line endings are escaped in the returned value (see
|
||||
// print_with_escapes) return value is valid UTF-8 It may contain NULL chars
|
||||
// within the string: get_string_length determines the true string length.
|
||||
inline const char *get_string() const {
|
||||
return reinterpret_cast<const char *>(
|
||||
pj.string_buf + (current_val & JSON_VALUE_MASK) + sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// return the length of the string in bytes
|
||||
inline uint32_t get_string_length() const {
|
||||
uint32_t answer;
|
||||
memcpy(&answer,
|
||||
reinterpret_cast<const char *>(pj.string_buf +
|
||||
(current_val & JSON_VALUE_MASK)),
|
||||
sizeof(uint32_t));
|
||||
return answer;
|
||||
}
|
||||
|
||||
// get the double value at this node; valid only if
|
||||
// we're at "d"
|
||||
inline double get_double() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return std::numeric_limits<double>::quiet_NaN(); // default value in
|
||||
// case of error
|
||||
}
|
||||
double answer;
|
||||
memcpy(&answer, &pj.tape[location + 1], sizeof(answer));
|
||||
return answer;
|
||||
}
|
||||
|
||||
inline bool is_object_or_array() const { return is_object() || is_array(); }
|
||||
|
||||
inline bool is_object() const { return get_type() == '{'; }
|
||||
|
||||
inline bool is_array() const { return get_type() == '['; }
|
||||
|
||||
inline bool is_string() const { return get_type() == '"'; }
|
||||
|
||||
inline bool is_integer() const { return get_type() == 'l'; }
|
||||
|
||||
inline bool is_double() const { return get_type() == 'd'; }
|
||||
|
||||
inline bool is_true() const { return get_type() == 't'; }
|
||||
|
||||
inline bool is_false() const { return get_type() == 'f'; }
|
||||
|
||||
inline bool is_null() const { return get_type() == 'n'; }
|
||||
|
||||
static bool is_object_or_array(uint8_t type) {
|
||||
return ((type == '[') || (type == '{'));
|
||||
}
|
||||
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// We seek the key using C's strcmp so if your JSON strings contain
|
||||
// NULL chars, this would trigger a false positive: if you expect that
|
||||
// to be the case, take extra precautions.
|
||||
inline bool move_to_key(const char *key);
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// The string we search for can contain NULL values.
|
||||
inline bool move_to_key(const char *key, uint32_t length);
|
||||
|
||||
// when at a key location within an object, this moves to the accompanying
|
||||
// value (located next to it). this is equivalent but much faster than
|
||||
// calling "next()".
|
||||
inline void move_to_value();
|
||||
|
||||
// when at [, go one level deep, and advance to the given index.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the array ([)
|
||||
inline bool move_to_index(uint32_t index);
|
||||
|
||||
// Moves the iterator to the value correspoding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer follows the rfc6901 standard's syntax:
|
||||
// https://tools.ietf.org/html/rfc6901 However, the standard says "If a
|
||||
// referenced member name is not unique in an object, the member that is
|
||||
// referenced is undefined, and evaluation fails". Here we just return the
|
||||
// first corresponding value. The length parameter is the length of the
|
||||
// jsonpointer string ('pointer').
|
||||
bool move_to(const char *pointer, uint32_t length);
|
||||
|
||||
// Moves the iterator to the value correspoding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer implementation follows the rfc6901 standard's
|
||||
// syntax: https://tools.ietf.org/html/rfc6901 However, the standard says
|
||||
// "If a referenced member name is not unique in an object, the member that
|
||||
// is referenced is undefined, and evaluation fails". Here we just return
|
||||
// the first corresponding value.
|
||||
inline bool move_to(const std::string &pointer) {
|
||||
return move_to(pointer.c_str(), pointer.length());
|
||||
}
|
||||
|
||||
private:
|
||||
// Almost the same as move_to(), except it searchs from the current
|
||||
// position. The pointer's syntax is identical, though that case is not
|
||||
// handled by the rfc6901 standard. The '/' is still required at the
|
||||
// beginning. However, contrary to move_to(), the URI Fragment Identifier
|
||||
// Representation is not supported here. Also, in case of failure, we are
|
||||
// left pointing at the closest value it could reach. For these reasons it
|
||||
// is private. It exists because it is used by move_to().
|
||||
bool relative_move_to(const char *pointer, uint32_t length);
|
||||
|
||||
public:
|
||||
// throughout return true if we can do the navigation, false
|
||||
// otherwise
|
||||
|
||||
// Withing a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move forward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit true, null, {
|
||||
// and [. At the object ({) or at the array ([), you can issue a "down" to
|
||||
// visit their content. valid if we're not at the end of a scope (returns
|
||||
// true).
|
||||
inline bool next();
|
||||
|
||||
// Withing a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move backward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit ], }, null, true
|
||||
// when starting at the end of the scope. At the object ({) or at the array
|
||||
// ([), you can issue a "down" to visit their content.
|
||||
inline bool prev();
|
||||
|
||||
// Moves back to either the containing array or object (type { or [) from
|
||||
// within a contained scope.
|
||||
// Valid unless we are at the first level of the document
|
||||
inline bool up();
|
||||
|
||||
// Valid if we're at a [ or { and it starts a non-empty scope; moves us to
|
||||
// start of that deeper scope if it not empty. Thus, given [true, null,
|
||||
// {"a":1}, [1,2]], if we are at the { node, we would move to the "a" node.
|
||||
inline bool down();
|
||||
|
||||
// move us to the start of our current scope,
|
||||
// a scope is a series of nodes at the same level
|
||||
inline void to_start_scope();
|
||||
|
||||
inline void rewind() {
|
||||
while (up())
|
||||
;
|
||||
}
|
||||
|
||||
// void to_end_scope(); // move us to
|
||||
// the start of our current scope; always succeeds
|
||||
|
||||
// print the thing we're currently pointing at
|
||||
bool print(std::ostream &os, bool escape_strings = true) const;
|
||||
typedef struct {
|
||||
size_t start_of_scope;
|
||||
uint8_t scope_type;
|
||||
} scopeindex_t;
|
||||
|
||||
private:
|
||||
Iterator &operator=(const Iterator &other) = delete;
|
||||
|
||||
ParsedJson &pj;
|
||||
size_t depth;
|
||||
size_t location; // our current location on a tape
|
||||
size_t tape_length;
|
||||
uint8_t current_type;
|
||||
uint64_t current_val;
|
||||
scopeindex_t *depth_index;
|
||||
};
|
||||
|
||||
size_t byte_capacity{0}; // indicates how many bits are meant to be supported
|
||||
|
||||
size_t depth_capacity{0}; // how deep we can go
|
||||
size_t tape_capacity{0};
|
||||
size_t string_capacity{0};
|
||||
uint32_t current_loc{0};
|
||||
uint32_t n_structural_indexes{0};
|
||||
|
||||
uint32_t *structural_indexes;
|
||||
|
||||
uint64_t *tape;
|
||||
uint32_t *containing_scope_offset;
|
||||
#ifdef SIMDJSON_USE_COMPUTED_GOTO
|
||||
void **ret_address;
|
||||
#else
|
||||
char *ret_address;
|
||||
#endif
|
||||
|
||||
uint8_t *string_buf; // should be at least byte_capacity
|
||||
uint8_t *current_string_buf_loc;
|
||||
bool valid{false};
|
||||
int error_code{simdjson::UNITIALIZED};
|
||||
|
||||
private:
|
||||
// we don't want the default constructor to be called
|
||||
ParsedJson(const ParsedJson &p) =
|
||||
delete; // we don't want the default constructor to be called
|
||||
// we don't want the assignment to be called
|
||||
ParsedJson &operator=(const ParsedJson &o) = delete;
|
||||
};
|
||||
|
||||
// dump bits low to high
|
||||
inline void dumpbits_always(uint64_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
std::cout << (((v >> static_cast<uint64_t>(i)) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
|
||||
inline void dumpbits32_always(uint32_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 32; i++) {
|
||||
std::cout << (((v >> i) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
|
||||
WARN_UNUSED
|
||||
bool ParsedJson::Iterator::is_ok() const { return location < tape_length; }
|
||||
|
||||
// useful for debuging purposes
|
||||
size_t ParsedJson::Iterator::get_tape_location() const { return location; }
|
||||
|
||||
// useful for debuging purposes
|
||||
size_t ParsedJson::Iterator::get_tape_length() const { return tape_length; }
|
||||
|
||||
// returns the current depth (start at 1 with 0 reserved for the fictitious root
|
||||
// node)
|
||||
size_t ParsedJson::Iterator::get_depth() const { return depth; }
|
||||
|
||||
// A scope is a series of nodes at the same depth, typically it is either an
|
||||
// object ({) or an array ([). The root node has type 'r'.
|
||||
uint8_t ParsedJson::Iterator::get_scope_type() const {
|
||||
return depth_index[depth].scope_type;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::move_forward() {
|
||||
if (location + 1 >= tape_length) {
|
||||
return false; // we are at the end!
|
||||
}
|
||||
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// We are entering a new scope
|
||||
depth++;
|
||||
depth_index[depth].start_of_scope = location;
|
||||
depth_index[depth].scope_type = current_type;
|
||||
} else if ((current_type == ']') || (current_type == '}')) {
|
||||
// Leaving a scope.
|
||||
depth--;
|
||||
} else if ((current_type == 'd') || (current_type == 'l')) {
|
||||
// d and l types use 2 locations on the tape, not just one.
|
||||
location += 1;
|
||||
}
|
||||
|
||||
location += 1;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParsedJson::Iterator::move_to_value() {
|
||||
// assume that we are on a key, so move by 1.
|
||||
location += 1;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::move_to_key(const char *key) {
|
||||
if (down()) {
|
||||
do {
|
||||
assert(is_string());
|
||||
bool right_key =
|
||||
(strcmp(get_string(), key) == 0); // null chars would fool this
|
||||
move_to_value();
|
||||
if (right_key) {
|
||||
return true;
|
||||
}
|
||||
} while (next());
|
||||
assert(up()); // not found
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::move_to_key(const char *key, uint32_t length) {
|
||||
if (down()) {
|
||||
do {
|
||||
assert(is_string());
|
||||
bool right_key = ((get_string_length() == length) &&
|
||||
(memcmp(get_string(), key, length) == 0));
|
||||
move_to_value();
|
||||
if (right_key) {
|
||||
return true;
|
||||
}
|
||||
} while (next());
|
||||
assert(up()); // not found
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::move_to_index(uint32_t index) {
|
||||
assert(is_array());
|
||||
if (down()) {
|
||||
uint32_t i = 0;
|
||||
for (; i < index; i++) {
|
||||
if (!next()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == index) {
|
||||
return true;
|
||||
}
|
||||
assert(up());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::prev() {
|
||||
if (location - 1 < depth_index[depth].start_of_scope) {
|
||||
return false;
|
||||
}
|
||||
location -= 1;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
if ((current_type == ']') || (current_type == '}')) {
|
||||
// we need to jump
|
||||
size_t new_location = (current_val & JSON_VALUE_MASK);
|
||||
if (new_location < depth_index[depth].start_of_scope) {
|
||||
return false; // shoud never happen
|
||||
}
|
||||
location = new_location;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::up() {
|
||||
if (depth == 1) {
|
||||
return false; // don't allow moving back to root
|
||||
}
|
||||
to_start_scope();
|
||||
// next we just move to the previous value
|
||||
depth--;
|
||||
location -= 1;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::down() {
|
||||
if (location + 1 >= tape_length) {
|
||||
return false;
|
||||
}
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
size_t npos = (current_val & JSON_VALUE_MASK);
|
||||
if (npos == location + 2) {
|
||||
return false; // we have an empty scope
|
||||
}
|
||||
depth++;
|
||||
location = location + 1;
|
||||
depth_index[depth].start_of_scope = location;
|
||||
depth_index[depth].scope_type = current_type;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ParsedJson::Iterator::to_start_scope() {
|
||||
location = depth_index[depth].start_of_scope;
|
||||
current_val = pj.tape[location];
|
||||
current_type = (current_val >> 56);
|
||||
}
|
||||
|
||||
bool ParsedJson::Iterator::next() {
|
||||
size_t npos;
|
||||
if ((current_type == '[') || (current_type == '{')) {
|
||||
// we need to jump
|
||||
npos = (current_val & JSON_VALUE_MASK);
|
||||
} else {
|
||||
npos = location + ((current_type == 'd' || current_type == 'l') ? 2 : 1);
|
||||
}
|
||||
uint64_t next_val = pj.tape[npos];
|
||||
uint8_t next_type = (next_val >> 56);
|
||||
if ((next_type == ']') || (next_type == '}')) {
|
||||
return false; // we reached the end of the scope
|
||||
}
|
||||
location = npos;
|
||||
current_val = next_val;
|
||||
current_type = next_type;
|
||||
return true;
|
||||
}
|
||||
} // namespace simdjson
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_PARSEDJSON_ITERATOR_H
|
||||
#define SIMDJSON_PARSEDJSON_ITERATOR_H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "simdjson/document.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/internal/jsonformatutils.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
class [[deprecated("Use the new DOM navigation API instead (see doc/usage.md)")]] dom::parser::Iterator {
|
||||
public:
|
||||
inline Iterator(const dom::parser &parser) noexcept(false);
|
||||
inline Iterator(const Iterator &o) noexcept;
|
||||
inline ~Iterator() noexcept;
|
||||
|
||||
inline bool is_ok() const;
|
||||
|
||||
// useful for debugging purposes
|
||||
inline size_t get_tape_location() const;
|
||||
|
||||
// useful for debugging purposes
|
||||
inline size_t get_tape_length() const;
|
||||
|
||||
// returns the current depth (start at 1 with 0 reserved for the fictitious
|
||||
// root node)
|
||||
inline size_t get_depth() const;
|
||||
|
||||
// A scope is a series of nodes at the same depth, typically it is either an
|
||||
// object ({) or an array ([). The root node has type 'r'.
|
||||
inline uint8_t get_scope_type() const;
|
||||
|
||||
// move forward in document order
|
||||
inline bool move_forward();
|
||||
|
||||
// retrieve the character code of what we're looking at:
|
||||
// [{"slutfn are the possibilities
|
||||
inline uint8_t get_type() const {
|
||||
return current_type; // short functions should be inlined!
|
||||
}
|
||||
|
||||
// get the int64_t value at this node; valid only if get_type is "l"
|
||||
inline int64_t get_integer() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return 0; // default value in case of error
|
||||
}
|
||||
return static_cast<int64_t>(doc.tape[location + 1]);
|
||||
}
|
||||
|
||||
// get the value as uint64; valid only if if get_type is "u"
|
||||
inline uint64_t get_unsigned_integer() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return 0; // default value in case of error
|
||||
}
|
||||
return doc.tape[location + 1];
|
||||
}
|
||||
|
||||
// get the string value at this node (NULL ended); valid only if get_type is "
|
||||
// note that tabs, and line endings are escaped in the returned value (see
|
||||
// print_with_escapes) return value is valid UTF-8, it may contain NULL chars
|
||||
// within the string: get_string_length determines the true string length.
|
||||
inline const char *get_string() const {
|
||||
return reinterpret_cast<const char *>(
|
||||
doc.string_buf.get() + (current_val & internal::JSON_VALUE_MASK) + sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// return the length of the string in bytes
|
||||
inline uint32_t get_string_length() const {
|
||||
uint32_t answer;
|
||||
memcpy(&answer,
|
||||
reinterpret_cast<const char *>(doc.string_buf.get() +
|
||||
(current_val & internal::JSON_VALUE_MASK)),
|
||||
sizeof(uint32_t));
|
||||
return answer;
|
||||
}
|
||||
|
||||
// get the double value at this node; valid only if
|
||||
// get_type() is "d"
|
||||
inline double get_double() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return std::numeric_limits<double>::quiet_NaN(); // default value in
|
||||
// case of error
|
||||
}
|
||||
double answer;
|
||||
memcpy(&answer, &doc.tape[location + 1], sizeof(answer));
|
||||
return answer;
|
||||
}
|
||||
|
||||
inline bool is_object_or_array() const { return is_object() || is_array(); }
|
||||
|
||||
inline bool is_object() const { return get_type() == '{'; }
|
||||
|
||||
inline bool is_array() const { return get_type() == '['; }
|
||||
|
||||
inline bool is_string() const { return get_type() == '"'; }
|
||||
|
||||
// Returns true if the current type of node is an signed integer.
|
||||
// You can get its value with `get_integer()`.
|
||||
inline bool is_integer() const { return get_type() == 'l'; }
|
||||
|
||||
// Returns true if the current type of node is an unsigned integer.
|
||||
// You can get its value with `get_unsigned_integer()`.
|
||||
//
|
||||
// NOTE:
|
||||
// Only a large value, which is out of range of a 64-bit signed integer, is
|
||||
// represented internally as an unsigned node. On the other hand, a typical
|
||||
// positive integer, such as 1, 42, or 1000000, is as a signed node.
|
||||
// Be aware this function returns false for a signed node.
|
||||
inline bool is_unsigned_integer() const { return get_type() == 'u'; }
|
||||
|
||||
inline bool is_double() const { return get_type() == 'd'; }
|
||||
|
||||
inline bool is_number() const {
|
||||
return is_integer() || is_unsigned_integer() || is_double();
|
||||
}
|
||||
|
||||
inline bool is_true() const { return get_type() == 't'; }
|
||||
|
||||
inline bool is_false() const { return get_type() == 'f'; }
|
||||
|
||||
inline bool is_null() const { return get_type() == 'n'; }
|
||||
|
||||
static bool is_object_or_array(uint8_t type) {
|
||||
return ((type == '[') || (type == '{'));
|
||||
}
|
||||
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// We seek the key using C's strcmp so if your JSON strings contain
|
||||
// NULL chars, this would trigger a false positive: if you expect that
|
||||
// to be the case, take extra precautions.
|
||||
// Furthermore, we do the comparison character-by-character
|
||||
// without taking into account Unicode equivalence.
|
||||
inline bool move_to_key(const char *key);
|
||||
|
||||
// as above, but case insensitive lookup (strcmpi instead of strcmp)
|
||||
inline bool move_to_key_insensitive(const char *key);
|
||||
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// The string we search for can contain NULL values.
|
||||
// Furthermore, we do the comparison character-by-character
|
||||
// without taking into account Unicode equivalence.
|
||||
inline bool move_to_key(const char *key, uint32_t length);
|
||||
|
||||
// when at a key location within an object, this moves to the accompanying
|
||||
// value (located next to it). This is equivalent but much faster than
|
||||
// calling "next()".
|
||||
inline void move_to_value();
|
||||
|
||||
// when at [, go one level deep, and advance to the given index.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the array ([)
|
||||
inline bool move_to_index(uint32_t index);
|
||||
|
||||
// Moves the iterator to the value corresponding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer follows the rfc6901 standard's syntax:
|
||||
// https://tools.ietf.org/html/rfc6901 However, the standard says "If a
|
||||
// referenced member name is not unique in an object, the member that is
|
||||
// referenced is undefined, and evaluation fails". Here we just return the
|
||||
// first corresponding value. The length parameter is the length of the
|
||||
// jsonpointer string ('pointer').
|
||||
inline bool move_to(const char *pointer, uint32_t length);
|
||||
|
||||
// Moves the iterator to the value corresponding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer implementation follows the rfc6901 standard's
|
||||
// syntax: https://tools.ietf.org/html/rfc6901 However, the standard says
|
||||
// "If a referenced member name is not unique in an object, the member that
|
||||
// is referenced is undefined, and evaluation fails". Here we just return
|
||||
// the first corresponding value.
|
||||
inline bool move_to(const std::string &pointer) {
|
||||
return move_to(pointer.c_str(), pointer.length());
|
||||
}
|
||||
|
||||
private:
|
||||
// Almost the same as move_to(), except it searches from the current
|
||||
// position. The pointer's syntax is identical, though that case is not
|
||||
// handled by the rfc6901 standard. The '/' is still required at the
|
||||
// beginning. However, contrary to move_to(), the URI Fragment Identifier
|
||||
// Representation is not supported here. Also, in case of failure, we are
|
||||
// left pointing at the closest value it could reach. For these reasons it
|
||||
// is private. It exists because it is used by move_to().
|
||||
inline bool relative_move_to(const char *pointer, uint32_t length);
|
||||
|
||||
public:
|
||||
// throughout return true if we can do the navigation, false
|
||||
// otherwise
|
||||
|
||||
// Withing a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move forward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit true, null, {
|
||||
// and [. At the object ({) or at the array ([), you can issue a "down" to
|
||||
// visit their content. valid if we're not at the end of a scope (returns
|
||||
// true).
|
||||
inline bool next();
|
||||
|
||||
// Within a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move backward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit ], }, null, true
|
||||
// when starting at the end of the scope. At the object ({) or at the array
|
||||
// ([), you can issue a "down" to visit their content.
|
||||
// Performance warning: This function is implemented by starting again
|
||||
// from the beginning of the scope and scanning forward. You should expect
|
||||
// it to be relatively slow.
|
||||
inline bool prev();
|
||||
|
||||
// Moves back to either the containing array or object (type { or [) from
|
||||
// within a contained scope.
|
||||
// Valid unless we are at the first level of the document
|
||||
inline bool up();
|
||||
|
||||
// Valid if we're at a [ or { and it starts a non-empty scope; moves us to
|
||||
// start of that deeper scope if it not empty. Thus, given [true, null,
|
||||
// {"a":1}, [1,2]], if we are at the { node, we would move to the "a" node.
|
||||
inline bool down();
|
||||
|
||||
// move us to the start of our current scope,
|
||||
// a scope is a series of nodes at the same level
|
||||
inline void to_start_scope();
|
||||
|
||||
inline void rewind() {
|
||||
while (up())
|
||||
;
|
||||
}
|
||||
|
||||
// void to_end_scope(); // move us to
|
||||
// the start of our current scope; always succeeds
|
||||
|
||||
// print the node we are currently pointing at
|
||||
inline bool print(std::ostream &os, bool escape_strings = true) const;
|
||||
typedef struct {
|
||||
size_t start_of_scope;
|
||||
uint8_t scope_type;
|
||||
} scopeindex_t;
|
||||
|
||||
private:
|
||||
const document &doc;
|
||||
size_t max_depth;
|
||||
size_t depth;
|
||||
size_t location; // our current location on a tape
|
||||
size_t tape_length;
|
||||
uint8_t current_type;
|
||||
uint64_t current_val;
|
||||
scopeindex_t *depth_index;
|
||||
};
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,13 @@
|
||||
#ifndef SIMDJSON_PORTABILITY_H
|
||||
#define SIMDJSON_PORTABILITY_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#ifdef _MSC_VER
|
||||
#include <iso646.h>
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_AMD64)
|
||||
#define IS_X86_64 1
|
||||
#endif
|
||||
@@ -9,7 +16,32 @@
|
||||
#endif
|
||||
|
||||
// this is almost standard?
|
||||
#define STRINGIFY(a) #a
|
||||
#undef STRINGIFY_IMPLEMENTATION_
|
||||
#undef STRINGIFY
|
||||
#define STRINGIFY_IMPLEMENTATION_(a) #a
|
||||
#define STRINGIFY(a) STRINGIFY_IMPLEMENTATION_(a)
|
||||
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_FALLBACK
|
||||
#define SIMDJSON_IMPLEMENTATION_FALLBACK 1
|
||||
#endif
|
||||
|
||||
#if IS_ARM64
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_ARM64
|
||||
#define SIMDJSON_IMPLEMENTATION_ARM64 1
|
||||
#endif
|
||||
#define SIMDJSON_IMPLEMENTATION_HASWELL 0
|
||||
#define SIMDJSON_IMPLEMENTATION_WESTMERE 0
|
||||
#endif // IS_ARM64
|
||||
|
||||
#if IS_X86_64
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_HASWELL
|
||||
#define SIMDJSON_IMPLEMENTATION_HASWELL 1
|
||||
#endif
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_WESTMERE
|
||||
#define SIMDJSON_IMPLEMENTATION_WESTMERE 1
|
||||
#endif
|
||||
#define SIMDJSON_IMPLEMENTATION_ARM64 0
|
||||
#endif // IS_X86_64
|
||||
|
||||
// we are going to use runtime dispatch
|
||||
#ifdef IS_X86_64
|
||||
@@ -27,111 +59,48 @@
|
||||
#define TARGET_REGION(T) \
|
||||
_Pragma("GCC push_options") _Pragma(STRINGIFY(GCC target(T)))
|
||||
#define UNTARGET_REGION _Pragma("GCC pop_options")
|
||||
#else
|
||||
#define TARGET_REGION(T)
|
||||
#define UNTARGET_REGION
|
||||
#endif // clang then gcc
|
||||
|
||||
// under GCC and CLANG, we use these two macros
|
||||
#define TARGET_HASWELL TARGET_REGION("avx2,bmi,pclmul")
|
||||
#define TARGET_WESTMERE TARGET_REGION("sse4.2,pclmul")
|
||||
|
||||
#endif // x86
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#if IS_X86_64
|
||||
#include <x86intrin.h>
|
||||
#elif IS_ARM64
|
||||
#include <arm_neon.h>
|
||||
// Default target region macros don't do anything.
|
||||
#ifndef TARGET_REGION
|
||||
#define TARGET_REGION(T)
|
||||
#define UNTARGET_REGION
|
||||
#endif
|
||||
|
||||
// under GCC and CLANG, we use these two macros
|
||||
#define TARGET_HASWELL TARGET_REGION("avx2,bmi,pclmul,lzcnt")
|
||||
#define TARGET_WESTMERE TARGET_REGION("sse4.2,pclmul")
|
||||
#define TARGET_ARM64
|
||||
|
||||
// Threading is disabled
|
||||
#undef SIMDJSON_THREADS_ENABLED
|
||||
// Is threading enabled?
|
||||
#if defined(BOOST_HAS_THREADS) || defined(_REENTRANT) || defined(_MT)
|
||||
#define SIMDJSON_THREADS_ENABLED
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#define NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
|
||||
#elif defined(__GNUC__)
|
||||
#define NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined))
|
||||
#else
|
||||
#define NO_SANITIZE_UNDEFINED
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* Microsoft C/C++-compatible compiler */
|
||||
#include <cstdint>
|
||||
#include <iso646.h>
|
||||
#include <intrin.h> // visual studio
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define simdjson_strcasecmp _stricmp
|
||||
#else
|
||||
#define simdjson_strcasecmp strcasecmp
|
||||
#endif
|
||||
|
||||
namespace simdjson {
|
||||
static inline bool add_overflow(uint64_t value1, uint64_t value2,
|
||||
uint64_t *result) {
|
||||
return _addcarry_u64(0, value1, value2,
|
||||
reinterpret_cast<unsigned __int64 *>(result));
|
||||
}
|
||||
|
||||
#pragma intrinsic(_umul128)
|
||||
static inline bool mul_overflow(uint64_t value1, uint64_t value2,
|
||||
uint64_t *result) {
|
||||
uint64_t high;
|
||||
*result = _umul128(value1, value2, &high);
|
||||
return high;
|
||||
}
|
||||
|
||||
static inline int trailing_zeroes(uint64_t input_num) {
|
||||
return static_cast<int>(_tzcnt_u64(input_num));
|
||||
}
|
||||
|
||||
static inline int leading_zeroes(uint64_t input_num) {
|
||||
return static_cast<int>(_lzcnt_u64(input_num));
|
||||
}
|
||||
|
||||
static inline int hamming(uint64_t input_num) {
|
||||
#ifdef _WIN64 // highly recommended!!!
|
||||
return (int)__popcnt64(input_num);
|
||||
#else // if we must support 32-bit Windows
|
||||
return (int)(__popcnt((uint32_t)input_num) +
|
||||
__popcnt((uint32_t)(input_num >> 32)));
|
||||
#endif
|
||||
}
|
||||
} // namespace simdjson
|
||||
#else
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace simdjson {
|
||||
static inline bool add_overflow(uint64_t value1, uint64_t value2,
|
||||
uint64_t *result) {
|
||||
return __builtin_uaddll_overflow(value1, value2,
|
||||
(unsigned long long *)result);
|
||||
}
|
||||
static inline bool mul_overflow(uint64_t value1, uint64_t value2,
|
||||
uint64_t *result) {
|
||||
return __builtin_umulll_overflow(value1, value2,
|
||||
(unsigned long long *)result);
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
static inline int trailing_zeroes(uint64_t input_num) {
|
||||
#ifdef __BMI__ // tzcnt is BMI1
|
||||
return _tzcnt_u64(input_num);
|
||||
#else
|
||||
return __builtin_ctzll(input_num);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
static inline int leading_zeroes(uint64_t input_num) {
|
||||
#ifdef __BMI2__
|
||||
return _lzcnt_u64(input_num);
|
||||
#else
|
||||
return __builtin_clzll(input_num);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* result might be undefined when input_num is zero */
|
||||
static inline int hamming(uint64_t input_num) {
|
||||
#ifdef __POPCOUNT__
|
||||
return _popcnt64(input_num);
|
||||
#else
|
||||
return __builtin_popcountll(input_num);
|
||||
#endif
|
||||
}
|
||||
} // namespace simdjson
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace simdjson {
|
||||
// portable version of posix_memalign
|
||||
/** @private portable version of posix_memalign */
|
||||
static inline void *aligned_malloc(size_t alignment, size_t size) {
|
||||
void *p;
|
||||
#ifdef _MSC_VER
|
||||
@@ -148,10 +117,12 @@ static inline void *aligned_malloc(size_t alignment, size_t size) {
|
||||
return p;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
static inline char *aligned_malloc_char(size_t alignment, size_t size) {
|
||||
return (char *)aligned_malloc(alignment, size);
|
||||
}
|
||||
|
||||
/** @private */
|
||||
static inline void aligned_free(void *mem_block) {
|
||||
if (mem_block == nullptr) {
|
||||
return;
|
||||
@@ -165,6 +136,7 @@ static inline void aligned_free(void *mem_block) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/** @private */
|
||||
static inline void aligned_free_char(char *mem_block) {
|
||||
aligned_free((void *)mem_block);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,11 @@
|
||||
#ifndef SIMDJSON_ERR_H
|
||||
#define SIMDJSON_ERR_H
|
||||
/**
|
||||
* @file
|
||||
* @deprecated We'll be removing this file so it isn't confused with the top level simdjson.h
|
||||
*/
|
||||
#ifndef SIMDJSON_SIMDJSON_H
|
||||
#define SIMDJSON_SIMDJSON_H
|
||||
|
||||
#include <string>
|
||||
#include "simdjson/compiler_check.h"
|
||||
#include "simdjson/error.h"
|
||||
|
||||
namespace simdjson {
|
||||
// Represents the minimal architecture that would support an implementation
|
||||
enum class Architecture {
|
||||
WESTMERE,
|
||||
HASWELL,
|
||||
ARM64,
|
||||
NONE,
|
||||
// TODO remove 'native' in favor of runtime dispatch?
|
||||
// the 'native' enum class value should point at a good default on the current
|
||||
// machine
|
||||
#ifdef IS_X86_64
|
||||
NATIVE = WESTMERE
|
||||
#elif defined(IS_ARM64)
|
||||
NATIVE = ARM64
|
||||
#endif
|
||||
};
|
||||
|
||||
enum ErrorValues {
|
||||
SUCCESS = 0,
|
||||
CAPACITY, // This ParsedJson can't support a document that big
|
||||
MEMALLOC, // Error allocating memory, most likely out of memory
|
||||
TAPE_ERROR, // Something went wrong while writing to the tape (stage 2), this
|
||||
// is a generic error
|
||||
DEPTH_ERROR, // Your document exceeds the user-specified depth limitation
|
||||
STRING_ERROR, // Problem while parsing a string
|
||||
T_ATOM_ERROR, // Problem while parsing an atom starting with the letter 't'
|
||||
F_ATOM_ERROR, // Problem while parsing an atom starting with the letter 'f'
|
||||
N_ATOM_ERROR, // Problem while parsing an atom starting with the letter 'n'
|
||||
NUMBER_ERROR, // Problem while parsing a number
|
||||
UTF8_ERROR, // the input is not valid UTF-8
|
||||
UNITIALIZED, // unknown error, or uninitialized document
|
||||
EMPTY, // no structural document found
|
||||
UNESCAPED_CHARS, // found unescaped characters in a string.
|
||||
UNCLOSED_STRING, // missing quote at the end
|
||||
UNEXPECTED_ERROR // indicative of a bug in simdjson
|
||||
};
|
||||
const std::string &error_message(const int);
|
||||
} // namespace simdjson
|
||||
#endif
|
||||
#endif // SIMDJSON_H
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
// /include/simdjson/simdjson_version.h automatically generated by release.py,
|
||||
// do not change by hand
|
||||
#ifndef SIMDJSON_INCLUDE_SIMDJSON_VERSION
|
||||
#define SIMDJSON_INCLUDE_SIMDJSON_VERSION
|
||||
#define SIMDJSON_VERSION 0.2.0
|
||||
#ifndef SIMDJSON_SIMDJSON_VERSION_H
|
||||
#define SIMDJSON_SIMDJSON_VERSION_H
|
||||
|
||||
/** The version of simdjson being used (major.minor.revision) */
|
||||
#define SIMDJSON_VERSION 0.3.1
|
||||
|
||||
namespace simdjson {
|
||||
enum {
|
||||
SIMDJSON_VERSION_MAJOR = 0,
|
||||
SIMDJSON_VERSION_MINOR = 2,
|
||||
SIMDJSON_VERSION_REVISION = 0
|
||||
/**
|
||||
* The major version (MAJOR.minor.revision) of simdjson being used.
|
||||
*/
|
||||
SIMDJSON_VERSION_MAJOR = 0,
|
||||
/**
|
||||
* The minor version (major.MINOR.revision) of simdjson being used.
|
||||
*/
|
||||
SIMDJSON_VERSION_MINOR = 3,
|
||||
/**
|
||||
* The revision (major.minor.REVISION) of simdjson being used.
|
||||
*/
|
||||
SIMDJSON_VERSION_REVISION = 1
|
||||
};
|
||||
}
|
||||
#endif // SIMDJSON_INCLUDE_SIMDJSON_VERSION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_SIMDJSON_VERSION_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,180 +0,0 @@
|
||||
// From https://github.com/cyb70289/utf8/blob/master/lemire-neon.c
|
||||
// Adapted from https://github.com/lemire/fastvalidate-utf-8
|
||||
|
||||
#ifndef SIMDJSON_SIMDUTF8CHECK_ARM64_H
|
||||
#define SIMDJSON_SIMDUTF8CHECK_ARM64_H
|
||||
|
||||
#if defined(_ARM_NEON) || defined(__aarch64__) || \
|
||||
(defined(_MSC_VER) && defined(_M_ARM64))
|
||||
|
||||
#include <arm_neon.h>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
/*
|
||||
* legal utf-8 byte sequence
|
||||
* http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94
|
||||
*
|
||||
* Code Points 1st 2s 3s 4s
|
||||
* U+0000..U+007F 00..7F
|
||||
* U+0080..U+07FF C2..DF 80..BF
|
||||
* U+0800..U+0FFF E0 A0..BF 80..BF
|
||||
* U+1000..U+CFFF E1..EC 80..BF 80..BF
|
||||
* U+D000..U+D7FF ED 80..9F 80..BF
|
||||
* U+E000..U+FFFF EE..EF 80..BF 80..BF
|
||||
* U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||
* U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
|
||||
* U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||
*
|
||||
*/
|
||||
namespace simdjson {
|
||||
|
||||
// all byte values must be no larger than 0xF4
|
||||
static inline void check_smaller_than_0xF4(int8x16_t current_bytes,
|
||||
int8x16_t *has_error) {
|
||||
// unsigned, saturates to 0 below max
|
||||
*has_error = vorrq_s8(
|
||||
*has_error, vreinterpretq_s8_u8(vqsubq_u8(
|
||||
vreinterpretq_u8_s8(current_bytes), vdupq_n_u8(0xF4))));
|
||||
}
|
||||
|
||||
static const int8_t _nibbles[] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII)
|
||||
0, 0, 0, 0, // 10xx (continuation)
|
||||
2, 2, // 110x
|
||||
3, // 1110
|
||||
4, // 1111, next should be 0 (not checked here)
|
||||
};
|
||||
|
||||
static inline int8x16_t continuation_lengths(int8x16_t high_nibbles) {
|
||||
return vqtbl1q_s8(vld1q_s8(_nibbles), vreinterpretq_u8_s8(high_nibbles));
|
||||
}
|
||||
|
||||
static inline int8x16_t carry_continuations(int8x16_t initial_lengths,
|
||||
int8x16_t previous_carries) {
|
||||
|
||||
int8x16_t right1 = vreinterpretq_s8_u8(vqsubq_u8(
|
||||
vreinterpretq_u8_s8(vextq_s8(previous_carries, initial_lengths, 16 - 1)),
|
||||
vdupq_n_u8(1)));
|
||||
int8x16_t sum = vaddq_s8(initial_lengths, right1);
|
||||
|
||||
int8x16_t right2 = vreinterpretq_s8_u8(
|
||||
vqsubq_u8(vreinterpretq_u8_s8(vextq_s8(previous_carries, sum, 16 - 2)),
|
||||
vdupq_n_u8(2)));
|
||||
return vaddq_s8(sum, right2);
|
||||
}
|
||||
|
||||
static inline void check_continuations(int8x16_t initial_lengths,
|
||||
int8x16_t carries,
|
||||
int8x16_t *has_error) {
|
||||
|
||||
// overlap || underlap
|
||||
// carry > length && length > 0 || !(carry > length) && !(length > 0)
|
||||
// (carries > length) == (lengths > 0)
|
||||
uint8x16_t overunder = vceqq_u8(vcgtq_s8(carries, initial_lengths),
|
||||
vcgtq_s8(initial_lengths, vdupq_n_s8(0)));
|
||||
|
||||
*has_error = vorrq_s8(*has_error, vreinterpretq_s8_u8(overunder));
|
||||
}
|
||||
|
||||
// when 0xED is found, next byte must be no larger than 0x9F
|
||||
// when 0xF4 is found, next byte must be no larger than 0x8F
|
||||
// next byte must be continuation, ie sign bit is set, so signed < is ok
|
||||
static inline void check_first_continuation_max(int8x16_t current_bytes,
|
||||
int8x16_t off1_current_bytes,
|
||||
int8x16_t *has_error) {
|
||||
uint8x16_t maskED = vceqq_s8(off1_current_bytes, vdupq_n_s8(0xED));
|
||||
uint8x16_t maskF4 = vceqq_s8(off1_current_bytes, vdupq_n_s8(0xF4));
|
||||
|
||||
uint8x16_t badfollowED =
|
||||
vandq_u8(vcgtq_s8(current_bytes, vdupq_n_s8(0x9F)), maskED);
|
||||
uint8x16_t badfollowF4 =
|
||||
vandq_u8(vcgtq_s8(current_bytes, vdupq_n_s8(0x8F)), maskF4);
|
||||
|
||||
*has_error = vorrq_s8(
|
||||
*has_error, vreinterpretq_s8_u8(vorrq_u8(badfollowED, badfollowF4)));
|
||||
}
|
||||
|
||||
static const int8_t _initial_mins[] = {
|
||||
-128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, -128, -128, -128, // 10xx => false
|
||||
(int8_t)0xC2, -128, // 110x
|
||||
(int8_t)0xE1, // 1110
|
||||
(int8_t)0xF1,
|
||||
};
|
||||
|
||||
static const int8_t _second_mins[] = {
|
||||
-128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, -128, -128, -128, // 10xx => false
|
||||
127, 127, // 110x => true
|
||||
(int8_t)0xA0, // 1110
|
||||
(int8_t)0x90,
|
||||
};
|
||||
|
||||
// map off1_hibits => error condition
|
||||
// hibits off1 cur
|
||||
// C => < C2 && true
|
||||
// E => < E1 && < A0
|
||||
// F => < F1 && < 90
|
||||
// else false && false
|
||||
static inline void check_overlong(int8x16_t current_bytes,
|
||||
int8x16_t off1_current_bytes,
|
||||
int8x16_t hibits, int8x16_t previous_hibits,
|
||||
int8x16_t *has_error) {
|
||||
int8x16_t off1_hibits = vextq_s8(previous_hibits, hibits, 16 - 1);
|
||||
int8x16_t initial_mins =
|
||||
vqtbl1q_s8(vld1q_s8(_initial_mins), vreinterpretq_u8_s8(off1_hibits));
|
||||
|
||||
uint8x16_t initial_under = vcgtq_s8(initial_mins, off1_current_bytes);
|
||||
|
||||
int8x16_t second_mins =
|
||||
vqtbl1q_s8(vld1q_s8(_second_mins), vreinterpretq_u8_s8(off1_hibits));
|
||||
uint8x16_t second_under = vcgtq_s8(second_mins, current_bytes);
|
||||
*has_error = vorrq_s8(
|
||||
*has_error, vreinterpretq_s8_u8(vandq_u8(initial_under, second_under)));
|
||||
}
|
||||
|
||||
struct processed_utf_bytes {
|
||||
int8x16_t raw_bytes;
|
||||
int8x16_t high_nibbles;
|
||||
int8x16_t carried_continuations;
|
||||
};
|
||||
|
||||
static inline void count_nibbles(int8x16_t bytes,
|
||||
struct processed_utf_bytes *answer) {
|
||||
answer->raw_bytes = bytes;
|
||||
answer->high_nibbles =
|
||||
vreinterpretq_s8_u8(vshrq_n_u8(vreinterpretq_u8_s8(bytes), 4));
|
||||
}
|
||||
|
||||
// check whether the current bytes are valid UTF-8
|
||||
// at the end of the function, previous gets updated
|
||||
static inline struct processed_utf_bytes
|
||||
check_utf8_bytes(int8x16_t current_bytes, struct processed_utf_bytes *previous,
|
||||
int8x16_t *has_error) {
|
||||
struct processed_utf_bytes pb;
|
||||
count_nibbles(current_bytes, &pb);
|
||||
|
||||
check_smaller_than_0xF4(current_bytes, has_error);
|
||||
|
||||
int8x16_t initial_lengths = continuation_lengths(pb.high_nibbles);
|
||||
|
||||
pb.carried_continuations =
|
||||
carry_continuations(initial_lengths, previous->carried_continuations);
|
||||
|
||||
check_continuations(initial_lengths, pb.carried_continuations, has_error);
|
||||
|
||||
int8x16_t off1_current_bytes =
|
||||
vextq_s8(previous->raw_bytes, pb.raw_bytes, 16 - 1);
|
||||
check_first_continuation_max(current_bytes, off1_current_bytes, has_error);
|
||||
|
||||
check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles,
|
||||
previous->high_nibbles, has_error);
|
||||
return pb;
|
||||
}
|
||||
} // namespace simdjson
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,198 +0,0 @@
|
||||
#ifndef SIMDJSON_SIMDUTF8CHECK_HASWELL_H
|
||||
#define SIMDJSON_SIMDUTF8CHECK_HASWELL_H
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef IS_X86_64
|
||||
/*
|
||||
* legal utf-8 byte sequence
|
||||
* http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94
|
||||
*
|
||||
* Code Points 1st 2s 3s 4s
|
||||
* U+0000..U+007F 00..7F
|
||||
* U+0080..U+07FF C2..DF 80..BF
|
||||
* U+0800..U+0FFF E0 A0..BF 80..BF
|
||||
* U+1000..U+CFFF E1..EC 80..BF 80..BF
|
||||
* U+D000..U+D7FF ED 80..9F 80..BF
|
||||
* U+E000..U+FFFF EE..EF 80..BF 80..BF
|
||||
* U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||
* U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
|
||||
* U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||
*
|
||||
*/
|
||||
|
||||
// all byte values must be no larger than 0xF4
|
||||
|
||||
TARGET_HASWELL
|
||||
namespace simdjson {
|
||||
static inline __m256i push_last_byte_of_a_to_b(__m256i a, __m256i b) {
|
||||
return _mm256_alignr_epi8(b, _mm256_permute2x128_si256(a, b, 0x21), 15);
|
||||
}
|
||||
|
||||
static inline __m256i push_last_2bytes_of_a_to_b(__m256i a, __m256i b) {
|
||||
return _mm256_alignr_epi8(b, _mm256_permute2x128_si256(a, b, 0x21), 14);
|
||||
}
|
||||
|
||||
// all byte values must be no larger than 0xF4
|
||||
static inline void avx_check_smaller_than_0xF4(__m256i current_bytes,
|
||||
__m256i *has_error) {
|
||||
// unsigned, saturates to 0 below max
|
||||
*has_error = _mm256_or_si256(
|
||||
*has_error, _mm256_subs_epu8(current_bytes, _mm256_set1_epi8(0xF4u)));
|
||||
}
|
||||
|
||||
static inline __m256i avx_continuation_lengths(__m256i high_nibbles) {
|
||||
return _mm256_shuffle_epi8(
|
||||
_mm256_setr_epi8(1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII)
|
||||
0, 0, 0, 0, // 10xx (continuation)
|
||||
2, 2, // 110x
|
||||
3, // 1110
|
||||
4, // 1111, next should be 0 (not checked here)
|
||||
1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII)
|
||||
0, 0, 0, 0, // 10xx (continuation)
|
||||
2, 2, // 110x
|
||||
3, // 1110
|
||||
4 // 1111, next should be 0 (not checked here)
|
||||
),
|
||||
high_nibbles);
|
||||
}
|
||||
|
||||
static inline __m256i avx_carry_continuations(__m256i initial_lengths,
|
||||
__m256i previous_carries) {
|
||||
|
||||
__m256i right1 = _mm256_subs_epu8(
|
||||
push_last_byte_of_a_to_b(previous_carries, initial_lengths),
|
||||
_mm256_set1_epi8(1));
|
||||
__m256i sum = _mm256_add_epi8(initial_lengths, right1);
|
||||
|
||||
__m256i right2 = _mm256_subs_epu8(
|
||||
push_last_2bytes_of_a_to_b(previous_carries, sum), _mm256_set1_epi8(2));
|
||||
return _mm256_add_epi8(sum, right2);
|
||||
}
|
||||
|
||||
static inline void avx_check_continuations(__m256i initial_lengths,
|
||||
__m256i carries,
|
||||
__m256i *has_error) {
|
||||
|
||||
// overlap || underlap
|
||||
// carry > length && length > 0 || !(carry > length) && !(length > 0)
|
||||
// (carries > length) == (lengths > 0)
|
||||
__m256i overunder = _mm256_cmpeq_epi8(
|
||||
_mm256_cmpgt_epi8(carries, initial_lengths),
|
||||
_mm256_cmpgt_epi8(initial_lengths, _mm256_setzero_si256()));
|
||||
|
||||
*has_error = _mm256_or_si256(*has_error, overunder);
|
||||
}
|
||||
|
||||
// when 0xED is found, next byte must be no larger than 0x9F
|
||||
// when 0xF4 is found, next byte must be no larger than 0x8F
|
||||
// next byte must be continuation, ie sign bit is set, so signed < is ok
|
||||
static inline void avx_check_first_continuation_max(__m256i current_bytes,
|
||||
__m256i off1_current_bytes,
|
||||
__m256i *has_error) {
|
||||
__m256i maskED =
|
||||
_mm256_cmpeq_epi8(off1_current_bytes, _mm256_set1_epi8(0xEDu));
|
||||
__m256i maskF4 =
|
||||
_mm256_cmpeq_epi8(off1_current_bytes, _mm256_set1_epi8(0xF4u));
|
||||
|
||||
__m256i badfollowED = _mm256_and_si256(
|
||||
_mm256_cmpgt_epi8(current_bytes, _mm256_set1_epi8(0x9Fu)), maskED);
|
||||
__m256i badfollowF4 = _mm256_and_si256(
|
||||
_mm256_cmpgt_epi8(current_bytes, _mm256_set1_epi8(0x8Fu)), maskF4);
|
||||
|
||||
*has_error =
|
||||
_mm256_or_si256(*has_error, _mm256_or_si256(badfollowED, badfollowF4));
|
||||
}
|
||||
|
||||
// map off1_hibits => error condition
|
||||
// hibits off1 cur
|
||||
// C => < C2 && true
|
||||
// E => < E1 && < A0
|
||||
// F => < F1 && < 90
|
||||
// else false && false
|
||||
static inline void avx_check_overlong(__m256i current_bytes,
|
||||
__m256i off1_current_bytes,
|
||||
__m256i hibits, __m256i previous_hibits,
|
||||
__m256i *has_error) {
|
||||
__m256i off1_hibits = push_last_byte_of_a_to_b(previous_hibits, hibits);
|
||||
__m256i initial_mins = _mm256_shuffle_epi8(
|
||||
_mm256_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, // 10xx => false
|
||||
0xC2u, -128, // 110x
|
||||
0xE1u, // 1110
|
||||
0xF1u, // 1111
|
||||
-128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, // 10xx => false
|
||||
0xC2u, -128, // 110x
|
||||
0xE1u, // 1110
|
||||
0xF1u), // 1111
|
||||
off1_hibits);
|
||||
|
||||
__m256i initial_under = _mm256_cmpgt_epi8(initial_mins, off1_current_bytes);
|
||||
|
||||
__m256i second_mins = _mm256_shuffle_epi8(
|
||||
_mm256_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, // 10xx => false
|
||||
127, 127, // 110x => true
|
||||
0xA0u, // 1110
|
||||
0x90u, // 1111
|
||||
-128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, -128, // 10xx => false
|
||||
127, 127, // 110x => true
|
||||
0xA0u, // 1110
|
||||
0x90u), // 1111
|
||||
off1_hibits);
|
||||
__m256i second_under = _mm256_cmpgt_epi8(second_mins, current_bytes);
|
||||
*has_error = _mm256_or_si256(*has_error,
|
||||
_mm256_and_si256(initial_under, second_under));
|
||||
}
|
||||
|
||||
struct avx_processed_utf_bytes {
|
||||
__m256i raw_bytes;
|
||||
__m256i high_nibbles;
|
||||
__m256i carried_continuations;
|
||||
};
|
||||
|
||||
static inline void avx_count_nibbles(__m256i bytes,
|
||||
struct avx_processed_utf_bytes *answer) {
|
||||
answer->raw_bytes = bytes;
|
||||
answer->high_nibbles =
|
||||
_mm256_and_si256(_mm256_srli_epi16(bytes, 4), _mm256_set1_epi8(0x0F));
|
||||
}
|
||||
|
||||
// check whether the current bytes are valid UTF-8
|
||||
// at the end of the function, previous gets updated
|
||||
static inline struct avx_processed_utf_bytes
|
||||
avx_check_utf8_bytes(__m256i current_bytes,
|
||||
struct avx_processed_utf_bytes *previous,
|
||||
__m256i *has_error) {
|
||||
struct avx_processed_utf_bytes pb {};
|
||||
avx_count_nibbles(current_bytes, &pb);
|
||||
|
||||
avx_check_smaller_than_0xF4(current_bytes, has_error);
|
||||
|
||||
__m256i initial_lengths = avx_continuation_lengths(pb.high_nibbles);
|
||||
|
||||
pb.carried_continuations =
|
||||
avx_carry_continuations(initial_lengths, previous->carried_continuations);
|
||||
|
||||
avx_check_continuations(initial_lengths, pb.carried_continuations, has_error);
|
||||
|
||||
__m256i off1_current_bytes =
|
||||
push_last_byte_of_a_to_b(previous->raw_bytes, pb.raw_bytes);
|
||||
avx_check_first_continuation_max(current_bytes, off1_current_bytes,
|
||||
has_error);
|
||||
|
||||
avx_check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles,
|
||||
previous->high_nibbles, has_error);
|
||||
return pb;
|
||||
}
|
||||
} // namespace simdjson
|
||||
UNTARGET_REGION // haswell
|
||||
|
||||
#endif // IS_X86_64
|
||||
|
||||
#endif
|
||||
@@ -1,169 +0,0 @@
|
||||
#ifndef SIMDJSON_SIMDUTF8CHECK_WESTMERE_H
|
||||
#define SIMDJSON_SIMDUTF8CHECK_WESTMERE_H
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#ifdef IS_X86_64
|
||||
|
||||
/*
|
||||
* legal utf-8 byte sequence
|
||||
* http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94
|
||||
*
|
||||
* Code Points 1st 2s 3s 4s
|
||||
* U+0000..U+007F 00..7F
|
||||
* U+0080..U+07FF C2..DF 80..BF
|
||||
* U+0800..U+0FFF E0 A0..BF 80..BF
|
||||
* U+1000..U+CFFF E1..EC 80..BF 80..BF
|
||||
* U+D000..U+D7FF ED 80..9F 80..BF
|
||||
* U+E000..U+FFFF EE..EF 80..BF 80..BF
|
||||
* U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||
* U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
|
||||
* U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||
*
|
||||
*/
|
||||
|
||||
// all byte values must be no larger than 0xF4
|
||||
|
||||
/********** sse code **********/
|
||||
TARGET_WESTMERE
|
||||
|
||||
namespace simdjson {
|
||||
// all byte values must be no larger than 0xF4
|
||||
static inline void check_smaller_than_0xF4(__m128i current_bytes,
|
||||
__m128i *has_error) {
|
||||
// unsigned, saturates to 0 below max
|
||||
*has_error = _mm_or_si128(*has_error,
|
||||
_mm_subs_epu8(current_bytes, _mm_set1_epi8(0xF4u)));
|
||||
}
|
||||
|
||||
static inline __m128i continuation_lengths(__m128i high_nibbles) {
|
||||
return _mm_shuffle_epi8(
|
||||
_mm_setr_epi8(1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII)
|
||||
0, 0, 0, 0, // 10xx (continuation)
|
||||
2, 2, // 110x
|
||||
3, // 1110
|
||||
4), // 1111, next should be 0 (not checked here)
|
||||
high_nibbles);
|
||||
}
|
||||
|
||||
static inline __m128i carry_continuations(__m128i initial_lengths,
|
||||
__m128i previous_carries) {
|
||||
|
||||
__m128i right1 =
|
||||
_mm_subs_epu8(_mm_alignr_epi8(initial_lengths, previous_carries, 16 - 1),
|
||||
_mm_set1_epi8(1));
|
||||
__m128i sum = _mm_add_epi8(initial_lengths, right1);
|
||||
|
||||
__m128i right2 = _mm_subs_epu8(_mm_alignr_epi8(sum, previous_carries, 16 - 2),
|
||||
_mm_set1_epi8(2));
|
||||
return _mm_add_epi8(sum, right2);
|
||||
}
|
||||
|
||||
static inline void check_continuations(__m128i initial_lengths, __m128i carries,
|
||||
__m128i *has_error) {
|
||||
|
||||
// overlap || underlap
|
||||
// carry > length && length > 0 || !(carry > length) && !(length > 0)
|
||||
// (carries > length) == (lengths > 0)
|
||||
__m128i overunder =
|
||||
_mm_cmpeq_epi8(_mm_cmpgt_epi8(carries, initial_lengths),
|
||||
_mm_cmpgt_epi8(initial_lengths, _mm_setzero_si128()));
|
||||
|
||||
*has_error = _mm_or_si128(*has_error, overunder);
|
||||
}
|
||||
|
||||
// when 0xED is found, next byte must be no larger than 0x9F
|
||||
// when 0xF4 is found, next byte must be no larger than 0x8F
|
||||
// next byte must be continuation, ie sign bit is set, so signed < is ok
|
||||
static inline void check_first_continuation_max(__m128i current_bytes,
|
||||
__m128i off1_current_bytes,
|
||||
__m128i *has_error) {
|
||||
__m128i maskED = _mm_cmpeq_epi8(off1_current_bytes, _mm_set1_epi8(0xEDu));
|
||||
__m128i maskF4 = _mm_cmpeq_epi8(off1_current_bytes, _mm_set1_epi8(0xF4u));
|
||||
|
||||
__m128i badfollowED = _mm_and_si128(
|
||||
_mm_cmpgt_epi8(current_bytes, _mm_set1_epi8(0x9Fu)), maskED);
|
||||
__m128i badfollowF4 = _mm_and_si128(
|
||||
_mm_cmpgt_epi8(current_bytes, _mm_set1_epi8(0x8Fu)), maskF4);
|
||||
|
||||
*has_error = _mm_or_si128(*has_error, _mm_or_si128(badfollowED, badfollowF4));
|
||||
}
|
||||
|
||||
// map off1_hibits => error condition
|
||||
// hibits off1 cur
|
||||
// C => < C2 && true
|
||||
// E => < E1 && < A0
|
||||
// F => < F1 && < 90
|
||||
// else false && false
|
||||
static inline void check_overlong(__m128i current_bytes,
|
||||
__m128i off1_current_bytes, __m128i hibits,
|
||||
__m128i previous_hibits, __m128i *has_error) {
|
||||
__m128i off1_hibits = _mm_alignr_epi8(hibits, previous_hibits, 16 - 1);
|
||||
__m128i initial_mins = _mm_shuffle_epi8(
|
||||
_mm_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, // 10xx => false
|
||||
0xC2u, -128, // 110x
|
||||
0xE1u, // 1110
|
||||
0xF1u),
|
||||
off1_hibits);
|
||||
|
||||
__m128i initial_under = _mm_cmpgt_epi8(initial_mins, off1_current_bytes);
|
||||
|
||||
__m128i second_mins = _mm_shuffle_epi8(
|
||||
_mm_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128,
|
||||
-128, -128, // 10xx => false
|
||||
127, 127, // 110x => true
|
||||
0xA0u, // 1110
|
||||
0x90u),
|
||||
off1_hibits);
|
||||
__m128i second_under = _mm_cmpgt_epi8(second_mins, current_bytes);
|
||||
*has_error =
|
||||
_mm_or_si128(*has_error, _mm_and_si128(initial_under, second_under));
|
||||
}
|
||||
|
||||
struct processed_utf_bytes {
|
||||
__m128i raw_bytes;
|
||||
__m128i high_nibbles;
|
||||
__m128i carried_continuations;
|
||||
};
|
||||
|
||||
static inline void count_nibbles(__m128i bytes,
|
||||
struct processed_utf_bytes *answer) {
|
||||
answer->raw_bytes = bytes;
|
||||
answer->high_nibbles =
|
||||
_mm_and_si128(_mm_srli_epi16(bytes, 4), _mm_set1_epi8(0x0F));
|
||||
}
|
||||
|
||||
// check whether the current bytes are valid UTF-8
|
||||
// at the end of the function, previous gets updated
|
||||
static struct processed_utf_bytes
|
||||
check_utf8_bytes(__m128i current_bytes, struct processed_utf_bytes *previous,
|
||||
__m128i *has_error) {
|
||||
struct processed_utf_bytes pb;
|
||||
count_nibbles(current_bytes, &pb);
|
||||
|
||||
check_smaller_than_0xF4(current_bytes, has_error);
|
||||
|
||||
__m128i initial_lengths = continuation_lengths(pb.high_nibbles);
|
||||
|
||||
pb.carried_continuations =
|
||||
carry_continuations(initial_lengths, previous->carried_continuations);
|
||||
|
||||
check_continuations(initial_lengths, pb.carried_continuations, has_error);
|
||||
|
||||
__m128i off1_current_bytes =
|
||||
_mm_alignr_epi8(pb.raw_bytes, previous->raw_bytes, 16 - 1);
|
||||
check_first_continuation_max(current_bytes, off1_current_bytes, has_error);
|
||||
|
||||
check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles,
|
||||
previous->high_nibbles, has_error);
|
||||
return pb;
|
||||
}
|
||||
} // namespace simdjson
|
||||
UNTARGET_REGION // westmere
|
||||
|
||||
#endif // IS_X86_64
|
||||
|
||||
#endif
|
||||
@@ -1,121 +0,0 @@
|
||||
#ifndef SIMDJSON_STAGE1_FIND_MARKS_H
|
||||
#define SIMDJSON_STAGE1_FIND_MARKS_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/portability.h"
|
||||
#include "simdjson/simdjson.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
template <Architecture> struct simd_input;
|
||||
|
||||
template <Architecture> uint64_t compute_quote_mask(uint64_t quote_bits);
|
||||
|
||||
namespace {
|
||||
// for when clmul is unavailable
|
||||
[[maybe_unused]] uint64_t portable_compute_quote_mask(uint64_t quote_bits) {
|
||||
uint64_t quote_mask = quote_bits ^ (quote_bits << 1);
|
||||
quote_mask = quote_mask ^ (quote_mask << 2);
|
||||
quote_mask = quote_mask ^ (quote_mask << 4);
|
||||
quote_mask = quote_mask ^ (quote_mask << 8);
|
||||
quote_mask = quote_mask ^ (quote_mask << 16);
|
||||
quote_mask = quote_mask ^ (quote_mask << 32);
|
||||
return quote_mask;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Holds the state required to perform check_utf8().
|
||||
template <Architecture> struct utf8_checking_state;
|
||||
|
||||
template <Architecture T>
|
||||
void check_utf8(simd_input<T> in, utf8_checking_state<T> &state);
|
||||
|
||||
// Checks if the utf8 validation has found any error.
|
||||
template <Architecture T>
|
||||
ErrorValues check_utf8_errors(utf8_checking_state<T> &state);
|
||||
|
||||
// a straightforward comparison of a mask against input.
|
||||
template <Architecture T>
|
||||
uint64_t cmp_mask_against_input(simd_input<T> in, uint8_t m);
|
||||
|
||||
template <Architecture T> simd_input<T> fill_input(const uint8_t *ptr);
|
||||
|
||||
// find all values less than or equal than the content of maxval (using unsigned
|
||||
// arithmetic)
|
||||
template <Architecture T>
|
||||
uint64_t unsigned_lteq_against_input(simd_input<T> in, uint8_t m);
|
||||
|
||||
template <Architecture T>
|
||||
really_inline uint64_t find_odd_backslash_sequences(
|
||||
simd_input<T> in, uint64_t &prev_iter_ends_odd_backslash);
|
||||
|
||||
template <Architecture T>
|
||||
really_inline uint64_t find_quote_mask_and_bits(
|
||||
simd_input<T> in, uint64_t odd_ends, uint64_t &prev_iter_inside_quote,
|
||||
uint64_t "e_bits, uint64_t &error_mask);
|
||||
|
||||
// do a 'shufti' to detect structural JSON characters
|
||||
// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c
|
||||
// these go into the first 3 buckets of the comparison (1/2/4)
|
||||
|
||||
// we are also interested in the four whitespace characters
|
||||
// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d
|
||||
// these go into the next 2 buckets of the comparison (8/16)
|
||||
template <Architecture T>
|
||||
void find_whitespace_and_structurals(simd_input<T> in, uint64_t &whitespace,
|
||||
uint64_t &structurals);
|
||||
|
||||
// return a updated structural bit vector with quoted contents cleared out and
|
||||
// pseudo-structural characters added to the mask
|
||||
// updates prev_iter_ends_pseudo_pred which tells us whether the previous
|
||||
// iteration ended on a whitespace or a structural character (which means that
|
||||
// the next iteration
|
||||
// will have a pseudo-structural character at its start)
|
||||
really_inline uint64_t finalize_structurals(
|
||||
uint64_t structurals, uint64_t whitespace, uint64_t quote_mask,
|
||||
uint64_t quote_bits, uint64_t &prev_iter_ends_pseudo_pred) {
|
||||
// mask off anything inside quotes
|
||||
structurals &= ~quote_mask;
|
||||
// add the real quote bits back into our bit_mask as well, so we can
|
||||
// quickly traverse the strings we've spent all this trouble gathering
|
||||
structurals |= quote_bits;
|
||||
// Now, establish "pseudo-structural characters". These are non-whitespace
|
||||
// characters that are (a) outside quotes and (b) have a predecessor that's
|
||||
// either whitespace or a structural character. This means that subsequent
|
||||
// passes will get a chance to encounter the first character of every string
|
||||
// of non-whitespace and, if we're parsing an atom like true/false/null or a
|
||||
// number we can stop at the first whitespace or structural character
|
||||
// following it.
|
||||
|
||||
// a qualified predecessor is something that can happen 1 position before an
|
||||
// pseudo-structural character
|
||||
uint64_t pseudo_pred = structurals | whitespace;
|
||||
|
||||
uint64_t shifted_pseudo_pred =
|
||||
(pseudo_pred << 1) | prev_iter_ends_pseudo_pred;
|
||||
prev_iter_ends_pseudo_pred = pseudo_pred >> 63;
|
||||
uint64_t pseudo_structurals =
|
||||
shifted_pseudo_pred & (~whitespace) & (~quote_mask);
|
||||
structurals |= pseudo_structurals;
|
||||
|
||||
// now, we've used our close quotes all we need to. So let's switch them off
|
||||
// they will be off in the quote mask and on in quote bits.
|
||||
structurals &= ~(quote_bits & ~quote_mask);
|
||||
return structurals;
|
||||
}
|
||||
|
||||
template <Architecture T = Architecture::NATIVE>
|
||||
int find_structural_bits(const uint8_t *buf, size_t len,
|
||||
simdjson::ParsedJson &pj);
|
||||
|
||||
template <Architecture T = Architecture::NATIVE>
|
||||
int find_structural_bits(const char *buf, size_t len,
|
||||
simdjson::ParsedJson &pj) {
|
||||
return find_structural_bits((const uint8_t *)buf, len, pj);
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif
|
||||
@@ -1,210 +0,0 @@
|
||||
#ifndef SIMDJSON_STAGE1_FIND_MARKS_ARM64_H
|
||||
#define SIMDJSON_STAGE1_FIND_MARKS_ARM64_H
|
||||
|
||||
#include "simdjson/simdutf8check_arm64.h"
|
||||
#include "simdjson/stage1_find_marks.h"
|
||||
#include "simdjson/stage1_find_marks_flatten.h"
|
||||
#include "simdjson/stage1_find_marks_macros.h"
|
||||
|
||||
#ifdef IS_ARM64
|
||||
namespace simdjson {
|
||||
template <> struct simd_input<Architecture::ARM64> {
|
||||
uint8x16_t i0;
|
||||
uint8x16_t i1;
|
||||
uint8x16_t i2;
|
||||
uint8x16_t i3;
|
||||
};
|
||||
|
||||
template <>
|
||||
really_inline simd_input<Architecture::ARM64>
|
||||
fill_input<Architecture::ARM64>(const uint8_t *ptr) {
|
||||
struct simd_input<Architecture::ARM64> in;
|
||||
in.i0 = vld1q_u8(ptr + 0);
|
||||
in.i1 = vld1q_u8(ptr + 16);
|
||||
in.i2 = vld1q_u8(ptr + 32);
|
||||
in.i3 = vld1q_u8(ptr + 48);
|
||||
return in;
|
||||
}
|
||||
|
||||
really_inline uint16_t neon_movemask(uint8x16_t input) {
|
||||
const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||
uint8x16_t minput = vandq_u8(input, bit_mask);
|
||||
uint8x16_t tmp = vpaddq_u8(minput, minput);
|
||||
tmp = vpaddq_u8(tmp, tmp);
|
||||
tmp = vpaddq_u8(tmp, tmp);
|
||||
return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0);
|
||||
}
|
||||
|
||||
really_inline uint64_t neon_movemask_bulk(uint8x16_t p0, uint8x16_t p1,
|
||||
uint8x16_t p2, uint8x16_t p3) {
|
||||
const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||
uint8x16_t t0 = vandq_u8(p0, bit_mask);
|
||||
uint8x16_t t1 = vandq_u8(p1, bit_mask);
|
||||
uint8x16_t t2 = vandq_u8(p2, bit_mask);
|
||||
uint8x16_t t3 = vandq_u8(p3, bit_mask);
|
||||
uint8x16_t sum0 = vpaddq_u8(t0, t1);
|
||||
uint8x16_t sum1 = vpaddq_u8(t2, t3);
|
||||
sum0 = vpaddq_u8(sum0, sum1);
|
||||
sum0 = vpaddq_u8(sum0, sum0);
|
||||
return vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0);
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline uint64_t
|
||||
compute_quote_mask<Architecture::ARM64>(uint64_t quote_bits) {
|
||||
#ifdef __ARM_FEATURE_CRYPTO // some ARM processors lack this extension
|
||||
return vmull_p64(-1ULL, quote_bits);
|
||||
#else
|
||||
return portable_compute_quote_mask(quote_bits);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <> struct utf8_checking_state<Architecture::ARM64> {
|
||||
int8x16_t has_error{};
|
||||
processed_utf_bytes previous{};
|
||||
};
|
||||
|
||||
// Checks that all bytes are ascii
|
||||
really_inline bool check_ascii_neon(simd_input<Architecture::ARM64> in) {
|
||||
// checking if the most significant bit is always equal to 0.
|
||||
uint8x16_t high_bit = vdupq_n_u8(0x80);
|
||||
uint8x16_t t0 = vorrq_u8(in.i0, in.i1);
|
||||
uint8x16_t t1 = vorrq_u8(in.i2, in.i3);
|
||||
uint8x16_t t3 = vorrq_u8(t0, t1);
|
||||
uint8x16_t t4 = vandq_u8(t3, high_bit);
|
||||
uint64x2_t v64 = vreinterpretq_u64_u8(t4);
|
||||
uint32x2_t v32 = vqmovn_u64(v64);
|
||||
uint64x1_t result = vreinterpret_u64_u32(v32);
|
||||
return vget_lane_u64(result, 0) == 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline void check_utf8<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in,
|
||||
utf8_checking_state<Architecture::ARM64> &state) {
|
||||
if (check_ascii_neon(in)) {
|
||||
// All bytes are ascii. Therefore the byte that was just before must be
|
||||
// ascii too. We only check the byte that was just before simd_input. Nines
|
||||
// are arbitrary values.
|
||||
const int8x16_t verror =
|
||||
(int8x16_t){9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1};
|
||||
state.has_error =
|
||||
vorrq_s8(vreinterpretq_s8_u8(
|
||||
vcgtq_s8(state.previous.carried_continuations, verror)),
|
||||
state.has_error);
|
||||
} else {
|
||||
// it is not ascii so we have to do heavy work
|
||||
state.previous = check_utf8_bytes(vreinterpretq_s8_u8(in.i0),
|
||||
&(state.previous), &(state.has_error));
|
||||
state.previous = check_utf8_bytes(vreinterpretq_s8_u8(in.i1),
|
||||
&(state.previous), &(state.has_error));
|
||||
state.previous = check_utf8_bytes(vreinterpretq_s8_u8(in.i2),
|
||||
&(state.previous), &(state.has_error));
|
||||
state.previous = check_utf8_bytes(vreinterpretq_s8_u8(in.i3),
|
||||
&(state.previous), &(state.has_error));
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline ErrorValues check_utf8_errors<Architecture::ARM64>(
|
||||
utf8_checking_state<Architecture::ARM64> &state) {
|
||||
uint64x2_t v64 = vreinterpretq_u64_s8(state.has_error);
|
||||
uint32x2_t v32 = vqmovn_u64(v64);
|
||||
uint64x1_t result = vreinterpret_u64_u32(v32);
|
||||
return vget_lane_u64(result, 0) != 0 ? simdjson::UTF8_ERROR
|
||||
: simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline uint64_t cmp_mask_against_input<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in, uint8_t m) {
|
||||
const uint8x16_t mask = vmovq_n_u8(m);
|
||||
uint8x16_t cmp_res_0 = vceqq_u8(in.i0, mask);
|
||||
uint8x16_t cmp_res_1 = vceqq_u8(in.i1, mask);
|
||||
uint8x16_t cmp_res_2 = vceqq_u8(in.i2, mask);
|
||||
uint8x16_t cmp_res_3 = vceqq_u8(in.i3, mask);
|
||||
return neon_movemask_bulk(cmp_res_0, cmp_res_1, cmp_res_2, cmp_res_3);
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline uint64_t unsigned_lteq_against_input<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in, uint8_t m) {
|
||||
const uint8x16_t mask = vmovq_n_u8(m);
|
||||
uint8x16_t cmp_res_0 = vcleq_u8(in.i0, mask);
|
||||
uint8x16_t cmp_res_1 = vcleq_u8(in.i1, mask);
|
||||
uint8x16_t cmp_res_2 = vcleq_u8(in.i2, mask);
|
||||
uint8x16_t cmp_res_3 = vcleq_u8(in.i3, mask);
|
||||
return neon_movemask_bulk(cmp_res_0, cmp_res_1, cmp_res_2, cmp_res_3);
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline uint64_t find_odd_backslash_sequences<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in,
|
||||
uint64_t &prev_iter_ends_odd_backslash) {
|
||||
FIND_ODD_BACKSLASH_SEQUENCES(Architecture::ARM64, in,
|
||||
prev_iter_ends_odd_backslash);
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline uint64_t find_quote_mask_and_bits<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in, uint64_t odd_ends,
|
||||
uint64_t &prev_iter_inside_quote, uint64_t "e_bits,
|
||||
uint64_t &error_mask) {
|
||||
FIND_QUOTE_MASK_AND_BITS(Architecture::ARM64, in, odd_ends,
|
||||
prev_iter_inside_quote, quote_bits, error_mask)
|
||||
}
|
||||
|
||||
template <>
|
||||
really_inline void find_whitespace_and_structurals<Architecture::ARM64>(
|
||||
simd_input<Architecture::ARM64> in, uint64_t &whitespace,
|
||||
uint64_t &structurals) {
|
||||
const uint8x16_t low_nibble_mask =
|
||||
(uint8x16_t){16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0};
|
||||
const uint8x16_t high_nibble_mask =
|
||||
(uint8x16_t){8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0};
|
||||
const uint8x16_t structural_shufti_mask = vmovq_n_u8(0x7);
|
||||
const uint8x16_t whitespace_shufti_mask = vmovq_n_u8(0x18);
|
||||
const uint8x16_t low_nib_and_mask = vmovq_n_u8(0xf);
|
||||
|
||||
uint8x16_t nib_0_lo = vandq_u8(in.i0, low_nib_and_mask);
|
||||
uint8x16_t nib_0_hi = vshrq_n_u8(in.i0, 4);
|
||||
uint8x16_t shuf_0_lo = vqtbl1q_u8(low_nibble_mask, nib_0_lo);
|
||||
uint8x16_t shuf_0_hi = vqtbl1q_u8(high_nibble_mask, nib_0_hi);
|
||||
uint8x16_t v_0 = vandq_u8(shuf_0_lo, shuf_0_hi);
|
||||
|
||||
uint8x16_t nib_1_lo = vandq_u8(in.i1, low_nib_and_mask);
|
||||
uint8x16_t nib_1_hi = vshrq_n_u8(in.i1, 4);
|
||||
uint8x16_t shuf_1_lo = vqtbl1q_u8(low_nibble_mask, nib_1_lo);
|
||||
uint8x16_t shuf_1_hi = vqtbl1q_u8(high_nibble_mask, nib_1_hi);
|
||||
uint8x16_t v_1 = vandq_u8(shuf_1_lo, shuf_1_hi);
|
||||
|
||||
uint8x16_t nib_2_lo = vandq_u8(in.i2, low_nib_and_mask);
|
||||
uint8x16_t nib_2_hi = vshrq_n_u8(in.i2, 4);
|
||||
uint8x16_t shuf_2_lo = vqtbl1q_u8(low_nibble_mask, nib_2_lo);
|
||||
uint8x16_t shuf_2_hi = vqtbl1q_u8(high_nibble_mask, nib_2_hi);
|
||||
uint8x16_t v_2 = vandq_u8(shuf_2_lo, shuf_2_hi);
|
||||
|
||||
uint8x16_t nib_3_lo = vandq_u8(in.i3, low_nib_and_mask);
|
||||
uint8x16_t nib_3_hi = vshrq_n_u8(in.i3, 4);
|
||||
uint8x16_t shuf_3_lo = vqtbl1q_u8(low_nibble_mask, nib_3_lo);
|
||||
uint8x16_t shuf_3_hi = vqtbl1q_u8(high_nibble_mask, nib_3_hi);
|
||||
uint8x16_t v_3 = vandq_u8(shuf_3_lo, shuf_3_hi);
|
||||
|
||||
uint8x16_t tmp_0 = vtstq_u8(v_0, structural_shufti_mask);
|
||||
uint8x16_t tmp_1 = vtstq_u8(v_1, structural_shufti_mask);
|
||||
uint8x16_t tmp_2 = vtstq_u8(v_2, structural_shufti_mask);
|
||||
uint8x16_t tmp_3 = vtstq_u8(v_3, structural_shufti_mask);
|
||||
structurals = neon_movemask_bulk(tmp_0, tmp_1, tmp_2, tmp_3);
|
||||
|
||||
uint8x16_t tmp_ws_0 = vtstq_u8(v_0, whitespace_shufti_mask);
|
||||
uint8x16_t tmp_ws_1 = vtstq_u8(v_1, whitespace_shufti_mask);
|
||||
uint8x16_t tmp_ws_2 = vtstq_u8(v_2, whitespace_shufti_mask);
|
||||
uint8x16_t tmp_ws_3 = vtstq_u8(v_3, whitespace_shufti_mask);
|
||||
whitespace = neon_movemask_bulk(tmp_ws_0, tmp_ws_1, tmp_ws_2, tmp_ws_3);
|
||||
}
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // IS_ARM64
|
||||
#endif // SIMDJSON_STAGE1_FIND_MARKS_ARM64_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user