mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cc3b4c650 | |||
| 146191d791 | |||
| 80db053be3 | |||
| 6a86ef5a7d | |||
| 500e5d6759 | |||
| e3f2c9f29a | |||
| 3458e6248b | |||
| 14039d05a9 | |||
| c592da4937 | |||
| 61fb4244f2 | |||
| a503e022d8 | |||
| 0942dc0764 | |||
| 0d6919dd99 | |||
| 3e8e797bc2 | |||
| 0a907ec694 | |||
| f1b4a54991 | |||
| 58e7106df1 | |||
| 7bf391c54a | |||
| 07a6e098c8 | |||
| e4897d6b54 | |||
| 23026d966b | |||
| 3cd98df30d | |||
| 001be23258 | |||
| c85b6682e0 | |||
| bb2bc98a22 | |||
| 43da4f7ccc | |||
| 37e6d1e9c7 | |||
| 1d9926698e | |||
| b04f64e02c | |||
| c6d710b14b | |||
| 2e07850622 | |||
| 1b6888281e | |||
| ce94411dff | |||
| 58a3098cd8 | |||
| 8a68163905 | |||
| 1f98e64b71 | |||
| a9480a768b | |||
| ed94514fc2 | |||
| 1f41cc2030 | |||
| 6455ec36ec | |||
| 676a3d068c | |||
| 5533f8d87b | |||
| 93af7b61ce | |||
| 364ad5529d | |||
| 1974a46fe0 | |||
| 5327ab9903 | |||
| 235d191bae | |||
| 4eb80ec75a | |||
| 2900459222 | |||
| 9088792b0e | |||
| b41fe7beab | |||
| 00f9bb8a07 | |||
| a90e1637cb | |||
| 4bad5c0241 | |||
| ce09d82fc7 | |||
| 5b926b8196 | |||
| c719ccdb48 | |||
| 9f1786aeb1 | |||
| 0bb83e06bc | |||
| 2ba67c2bc2 | |||
| cae91983ec | |||
| 3190ef0c1f | |||
| c7c1372833 | |||
| ba02cda55f | |||
| 512a94afaa | |||
| 6d978c383a | |||
| 938678f87f | |||
| 9dcf5fca5b | |||
| 88f0dc4726 | |||
| 8ae7910aba | |||
| b70e85fd10 | |||
| 99bc591366 | |||
| f4963cd1c5 | |||
| 4859cb8528 | |||
| c42b91980b | |||
| 8b3c8820e0 | |||
| 29bc78a486 | |||
| 30fe86ed32 | |||
| 85cefd5a00 | |||
| b5a328e0ca | |||
| 209a2e8fc3 | |||
| 8b978e6aea | |||
| b4df0e7c9e | |||
| 76aeda6b00 | |||
| 1a0ad6d9c3 | |||
| 6b219e3e25 | |||
| 1e90691013 | |||
| 3577c87c88 | |||
| baf6607e74 | |||
| a700848bae | |||
| 9bbfd5804e | |||
| b234d74f43 | |||
| cd49ff330d | |||
| 49faf7af1a | |||
| c892b83c93 | |||
| 985b52331a | |||
| 021dded9dd | |||
| 8fd0cdc732 | |||
| fe7a4d42d3 | |||
| e89d6353af | |||
| c5bb74d184 | |||
| 874349c928 | |||
| 157604b3a5 | |||
| b935544d65 | |||
| 03271df579 | |||
| 0633d3a07d | |||
| 045377a594 | |||
| b5c8030f19 | |||
| 7c2072789c | |||
| f75e856d2b | |||
| 44d689bc6e | |||
| 9e433c2f19 | |||
| 21b6279b74 | |||
| 4d89076bdc | |||
| 71e4ff7e03 | |||
| d2dfda6583 | |||
| 6a855f528b | |||
| fb93109c2d | |||
| bd190af7a3 | |||
| 44268b0c6b | |||
| 6451e5e7d1 | |||
| 2b3c4c68e4 | |||
| 7030cf2433 | |||
| 26d7881b80 | |||
| 21fe42b28c | |||
| d5ecf68d26 | |||
| 0ddff4ec7d | |||
| 283ac3191f | |||
| 4dd0c80dad | |||
| 3b53c6ca47 | |||
| 4e3b4809ea | |||
| a90b8fb449 | |||
| 1da509027e | |||
| 5b96e4761e | |||
| 311ea79238 | |||
| 98be2c91df | |||
| 2657e5e226 | |||
| cfcb0d4fb7 | |||
| 97d03f3215 | |||
| 4065529bdf | |||
| 0a6260b1d8 | |||
| 12caf2510e | |||
| a58d2f710d | |||
| 6be2db8c42 | |||
| 5cf68416d8 | |||
| ebcb3c6b3b | |||
| 04267e0f6b | |||
| a540e6afc5 | |||
| f1841e48b3 | |||
| 9865bb6904 | |||
| e06ddea784 | |||
| 8b5a89c136 | |||
| da093c1982 | |||
| 048fb6278a | |||
| f1b0778f79 | |||
| 0e584fa4a5 | |||
| f44386008c | |||
| 60c139a844 | |||
| f410213003 | |||
| 19cb5d57db | |||
| 30b912fc81 | |||
| 7fc07e2d5e | |||
| 72c83d9430 | |||
| bfbac12f76 | |||
| 461f7dc9f9 | |||
| 3e5497e2f9 | |||
| 6ecbcc7c19 | |||
| 8cef02e8e8 | |||
| caabfd14b3 | |||
| 2ffbaa9578 | |||
| c40aeaec3a | |||
| 80e84a3ad0 | |||
| 0552335ec1 | |||
| 7aea774b21 | |||
| 4d4ed92055 | |||
| f0ec26992a | |||
| 62e8332b34 | |||
| 0925f71987 | |||
| 4c11652808 | |||
| 5b10c38e43 | |||
| 3316df9195 |
+1
-1
@@ -17,7 +17,7 @@ environment:
|
||||
- job_name: VS2019 (Win32)
|
||||
platform: Win32
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON # This should be the default. Testing anyway.
|
||||
CTEST_ARGS: -E checkperf
|
||||
CTEST_ARGS: -E "checkperf|ondemand_basictests"
|
||||
- job_name: VS2015
|
||||
image: Visual Studio 2015
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF
|
||||
|
||||
+64
-28
@@ -1,5 +1,8 @@
|
||||
version: 2.1
|
||||
|
||||
|
||||
# We constantly run out of memory so please do not use parallelism (-j, -j4).
|
||||
|
||||
# Reusable image / compiler definitions
|
||||
executors:
|
||||
gcc8:
|
||||
@@ -8,8 +11,8 @@ executors:
|
||||
environment:
|
||||
CXX: g++-8
|
||||
CC: gcc-8
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
gcc9:
|
||||
docker:
|
||||
@@ -17,8 +20,8 @@ executors:
|
||||
environment:
|
||||
CXX: g++-9
|
||||
CC: gcc-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
gcc10:
|
||||
docker:
|
||||
@@ -26,8 +29,8 @@ executors:
|
||||
environment:
|
||||
CXX: g++-10
|
||||
CC: gcc-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang10:
|
||||
docker:
|
||||
@@ -35,8 +38,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-10
|
||||
CC: clang-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang9:
|
||||
docker:
|
||||
@@ -44,8 +47,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-9
|
||||
CC: clang-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
clang6:
|
||||
docker:
|
||||
@@ -53,8 +56,8 @@ executors:
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
CC: clang-6.0
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
BUILD_FLAGS:
|
||||
CTEST_FLAGS: --output-on-failure
|
||||
|
||||
# Reusable test commands (and initializer for clang 6)
|
||||
commands:
|
||||
@@ -68,19 +71,22 @@ commands:
|
||||
- checkout
|
||||
- run: mkdir -p build
|
||||
|
||||
cmake_build:
|
||||
cmake_build_cache:
|
||||
steps:
|
||||
- cmake_prep
|
||||
- run: |
|
||||
cd build &&
|
||||
cmake $CMAKE_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
make $BUILD_FLAGS all
|
||||
- run: cmake $CMAKE_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination -B build .
|
||||
|
||||
cmake_build:
|
||||
steps:
|
||||
- cmake_build_cache
|
||||
- run: cmake --build build
|
||||
|
||||
cmake_test:
|
||||
steps:
|
||||
- cmake_build
|
||||
- run: |
|
||||
cd build && tools/json2json -h &&
|
||||
cd build &&
|
||||
tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -L acceptance &&
|
||||
ctest $CTEST_FLAGS -LE acceptance -E checkperf
|
||||
|
||||
@@ -88,13 +94,23 @@ commands:
|
||||
steps:
|
||||
- cmake_build
|
||||
- run: |
|
||||
cd build && tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -L acceptance -LE per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=haswell ctest $CTEST_FLAGS -L per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=westmere ctest $CTEST_FLAGS -L per_implementation &&SIMDJSON_FORCE_IMPLEMENTATION=fallback ctest $CTEST_FLAGS -L per_implementation &&
|
||||
cd build &&
|
||||
tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -DSIMDJSON_IMPLEMENTATION="haswell;westmere;fallback" -L acceptance -LE per_implementation &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=haswell ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=westmere ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
SIMDJSON_FORCE_IMPLEMENTATION=fallback ctest $CTEST_FLAGS -L per_implementation -E checkperf &&
|
||||
ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
|
||||
|
||||
cmake_perftest:
|
||||
steps:
|
||||
- cmake_build_cache
|
||||
- run: |
|
||||
cmake --build build --target checkperf &&
|
||||
cd build &&
|
||||
ctest --output-on-failure -R checkperf
|
||||
|
||||
# we not only want cmake to build and run tests, but we want also a successful installation from which we can build, link and run programs
|
||||
cmake_install_test: # this version builds, install, test and then verify from the installation
|
||||
steps:
|
||||
@@ -113,11 +129,16 @@ jobs:
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_JUST_LIBRARY=ON }
|
||||
steps: [ cmake_build, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
gcc10-perftest:
|
||||
description: Build and run performance tests on GCC 10 and AVX 2 with a cmake static build, this test performance regression
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_perftest ]
|
||||
gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake static build, this test performance regression
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake static build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test_all, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
steps: [ cmake_test, cmake_install_test, cmake_installed_test_cxx20 ]
|
||||
clang6:
|
||||
description: Build and run tests on clang 6 and AVX 2 with a cmake static build
|
||||
executor: clang6
|
||||
@@ -138,12 +159,12 @@ jobs:
|
||||
sanitize-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: -j4 --output-on-failure -E checkperf }
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
sanitize-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake sanitize build
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: -j4 --output-on-failure -E checkperf }
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
# dynamic
|
||||
@@ -188,15 +209,25 @@ jobs:
|
||||
|
||||
# make (test and checkperf)
|
||||
arch-haswell-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -march=haswell
|
||||
description: Build, run tests and check performance on GCC 10 with -march=haswell
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=haswell }
|
||||
steps: [ cmake_test ]
|
||||
arch-nehalem-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -march=nehalem
|
||||
description: Build, run tests and check performance on GCC 10 with -march=nehalem
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=nehalem }
|
||||
steps: [ cmake_test ]
|
||||
sanitize-haswell-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=haswell, CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
sanitize-haswell-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake sanitize build
|
||||
executor: clang10
|
||||
environment: { CXXFLAGS: -march=haswell, CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, CTEST_FLAGS: --output-on-failure -E checkperf }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
@@ -227,6 +258,11 @@ workflows:
|
||||
- arch-haswell-gcc10
|
||||
- arch-nehalem-gcc10
|
||||
|
||||
|
||||
# sanitized single-implementation tests
|
||||
- sanitize-haswell-gcc10
|
||||
- sanitize-haswell-clang10
|
||||
|
||||
# testing "just the library"
|
||||
- justlib-gcc10
|
||||
|
||||
|
||||
+28
-8
@@ -50,7 +50,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -76,7 +76,7 @@ steps:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- mkdir build
|
||||
@@ -140,7 +140,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -165,7 +165,7 @@ steps:
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION=haswell;westmere;fallback
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
@@ -189,7 +189,7 @@ steps:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -274,7 +274,7 @@ steps:
|
||||
image: gcc:8
|
||||
environment:
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
@@ -299,7 +299,7 @@ steps:
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON -DSIMDJSON_IMPLEMENTATION=arm64;fallback
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
@@ -326,7 +326,7 @@ steps:
|
||||
CXX: clang++-9
|
||||
BUILD_FLAGS: -- -j 4
|
||||
CMAKE_FLAGS: -GNinja -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CXXFLAGS: -stdlib=libc++
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
@@ -382,6 +382,26 @@ steps:
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-fuzz
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and run fuzzers shortly
|
||||
image: ubuntu:20.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -q -y clang cmake git wget zip ninja-build
|
||||
- wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
- tar xf corpus.tar && rm corpus.tar
|
||||
- fuzz/build_like_ossfuzz.sh
|
||||
- mkdir -p common_out
|
||||
- for fuzzer in build/fuzz/fuzz_* ; do echo $fuzzer;$fuzzer common_out out/* -max_total_time=40; done
|
||||
---
|
||||
kind: pipeline
|
||||
name: stylecheck
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
|
||||
@@ -12,6 +12,7 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
@@ -12,6 +12,7 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
We do not make changes to simdjson without clearly identifiable benefits, which typically means either performance improvements, bug fixes or new features. Avoid bike-shedding: we all have opinions about how to write code, but we want to focus on what makes simdjson objectively better.
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Before submitting an issue, please ensure that you have read the documentation:
|
||||
* Basics is an overview of how to use simdjson and its APIs: https://github.com/simdjson/simdjson/blob/master/doc/basics.md
|
||||
* Performance shows some more advanced scenarios and how to tune for them: https://github.com/simdjson/simdjson/blob/master/doc/performance.md
|
||||
* Contributing: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
* We follow the [JSON specification as described by RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt) (T. Bray, 2017).
|
||||
|
||||
We do not make changes to simdjson without clearly identifiable benefits, which typically means either performance improvements, bug fixes or new features. Avoid bike-shedding: we all have opinions about how to write code, but we want to focus on what makes simdjson objectively better.
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Alpine Linux
|
||||
'on':
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: start docker
|
||||
run: |
|
||||
docker run -w /src -dit --name alpine -v $PWD:/src alpine:latest
|
||||
echo 'docker exec alpine "$@";' > ./alpine.sh
|
||||
chmod +x ./alpine.sh
|
||||
- name: install packages
|
||||
run: |
|
||||
./alpine.sh apk update
|
||||
./alpine.sh apk add build-base cmake g++ linux-headers git bash
|
||||
- name: cmake
|
||||
run: |
|
||||
./alpine.sh cmake -B build_for_alpine
|
||||
- name: build
|
||||
run: |
|
||||
./alpine.sh cmake --build build_for_alpine
|
||||
- name: test
|
||||
run: |
|
||||
./alpine.sh bash -c "cd build_for_alpine && ctest"
|
||||
@@ -1,11 +1,9 @@
|
||||
name: Run fuzzers on stored corpus and test it with valgrind
|
||||
name: Fuzz and run valgrind
|
||||
|
||||
# In the case of a pull request happening at the same time as a cron
|
||||
# job, there is a risk two jobs run at the same time. Therefore,
|
||||
# the corpus is only uploaded for the master branch. Pull requests will
|
||||
# fuzz for a short while, but the results are not uploaded.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: 23 */8 * * *
|
||||
@@ -14,8 +12,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
allfuzzers: parser dump dump_raw_tape print_json
|
||||
artifactsprefix: -artifact_prefix=fuzzfailure/
|
||||
# fuzzers that use the default implementation
|
||||
defaultimplfuzzers: atpointer dump dump_raw_tape element minify parser print_json
|
||||
# fuzzers that loop over the implementations themselves
|
||||
implfuzzers: implementations minifyimpl utf8
|
||||
implementations: haswell westmere fallback
|
||||
UBSAN_OPTIONS: halt_on_error=1
|
||||
MAXLEN: -max_len=4000
|
||||
|
||||
steps:
|
||||
- name: Install packages necessary for building
|
||||
run: |
|
||||
@@ -23,105 +27,126 @@ jobs:
|
||||
sudo apt-get install --quiet ninja-build valgrind zip unzip
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 9
|
||||
sudo ./llvm.sh 10
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Create and prepare the initial seed corpus
|
||||
run: |
|
||||
fuzz/build_corpus.sh
|
||||
mv corpus.zip seed_corpus.zip
|
||||
mkdir seedcorpus
|
||||
unzip -q -d seedcorpus seed_corpus.zip
|
||||
|
||||
- name: Download the corpus from the last run
|
||||
run: |
|
||||
wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
tar xf corpus.tar
|
||||
rm corpus.tar
|
||||
|
||||
- name: List clang versions
|
||||
run: |
|
||||
ls /usr/bin/clang*
|
||||
which clang++
|
||||
clang++ --version
|
||||
|
||||
- name: Build all the variants
|
||||
run: fuzz/build_fuzzer_variants.sh
|
||||
- name: Verify that the oss-fuzz seed corpus passes without problems
|
||||
|
||||
- name: Explore fast (release build, default implementation)
|
||||
run: |
|
||||
mkdir seedcorpus
|
||||
unzip -q -d seedcorpus seed_corpus.zip
|
||||
for buildvariant in noavx withavx; do
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-$buildvariant/fuzz/fuzz_$fuzzer seedcorpus -max_total_time=1
|
||||
done
|
||||
done
|
||||
- name: Run the fastest fuzzer to explore fast
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
mkdir -p out/$fuzzer # in case this is a new fuzzer, or corpus.tar is broken
|
||||
build-ossfuzz-fast9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=30 $artifactsprefix || touch failed
|
||||
# make sure the failing output is visible in the log
|
||||
if [ -e failed ] ; then
|
||||
ls fuzzfailure/* |xargs -n1 base64
|
||||
exit 1
|
||||
fi
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-fast/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=30 $MAXLEN
|
||||
done
|
||||
- name: Run the other fuzzer variants for $fuzzer, with sanitizers etc
|
||||
|
||||
- name: Fuzz default impl. fuzzers with sanitizer+asserts (good at detecting errors)
|
||||
run: |
|
||||
set -x
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-withavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=20 $artifactsprefix || touch failed
|
||||
build-ossfuzz-noavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 $artifactsprefix || touch failed
|
||||
build-ossfuzz-noavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 $artifactsprefix || touch failed
|
||||
if [ -e failed ] ; then
|
||||
# make sure the failing output is visible in the log
|
||||
ls fuzzfailure/* |xargs -n1 base64
|
||||
exit 1
|
||||
fi
|
||||
echo disable msan runs, it fails inside the fuzzing engine and not the fuzzed code!
|
||||
echo build-ossfuzz-msan-noavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
echo build-ossfuzz-msan-withavx9/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 -reload=0 $artifactsprefix
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers; do
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
for implementation in $implementations; do
|
||||
export SIMDJSON_FORCE_IMPLEMENTATION=$implementation
|
||||
build-sanitizers/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=20 $MAXLEN
|
||||
done
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
- name: Minimize the corpus with the fast fuzzer
|
||||
|
||||
- name: Fuzz differential impl. fuzzers with sanitizer+asserts (good at detecting errors)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
set -eux
|
||||
for fuzzer in $implfuzzers; do
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-sanitizers/fuzz/fuzz_$fuzzer out/$fuzzer $others seedcorpus -max_total_time=20 $MAXLEN
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
|
||||
- name: Minimize the corpus with the fast fuzzer on the default implementation
|
||||
run: |
|
||||
set -eux
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
mkdir -p out/cmin/$fuzzer
|
||||
build-ossfuzz-fast9/fuzz/fuzz_$fuzzer -merge=1 out/cmin/$fuzzer out/$fuzzer
|
||||
# get input from everyone else (corpus cross pollination)
|
||||
others=$(find out -type d -not -name $fuzzer -not -name out -not -name cmin)
|
||||
build-fast/fuzz/fuzz_$fuzzer -merge=1 $MAXLEN out/cmin/$fuzzer out/$fuzzer $others seedcorpus
|
||||
rm -rf out/$fuzzer
|
||||
mv out/cmin/$fuzzer out/$fuzzer
|
||||
done
|
||||
|
||||
- name: Package the corpus into an artifact
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
tar rf corpus.tar out/$fuzzer
|
||||
done
|
||||
|
||||
- name: Save the corpus as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: corpus
|
||||
path: corpus.tar
|
||||
- name: Run the corpus through valgrind (normal build)
|
||||
|
||||
# This takes a subset of the minimized corpus and run it through valgrind. It is slow,
|
||||
# therefore take a "random" subset. The random selection is accomplished by sorting on filenames,
|
||||
# which are hashes of the content.
|
||||
- name: Run some of the minimized corpus through valgrind (replay build, default implementation)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-noavx/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-noavx.txt
|
||||
done
|
||||
- name: Run the corpus through valgrind (noavx build)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-normal/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-normal.txt
|
||||
done
|
||||
for fuzzer in $defaultimplfuzzers $implfuzzers; do
|
||||
find out/$fuzzer -type f |sort|head -n200|xargs -n40 valgrind build-replay/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer.txt
|
||||
done
|
||||
|
||||
- name: Compress the valgrind output
|
||||
run: tar cf valgrind.tar valgrind-*.txt
|
||||
|
||||
- name: Save valgrind output as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: valgrindresults
|
||||
path: valgrind.tar
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Upload the corpus and results to bintray if we are on master
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
run: |
|
||||
if [ $(git rev-parse --verify HEAD) = $(git rev-parse --verify origin/master) ] ; then
|
||||
echo uploading each artifact twice, otherwise it will not be published
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
else
|
||||
echo "not on master, won't upload to bintray"
|
||||
fi
|
||||
echo uploading each artifact twice, otherwise it will not be published
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
|
||||
- name: Archive any crashes as an artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: crashes
|
||||
path: |
|
||||
crash-*
|
||||
leak-*
|
||||
timeout-*
|
||||
if-no-files-found: ignore
|
||||
|
||||
|
||||
@@ -49,5 +49,5 @@ jobs:
|
||||
mkdir build32
|
||||
cd build32
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
|
||||
@@ -49,11 +49,11 @@ jobs:
|
||||
mkdir build64
|
||||
cd build64
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
cd ..
|
||||
mkdir build64debug
|
||||
cd build64debug
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target parse_many_test jsoncheck basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
cmake --build . --target parse_many_test jsoncheck basictests ondemand_basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest -R "(parse_many_test|jsoncheck|basictests|stringparsingcheck|numberparsingcheck|errortests|integer_tests|pointercheck)" --output-on-failure
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Performance check on Ubuntu 18.04 CI (GCC 7)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . --target checkperf &&
|
||||
ctest --output-on-failure -R checkperf ubuntu18-checkperf.yml
|
||||
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . &&
|
||||
ctest -j --output-on-failure &&
|
||||
ctest -j --output-on-failure -E checkperf &&
|
||||
make install &&
|
||||
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Performance check on Ubuntu 20.04 CI (GCC 9)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.0
|
||||
with:
|
||||
cmake-version: '3.9.x'
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . --target checkperf &&
|
||||
ctest --output-on-failure -R checkperf
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . &&
|
||||
ctest -j --output-on-failure &&
|
||||
ctest -j --output-on-failure -E checkperf &&
|
||||
make install &&
|
||||
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
|
||||
@@ -34,3 +34,9 @@
|
||||
[submodule "dependencies/cxxopts"]
|
||||
path = dependencies/cxxopts
|
||||
url = https://github.com/jarro2783/cxxopts
|
||||
[submodule "dependencies/boost.json"]
|
||||
path = dependencies/boost.json
|
||||
url = https://github.com/CPPAlliance/json.git
|
||||
[submodule "dependencies/yyjson"]
|
||||
path = dependencies/yyjson
|
||||
url = https://github.com/ibireme/yyjson.git
|
||||
|
||||
+8
-13
@@ -6,11 +6,11 @@ project(simdjson
|
||||
)
|
||||
|
||||
set(PROJECT_VERSION_MAJOR 0)
|
||||
set(PROJECT_VERSION_MINOR 5)
|
||||
set(PROJECT_VERSION_PATCH 0)
|
||||
set(SIMDJSON_SEMANTIC_VERSION "0.5.0" CACHE STRING "simdjson semantic version")
|
||||
set(SIMDJSON_LIB_VERSION "3.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "3" CACHE STRING "simdjson library soversion")
|
||||
set(PROJECT_VERSION_MINOR 6)
|
||||
set(PROJECT_VERSION_PATCH 1)
|
||||
set(SIMDJSON_SEMANTIC_VERSION "0.6.1" CACHE STRING "simdjson semantic version")
|
||||
set(SIMDJSON_LIB_VERSION "4.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "4" CACHE STRING "simdjson library soversion")
|
||||
set(SIMDJSON_GITHUB_REPOSITORY https://github.com/simdjson/simdjson)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
@@ -21,20 +21,15 @@ include(cmake/simdjson-user-cmakecache.cmake)
|
||||
|
||||
if(SIMDJSON_JUST_LIBRARY)
|
||||
message( STATUS "Building just the library, omitting all tests, tools and benchmarks." )
|
||||
endif()
|
||||
|
||||
#
|
||||
# Set up test data
|
||||
#
|
||||
if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
else(SIMDJSON_JUST_LIBRARY)
|
||||
# Setup tests
|
||||
enable_testing()
|
||||
add_subdirectory(jsonchecker)
|
||||
add_subdirectory(jsonexamples)
|
||||
add_library(test-data INTERFACE)
|
||||
target_link_libraries(test-data INTERFACE jsonchecker-data jsonchecker-minefield-data jsonexamples-data)
|
||||
endif()
|
||||
endif(SIMDJSON_JUST_LIBRARY)
|
||||
|
||||
#
|
||||
# Create the top level simdjson library (must be done at this level to use both src/ and include/
|
||||
# directories) and tools
|
||||
#
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "0.5.0"
|
||||
PROJECT_NUMBER = "0.6.1"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[/badge.svg)](https://simdjson.org/plots.html)
|
||||

|
||||

|
||||
[![][license img]][license] [](https://simdjson.org/api/0.5.0/index.html)
|
||||
[![][license img]][license] [](https://simdjson.org/api/0.6.0/index.html)
|
||||
|
||||
simdjson : Parsing gigabytes of JSON per second
|
||||
===============================================
|
||||
@@ -11,13 +11,16 @@ simdjson : Parsing gigabytes of JSON per second
|
||||
<img src="images/logo.png" width="10%" style="float: right">
|
||||
JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh
|
||||
approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms
|
||||
to parse JSON 2.5x faster than anything else out there.
|
||||
to parse JSON 2.5x faster than RapidJSON and 25x faster than JSON for Modern C++.
|
||||
|
||||
* **Fast:** Over 2.5x faster than other production-grade JSON parsers.
|
||||
* **Easy:** First-class, easy to use API.
|
||||
* **Fast:** Over 2.5x faster than commonly used production-grade JSON parsers.
|
||||
* **Record Breaking Features:** Minify JSON at 6 GB/s, validate UTF-8 at 13 GB/s, NDJSON at 3.5 GB/s.
|
||||
* **Easy:** First-class, easy to use and carefully documented APIs.
|
||||
* **Beyond DOM:** Try the new On Demand API for twice the speed (>4GB/s).
|
||||
* **Strict:** Full JSON and UTF-8 validation, lossless parsing. Performance with no compromises.
|
||||
* **Automatic:** Selects a CPU-tailored parser at runtime. No configuration needed.
|
||||
* **Reliable:** From memory allocation to error handling, simdjson's design avoids surprises.
|
||||
* **Peer Reviewed:** Our research appears in venues like VLDB Journal, Software: Practice and Experience.
|
||||
|
||||
This library is part of the [Awesome Modern C++](https://awesomecpp.com) list.
|
||||
|
||||
@@ -70,7 +73,7 @@ Usage documentation is available:
|
||||
* [Performance](doc/performance.md) shows some more advanced scenarios and how to tune for them.
|
||||
* [Implementation Selection](doc/implementation-selection.md) describes runtime CPU detection and
|
||||
how you can work with it.
|
||||
* [API](https://simdjson.org/api/0.5.0/annotated.html) contains the automatically generated API documentation.
|
||||
* [API](https://simdjson.org/api/0.6.0/annotated.html) contains the automatically generated API documentation.
|
||||
|
||||
Performance results
|
||||
-------------------
|
||||
@@ -119,9 +122,14 @@ or larger files (e.g., 3MB). The following plot presents parsing
|
||||
speed for [synthetic files over various sizes generated with a script](https://github.com/simdjson/simdjson_experiments_vldb2019/blob/master/experiments/growing/gen.py) on a 3.4 GHz Skylake processor (GNU GCC 9, -O3).
|
||||
<img src="doc/growing.png" width="90%">
|
||||
|
||||
|
||||
[All our experiments are reproducible](https://github.com/simdjson/simdjson_experiments_vldb2019).
|
||||
|
||||
|
||||
You can go beyond 4 GB/s with our new [On Demand API](https://github.com/simdjson/simdjson/blob/master/doc/ondemand.md).
|
||||
For NDJSON files, we can exceed 3 GB/s with [our multithreaded parsing functions](https://github.com/simdjson/simdjson/blob/master/doc/parse_many.md).
|
||||
|
||||
|
||||
|
||||
Real-world usage
|
||||
----------------
|
||||
|
||||
@@ -149,6 +157,8 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
|
||||
- [fast_jsonparser](https://github.com/anilmaurya/fast_jsonparser): Ruby bindings for the simdjson project.
|
||||
- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly.
|
||||
- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings.
|
||||
- [simdjson_erlang](https://github.com/ChomperT/simdjson_erlang): erlang bindings.
|
||||
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
@@ -158,8 +168,12 @@ instructions, reducing branch misprediction, and reducing data dependency to tak
|
||||
CPU's multiple execution cores.
|
||||
|
||||
Some people [enjoy reading our paper](https://arxiv.org/abs/1902.08318): A description of the design
|
||||
and implementation of simdjson is in our research article: Geoff Langdale, Daniel
|
||||
Lemire, [Parsing Gigabytes of JSON per Second](https://arxiv.org/abs/1902.08318), VLDB Journal 28 (6), 2019.
|
||||
and implementation of simdjson is in our research article:
|
||||
- Geoff Langdale, Daniel Lemire, [Parsing Gigabytes of JSON per Second](https://arxiv.org/abs/1902.08318), VLDB Journal 28 (6), 2019.
|
||||
|
||||
We have an in-depth paper focused on the UTF-8 validation:
|
||||
|
||||
- John Keiser, Daniel Lemire, [Validating UTF-8 In Less Than One Instruction Per Byte](https://arxiv.org/abs/2010.03090), Software: Practice & Experience (to appear)
|
||||
|
||||
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
|
||||
|
||||
|
||||
@@ -25,12 +25,16 @@ target_compile_definitions(parse_nostringparsing PRIVATE SIMDJSON_SKIPSTRINGPARS
|
||||
if (TARGET competition-all)
|
||||
add_executable(distinctuseridcompetition distinctuseridcompetition.cpp)
|
||||
target_link_libraries(distinctuseridcompetition competition-core)
|
||||
|
||||
add_executable(minifiercompetition minifiercompetition.cpp)
|
||||
target_link_libraries(minifiercompetition competition-core)
|
||||
|
||||
add_executable(parseandstatcompetition parseandstatcompetition.cpp)
|
||||
target_link_libraries(parseandstatcompetition competition-core)
|
||||
|
||||
add_executable(parsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(parsingcompetition competition-core)
|
||||
|
||||
add_executable(allparsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(allparsingcompetition competition-all)
|
||||
target_compile_definitions(allparsingcompetition PRIVATE ALLPARSER)
|
||||
@@ -40,6 +44,7 @@ if (TARGET benchmark::benchmark)
|
||||
link_libraries(benchmark::benchmark)
|
||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||
add_executable(bench_ondemand bench_ondemand.cpp)
|
||||
endif()
|
||||
|
||||
include(checkperf.cmake)
|
||||
|
||||
+145
-25
@@ -26,7 +26,7 @@ static void recover_one_string(State& state) {
|
||||
cerr << "could not parse string" << error << endl;
|
||||
return;
|
||||
}
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::string_view v;
|
||||
error = doc.get(v);
|
||||
if (error) {
|
||||
@@ -58,21 +58,141 @@ static void serialize_twitter(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::string serial = simdjson::minify(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// we validate the result
|
||||
{
|
||||
auto serial = simdjson::minify(doc);
|
||||
dom::element doc2; // we parse the minified output
|
||||
if ((error = parser.parse(serial).get(doc2))) { throw std::runtime_error("serialization error"); }
|
||||
auto serial2 = simdjson::minify(doc2); // we minify a second time
|
||||
if(serial != serial2) { throw std::runtime_error("serialization mismatch"); }
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void serialize_big_string_to_string(State& state) {
|
||||
dom::parser parser;
|
||||
std::vector<char> content;
|
||||
content.push_back('\"');
|
||||
for(size_t i = 0 ; i < 100000; i ++) {
|
||||
content.push_back('0' + char(i%10)); // we add what looks like a long list of digits
|
||||
}
|
||||
content.push_back('\"');
|
||||
dom::element doc;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.parse(content.data(), content.size()).get(doc))) {
|
||||
cerr << "could not parse big string" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto serial = simdjson::to_string(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter)->Repetitions(10)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
BENCHMARK(serialize_big_string_to_string)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void serialize_twitter_to_string(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
if((error = parser.allocate(docdata.size()))) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto serial = simdjson::to_string(doc);
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// we validate the result
|
||||
{
|
||||
auto serial = simdjson::to_string(doc);
|
||||
dom::element doc2; // we parse the stringify output
|
||||
if ((error = parser.parse(serial).get(doc2))) { throw std::runtime_error("serialization error"); }
|
||||
auto serial2 = simdjson::to_string(doc2); // we stringify again
|
||||
if(serial != serial2) { throw std::runtime_error("serialization mismatch"); }
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter_to_string)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
static void serialize_twitter_string_builder(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
if((error = parser.allocate(docdata.size()))) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
simdjson::internal::string_builder<> sb;// not part of our public API, for internal use
|
||||
for (simdjson_unused auto _ : state) {
|
||||
sb.clear();
|
||||
sb.append(doc);
|
||||
std::string_view serial = sb.str();
|
||||
bytes += serial.size();
|
||||
benchmark::DoNotOptimize(serial);
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(serialize_twitter_string_builder)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void numbers_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
@@ -82,7 +202,7 @@ static void numbers_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
@@ -104,7 +224,7 @@ static void numbers_size_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -130,7 +250,7 @@ static void numbers_type_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array" << endl;
|
||||
return;
|
||||
}
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
@@ -156,7 +276,7 @@ static void numbers_type_size_scan(State& state) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -181,7 +301,7 @@ static void numbers_load_scan(State& state) {
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
@@ -204,7 +324,7 @@ static void numbers_load_size_scan(State& state) {
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array" << endl;
|
||||
@@ -233,7 +353,7 @@ static void numbers_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (double x : arr) {
|
||||
container.push_back(x);
|
||||
@@ -248,7 +368,7 @@ static void numbers_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -268,7 +388,7 @@ static void numbers_type_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
@@ -287,7 +407,7 @@ static void numbers_type_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
@@ -308,7 +428,7 @@ BENCHMARK(numbers_type_exceptions_size_scan);
|
||||
static void numbers_exceptions_load_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
@@ -324,7 +444,7 @@ BENCHMARK(numbers_exceptions_load_scan);
|
||||
static void numbers_exceptions_load_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
@@ -345,7 +465,7 @@ static void twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
@@ -358,7 +478,7 @@ static void iterator_twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
padded_string json = padded_string::load(TWITTER_JSON);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
ParsedJson::Iterator iter(pj);
|
||||
// uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (!iter.move_to_key("search_metadata")) { return; }
|
||||
@@ -376,7 +496,7 @@ static void twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::object user = tweet["user"];
|
||||
@@ -394,7 +514,7 @@ static void twitter_image_sizes(State& state) {
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
simdjson::error_code error;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::array media;
|
||||
@@ -419,7 +539,7 @@ static void error_code_twitter_count(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
uint64_t value;
|
||||
if ((error = doc["search_metadata"]["count"].get(value))) { return; }
|
||||
if (value != 100) { return; }
|
||||
@@ -433,7 +553,7 @@ static void error_code_twitter_default_profile(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
|
||||
dom::array tweets;
|
||||
@@ -463,7 +583,7 @@ static void iterator_twitter_default_profile(State& state) {
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
@@ -504,7 +624,7 @@ static void error_code_twitter_image_sizes(State& state) noexcept {
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
dom::array statuses;
|
||||
if ((error = doc["statuses"].get(statuses))) { return; }
|
||||
@@ -536,7 +656,7 @@ static void iterator_twitter_image_sizes(State& state) {
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
@@ -601,7 +721,7 @@ static void print_json(State& state) noexcept {
|
||||
|
||||
int code = json_parse(json, parser);
|
||||
if (code) { cerr << error_message(code) << endl; return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
std::stringstream s;
|
||||
if (!parser.print_json(s)) { cerr << "print_json failed" << endl; return; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "simdjson.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <benchmark/benchmark.h>
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#include "partial_tweets/ondemand.h"
|
||||
#include "partial_tweets/iter.h"
|
||||
#include "partial_tweets/dom.h"
|
||||
|
||||
#include "largerandom/ondemand.h"
|
||||
#include "largerandom/iter.h"
|
||||
#include "largerandom/dom.h"
|
||||
|
||||
#include "kostya/ondemand.h"
|
||||
#include "kostya/iter.h"
|
||||
#include "kostya/dom.h"
|
||||
|
||||
#include "distinctuserid/ondemand.h"
|
||||
#include "distinctuserid/dom.h"
|
||||
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -25,7 +25,7 @@ static void unicode_validate_twitter(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
bool is_ok = simdjson::validate_utf8(docdata.data(), docdata.size());
|
||||
bytes += docdata.size();
|
||||
benchmark::DoNotOptimize(is_ok);
|
||||
@@ -55,7 +55,7 @@ static void parse_twitter(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::element doc;
|
||||
bytes += docdata.size();
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
@@ -90,7 +90,7 @@ static void parse_gsoc(State& state) {
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
bytes += docdata.size();
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
@@ -116,7 +116,7 @@ SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void json_parse(State& state) {
|
||||
ParsedJson pj;
|
||||
if (!pj.allocate_capacity(EMPTY_ARRAY.length())) { return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto error = json_parse(EMPTY_ARRAY, pj);
|
||||
if (error) { return; }
|
||||
}
|
||||
@@ -126,7 +126,7 @@ BENCHMARK(json_parse);
|
||||
static void parser_parse_error_code(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
}
|
||||
@@ -138,9 +138,9 @@ BENCHMARK(parser_parse_error_code);
|
||||
static void parser_parse_exception(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
try {
|
||||
SIMDJSON_UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
simdjson_unused dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
@@ -154,7 +154,7 @@ BENCHMARK(parser_parse_exception);
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void build_parsed_json(State& state) {
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::parser parser = simdjson::build_parsed_json(EMPTY_ARRAY);
|
||||
if (!parser.valid) { return; }
|
||||
}
|
||||
@@ -163,7 +163,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
BENCHMARK(build_parsed_json);
|
||||
static void document_parse_error_code(State& state) {
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
dom::parser parser;
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
@@ -174,10 +174,10 @@ BENCHMARK(document_parse_error_code);
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void document_parse_exception(State& state) {
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
for (simdjson_unused auto _ : state) {
|
||||
try {
|
||||
dom::parser parser;
|
||||
SIMDJSON_UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
simdjson_unused dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
|
||||
+5
-350
@@ -1,359 +1,14 @@
|
||||
#define SIMDJSON_IMPLEMENTATION_FALLBACK 0
|
||||
#define SIMDJSON_IMPLEMENTATION_WESTMERE 0
|
||||
#define SIMDJSON_IMPLEMENTATION_AMD64 0
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "simdjson.cpp"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <vector>
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <benchmark/benchmark.h>
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#include "simdjson.cpp"
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
using namespace benchmark;
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
const int REPETITIONS = 10;
|
||||
|
||||
#if SIMDJSON_IMPLEMENTATION_HASWELL
|
||||
|
||||
#include "twitter/sax_tweet_reader.h"
|
||||
|
||||
static void sax_tweets(State &state) {
|
||||
// Load twitter.json to a buffer
|
||||
padded_string json;
|
||||
if (auto error = padded_string::load(TWITTER_JSON).get(json)) { cerr << error << endl; return; }
|
||||
|
||||
// Allocate
|
||||
twitter::sax_tweet_reader reader;
|
||||
if (auto error = reader.set_capacity(json.size())) { cerr << error << endl; return; }
|
||||
|
||||
// Warm the vector
|
||||
if (auto error = reader.read_tweets(json)) { throw error; }
|
||||
|
||||
// Read tweets
|
||||
size_t bytes = 0;
|
||||
size_t tweets = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
if (auto error = reader.read_tweets(json)) { throw error; }
|
||||
bytes += json.size();
|
||||
tweets += reader.tweets.size();
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
state.counters["tweets"] = Counter(double(tweets), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(sax_tweets)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_HASWELL
|
||||
|
||||
#include "twitter/tweet.h"
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(dom::element element) {
|
||||
if (element.is_null()) { return 0; }
|
||||
return element;
|
||||
}
|
||||
simdjson_really_inline void read_dom_tweets(dom::parser &parser, padded_string &json, std::vector<twitter::tweet> &tweets) {
|
||||
for (dom::element tweet : parser.parse(json)["statuses"]) {
|
||||
auto user = tweet["user"];
|
||||
tweets.push_back(
|
||||
{
|
||||
tweet["id"],
|
||||
tweet["text"],
|
||||
tweet["created_at"],
|
||||
nullable_int(tweet["in_reply_to_status_id"]),
|
||||
tweet["retweet_count"],
|
||||
tweet["favorite_count"],
|
||||
{ user["id"], user["screen_name"] }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void dom_tweets(State &state) {
|
||||
// Load twitter.json to a buffer
|
||||
padded_string json;
|
||||
if (auto error = padded_string::load(TWITTER_JSON).get(json)) { cerr << error << endl; return; }
|
||||
|
||||
// Allocate
|
||||
dom::parser parser;
|
||||
if (auto error = parser.allocate(json.size())) { cerr << error << endl; return; };
|
||||
|
||||
// Warm the vector
|
||||
std::vector<twitter::tweet> tweets;
|
||||
read_dom_tweets(parser, json, tweets);
|
||||
|
||||
// Read tweets
|
||||
size_t bytes = 0;
|
||||
size_t num_tweets = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
tweets.clear();
|
||||
read_dom_tweets(parser, json, tweets);
|
||||
bytes += json.size();
|
||||
num_tweets += tweets.size();
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
state.counters["tweets"] = Counter(double(num_tweets), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(dom_tweets)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
static void dom_parse(State &state) {
|
||||
// Load twitter.json to a buffer
|
||||
padded_string json;
|
||||
if (auto error = padded_string::load(TWITTER_JSON).get(json)) { cerr << error << endl; return; }
|
||||
|
||||
// Allocate
|
||||
dom::parser parser;
|
||||
if (auto error = parser.allocate(json.size())) { cerr << error << endl; return; };
|
||||
|
||||
// Read tweets
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
if (parser.parse(json).error()) { throw "Parsing failed"; };
|
||||
bytes += json.size();
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(dom_parse)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
/********************
|
||||
* Large file parsing benchmarks:
|
||||
********************/
|
||||
|
||||
static std::string build_json_array(size_t N) {
|
||||
std::default_random_engine e;
|
||||
std::uniform_real_distribution<> dis(0, 1);
|
||||
std::stringstream myss;
|
||||
myss << "[" << std::endl;
|
||||
if(N > 0) {
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}" << std::endl;
|
||||
}
|
||||
for(size_t i = 1; i < N; i++) {
|
||||
myss << "," << std::endl;
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}";
|
||||
}
|
||||
myss << std::endl;
|
||||
myss << "]" << std::endl;
|
||||
std::string answer = myss.str();
|
||||
std::cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << std::endl;
|
||||
return answer;
|
||||
}
|
||||
|
||||
static const simdjson::padded_string& get_my_json_str() {
|
||||
static simdjson::padded_string s = build_json_array(1000000);
|
||||
return s;
|
||||
}
|
||||
|
||||
struct my_point {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
};
|
||||
|
||||
// ./benchmark/bench_sax --benchmark_filter=largerandom
|
||||
|
||||
|
||||
/***
|
||||
* We start with the naive DOM-based approach.
|
||||
**/
|
||||
static void dom_parse_largerandom(State &state) {
|
||||
// Load twitter.json to a buffer
|
||||
const padded_string& json = get_my_json_str();
|
||||
|
||||
// Allocate
|
||||
dom::parser parser;
|
||||
if (auto error = parser.allocate(json.size())) { cerr << error << endl; return; };
|
||||
|
||||
// Read
|
||||
size_t bytes = 0;
|
||||
simdjson::error_code error;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
std::vector<my_point> container;
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(json).get(doc))) {
|
||||
std::cerr << "failure: " << error << std::endl;
|
||||
throw "Parsing failed";
|
||||
};
|
||||
for (auto p : doc) {
|
||||
container.emplace_back(my_point{p["x"], p["y"], p["z"]});
|
||||
}
|
||||
bytes += json.size();
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
|
||||
BENCHMARK(dom_parse_largerandom)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
#if SIMDJSON_IMPLEMENTATION_HASWELL
|
||||
|
||||
/***
|
||||
* Next we are going to code the SAX approach.
|
||||
**/
|
||||
|
||||
SIMDJSON_TARGET_HASWELL
|
||||
|
||||
namespace largerandom {
|
||||
namespace {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace haswell;
|
||||
using namespace haswell::stage2;
|
||||
struct sax_point_reader_visitor {
|
||||
public:
|
||||
sax_point_reader_visitor(std::vector<my_point> &_points) : points(_points) {
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code visit_document_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_object_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_key(json_iterator &, const uint8_t *key) {
|
||||
switch(key[0]) {
|
||||
case 'x':
|
||||
idx = 0;
|
||||
break;
|
||||
case 'y':
|
||||
idx = 2;
|
||||
break;
|
||||
case 'z':
|
||||
idx = 3;
|
||||
break;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code visit_primitive(json_iterator &, const uint8_t *value) {
|
||||
return numberparsing::parse_double(value).get(buffer[idx]);
|
||||
}
|
||||
simdjson_really_inline error_code visit_array_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_array_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_object_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_document_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_array(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_object(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_root_primitive(json_iterator &, const uint8_t *) { return SUCCESS; }
|
||||
simdjson_really_inline error_code increment_count(json_iterator &) { return SUCCESS; }
|
||||
std::vector<my_point> &points;
|
||||
size_t idx{0};
|
||||
double buffer[3];
|
||||
};
|
||||
|
||||
struct sax_point_reader {
|
||||
std::vector<my_point> points;
|
||||
std::unique_ptr<uint8_t[]> string_buf;
|
||||
size_t capacity;
|
||||
dom_parser_implementation dom_parser;
|
||||
|
||||
sax_point_reader();
|
||||
error_code set_capacity(size_t new_capacity);
|
||||
error_code read_points(const padded_string &json);
|
||||
}; // struct sax_point_reader
|
||||
|
||||
sax_point_reader::sax_point_reader() : points{}, string_buf{}, capacity{0}, dom_parser() {
|
||||
}
|
||||
|
||||
error_code sax_point_reader::set_capacity(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + 32, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
error_code sax_point_reader::read_points(const padded_string &json) {
|
||||
// Allocate capacity if needed
|
||||
points.clear();
|
||||
if (capacity < json.size()) {
|
||||
if (auto error = set_capacity(capacity)) { return error; }
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
if (auto error = dom_parser.stage1((uint8_t *)json.data(), json.size(), false)) { return error; }
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_point_reader_visitor visitor(points);
|
||||
if (auto error = iter.walk_document<false>(visitor)) { return error; }
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
} // namespace largerandom
|
||||
|
||||
SIMDJSON_UNTARGET_REGION
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ./benchmark/bench_sax --benchmark_filter=largerandom
|
||||
static void sax_parse_largerandom(State &state) {
|
||||
// Load twitter.json to a buffer
|
||||
const padded_string& json = get_my_json_str();
|
||||
|
||||
// Allocate
|
||||
largerandom::sax_point_reader reader;
|
||||
if (auto error = reader.set_capacity(json.size())) { throw error; }
|
||||
// warming
|
||||
for(size_t i = 0; i < 10; i++) {
|
||||
if (auto error = reader.read_points(json)) { throw error; }
|
||||
}
|
||||
|
||||
// Read
|
||||
size_t bytes = 0;
|
||||
for (SIMDJSON_UNUSED auto _ : state) {
|
||||
if (auto error = reader.read_points(json)) { throw error; }
|
||||
bytes += json.size();
|
||||
benchmark::DoNotOptimize(reader.points.data());
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
state.counters["Gigabytes"] = benchmark::Counter(
|
||||
double(bytes), benchmark::Counter::kIsRate,
|
||||
benchmark::Counter::OneK::kIs1000); // For GiB : kIs1024
|
||||
state.counters["docs"] = Counter(double(state.iterations()), benchmark::Counter::kIsRate);
|
||||
}
|
||||
BENCHMARK(sax_parse_largerandom)->Repetitions(REPETITIONS)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_HASWELL
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
#include "partial_tweets/sax.h"
|
||||
#include "largerandom/sax.h"
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
|
||||
@@ -96,8 +96,14 @@ struct option_struct {
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a':
|
||||
simdjson::active_implementation = simdjson::available_implementations[optarg];
|
||||
case 'a': {
|
||||
auto impl = simdjson::available_implementations[optarg];
|
||||
if(impl && impl->supported_by_runtime_system()) {
|
||||
simdjson::active_implementation = impl;
|
||||
} else {
|
||||
std::cerr << "implementation " << optarg << " not found or not supported " << std::endl;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
|
||||
+37
-32
@@ -12,9 +12,9 @@
|
||||
#define BEST_TIME(name, test, expected, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
std::printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
std::printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
@@ -23,7 +23,8 @@
|
||||
std::atomic_thread_fence(std::memory_order_acquire); \
|
||||
collector.start(); \
|
||||
if (test != expected) { \
|
||||
fprintf(stderr, "not expected (%d , %d )", (int)test, (int)expected); \
|
||||
std::fprintf(stderr, "not expected (%d , %d )", (int)test, \
|
||||
(int)expected); \
|
||||
break; \
|
||||
} \
|
||||
std::atomic_thread_fence(std::memory_order_release); \
|
||||
@@ -31,39 +32,41 @@
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
std::printf(" cycles/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
std::printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
std::printf("\t"); \
|
||||
} \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
std::printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
std::printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
std::printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
std::printf("\n"); \
|
||||
std::fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
// like BEST_TIME, but no check
|
||||
#define BEST_TIME_NOCHECK(name, test, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
std::printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
std::printf("\"%-40s\"", name); \
|
||||
std::fflush(NULL); \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) { \
|
||||
@@ -76,29 +79,31 @@
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
std::printf(" cycles/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
std::printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
std::printf("\t"); \
|
||||
} \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
std::printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
std::printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
std::printf("\t"); \
|
||||
std::printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
std::printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
std::printf("\n"); \
|
||||
std::fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include "event_counter.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
|
||||
bool equals(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
|
||||
|
||||
void remove_duplicates(std::vector<int64_t> &v) {
|
||||
std::sort(v.begin(), v.end());
|
||||
auto last = std::unique(v.begin(), v.end());
|
||||
v.erase(last, v.end());
|
||||
}
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace distinct_user_id {
|
||||
template<typename T> static void DistinctUserID(benchmark::State &state);
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
#include "dom.h"
|
||||
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
template<typename T> static void DistinctUserID(benchmark::State &state) {
|
||||
//
|
||||
// Load the JSON file
|
||||
//
|
||||
constexpr const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
error_code error;
|
||||
padded_string json;
|
||||
if ((error = padded_string::load(TWITTER_JSON).get(json))) {
|
||||
std::cerr << error << std::endl;
|
||||
state.SkipWithError("error loading");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonBenchmark<T, Dom>(state, json);
|
||||
}
|
||||
|
||||
} // namespace distinct_user_id
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "distinctuserid.h"
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element);
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::array array) {
|
||||
for (auto child : array) {
|
||||
simdjson_recurse(v, child);
|
||||
}
|
||||
}
|
||||
void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::object object) {
|
||||
for (auto [key, value] : object) {
|
||||
if((key.size() == 4) && (memcmp(key.data(), "user", 4) == 0)) {
|
||||
// we are in an object under the key "user"
|
||||
simdjson::error_code error;
|
||||
simdjson::dom::object child_object;
|
||||
simdjson::dom::object child_array;
|
||||
if (not (error = value.get(child_object))) {
|
||||
for (auto [child_key, child_value] : child_object) {
|
||||
if((child_key.size() == 2) && (memcmp(child_key.data(), "id", 2) == 0)) {
|
||||
int64_t x;
|
||||
if (not (error = child_value.get(x))) {
|
||||
v.push_back(x);
|
||||
}
|
||||
}
|
||||
simdjson_recurse(v, child_value);
|
||||
}
|
||||
} else if (not (error = value.get(child_array))) {
|
||||
simdjson_recurse(v, child_array);
|
||||
}
|
||||
// end of: we are in an object under the key "user"
|
||||
} else {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
simdjson_unused simdjson::error_code error;
|
||||
simdjson::dom::array array;
|
||||
simdjson::dom::object object;
|
||||
if (not (error = element.get(array))) {
|
||||
simdjson_recurse(v, array);
|
||||
} else if (not (error = element.get(object))) {
|
||||
simdjson_recurse(v, object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<int64_t> &Result() { return ids; }
|
||||
simdjson_really_inline size_t ItemCount() { return ids.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<int64_t> ids{};
|
||||
|
||||
};
|
||||
void print_vec(const std::vector<int64_t> &v) {
|
||||
for (auto i : v) {
|
||||
std::cout << i << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
ids.clear();
|
||||
dom::element doc = parser.parse(json);
|
||||
simdjson_recurse(ids, doc);
|
||||
remove_duplicates(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(DistinctUserID, Dom);
|
||||
|
||||
} // namespace distinct_user_id
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "distinctuserid.h"
|
||||
|
||||
namespace distinct_user_id {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
OnDemand() {
|
||||
if(!displayed_implementation) {
|
||||
std::cout << "On Demand implementation: " << builtin_implementation()->name() << std::endl;
|
||||
displayed_implementation = true;
|
||||
}
|
||||
}
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<int64_t> &Result() { return ids; }
|
||||
simdjson_really_inline size_t ItemCount() { return ids.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<int64_t> ids{};
|
||||
|
||||
static inline bool displayed_implementation = false;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
ids.clear();
|
||||
// Walk the document, parsing as we go
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
// We believe that all statuses have a matching
|
||||
// user, and we are willing to throw when they do not:
|
||||
//
|
||||
// You might think that you do not need the braces, but
|
||||
// you do, otherwise you will get the wrong answer. That is
|
||||
// because you can only have one active object or array
|
||||
// at a time.
|
||||
{
|
||||
ondemand::object user = tweet["user"];
|
||||
int64_t id = user["id"];
|
||||
ids.push_back(id);
|
||||
}
|
||||
// Not all tweets have a "retweeted_status", but when they do
|
||||
// we want to go and find the user within.
|
||||
auto retweet = tweet["retweeted_status"];
|
||||
if(!retweet.error()) {
|
||||
ondemand::object retweet_content = retweet;
|
||||
ondemand::object reuser = retweet_content["user"];
|
||||
int64_t rid = reuser["id"];
|
||||
ids.push_back(rid);
|
||||
}
|
||||
}
|
||||
remove_duplicates(ids);
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(DistinctUserID, OnDemand);
|
||||
|
||||
} // namespace distinct_user_id
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -38,7 +38,7 @@ void print_vec(const std::vector<int64_t> &v) {
|
||||
|
||||
// clang-format off
|
||||
|
||||
// simdjson_recurse below come be implemented like so but it is slow:
|
||||
// simdjson_recurse below can be implemented like so but it is slow:
|
||||
/*void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
error_code error;
|
||||
if (element.is_array()) {
|
||||
@@ -98,7 +98,7 @@ void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::object object) {
|
||||
}
|
||||
}
|
||||
simdjson_really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
SIMDJSON_UNUSED simdjson::error_code error;
|
||||
simdjson_unused simdjson::error_code error;
|
||||
simdjson::dom::array array;
|
||||
simdjson::dom::object object;
|
||||
if (not (error = element.get(array))) {
|
||||
|
||||
@@ -116,18 +116,18 @@ struct event_collector {
|
||||
|
||||
#if defined(__linux__)
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
|
||||
event_collector() : linux_events(vector<int>{
|
||||
event_collector(bool quiet = false) : linux_events(vector<int>{
|
||||
PERF_COUNT_HW_CPU_CYCLES,
|
||||
PERF_COUNT_HW_INSTRUCTIONS,
|
||||
PERF_COUNT_HW_BRANCH_MISSES,
|
||||
PERF_COUNT_HW_CACHE_REFERENCES,
|
||||
PERF_COUNT_HW_CACHE_MISSES
|
||||
}) {}
|
||||
}, quiet) {}
|
||||
bool has_events() {
|
||||
return linux_events.is_working();
|
||||
}
|
||||
#else
|
||||
event_collector() {}
|
||||
event_collector(simdjson_unused bool _quiet = false) {}
|
||||
bool has_events() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
template<typename B, typename R> static void JsonBenchmark(benchmark::State &state, const simdjson::padded_string &json) {
|
||||
event_collector collector(true);
|
||||
event_aggregate events;
|
||||
|
||||
// Warmup and equality check (make sure the data is right!)
|
||||
B bench;
|
||||
if (!bench.Run(json)) { state.SkipWithError("warmup tweet reading failed"); return; }
|
||||
{
|
||||
R reference;
|
||||
if (!reference.Run(json)) { state.SkipWithError("reference tweet reading failed"); return; }
|
||||
if (bench.Result() != reference.Result()) { state.SkipWithError("results are not the same"); return; }
|
||||
}
|
||||
|
||||
// Run the benchmark
|
||||
for (simdjson_unused auto _ : state) {
|
||||
collector.start();
|
||||
|
||||
if (!bench.Run(json)) { state.SkipWithError("tweet reading failed"); return; }
|
||||
|
||||
events << collector.end();
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(json.size() * state.iterations());
|
||||
state.SetItemsProcessed(bench.ItemCount() * state.iterations());
|
||||
state.counters["best_bytes_per_sec"] = benchmark::Counter(double(json.size()) / events.best.elapsed_sec());
|
||||
state.counters["best_items_per_sec"] = benchmark::Counter(double(bench.ItemCount()) / events.best.elapsed_sec());
|
||||
|
||||
state.counters["docs_per_sec"] = benchmark::Counter(1.0, benchmark::Counter::kIsIterationInvariantRate);
|
||||
state.counters["best_docs_per_sec"] = benchmark::Counter(1.0 / events.best.elapsed_sec());
|
||||
|
||||
if (collector.has_events()) {
|
||||
state.counters["instructions"] = events.instructions();
|
||||
state.counters["cycles"] = events.cycles();
|
||||
state.counters["branch_miss"] = events.branch_misses();
|
||||
state.counters["cache_miss"] = events.cache_misses();
|
||||
state.counters["cache_ref"] = events.cache_references();
|
||||
|
||||
state.counters["instructions_per_byte"] = events.instructions() / double(json.size());
|
||||
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
|
||||
state.counters["cycles_per_byte"] = events.cycles() / double(json.size());
|
||||
state.counters["frequency"] = benchmark::Counter(events.cycles(), benchmark::Counter::kIsIterationInvariantRate);
|
||||
|
||||
state.counters["best_instructions"] = events.best.instructions();
|
||||
state.counters["best_cycles"] = events.best.cycles();
|
||||
state.counters["best_branch_miss"] = events.best.branch_misses();
|
||||
state.counters["best_cache_miss"] = events.best.cache_misses();
|
||||
state.counters["best_cache_ref"] = events.best.cache_references();
|
||||
|
||||
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(json.size());
|
||||
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
|
||||
state.counters["best_cycles_per_byte"] = events.best.cycles() / double(json.size());
|
||||
state.counters["best_frequency"] = events.best.cycles() / events.best.elapsed_sec();
|
||||
}
|
||||
state.counters["bytes"] = benchmark::Counter(double(json.size()));
|
||||
state.counters["items"] = benchmark::Counter(double(bench.ItemCount()));
|
||||
|
||||
// Build the label
|
||||
using namespace std;
|
||||
stringstream label;
|
||||
label << fixed << setprecision(2);
|
||||
label << "[best:";
|
||||
label << " throughput=" << setw(6) << (double(json.size()) / 1000000000.0 / events.best.elapsed_sec()) << " GB/s";
|
||||
label << " doc_throughput=" << setw(6) << uint64_t(1.0 / events.best.elapsed_sec()) << " docs/s";
|
||||
|
||||
if (collector.has_events()) {
|
||||
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
|
||||
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
|
||||
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
|
||||
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
|
||||
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
|
||||
}
|
||||
|
||||
label << " items=" << setw(10) << bench.ItemCount() << setw(0);
|
||||
label << " avg_time=" << setw(10) << uint64_t(events.elapsed_ns()) << setw(0) << " ns";
|
||||
label << "]";
|
||||
|
||||
state.SetLabel(label.str());
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
for (auto point : parser.parse(json)["coordinates"]) {
|
||||
container.emplace_back(my_point{point["x"], point["y"], point["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, Dom);
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
sum = { 0, 0, 0 };
|
||||
count = 0;
|
||||
|
||||
for (auto coord : parser.parse(json)["coordinates"]) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, Dom);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
|
||||
simdjson_really_inline simdjson_result<double> first_double(ondemand::json_iterator &iter, const char *key) {
|
||||
if (!iter.start_object() || ondemand::raw_json_string(iter.field_key()) != key || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
simdjson_really_inline simdjson_result<double> next_double(ondemand::json_iterator &iter, const char *key) {
|
||||
if (!iter.has_next_field() || ondemand::raw_json_string(iter.field_key()) != key || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("coordinates")) { cerr << "find coordinates field failed" << endl; return false; }
|
||||
if (iter.start_array()) {
|
||||
do {
|
||||
container.emplace_back(my_point{first_double(iter, "x"), next_double(iter, "y"), next_double(iter, "z")});
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the coordinates object
|
||||
} while (iter.has_next_element());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, Iter);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("coordinates")) { return false; }
|
||||
if (!iter.start_array()) { return false; }
|
||||
do {
|
||||
if (!iter.start_object() || !iter.find_field_raw("x")) { return false; }
|
||||
sum.x += iter.consume_double();
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("y")) { return false; }
|
||||
sum.y += iter.consume_double();
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("z")) { return false; }
|
||||
sum.z += iter.consume_double();
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the coordinates object
|
||||
count++;
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, Iter);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace kostya {
|
||||
template<typename T> static void Kostya(benchmark::State &state);
|
||||
namespace sum {
|
||||
template<typename T> static void KostyaSum(benchmark::State &state);
|
||||
}
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
static void append_coordinate(std::default_random_engine &e, std::uniform_real_distribution<> &dis, std::stringstream &myss) {
|
||||
using std::endl;
|
||||
myss << R"( {)" << endl;
|
||||
myss << R"( "x": )" << dis(e) << "," << endl;
|
||||
myss << R"( "y": )" << dis(e) << "," << endl;
|
||||
myss << R"( "z": )" << dis(e) << "," << endl;
|
||||
myss << R"( "name": ")" << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << char('a'+dis(e)*25) << " " << int(dis(e)*10000) << "\"," << endl;
|
||||
myss << R"( "opts": {)" << endl;
|
||||
myss << R"( "1": [)" << endl;
|
||||
myss << R"( 1,)" << endl;
|
||||
myss << R"( true)" << endl;
|
||||
myss << R"( ])" << endl;
|
||||
myss << R"( })" << endl;
|
||||
myss << R"( })";
|
||||
}
|
||||
|
||||
static std::string build_json_array(size_t N) {
|
||||
using namespace std;
|
||||
default_random_engine e;
|
||||
uniform_real_distribution<> dis(0, 1);
|
||||
stringstream myss;
|
||||
myss << R"({)" << endl;
|
||||
myss << R"( "coordinates": [)" << endl;
|
||||
for (size_t i=1; i<N; i++) {
|
||||
append_coordinate(e, dis, myss); myss << "," << endl;
|
||||
}
|
||||
append_coordinate(e, dis, myss); myss << endl;
|
||||
myss << R"( ],)" << endl;
|
||||
myss << R"( "info": "some info")" << endl;
|
||||
myss << R"(})" << endl;
|
||||
string answer = myss.str();
|
||||
cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << endl;
|
||||
return answer;
|
||||
}
|
||||
|
||||
static const padded_string &get_built_json_array() {
|
||||
static padded_string json = build_json_array(524288);
|
||||
return json;
|
||||
}
|
||||
|
||||
struct my_point {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
simdjson_really_inline bool operator==(const my_point &other) const {
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const my_point &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const my_point &p) {
|
||||
return o << p.x << "," << p.y << "," << p.z << std::endl;
|
||||
}
|
||||
|
||||
} // namespace kostya
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "dom.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
template<typename T> static void Kostya(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
namespace sum {
|
||||
template<typename T> static void KostyaSum(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "kostya.h"
|
||||
|
||||
namespace kostya {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc["coordinates"]) {
|
||||
container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(Kostya, OnDemand);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc["coordinates"]) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(KostyaSum, OnDemand);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace kostya
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
for (auto point : parser.parse(json)) {
|
||||
container.emplace_back(my_point{point["x"], point["y"], point["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Dom);
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
sum = { 0, 0, 0 };
|
||||
count = 0;
|
||||
|
||||
for (auto coord : parser.parse(json)) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, Dom);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
|
||||
simdjson_really_inline double first_double(ondemand::json_iterator &iter) {
|
||||
if (iter.start_object().error() || iter.field_key().error() || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
simdjson_really_inline double next_double(ondemand::json_iterator &iter) {
|
||||
if (!iter.has_next_field() || iter.field_key().error() || iter.field_value()) { throw "Invalid field"; }
|
||||
return iter.consume_double();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (iter.start_array()) {
|
||||
do {
|
||||
container.emplace_back(my_point{first_double(iter), next_double(iter), next_double(iter)});
|
||||
if (iter.has_next_field()) { throw "Too many fields"; }
|
||||
} while (iter.has_next_element());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Iter);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_array()) { return false; }
|
||||
do {
|
||||
if (!iter.start_object() || iter.field_key().value() != "x" || iter.field_value()) { return false; }
|
||||
sum.x += iter.consume_double();
|
||||
if (!iter.has_next_field() || iter.field_key().value() != "y" || iter.field_value()) { return false; }
|
||||
sum.y += iter.consume_double();
|
||||
if (!iter.has_next_field() || iter.field_key().value() != "z" || iter.field_value()) { return false; }
|
||||
sum.z += iter.consume_double();
|
||||
if (*iter.advance() != '}') { return false; }
|
||||
count++;
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, Iter);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace largerandom {
|
||||
template<typename T> static void LargeRandom(benchmark::State &state);
|
||||
namespace sum {
|
||||
template<typename T> static void LargeRandomSum(benchmark::State &state);
|
||||
}
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
static std::string build_json_array(size_t N) {
|
||||
std::default_random_engine e;
|
||||
std::uniform_real_distribution<> dis(0, 1);
|
||||
std::stringstream myss;
|
||||
myss << "[" << std::endl;
|
||||
if(N > 0) {
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}" << std::endl;
|
||||
}
|
||||
for(size_t i = 1; i < N; i++) {
|
||||
myss << "," << std::endl;
|
||||
myss << "{ \"x\":" << dis(e) << ", \"y\":" << dis(e) << ", \"z\":" << dis(e) << "}";
|
||||
}
|
||||
myss << std::endl;
|
||||
myss << "]" << std::endl;
|
||||
std::string answer = myss.str();
|
||||
std::cout << "Creating a source file spanning " << (answer.size() + 512) / 1024 << " KB " << std::endl;
|
||||
return answer;
|
||||
}
|
||||
|
||||
static const padded_string &get_built_json_array() {
|
||||
static padded_string json = build_json_array(1000000);
|
||||
return json;
|
||||
}
|
||||
|
||||
struct my_point {
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
simdjson_really_inline bool operator==(const my_point &other) const {
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const my_point &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const my_point &p) {
|
||||
return o << p.x << "," << p.y << "," << p.z << std::endl;
|
||||
}
|
||||
|
||||
} // namespace largerandom
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "dom.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
template<typename T> static void LargeRandom(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
namespace sum {
|
||||
|
||||
template<typename T> static void LargeRandomSum(benchmark::State &state) {
|
||||
JsonBenchmark<T, Dom>(state, get_built_json_array());
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
container.clear();
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc) {
|
||||
container.emplace_back(my_point{coord["x"], coord["y"], coord["z"]});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, OnDemand);
|
||||
|
||||
|
||||
namespace sum {
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline my_point &Result() { return sum; }
|
||||
simdjson_really_inline size_t ItemCount() { return count; }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
my_point sum{};
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
sum = {0,0,0};
|
||||
count = 0;
|
||||
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object coord : doc.get_array()) {
|
||||
sum.x += double(coord["x"]);
|
||||
sum.y += double(coord["y"]);
|
||||
sum.z += double(coord["z"]);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandomSum, OnDemand);
|
||||
|
||||
} // namespace sum
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "largerandom.h"
|
||||
|
||||
namespace largerandom {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
class Sax {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<my_point> &Result() { return container; }
|
||||
simdjson_really_inline size_t ItemCount() { return container.size(); }
|
||||
|
||||
private:
|
||||
simdjson_really_inline error_code RunNoExcept(const padded_string &json) noexcept;
|
||||
error_code Allocate(size_t new_capacity);
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
size_t capacity{};
|
||||
dom_parser_implementation dom_parser{};
|
||||
std::vector<my_point> container{};
|
||||
};
|
||||
|
||||
struct sax_point_reader_visitor {
|
||||
public:
|
||||
std::vector<my_point> &points;
|
||||
enum {GOT_X=0, GOT_Y=1, GOT_Z=2, GOT_SOMETHING_ELSE=4};
|
||||
size_t idx{GOT_SOMETHING_ELSE};
|
||||
double buffer[3]={};
|
||||
|
||||
explicit sax_point_reader_visitor(std::vector<my_point> &_points) : points(_points) {}
|
||||
|
||||
simdjson_really_inline error_code visit_object_start(json_iterator &) {
|
||||
idx = 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code visit_primitive(json_iterator &, const uint8_t *value) {
|
||||
if(idx == GOT_SOMETHING_ELSE) { return simdjson::SUCCESS; }
|
||||
return numberparsing::parse_double(value).get(buffer[idx]);
|
||||
}
|
||||
simdjson_really_inline error_code visit_object_end(json_iterator &) {
|
||||
points.emplace_back(my_point{buffer[0], buffer[1], buffer[2]});
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code visit_document_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_key(json_iterator &, const uint8_t * key) {
|
||||
switch(key[1]) {
|
||||
// Technically, we should check the other characters
|
||||
// in the key, but we are cheating to go as fast
|
||||
// as possible.
|
||||
case 'x':
|
||||
idx = GOT_X;
|
||||
break;
|
||||
case 'y':
|
||||
idx = GOT_Y;
|
||||
break;
|
||||
case 'z':
|
||||
idx = GOT_Z;
|
||||
break;
|
||||
default:
|
||||
idx = GOT_SOMETHING_ELSE;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code visit_array_start(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_array_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_document_end(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_array(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_empty_object(json_iterator &) { return SUCCESS; }
|
||||
simdjson_really_inline error_code visit_root_primitive(json_iterator &, const uint8_t *) { return SUCCESS; }
|
||||
simdjson_really_inline error_code increment_count(json_iterator &) { return SUCCESS; }
|
||||
};
|
||||
|
||||
// NOTE: this assumes the dom_parser is already allocated
|
||||
bool Sax::Run(const padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
error_code Sax::RunNoExcept(const padded_string &json) noexcept {
|
||||
container.clear();
|
||||
|
||||
// Allocate capacity if needed
|
||||
if (capacity < json.size()) {
|
||||
SIMDJSON_TRY( Allocate(json.size()) );
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
SIMDJSON_TRY( dom_parser.stage1(json.u8data(), json.size(), false) );
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_point_reader_visitor visitor(container);
|
||||
SIMDJSON_TRY( iter.walk_document<false>(visitor) );
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
error_code Sax::Allocate(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(LargeRandom, Sax);
|
||||
|
||||
} // namespace largerandom
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -1,14 +1,22 @@
|
||||
// https://github.com/WojciechMula/toys/blob/master/000helpers/linux-perf-events.h
|
||||
#pragma once
|
||||
#ifdef __linux__
|
||||
|
||||
#ifdef __has_include
|
||||
#if __has_include(<asm/unistd.h>)
|
||||
#include <asm/unistd.h> // for __NR_perf_event_open
|
||||
#else
|
||||
#warning "Header asm/unistd.h cannot be found though it is a linux system. Are linux headers missing?"
|
||||
#endif
|
||||
#else // no __has_include
|
||||
// Please insure that linux headers have been installed.
|
||||
#include <asm/unistd.h> // for __NR_perf_event_open
|
||||
#endif
|
||||
#include <linux/perf_event.h> // for perf event constants
|
||||
#include <sys/ioctl.h> // for ioctl
|
||||
#include <unistd.h> // for syscall
|
||||
|
||||
#include <cerrno> // for errno
|
||||
#include <cstring> // for memset
|
||||
#include <cstring> // for std::memset
|
||||
#include <stdexcept>
|
||||
|
||||
#include <iostream>
|
||||
@@ -21,10 +29,11 @@ template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
|
||||
size_t num_events{};
|
||||
std::vector<uint64_t> temp_result_vec{};
|
||||
std::vector<uint64_t> ids{};
|
||||
bool quiet;
|
||||
|
||||
public:
|
||||
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
|
||||
memset(&attribs, 0, sizeof(attribs));
|
||||
explicit LinuxEvents(std::vector<int> config_vec, bool _quiet=false) : fd(0), working(true), quiet{_quiet} {
|
||||
std::memset(&attribs, 0, sizeof(attribs));
|
||||
attribs.type = TYPE;
|
||||
attribs.size = sizeof(attribs);
|
||||
attribs.disabled = 1;
|
||||
@@ -93,8 +102,11 @@ public:
|
||||
|
||||
private:
|
||||
void report_error(const std::string &context) {
|
||||
if (working)
|
||||
std::cerr << (context + ": " + std::string(strerror(errno))) << std::endl;
|
||||
if (!quiet) {
|
||||
if (working) {
|
||||
std::cerr << (context + ": " + std::string(strerror(errno))) << std::endl;
|
||||
}
|
||||
}
|
||||
working = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +48,9 @@ std::string rapid_stringme(char *json) {
|
||||
std::string simdjson_stringme(simdjson::padded_string & json) {
|
||||
std::stringstream ss;
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse(json);
|
||||
dom::element doc;
|
||||
auto error = parser.parse(json).get(doc);
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
ss << simdjson::minify(doc);
|
||||
return ss.str();
|
||||
}
|
||||
@@ -125,9 +127,11 @@ int main(int argc, char *argv[]) {
|
||||
size_t outlength;
|
||||
uint8_t *cbuffer = (uint8_t *)buffer;
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) == simdjson::SUCCESS ? outlength : -1),
|
||||
if(imple->supported_by_runtime_system()) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) == simdjson::SUCCESS ? outlength : -1),
|
||||
outlength, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
}
|
||||
|
||||
printf("minisize = %zu, original size = %zu (minified down to %.2f percent "
|
||||
|
||||
+8
-4
@@ -67,7 +67,9 @@ void print_usage(ostream& out) {
|
||||
out << "-a IMPL - Use the given parser implementation. By default, detects the most advanced" << endl;
|
||||
out << " implementation supported on the host machine." << endl;
|
||||
for (auto impl : simdjson::available_implementations) {
|
||||
out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl;
|
||||
if(impl->supported_by_runtime_system()) {
|
||||
out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +117,13 @@ struct option_struct {
|
||||
break;
|
||||
case 'a': {
|
||||
const implementation *impl = simdjson::available_implementations[optarg];
|
||||
if (!impl) {
|
||||
if ((!impl) || (!impl->supported_by_runtime_system())) {
|
||||
std::string exit_message = string("Unsupported option value -a ") + optarg + ": expected -a with one of ";
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
exit_message += imple->name();
|
||||
exit_message += " ";
|
||||
if(imple->supported_by_runtime_system()) {
|
||||
exit_message += imple->name();
|
||||
exit_message += " ";
|
||||
}
|
||||
}
|
||||
exit_usage(exit_message);
|
||||
}
|
||||
|
||||
@@ -131,24 +131,34 @@ struct Stat {
|
||||
size_t stringLength; // Number of code units in all strings
|
||||
};
|
||||
|
||||
static void GenStatPlus(Stat &stat, const dom::element v) {
|
||||
static error_code GenStatPlus(Stat &stat, const dom::element &v);
|
||||
static error_code GenStatPlus(Stat &stat, const simdjson_result<dom::element> &r) {
|
||||
dom::element v;
|
||||
SIMDJSON_TRY( r.get(v) );
|
||||
return GenStatPlus(stat, v);
|
||||
}
|
||||
static error_code GenStatPlus(Stat &stat, const dom::element &v) {
|
||||
switch (v.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
for (dom::element child : dom::array(v)) {
|
||||
case dom::element_type::ARRAY: {
|
||||
dom::array a;
|
||||
SIMDJSON_TRY( v.get(a) )
|
||||
for (auto child : a) {
|
||||
GenStatPlus(stat, child);
|
||||
stat.elementCount++;
|
||||
}
|
||||
stat.arrayCount++;
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
for (dom::key_value_pair kv : dom::object(v)) {
|
||||
} break;
|
||||
case dom::element_type::OBJECT: {
|
||||
dom::object o;
|
||||
SIMDJSON_TRY( v.get(o) );
|
||||
for (dom::key_value_pair kv : o) {
|
||||
GenStatPlus(stat, kv.value);
|
||||
stat.stringLength += kv.key.size();
|
||||
stat.memberCount++;
|
||||
stat.stringCount++;
|
||||
}
|
||||
stat.objectCount++;
|
||||
break;
|
||||
} break;
|
||||
case dom::element_type::INT64:
|
||||
case dom::element_type::UINT64:
|
||||
case dom::element_type::DOUBLE:
|
||||
@@ -156,20 +166,24 @@ static void GenStatPlus(Stat &stat, const dom::element v) {
|
||||
break;
|
||||
case dom::element_type::STRING: {
|
||||
stat.stringCount++;
|
||||
auto sv = std::string_view(v);
|
||||
std::string_view sv;
|
||||
SIMDJSON_TRY( v.get(sv) );
|
||||
stat.stringLength += sv.size();
|
||||
} break;
|
||||
case dom::element_type::BOOL:
|
||||
if (bool(v)) {
|
||||
case dom::element_type::BOOL: {
|
||||
bool b;
|
||||
SIMDJSON_TRY( v.get(b) );
|
||||
if (b) {
|
||||
stat.trueCount++;
|
||||
} else {
|
||||
stat.falseCount++;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
++stat.nullCount;
|
||||
break;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void RapidGenStat(Stat &stat, const rapidjson::Value &v) {
|
||||
@@ -221,7 +235,8 @@ simdjson_never_inline Stat rapidjson_compute_stats_ref(const rapidjson::Value &d
|
||||
simdjson_never_inline Stat
|
||||
simdjson_compute_stats_refplus(const simdjson::dom::element &doc) {
|
||||
Stat s{};
|
||||
GenStatPlus(s, doc);
|
||||
auto error = GenStatPlus(s, doc);
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -469,9 +484,7 @@ int main(int argc, char *argv[]) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (error) {
|
||||
std::cerr << error << std::endl;
|
||||
}
|
||||
if (error) { std::cerr << error << std::endl; abort(); }
|
||||
size_t refval = simdjson_compute_stats_refplus(doc).objectCount;
|
||||
|
||||
BEST_TIME("simdjson ",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include "yyjson.h"
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
@@ -26,6 +27,9 @@ SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include <boost/json/parser.hpp>
|
||||
#include <boost/json/monotonic_resource.hpp>
|
||||
|
||||
#ifdef ALLPARSER
|
||||
|
||||
#include "fastjson.cpp"
|
||||
@@ -53,7 +57,7 @@ using namespace rapidjson;
|
||||
|
||||
#ifdef ALLPARSER
|
||||
// fastjson has a tricky interface
|
||||
void on_json_error(void *, SIMDJSON_UNUSED const fastjson::ErrorContext &ec) {
|
||||
void on_json_error(void *, simdjson_unused const fastjson::ErrorContext &ec) {
|
||||
// std::cerr<<"ERROR: "<<ec.mesg<<std::endl;
|
||||
}
|
||||
bool fastjson_parse(const char *input) {
|
||||
@@ -65,32 +69,35 @@ bool fastjson_parse(const char *input) {
|
||||
// end of fastjson stuff
|
||||
#endif
|
||||
|
||||
simdjson_never_inline size_t sum_line_lengths(std::stringstream & is) {
|
||||
simdjson_never_inline size_t sum_line_lengths(std::stringstream &is) {
|
||||
std::string line;
|
||||
size_t sumofalllinelengths{0};
|
||||
while(std::getline(is, line)) {
|
||||
while (std::getline(is, line)) {
|
||||
sumofalllinelengths += line.size();
|
||||
}
|
||||
return sumofalllinelengths;
|
||||
}
|
||||
|
||||
inline void reset_stream(std::stringstream & is) {
|
||||
inline void reset_stream(std::stringstream &is) {
|
||||
is.clear();
|
||||
is.seekg(0,std::ios::beg);
|
||||
is.seekg(0, std::ios::beg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool bench(const char *filename, bool verbose, bool just_data, double repeat_multiplier) {
|
||||
bool bench(const char *filename, bool verbose, bool just_data,
|
||||
double repeat_multiplier) {
|
||||
simdjson::padded_string p;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << ": " << error << std::endl;
|
||||
std::cerr << "Could not load the file " << filename << ": " << error
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int repeat = static_cast<int>((50000000 * repeat_multiplier) / static_cast<double>(p.size()));
|
||||
if (repeat < 10) { repeat = 10; }
|
||||
int repeat = static_cast<int>((50000000 * repeat_multiplier) /
|
||||
static_cast<double>(p.size()));
|
||||
if (repeat < 10) {
|
||||
repeat = 10;
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input " << filename << " has ";
|
||||
@@ -104,68 +111,97 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
}
|
||||
size_t volume = p.size();
|
||||
if (just_data) {
|
||||
printf("%-42s %20s %20s %20s %20s \n", "name", "cycles_per_byte",
|
||||
"cycles_per_byte_err", "gb_per_s", "gb_per_s_err");
|
||||
std::printf("%-42s %20s %20s %20s %20s \n", "name", "cycles_per_byte",
|
||||
"cycles_per_byte_err", "gb_per_s", "gb_per_s_err");
|
||||
}
|
||||
if (!just_data) {
|
||||
const std::string inputcopy(p.data(), p.data()+p.size());
|
||||
const std::string inputcopy(p.data(), p.data() + p.size());
|
||||
std::stringstream is;
|
||||
is.str(inputcopy);
|
||||
const size_t lc = sum_line_lengths(is);
|
||||
BEST_TIME("getline ",sum_line_lengths(is) , lc, reset_stream(is),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("getline ", sum_line_lengths(is), lc, reset_stream(is), repeat,
|
||||
volume, !just_data);
|
||||
}
|
||||
|
||||
if (!just_data) {
|
||||
auto parse_dynamic=[](auto& str){
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(str).error();
|
||||
auto parse_dynamic = [](auto &str) {
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(str).error();
|
||||
};
|
||||
BEST_TIME("simdjson (dynamic mem) ", parse_dynamic(p), simdjson::SUCCESS,
|
||||
, repeat, volume, !just_data);
|
||||
BEST_TIME("simdjson (dynamic mem) ", parse_dynamic(p), simdjson::SUCCESS, ,
|
||||
repeat, volume, !just_data);
|
||||
}
|
||||
// (static alloc)
|
||||
simdjson::dom::parser parser;
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat, volume,
|
||||
!just_data);
|
||||
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat,
|
||||
volume, !just_data);
|
||||
|
||||
rapidjson::Document d;
|
||||
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
char *buffer = (char *)std::malloc(p.size() + 1);
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON ",
|
||||
d.Parse<kParseValidateEncodingFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
false, , repeat, volume, !just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON (accurate number parsing) ",
|
||||
d.Parse<kParseValidateEncodingFlag|kParseFullPrecisionFlag>((const char *)buffer)
|
||||
d.Parse<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(
|
||||
(const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
false, , repeat, volume, !just_data);
|
||||
}
|
||||
BEST_TIME("RapidJSON (insitu)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME(
|
||||
"RapidJSON (insitu)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError(), false,
|
||||
std::memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON (insitu, accurate number parsing)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag|kParseFullPrecisionFlag>(buffer).HasParseError(),
|
||||
d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(
|
||||
buffer)
|
||||
.HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
std::memcpy(buffer, p.data(), p.size()) &&
|
||||
(buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
|
||||
{
|
||||
const boost::json::string_view sv(p.data(), p.size());
|
||||
boost::json::parser p;
|
||||
auto execute = [&p](auto sv) -> bool {
|
||||
boost::json::error_code ec;
|
||||
boost::json::monotonic_resource mr;
|
||||
p.reset( &mr );
|
||||
p.write(sv,ec);
|
||||
if(!ec)
|
||||
auto jv=p.release();
|
||||
return !!ec;
|
||||
};
|
||||
|
||||
BEST_TIME("Boost.json", execute(sv), false, , repeat, volume, !just_data);
|
||||
}
|
||||
{
|
||||
|
||||
auto execute = [&p]() -> bool {
|
||||
yyjson_doc *doc = yyjson_read(p.data(), p.size(), 0);
|
||||
bool is_ok = doc != nullptr;
|
||||
yyjson_doc_free(doc);
|
||||
return is_ok;
|
||||
};
|
||||
|
||||
BEST_TIME("yyjson", execute(), true, , repeat, volume, !just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
@@ -173,59 +209,61 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
sajson::parse(sajson::dynamic_allocation(),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
true, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
size_t ast_buffer_size = p.size();
|
||||
size_t *ast_buffer = (size_t *)malloc(ast_buffer_size * sizeof(size_t));
|
||||
size_t *ast_buffer = (size_t *)std::malloc(ast_buffer_size * sizeof(size_t));
|
||||
// (static alloc, insitu)
|
||||
BEST_TIME(
|
||||
"sajson",
|
||||
sajson::parse(sajson::bounded_allocation(ast_buffer, ast_buffer_size),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
true, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
size_t expected = json::parse(p.data(), p.data() + p.size()).size();
|
||||
BEST_TIME("nlohmann-json", json::parse(buffer, buffer + p.size()).size(),
|
||||
expected, , repeat, volume,
|
||||
!just_data);
|
||||
expected, , repeat, volume, !just_data);
|
||||
|
||||
#ifdef ALLPARSER
|
||||
std::string json11err;
|
||||
BEST_TIME("dropbox (json11) ",
|
||||
((json11::Json::parse(buffer, json11err).is_null()) ||
|
||||
(!json11err.empty())),
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
false, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
BEST_TIME("fastjson ", fastjson_parse(buffer), true,
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
JsonValue value;
|
||||
JsonAllocator allocator;
|
||||
char *endptr;
|
||||
BEST_TIME("gason ", jsonParse(buffer, &endptr, &value, allocator),
|
||||
JSON_OK, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
JSON_OK, std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
void *state;
|
||||
BEST_TIME("ultrajson ",
|
||||
(UJDecode(buffer, p.size(), NULL, &state) == NULL), false,
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
std::memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
{
|
||||
std::unique_ptr<jsmntok_t[]> tokens =
|
||||
std::make_unique<jsmntok_t[]>(p.size());
|
||||
jsmn_parser jparser;
|
||||
jsmn_init(&jparser);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
BEST_TIME(
|
||||
"jsmn ",
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(), static_cast<unsigned int>(p.size())) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
BEST_TIME("jsmn ",
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(),
|
||||
static_cast<unsigned int>(p.size())) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
cJSON *tree = cJSON_Parse(buffer);
|
||||
BEST_TIME("cJSON ", ((tree = cJSON_Parse(buffer)) != NULL), true,
|
||||
@@ -243,12 +281,13 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
#endif
|
||||
if (!just_data)
|
||||
BEST_TIME("memcpy ",
|
||||
(memcpy(buffer, p.data(), p.size()) == buffer), true, , repeat,
|
||||
volume, !just_data);
|
||||
(std::memcpy(buffer, p.data(), p.size()) == buffer), true, ,
|
||||
repeat, volume, !just_data);
|
||||
#ifdef __linux__
|
||||
if (!just_data) {
|
||||
printf("\n \n <doing additional analysis with performance counters (Linux "
|
||||
"only)>\n");
|
||||
std::printf(
|
||||
"\n \n <doing additional analysis with performance counters (Linux "
|
||||
"only)>\n");
|
||||
std::vector<int> evts;
|
||||
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
|
||||
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
|
||||
@@ -265,64 +304,84 @@ bool bench(const char *filename, bool verbose, bool just_data, double repeat_mul
|
||||
unified.start();
|
||||
auto parse_error = parser.parse(p).error();
|
||||
if (parse_error)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("simdjson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
std::printf(
|
||||
"simdjson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::fill(stats.begin(), stats.end(), 0);
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
unified.start();
|
||||
if (d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError() !=
|
||||
false)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("RapidJSON: cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
std::printf(
|
||||
"RapidJSON: cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::fill(stats.begin(), stats.end(), 0); // unnecessary
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
std::memcpy(buffer, p.data(), p.size());
|
||||
unified.start();
|
||||
if (sajson::parse(sajson::bounded_allocation(ast_buffer, ast_buffer_size),
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid() != true)
|
||||
printf("bug\n");
|
||||
std::printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
std::plus<unsigned long long>());
|
||||
}
|
||||
printf("sajson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat), static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat), static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat), static_cast<double>(volume) * static_cast<double>(repeat) / static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]), static_cast<double>(stats[1]) / (static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
|
||||
std::printf(
|
||||
"sajson : cycles %10.0f instructions %10.0f branchmisses %10.0f "
|
||||
"cacheref %10.0f cachemisses %10.0f bytespercachemiss %10.0f "
|
||||
"inspercycle %10.1f insperbyte %10.1f\n",
|
||||
static_cast<double>(stats[0]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[2]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[3]) / static_cast<double>(repeat),
|
||||
static_cast<double>(stats[4]) / static_cast<double>(repeat),
|
||||
static_cast<double>(volume) * static_cast<double>(repeat) /
|
||||
static_cast<double>(stats[2]),
|
||||
static_cast<double>(stats[1]) / static_cast<double>(stats[0]),
|
||||
static_cast<double>(stats[1]) /
|
||||
(static_cast<double>(volume) * static_cast<double>(repeat)));
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
free(ast_buffer);
|
||||
free(buffer);
|
||||
std::free(ast_buffer);
|
||||
std::free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -349,13 +408,17 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
|
||||
std::cerr << "The '-t' flag outputs a table." << std::endl;
|
||||
std::cerr << "The '-r <N>' flag sets the repeat multiplier: set it above 1 to do more iterations, and below 1 to do fewer." << std::endl;
|
||||
std::cerr << "The '-r <N>' flag sets the repeat multiplier: set it above 1 "
|
||||
"to do more iterations, and below 1 to do fewer."
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
int result = EXIT_SUCCESS;
|
||||
for (int fileind = optind; fileind < argc; fileind++) {
|
||||
if (!bench(argv[fileind], verbose, just_data, repeat_multiplier)) { result = EXIT_FAILURE; }
|
||||
printf("\n\n");
|
||||
if (!bench(argv[fileind], verbose, just_data, repeat_multiplier)) {
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
std::printf("\n\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class Dom {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(dom::element element) {
|
||||
if (element.is_null()) { return 0; }
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Dom::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
for (dom::element tweet : parser.parse(json)["statuses"]) {
|
||||
auto user = tweet["user"];
|
||||
tweets.emplace_back(partial_tweets::tweet{
|
||||
tweet["created_at"],
|
||||
tweet["id"],
|
||||
tweet["text"],
|
||||
nullable_int(tweet["in_reply_to_status_id"]),
|
||||
{ user["id"], user["screen_name"] },
|
||||
tweet["retweet_count"],
|
||||
tweet["favorite_count"]
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Dom);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
class DomNoExcept {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const simdjson::padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
dom::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline simdjson_result<uint64_t> nullable_int(simdjson_result<dom::element> result) noexcept {
|
||||
dom::element element;
|
||||
SIMDJSON_TRY( result.get(element) );
|
||||
if (element.is_null()) { return 0; }
|
||||
return element.get_uint64();
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code RunNoExcept(const simdjson::padded_string &json) noexcept;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool DomNoExcept::Run(const simdjson::padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code DomNoExcept::RunNoExcept(const simdjson::padded_string &json) noexcept {
|
||||
tweets.clear();
|
||||
|
||||
dom::array tweet_array;
|
||||
SIMDJSON_TRY( parser.parse(json)["statuses"].get_array().get(tweet_array) );
|
||||
|
||||
for (auto tweet_element : tweet_array) {
|
||||
dom::object tweet;
|
||||
SIMDJSON_TRY( tweet_element.get_object().get(tweet) );
|
||||
|
||||
dom::object user;
|
||||
SIMDJSON_TRY( tweet["user"].get_object().get(user) );
|
||||
|
||||
partial_tweets::tweet t;
|
||||
SIMDJSON_TRY( tweet["created_at"] .get_string().get(t.created_at) );
|
||||
SIMDJSON_TRY( tweet["id"] .get_uint64().get(t.id) );
|
||||
SIMDJSON_TRY( tweet["text"] .get_string().get(t.text) );
|
||||
SIMDJSON_TRY( nullable_int(tweet["in_reply_to_status_id"]).get(t.in_reply_to_status_id) );
|
||||
SIMDJSON_TRY( user["id"] .get_uint64().get(t.user.id) );
|
||||
SIMDJSON_TRY( user["screen_name"] .get_string().get(t.user.screen_name) );
|
||||
SIMDJSON_TRY( tweet["retweet_count"] .get_uint64().get(t.retweet_count) );
|
||||
SIMDJSON_TRY( tweet["favorite_count"].get_uint64().get(t.favorite_count) );
|
||||
|
||||
tweets.push_back(t);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
class Iter {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(ondemand::value && value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
simdjson_really_inline twitter_user read_user(ondemand::object && user) {
|
||||
// Move user into a local object so it gets destroyed (and moves the iterator)
|
||||
ondemand::object u = std::move(user);
|
||||
return { u["id"], u["screen_name"] };
|
||||
}
|
||||
};
|
||||
|
||||
simdjson_really_inline bool Iter::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
// Walk the document, parsing the tweets as we go
|
||||
|
||||
// { "statuses":
|
||||
auto iter = parser.iterate_raw(json).value();
|
||||
if (!iter.start_object() || !iter.find_field_raw("statuses")) { return false; }
|
||||
// { "statuses": [
|
||||
if (!iter.start_array()) { return false; }
|
||||
|
||||
do {
|
||||
tweet tweet;
|
||||
|
||||
if (!iter.start_object() || !iter.find_field_raw("created_at")) { return false; }
|
||||
tweet.created_at = iter.consume_string();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("id")) { return false; }
|
||||
tweet.id = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("text")) { return false; }
|
||||
tweet.text = iter.consume_string();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("in_reply_to_status_id")) { return false; }
|
||||
if (!iter.is_null()) {
|
||||
tweet.in_reply_to_status_id = iter.consume_uint64();
|
||||
}
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("user")) { return false; }
|
||||
{
|
||||
if (!iter.start_object() || !iter.find_field_raw("id")) { return false; }
|
||||
tweet.user.id = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("screen_name")) { return false; }
|
||||
tweet.user.screen_name = iter.consume_string();
|
||||
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the user object
|
||||
}
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("retweet_count")) { return false; }
|
||||
tweet.retweet_count = iter.consume_uint64();
|
||||
|
||||
if (!iter.has_next_field() || !iter.find_field_raw("favorite_count")) { return false; }
|
||||
tweet.favorite_count = iter.consume_uint64();
|
||||
|
||||
tweets.push_back(tweet);
|
||||
|
||||
if (iter.skip_container()) { return false; } // Skip the rest of the tweet object
|
||||
|
||||
} while (iter.has_next_element());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Iter);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
|
||||
|
||||
class OnDemand {
|
||||
public:
|
||||
OnDemand() {
|
||||
if(!displayed_implementation) {
|
||||
std::cout << "On Demand implementation: " << builtin_implementation()->name() << std::endl;
|
||||
displayed_implementation = true;
|
||||
}
|
||||
}
|
||||
simdjson_really_inline bool Run(const padded_string &json);
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
ondemand::parser parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
|
||||
simdjson_really_inline uint64_t nullable_int(ondemand::value && value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
simdjson_really_inline twitter_user read_user(ondemand::object && user) {
|
||||
// Move user into a local object so it gets destroyed (and moves the iterator)
|
||||
ondemand::object u = std::move(user);
|
||||
return { u["id"], u["screen_name"] };
|
||||
}
|
||||
static inline bool displayed_implementation = false;
|
||||
};
|
||||
|
||||
simdjson_really_inline bool OnDemand::Run(const padded_string &json) {
|
||||
tweets.clear();
|
||||
|
||||
// Walk the document, parsing the tweets as we go
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
tweets.emplace_back(partial_tweets::tweet{
|
||||
tweet["created_at"],
|
||||
tweet["id"],
|
||||
tweet["text"],
|
||||
nullable_int(tweet["in_reply_to_status_id"]),
|
||||
read_user(tweet["user"]),
|
||||
tweet["retweet_count"],
|
||||
tweet["favorite_count"]
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, OnDemand);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Interface
|
||||
//
|
||||
|
||||
namespace partial_tweets {
|
||||
template<typename T> static void PartialTweets(benchmark::State &state);
|
||||
} // namespace partial_tweets
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
#include "tweet.h"
|
||||
#include <vector>
|
||||
#include "event_counter.h"
|
||||
#include "domnoexcept.h"
|
||||
#include "json_benchmark.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
template<typename T> static void PartialTweets(benchmark::State &state) {
|
||||
//
|
||||
// Load the JSON file
|
||||
//
|
||||
constexpr const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
error_code error;
|
||||
padded_string json;
|
||||
if ((error = padded_string::load(TWITTER_JSON).get(json))) {
|
||||
std::cerr << error << std::endl;
|
||||
state.SkipWithError("error loading");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonBenchmark<T, DomNoExcept>(state, json);
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "partial_tweets.h"
|
||||
#include "sax_tweet_reader_visitor.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
class Sax {
|
||||
public:
|
||||
simdjson_really_inline bool Run(const padded_string &json) noexcept;
|
||||
|
||||
simdjson_really_inline const std::vector<tweet> &Result() { return tweets; }
|
||||
simdjson_really_inline size_t ItemCount() { return tweets.size(); }
|
||||
|
||||
private:
|
||||
simdjson_really_inline error_code RunNoExcept(const padded_string &json) noexcept;
|
||||
error_code Allocate(size_t new_capacity);
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
size_t capacity{};
|
||||
dom_parser_implementation dom_parser{};
|
||||
std::vector<tweet> tweets{};
|
||||
};
|
||||
|
||||
// NOTE: this assumes the dom_parser is already allocated
|
||||
bool Sax::Run(const padded_string &json) noexcept {
|
||||
auto error = RunNoExcept(json);
|
||||
if (error) { std::cerr << error << std::endl; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
error_code Sax::RunNoExcept(const padded_string &json) noexcept {
|
||||
tweets.clear();
|
||||
|
||||
// Allocate capacity if needed
|
||||
if (capacity < json.size()) {
|
||||
SIMDJSON_TRY( Allocate(json.size()) );
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
SIMDJSON_TRY( dom_parser.stage1((uint8_t *)json.data(), json.size(), false) );
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_tweet_reader_visitor visitor(tweets, string_buf.get());
|
||||
SIMDJSON_TRY( iter.walk_document<false>(visitor) );
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
error_code Sax::Allocate(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(PartialTweets, Sax);
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
+24
-29
@@ -1,21 +1,18 @@
|
||||
#ifndef TWITTER_SAX_TWEET_READER_VISITOR_H
|
||||
#define TWITTER_SAX_TWEET_READER_VISITOR_H
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "tweet.h"
|
||||
#include <vector>
|
||||
|
||||
SIMDJSON_TARGET_HASWELL
|
||||
|
||||
namespace twitter {
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace haswell;
|
||||
using namespace haswell::stage2;
|
||||
using namespace simdjson::builtin;
|
||||
using namespace simdjson::builtin::stage2;
|
||||
|
||||
struct sax_tweet_reader_visitor {
|
||||
public:
|
||||
sax_tweet_reader_visitor(std::vector<tweet> &_tweets, uint8_t *string_buf);
|
||||
simdjson_really_inline sax_tweet_reader_visitor(std::vector<tweet> &tweets, uint8_t *string_buf);
|
||||
|
||||
simdjson_really_inline error_code visit_document_start(json_iterator &iter);
|
||||
simdjson_really_inline error_code visit_object_start(json_iterator &iter);
|
||||
@@ -68,8 +65,8 @@ private:
|
||||
field_type type{field_type::any};
|
||||
};
|
||||
|
||||
containers container{containers::document};
|
||||
std::vector<tweet> &tweets;
|
||||
containers container{containers::document};
|
||||
uint8_t *current_string_buf_loc;
|
||||
const uint8_t *current_key{};
|
||||
|
||||
@@ -94,9 +91,9 @@ private:
|
||||
static field_lookup fields;
|
||||
}; // sax_tweet_reader_visitor
|
||||
|
||||
sax_tweet_reader_visitor::sax_tweet_reader_visitor(std::vector<tweet> &_tweets, uint8_t *string_buf)
|
||||
simdjson_really_inline sax_tweet_reader_visitor::sax_tweet_reader_visitor(std::vector<tweet> &_tweets, uint8_t *_string_buf)
|
||||
: tweets{_tweets},
|
||||
current_string_buf_loc{string_buf} {
|
||||
current_string_buf_loc{_string_buf} {
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_document_start(json_iterator &iter) {
|
||||
@@ -112,6 +109,7 @@ simdjson_really_inline error_code sax_tweet_reader_visitor::visit_array_start(js
|
||||
switch (fields.get(current_key, container).type) {
|
||||
case field_type::array: // { "statuses": [
|
||||
start_container(iter);
|
||||
current_key = nullptr;
|
||||
return SUCCESS;
|
||||
case field_type::any:
|
||||
return SUCCESS;
|
||||
@@ -190,6 +188,7 @@ simdjson_really_inline error_code sax_tweet_reader_visitor::visit_primitive(json
|
||||
iter.log_error("unexpected primitive");
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
current_key = nullptr;
|
||||
}
|
||||
|
||||
// If it's not a field, it's a child of an array.
|
||||
@@ -202,16 +201,17 @@ simdjson_really_inline error_code sax_tweet_reader_visitor::visit_array_end(json
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_object_end(json_iterator &iter) {
|
||||
current_key = nullptr;
|
||||
if (in_container(iter)) { end_container(iter); }
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_document_end(json_iterator &iter) {
|
||||
iter.log_end_value("document");
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_document_end(json_iterator &) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_empty_array(json_iterator &) {
|
||||
current_key = nullptr;
|
||||
return SUCCESS;
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::visit_empty_object(json_iterator &) {
|
||||
@@ -233,15 +233,15 @@ simdjson_really_inline bool sax_tweet_reader_visitor::in_container_child(json_it
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::start_container(json_iterator &iter) {
|
||||
SIMDJSON_ASSUME(iter.depth <= MAX_SUPPORTED_DEPTH); // Asserts in debug mode
|
||||
container = containers(iter.depth);
|
||||
if (logger::LOG_ENABLED) { iter.log_start_value(STATE_NAMES[iter.depth]); }
|
||||
if (logger::LOG_ENABLED) { iter.log_value(STATE_NAMES[iter.depth]); }
|
||||
if (container == containers::tweet) { tweets.push_back({}); }
|
||||
}
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::end_container(json_iterator &iter) {
|
||||
if (logger::LOG_ENABLED) { iter.log_end_value(STATE_NAMES[int(container)]); }
|
||||
simdjson_really_inline void sax_tweet_reader_visitor::end_container(json_iterator &) {
|
||||
container = containers(int(container) - 1);
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_nullable_unsigned(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back() + f.offset));
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
if (auto error = numberparsing::parse_unsigned(value).get(*i)) {
|
||||
// If number parsing failed, check if it's null before returning the error
|
||||
if (!atomparsing::is_valid_null_atom(value)) { iter.log_error("expected number or null"); return error; }
|
||||
@@ -251,12 +251,12 @@ simdjson_really_inline error_code sax_tweet_reader_visitor::parse_nullable_unsig
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_unsigned(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back() + f.offset));
|
||||
auto i = reinterpret_cast<uint64_t *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
return numberparsing::parse_unsigned(value).get(*i);
|
||||
}
|
||||
simdjson_really_inline error_code sax_tweet_reader_visitor::parse_string(json_iterator &iter, const uint8_t *value, const field &f) {
|
||||
iter.log_value(f.key);
|
||||
auto s = reinterpret_cast<std::string_view *>(reinterpret_cast<char *>(&tweets.back() + f.offset));
|
||||
auto s = reinterpret_cast<std::string_view *>(reinterpret_cast<char *>(&tweets.back()) + f.offset);
|
||||
return stringparsing::parse_string_to_buffer(value, current_string_buf_loc, *s);
|
||||
}
|
||||
|
||||
@@ -291,13 +291,12 @@ simdjson_really_inline void sax_tweet_reader_visitor::field_lookup::neg(const ch
|
||||
auto index = hash(key, depth);
|
||||
if (entries[index].key) {
|
||||
fprintf(stderr, "%s (depth %d) conflicts with %s (depth %d) !\n", key, depth, entries[index].key, int(entries[index].container));
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
sax_tweet_reader_visitor::field_lookup::field_lookup() {
|
||||
add("\"statuses\"", strlen("\"statuses\""), containers::top_object, field_type::array, 0); // { "statuses": [...]
|
||||
#define TWEET_FIELD(KEY, TYPE) add("\"" #KEY "\"", strlen("\"" #KEY "\""), containers::tweet, TYPE, offsetof(tweet, KEY));
|
||||
add("\"statuses\"", std::strlen("\"statuses\""), containers::top_object, field_type::array, 0); // { "statuses": [...]
|
||||
#define TWEET_FIELD(KEY, TYPE) add("\"" #KEY "\"", std::strlen("\"" #KEY "\""), containers::tweet, TYPE, offsetof(tweet, KEY));
|
||||
TWEET_FIELD(id, field_type::unsigned_integer);
|
||||
TWEET_FIELD(in_reply_to_status_id, field_type::nullable_unsigned_integer);
|
||||
TWEET_FIELD(retweet_count, field_type::unsigned_integer);
|
||||
@@ -306,7 +305,7 @@ sax_tweet_reader_visitor::field_lookup::field_lookup() {
|
||||
TWEET_FIELD(created_at, field_type::string);
|
||||
TWEET_FIELD(user, field_type::object)
|
||||
#undef TWEET_FIELD
|
||||
#define USER_FIELD(KEY, TYPE) add("\"" #KEY "\"", strlen("\"" #KEY "\""), containers::user, TYPE, offsetof(tweet, user)+offsetof(twitter_user, KEY));
|
||||
#define USER_FIELD(KEY, TYPE) add("\"" #KEY "\"", std::strlen("\"" #KEY "\""), containers::user, TYPE, offsetof(tweet, user)+offsetof(twitter_user, KEY));
|
||||
USER_FIELD(id, field_type::unsigned_integer);
|
||||
USER_FIELD(screen_name, field_type::string);
|
||||
#undef USER_FIELD
|
||||
@@ -503,7 +502,7 @@ sax_tweet_reader_visitor::field_lookup::field_lookup() {
|
||||
// for (int a=0;a<4;a++) {
|
||||
// for (int b=0;b<4;b++) {
|
||||
// for (int c=0;c<4;c++) {
|
||||
// twitter::sax_tweet_reader_visitor::field_lookup fields(a,b,c);
|
||||
// sax_tweet_reader_visitor::field_lookup fields(a,b,c);
|
||||
// if (fields.collision_count) { continue; }
|
||||
// if (fields.zero_emission) { continue; }
|
||||
// if (fields.conflict_count < min_count) { printf("min=%d,%d,%d (%d)", a, b, c, fields.conflict_count); }
|
||||
@@ -512,8 +511,4 @@ sax_tweet_reader_visitor::field_lookup::field_lookup() {
|
||||
// }
|
||||
// }
|
||||
|
||||
} // namespace twitter
|
||||
|
||||
SIMDJSON_UNTARGET_REGION
|
||||
|
||||
#endif // TWITTER_SAX_TWEET_READER_VISITOR_H
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "twitter_user.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
// {
|
||||
// "statuses": [
|
||||
// {
|
||||
// "created_at": "Sun Aug 31 00:29:15 +0000 2014",
|
||||
// "id": 505874924095815700,
|
||||
// "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
|
||||
// "in_reply_to_status_id": null,
|
||||
// "user": {
|
||||
// "id": 1186275104,
|
||||
// "screen_name": "ayuu0123"
|
||||
// },
|
||||
// "retweet_count": 0,
|
||||
// "favorite_count": 0
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
struct tweet {
|
||||
std::string_view created_at{};
|
||||
uint64_t id{};
|
||||
std::string_view text{};
|
||||
uint64_t in_reply_to_status_id{};
|
||||
twitter_user user{};
|
||||
uint64_t retweet_count{};
|
||||
uint64_t favorite_count{};
|
||||
simdjson_really_inline bool operator==(const tweet &other) const {
|
||||
return created_at == other.created_at &&
|
||||
id == other.id &&
|
||||
text == other.text &&
|
||||
in_reply_to_status_id == other.in_reply_to_status_id &&
|
||||
user == other.user &&
|
||||
retweet_count == other.retweet_count &&
|
||||
favorite_count == other.favorite_count;
|
||||
}
|
||||
simdjson_really_inline bool operator!=(const tweet &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
simdjson_unused static std::ostream &operator<<(std::ostream &o, const tweet &t) {
|
||||
o << "created_at: " << t.created_at << std::endl;
|
||||
o << "id: " << t.id << std::endl;
|
||||
o << "text: " << t.text << std::endl;
|
||||
o << "in_reply_to_status_id: " << t.in_reply_to_status_id << std::endl;
|
||||
o << "user.id: " << t.user.id << std::endl;
|
||||
o << "user.screen_name: " << t.user.screen_name << std::endl;
|
||||
o << "retweet_count: " << t.retweet_count << std::endl;
|
||||
o << "favorite_count: " << t.favorite_count << std::endl;
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "simdjson.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
struct twitter_user {
|
||||
uint64_t id{};
|
||||
std::string_view screen_name{};
|
||||
|
||||
bool operator==(const twitter_user &other) const {
|
||||
return id == other.id &&
|
||||
screen_name == other.screen_name;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace partial_tweets
|
||||
@@ -1,67 +0,0 @@
|
||||
#ifndef TWITTER_SAX_TWEET_READER_H
|
||||
#define TWITTER_SAX_TWEET_READER_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "sax_tweet_reader_visitor.h"
|
||||
#include "tweet.h"
|
||||
#include <vector>
|
||||
|
||||
SIMDJSON_TARGET_HASWELL
|
||||
|
||||
namespace twitter {
|
||||
namespace {
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace haswell;
|
||||
using namespace haswell::stage2;
|
||||
|
||||
struct sax_tweet_reader {
|
||||
std::vector<tweet> tweets;
|
||||
std::unique_ptr<uint8_t[]> string_buf;
|
||||
size_t capacity;
|
||||
dom_parser_implementation dom_parser;
|
||||
|
||||
sax_tweet_reader();
|
||||
error_code set_capacity(size_t new_capacity);
|
||||
error_code read_tweets(padded_string &json);
|
||||
}; // struct tweet_reader
|
||||
|
||||
sax_tweet_reader::sax_tweet_reader() : tweets{}, string_buf{}, capacity{0}, dom_parser() {
|
||||
}
|
||||
|
||||
error_code sax_tweet_reader::set_capacity(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + 32, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
if (auto error = dom_parser.set_max_depth(DEFAULT_MAX_DEPTH)) { return error; }
|
||||
}
|
||||
capacity = new_capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// NOTE: this assumes the dom_parser is already allocated
|
||||
error_code sax_tweet_reader::read_tweets(padded_string &json) {
|
||||
// Allocate capacity if needed
|
||||
tweets.clear();
|
||||
if (capacity < json.size()) {
|
||||
if (auto error = set_capacity(capacity)) { return error; }
|
||||
}
|
||||
|
||||
// Run stage 1 first.
|
||||
if (auto error = dom_parser.stage1((uint8_t *)json.data(), json.size(), false)) { return error; }
|
||||
|
||||
// Then walk the document, parsing the tweets as we go
|
||||
json_iterator iter(dom_parser, 0);
|
||||
sax_tweet_reader_visitor visitor(tweets, string_buf.get());
|
||||
if (auto error = iter.walk_document<false>(visitor)) { return error; }
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
} // namespace twitter
|
||||
|
||||
SIMDJSON_UNTARGET_REGION
|
||||
|
||||
#endif // TWITTER_SAX_TWEET_READER_H
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef TWEET_H
|
||||
#define TWEET_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "twitter_user.h"
|
||||
|
||||
namespace twitter {
|
||||
|
||||
struct tweet {
|
||||
uint64_t id{};
|
||||
std::string_view text{};
|
||||
std::string_view created_at{};
|
||||
uint64_t in_reply_to_status_id{};
|
||||
uint64_t retweet_count{};
|
||||
uint64_t favorite_count{};
|
||||
twitter_user user{};
|
||||
};
|
||||
|
||||
} // namespace twitter
|
||||
|
||||
#endif // TWEET_H
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef TWITTER_USER_H
|
||||
#define TWITTER_USER_H
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
namespace twitter {
|
||||
|
||||
struct twitter_user {
|
||||
uint64_t id{};
|
||||
std::string_view screen_name{};
|
||||
};
|
||||
|
||||
} // namespace twitter
|
||||
|
||||
#endif // TWITTER_USER_H
|
||||
@@ -94,24 +94,88 @@ else()
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Optional flags
|
||||
#
|
||||
|
||||
#
|
||||
# Implementation selection
|
||||
#
|
||||
set(SIMDJSON_ALL_IMPLEMENTATIONS "fallback;westmere;haswell;arm64")
|
||||
|
||||
set(SIMDJSON_IMPLEMENTATION "" CACHE STRING "Semicolon-separated list of implementations to include (${SIMDJSON_ALL_IMPLEMENTATIONS}). If this is not set, any implementations that are supported at compile time and may be selected at runtime will be included.")
|
||||
foreach(implementation ${SIMDJSON_IMPLEMENTATION})
|
||||
if(NOT (implementation IN_LIST SIMDJSON_ALL_IMPLEMENTATIONS))
|
||||
message(ERROR "Implementation ${implementation} not supported by simdjson. Possible implementations: ${SIMDJSON_ALL_IMPLEMENTATIONS}")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
set(SIMDJSON_EXCLUDE_IMPLEMENTATION "" CACHE STRING "Semicolon-separated list of implementations to exclude (haswell/westmere/arm64/fallback). By default, excludes any implementations that are unsupported at compile time or cannot be selected at runtime.")
|
||||
foreach(implementation ${SIMDJSON_EXCLUDE_IMPLEMENTATION})
|
||||
if(NOT (implementation IN_LIST SIMDJSON_ALL_IMPLEMENTATIONS))
|
||||
message(ERROR "Implementation ${implementation} not supported by simdjson. Possible implementations: ${SIMDJSON_ALL_IMPLEMENTATIONS}")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
foreach(implementation ${SIMDJSON_ALL_IMPLEMENTATIONS})
|
||||
string(TOUPPER ${implementation} implementation_upper)
|
||||
if(implementation IN_LIST SIMDJSON_EXCLUDE_IMPLEMENTATION)
|
||||
message(STATUS "Excluding implementation ${implementation} due to SIMDJSON_EXCLUDE_IMPLEMENTATION=${SIMDJSON_EXCLUDE_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=0")
|
||||
elseif(implementation IN_LIST SIMDJSON_IMPLEMENTATION)
|
||||
message(STATUS "Including implementation ${implementation} due to SIMDJSON_IMPLEMENTATION=${SIMDJSON_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=1")
|
||||
elseif(SIMDJSON_IMPLEMENTATION)
|
||||
message(STATUS "Excluding implementation ${implementation} due to SIMDJSON_IMPLEMENTATION=${SIMDJSON_IMPLEMENTATION}")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_IMPLEMENTATION_${implementation_upper}=0")
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
|
||||
# TODO make it so this generates the necessary compiler flags to select the given implementation as the builtin automatically!
|
||||
option(SIMDJSON_BUILTIN_IMPLEMENTATION "Select the implementation that will be used for user code. Defaults to the most universal implementation in SIMDJSON_IMPLEMENTATION (in the order ${SIMDJSON_ALL_IMPLEMENTATIONS}) if specified; otherwise, by default the compiler will pick the best implementation that can always be selected given the compiler flags." "")
|
||||
if(SIMDJSON_BUILTIN_IMPLEMENTATION)
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_BUILTIN_IMPLEMENTATION=${SIMDJSON_BUILTIN_IMPLEMENTATION}")
|
||||
else()
|
||||
# Pick the most universal implementation out of the selected implementations (if any)
|
||||
foreach(implementation ${SIMDJSON_ALL_IMPLEMENTATIONS})
|
||||
if(implementation IN_LIST SIMDJSON_IMPLEMENTATION AND NOT (implementation IN_LIST SIMDJSON_EXCLUDE_IMPLEMENTATION))
|
||||
message(STATUS "Selected implementation ${implementation} as builtin implementation based on ${SIMDJSON_IMPLEMENTATION}.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE "SIMDJSON_BUILTIN_IMPLEMENTATION=${implementation}")
|
||||
break()
|
||||
endif()
|
||||
endforeach(implementation)
|
||||
endif(SIMDJSON_BUILTIN_IMPLEMENTATION)
|
||||
|
||||
option(SIMDJSON_IMPLEMENTATION_HASWELL "Include the haswell implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_HASWELL)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_HASWELL=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_HASWELL is deprecated. Use SIMDJSON_IMPLEMENTATION=-haswell instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_HASWELL=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_WESTMERE "Include the westmere implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_WESTMERE)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_WESTMERE=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_WESTMERE is deprecated. SIMDJSON_IMPLEMENTATION=-westmere instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_WESTMERE=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_ARM64 "Include the arm64 implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_ARM64)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_ARM64=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_ARM64 is deprecated. Use SIMDJSON_IMPLEMENTATION=-arm64 instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_ARM64=0)
|
||||
endif()
|
||||
option(SIMDJSON_IMPLEMENTATION_FALLBACK "Include the fallback implementation" ON)
|
||||
if(NOT SIMDJSON_IMPLEMENTATION_FALLBACK)
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_IMPLEMENTATION_FALLBACK=0)
|
||||
message(DEPRECATION "SIMDJSON_IMPLEMENTATION_FALLBACK is deprecated. Use SIMDJSON_IMPLEMENTATION=-fallback instead.")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_IMPLEMENTATION_FALLBACK=0)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Other optional flags
|
||||
#
|
||||
option(SIMDJSON_ONDEMAND_SAFETY_RAILS "Validate ondemand user code at runtime to ensure it is being used correctly. Defaults to ON for debug builds, OFF for release builds." $<IF:$<CONFIG:DEBUG>,ON,OFF>)
|
||||
if(SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
message(STATUS "Ondemand safety rails enabled. Ondemand user code will be checked at runtime. This will be slower than normal!")
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
endif(SIMDJSON_ONDEMAND_SAFETY_RAILS)
|
||||
|
||||
option(SIMDJSON_BASH "Allow usage of bash within CMake" ON)
|
||||
|
||||
option(SIMDJSON_GIT "Allow usage of git within CMake" ON)
|
||||
@@ -119,7 +183,7 @@ option(SIMDJSON_GIT "Allow usage of git within CMake" ON)
|
||||
option(SIMDJSON_EXCEPTIONS "Enable simdjson's exception-throwing interface" ON)
|
||||
if(NOT SIMDJSON_EXCEPTIONS)
|
||||
message(STATUS "simdjson exception interface turned off. Code that does not check error codes will not compile.")
|
||||
target_compile_definitions(simdjson-internal-flags INTERFACE SIMDJSON_EXCEPTIONS=0)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_EXCEPTIONS=0)
|
||||
endif()
|
||||
|
||||
option(SIMDJSON_ENABLE_THREADS "Link with thread support" ON)
|
||||
@@ -133,6 +197,11 @@ if(SIMDJSON_ENABLE_THREADS)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_THREADS_ENABLED=1) # This will be set in the code automatically.
|
||||
endif()
|
||||
|
||||
option(SIMDJSON_VERBOSE_LOGGING, "Enable verbose logging for internal simdjson library development." OFF)
|
||||
if (SIMDJSON_VERBOSE_LOGGING)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_VERBOSE_LOGGING=1)
|
||||
endif()
|
||||
|
||||
if(SIMDJSON_USE_LIBCPP)
|
||||
target_link_libraries(simdjson-flags INTERFACE -stdlib=libc++ -lc++abi)
|
||||
# instead of the above line, we could have used
|
||||
|
||||
Vendored
+12
-3
@@ -67,8 +67,17 @@ if ((Git_FOUND) AND SIMDJSON_GIT AND (SIMDJSON_IS_UNDER_GIT))
|
||||
add_library(competition-ujson4c ujson4c/src/ujdecode.c)
|
||||
target_include_directories(competition-ujson4c PUBLIC ujson4c/3rdparty ujson4c/src)
|
||||
|
||||
initialize_submodule(boost.json)
|
||||
add_library(boostjson boost.json/src/src.cpp)
|
||||
target_compile_definitions(boostjson PUBLIC BOOST_JSON_STANDALONE)
|
||||
target_include_directories(boostjson PUBLIC boost.json/include)
|
||||
|
||||
initialize_submodule(yyjson)
|
||||
add_library(yyjson yyjson/src/yyjson.c)
|
||||
target_include_directories(yyjson PUBLIC yyjson/src)
|
||||
|
||||
add_library(competition-core INTERFACE)
|
||||
target_link_libraries(competition-core INTERFACE competition-json competition-rapidjson competition-sajson competition-cJSON competition-jsmn)
|
||||
target_link_libraries(competition-core INTERFACE competition-json competition-rapidjson competition-sajson competition-cJSON competition-jsmn boostjson yyjson)
|
||||
|
||||
add_library(competition-all INTERFACE)
|
||||
target_link_libraries(competition-all INTERFACE competition-core competition-jsoncppdist competition-json11 competition-fastjson competition-gason competition-ujson4c)
|
||||
@@ -81,9 +90,9 @@ if ((Git_FOUND) AND SIMDJSON_GIT AND (SIMDJSON_IS_UNDER_GIT))
|
||||
else()
|
||||
message(STATUS "Git is unavailable.")
|
||||
if(SIMDJSON_COMPETITION)
|
||||
message (STATUS "'SIMDJSON_COMPETITION' is requested, but we cannot download the remote repositories." )
|
||||
message (STATUS "'SIMDJSON_COMPETITION' is requested, but we cannot download the remote repositories." )
|
||||
endif()
|
||||
if(SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
message (STATUS "'SIMDJSON_GOOGLE_BENCHMARKS' is requested, but we cannot download the remote repositories." )
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
+1
Submodule dependencies/boost.json added at a0983f788b
+1
Submodule dependencies/yyjson added at aa33ec5a47
+41
-13
@@ -108,7 +108,7 @@ SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string, the _padded suffix creates a simdjson::padded_string instance
|
||||
```
|
||||
|
||||
The parsed document resulting from the `parser.load` and `parser.parse` calls depends on the `parser` instance. Thus the `parser` instance must remain in scope. Furthermore, you must have at most one parsed document in play per `parser` instance.
|
||||
@@ -117,6 +117,9 @@ During the`load` or `parse` calls, neither the input file nor the input string a
|
||||
|
||||
For best performance, a `parser` instance should be reused over several files: otherwise you will needlessly reallocate memory, an expensive process. It is also possible to avoid entirely memory allocations during parsing when using simdjson. [See our performance notes for details](performance.md).
|
||||
|
||||
If you need a lower-level interface, you may call the function `parser.parse(const char * p, size_t l)` on a pointer `p` while specifying the
|
||||
length of your input `l` in bytes. To see how to get the very best performance from a low-level approach, you way want to read our [performance notes](https://github.com/simdjson/simdjson/blob/master/doc/performance.md#padding-and-temporary-copies) on this topic (see the Padding and Temporary Copies section).
|
||||
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
@@ -126,7 +129,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Extracting Values (with exceptions):** You can cast a JSON element to a native type: `double(element)` or
|
||||
`double x = json_element`. This works for double, uint64_t, int64_t, bool,
|
||||
dom::object and dom::array. An exception is thrown if the cast is not possible.
|
||||
* **Extracting Values (without expceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
* **Extracting Values (without exceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
`dom::object` and `dom::array`) and pass it by reference to `get()` which gives you back an error code: e.g.,
|
||||
```c++
|
||||
simdjson::error_code error;
|
||||
@@ -149,10 +152,11 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
|
||||
with the `size()` method.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type`.
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
|
||||
* **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`).
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
The following code illustrates all of the above:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
@@ -271,16 +275,15 @@ for (dom::key_value_pair field : object) {
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`minify`). This function does not validate your content, and it does not parse it. Instead, it assumes that your string is valid UTF-8. It is much faster than parsing the string and re-serializing it in minified form. Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is slightly overallocated (by `simdjson::SIMDJSON_PADDING`) compared to the original string length. The input pointer and input length are read, but not written to.
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`simdjson::minify(const char * input, size_t length, const char * output, size_t& new_length)`). This function does not validate your content, and it does not parse it. It is much faster than parsing the string and re-serializing it in minified form (`simdjson::minify(parser.parse())`). Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is as large as the original string length. The input pointer and input length are read, but not written to.
|
||||
|
||||
```C++
|
||||
// Starts with a valid JSON document as a string.
|
||||
// It does not have to be null-terminated.
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room,
|
||||
// including some padding (simdjson::SIMDJSON_PADDING).
|
||||
std::unique_ptr<char[]> buffer{new(std::nothrow) char[length + simdjson::SIMDJSON_PADDING]};
|
||||
size_t length = std::strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room (length bytes).
|
||||
std::unique_ptr<char[]> buffer{new char[length]};
|
||||
size_t new_length{}; // It will receive the minified length.
|
||||
auto error = simdjson::minify(some_string, length, buffer.get(), new_length);
|
||||
// The buffer variable now has "[1,2,3,4]" and new_length has value 9.
|
||||
@@ -296,7 +299,7 @@ The simdjson library has fast functions to validate UTF-8 strings. They are many
|
||||
|
||||
```C++
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
size_t length = std::strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
@@ -582,7 +585,7 @@ format. If your JSON documents all contain arrays or objects, we even support di
|
||||
concatenation without whitespace. The concatenated file has no size restrictions (including larger
|
||||
than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
Here is a simple example, given `x.json` with this content:
|
||||
|
||||
```json
|
||||
{ "foo": 1 }
|
||||
@@ -592,17 +595,42 @@ Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
dom::document_stream docs = parser.load_many("x.json");
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`.
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`:
|
||||
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
auto json = R"({ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 })"_padded;
|
||||
dom::document_stream docs = parser.parse_many(json);
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
|
||||
Unlike `parser.parse`, both `parser.load_many(filename)` and `parser.parse_many(string)` may parse
|
||||
"on demand" (lazily). That is, no parsing may have been done before you enter the loop
|
||||
`for (dom::element doc : docs) {` and you should expect the parser to only ever fully parse one JSON
|
||||
document at a time.
|
||||
|
||||
1. When calling `parser.load_many(filename)`, the file's content is loaded up in a memory buffer owned by the `parser`'s instance. Thus the file can be safely deleted after calling `parser.load_many(filename)` as the parser instance owns all of the data.
|
||||
2. When calling `parser.parse_many(string)`, no copy is made of the provided string input. The provided memory buffer may be accessed each time a JSON document is parsed. Calling `parser.parse_many(string)` on a temporary string buffer (e.g., `docs = parser.parse_many("[1,2,3]"_padded)`) is unsafe (and will not compile) because the `document_stream` instance needs access to the buffer to return the JSON documents. In constrast, calling `doc = parser.parse("[1,2,3]"_padded)` is safe because `parser.parse` eagerly parses the input.
|
||||
|
||||
|
||||
Both `load_many` and `parse_many` take an optional parameter `size_t batch_size` which defines the window processing size. It is set by default to a large value (`1000000` corresponding to 1 MB). None of your JSON documents should exceed this window size, or else you will get the error `simdjson::CAPACITY`. You cannot set this window size larger than 4 GB: you will get the error `simdjson::CAPACITY`. The smaller the window size is, the less memory the function will use. Setting the window size too small (e.g., less than 100 kB) may also impact performance negatively. Leaving it to 1 MB is expected to be a good choice, unless you have some larger documents.
|
||||
|
||||
If your documents are large (e.g., larger than a megabyte), then the `load_many` and `parse_many` functions are maybe ill-suited. They are really meant to support reading efficiently streams of relatively small documents (e.g., a few kilobytes each). If you have larger documents, you should use other functions like `parse`.
|
||||
|
||||
See [parse_many.md](parse_many.md) for detailed information and design.
|
||||
|
||||
Thread Safety
|
||||
|
||||
+40
-13
@@ -89,7 +89,7 @@ SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string, the _padded suffix creates a simdjson::padded_string instance
|
||||
```
|
||||
|
||||
The parsed document resulting from the `parser.load` and `parser.parse` calls depends on the `parser` instance. Thus the `parser` instance must remain in scope. Furthermore, you must have at most one parsed document in play per `parser` instance.
|
||||
@@ -99,6 +99,9 @@ During the`load` or `parse` calls, neither the input file nor the input string a
|
||||
For best performance, a `parser` instance should be reused over several files: otherwise you will needlessly reallocate memory, an expensive process. It is also possible to avoid entirely memory allocations during parsing when using simdjson.
|
||||
|
||||
|
||||
If you need a lower-level interface, you may call the function `parser.parse(const char * p, size_t l)` on a pointer `p` while specifying the
|
||||
length of your input `l` in bytes. To see how to get the very best performance from a low-level approach, you way want to read our [performance notes](https://github.com/simdjson/simdjson/blob/master/doc/performance.md#padding-and-temporary-copies) on this topic (see the Padding and Temporary Copies section).
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
@@ -107,7 +110,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Extracting Values (with exceptions):** You can cast a JSON element to a native type: `double(element)` or
|
||||
`double x = json_element`. This works for double, uint64_t, int64_t, bool,
|
||||
dom::object and dom::array. An exception is thrown if the cast is not possible.
|
||||
* **Extracting Values (without expceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
* **Extracting Values (without exceptions):** You can use a variant usage of `get()` with error codes to avoid exceptions. You first declare the variable of the appropriate type (`double`, `uint64_t`, `int64_t`, `bool`,
|
||||
`dom::object` and `dom::array`) and pass it by reference to `get()` which gives you back an error code: e.g.,
|
||||
```
|
||||
simdjson::error_code error;
|
||||
@@ -130,10 +133,11 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
|
||||
with the `size()` method.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type`.
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
|
||||
* **Output to Streams and Strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`).
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
The following code illustrates all of the above:
|
||||
|
||||
```
|
||||
auto cars_json = R"( [
|
||||
@@ -252,16 +256,16 @@ for (dom::key_value_pair field : object) {
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`minify`). This function does not validate your content, and it does not parse it. Instead, it assumes that your string is valid UTF-8. It is much faster than parsing the string and re-serializing it in minified form. Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is slightly overallocated (by `simdjson::SIMDJSON_PADDING`) compared to the original string length. The input pointer and input length are read, but not written to.
|
||||
In some cases, you may have valid JSON strings that you do not wish to parse but that you wish to minify. That is, you wish to remove all unnecessary spaces. We have a fast function for this purpose (`simdjson::minify(const char * input, size_t length, const char * output, size_t& new_length)`). This function does not validate your content, and it does not parse it. It is much faster than parsing the string and re-serializing it in minified form (`simdjson::minify(parser.parse())`). Usage is relatively simple. You must pass an input pointer with a length parameter, as well as an output pointer and an output length parameter (by reference). The output length parameter is not read, but written to. The output pointer should point to a valid memory region that is as large as the original string length. The input pointer and input length are read, but not written to.
|
||||
|
||||
|
||||
```
|
||||
// Starts with a valid JSON document as a string.
|
||||
// It does not have to be null-terminated.
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room,
|
||||
// including some padding (simdjson::SIMDJSON_PADDING).
|
||||
std::unique_ptr<char[]> buffer{new(std::nothrow) char[length + simdjson::SIMDJSON_PADDING]};
|
||||
size_t length = std::strlen(some_string);
|
||||
// Create a buffer to receive the minified string. Make sure that there is enough room (length bytes).
|
||||
std::unique_ptr<char[]> buffer{new char[length]};
|
||||
size_t new_length{}; // It will receive the minified length.
|
||||
auto error = simdjson::minify(some_string, length, buffer.get(), new_length);
|
||||
// The buffer variable now has "[1,2,3,4]" and new_length has value 9.
|
||||
@@ -277,7 +281,7 @@ The simdjson library has fast functions to validate UTF-8 strings. They are many
|
||||
|
||||
```
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
size_t length = std::strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
@@ -564,7 +568,7 @@ than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```json
|
||||
```
|
||||
{ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 }
|
||||
@@ -572,14 +576,37 @@ Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
dom::document_stream docs = parser.load_many("x.json");
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`.
|
||||
|
||||
In-memory ndjson strings can be parsed as well, with `parser.parse_many(string)`:
|
||||
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
auto json = R"({ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 })"_padded;
|
||||
dom::document_stream docs = parser.parse_many(json);
|
||||
for (dom::element doc : docs) {
|
||||
cout << doc["foo"] << endl;
|
||||
}
|
||||
// Prints 1 2 3
|
||||
```
|
||||
|
||||
|
||||
Unlike `parser.parse`, both `parser.load_many(filename)` and `parser.parse_many(string)` may parse
|
||||
"on demand" (lazily). That is, no parsing may have been done before you enter the loop
|
||||
`for (dom::element doc : docs) {` and you should expect the parser to only ever fully parse one JSON
|
||||
document at a time.
|
||||
|
||||
1. When calling `parser.load_many(filename)`, the file's content is loaded up in a memory buffer owned by the `parser`'s instance. Thus the file can be safely deleted after calling `parser.load_many(filename)` as the parser instance owns all of the data.
|
||||
2. When calling `parser.parse_many(string)`, no copy is made of the provided string input. The provided memory buffer may be accessed each time a JSON document is parsed. Calling `parser.parse_many(string)` on a temporary string buffer (e.g., `docs = parser.parse_many("[1,2,3]"_padded)`) is unsafe (and will not compile) because the `document_stream` instance needs access to the buffer to return the JSON documents. In constrast, calling `doc = parser.parse("[1,2,3]"_padded)` is safe because `parser.parse` eagerly parses the input.
|
||||
|
||||
Both `load_many` and `parse_many` take an optional parameter `size_t batch_size` which defines the window processing size. It is set by default to a large value (`1000000` corresponding to 1 MB). None of your JSON documents should exceed this window size, or else you will get the error `simdjson::CAPACITY`. You cannot set this window size larger than 4 GB: you will get the error `simdjson::CAPACITY`. The smaller the window size is, the less memory the function will use. Setting the window size too small (e.g., less than 100 kB) may also impact performance negatively. Leaving it to 1 MB is expected to be a good choice, unless you have some larger documents.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ CPU Architecture-Specific Implementations
|
||||
* [Inspecting the Detected Implementation](#inspecting-the-detected-implementation)
|
||||
* [Querying Available Implementations](#querying-available-implementations)
|
||||
* [Manually Selecting the Implementation](#manually-selecting-the-implementation)
|
||||
* [Checking that an Implementation can Run on your System](#checking-that-an-implementation-can-run-on-your-system)
|
||||
|
||||
Overview
|
||||
--------
|
||||
@@ -70,6 +71,14 @@ And look them up by name:
|
||||
```c++
|
||||
cout << simdjson::available_implementations["fallback"]->description() << endl;
|
||||
```
|
||||
Though the fallback implementation should always be available, others might be missing. When
|
||||
an implementation is not available, the bracket call `simdjson::available_implementations[name]`
|
||||
will return the null pointer.
|
||||
|
||||
The available implementations have been compiled but may not necessarily be run safely on your system
|
||||
see [Checking that an Implementation can Run on your System](#checking-that-an-implementation-can-run-on-your-system).
|
||||
|
||||
|
||||
|
||||
Manually Selecting the Implementation
|
||||
-------------------------------------
|
||||
@@ -81,3 +90,30 @@ can select the CPU architecture yourself:
|
||||
// Use the fallback implementation, even though my machine is fast enough for anything
|
||||
simdjson::active_implementation = simdjson::available_implementations["fallback"];
|
||||
```
|
||||
|
||||
You are responsible for ensuring that the requirements of the selected implementation match your current system.
|
||||
Furthermore, you should check that the implementation is available before setting it to `simdjson::active_implementation`
|
||||
by comparing it with the null pointer.
|
||||
|
||||
```c++
|
||||
auto my_implementation = simdjson::available_implementations["haswell"];
|
||||
if(! my_implementation) { exit(1); }
|
||||
if(! my_implementation->supported_by_runtime_system()) { exit(1); }
|
||||
simdjson::active_implementation = my_implementation;
|
||||
```
|
||||
|
||||
Checking that an Implementation can Run on your System
|
||||
-------------------------------------
|
||||
|
||||
You should call `supported_by_runtime_system()` to compare the processor's features with the need of the implementation.
|
||||
|
||||
```c++
|
||||
for (auto implementation : simdjson::available_implementations) {
|
||||
if(implementation->supported_by_runtime_system()) {
|
||||
cout << implementation->name() << ": " << implementation->description() << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The call to `supported_by_runtime_system()` maybe relatively expensive. Do not call `supported_by_runtime_system()` each
|
||||
time you parse a JSON input (for example). It is meant to be called a handful of times at most in the life of a program.
|
||||
+629
@@ -0,0 +1,629 @@
|
||||
|
||||
A Better Way to Parse Documents?
|
||||
====================
|
||||
|
||||
Whether we parse JSON or XML, or any other serialized format, there are relatively few common strategies:
|
||||
|
||||
- The most established approach is the construction of document-object-model (DOM).
|
||||
- Another established approach is a event-based approach (like SAX, SAJ).
|
||||
- Another popular approach is the schema-based deserialization model.
|
||||
|
||||
We propose an approach that is as easy to use and often as flexible as the DOM approach, yet as fast and
|
||||
efficient as the schema-based or event-based approaches. We call this new approach "On Demand". The
|
||||
simdjson On Demand API offers a familiar, friendly DOM API and
|
||||
provides the performance of just-in-time parsing on top of the simdjson superior performance.
|
||||
|
||||
To achieve ease of use, we mimicked the *form* of a traditional DOM API: you can iterate over
|
||||
arrays, look up fields in objects, and extract native values like `double`, `uint64_t`, `string` and `bool`.
|
||||
|
||||
To achieve performance, we introduced some key limitations that make the DOM API *streaming*:
|
||||
array/object iteration cannot be restarted, and fields must be looked up in order, and string/number
|
||||
values can only be parsed once. If these limitations are acceptable to you, the On Demand API could
|
||||
help you write maintainable applications with a computation efficiency that is difficult to surpass.
|
||||
|
||||
A code example illustrates our API from a programmer's point of view:
|
||||
|
||||
```c++
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
for (auto tweet : doc["statuses"]) {
|
||||
std::string_view text = tweet["text"];
|
||||
std::string_view screen_name = tweet["user"]["screen_name"];
|
||||
std::string_view screen_name;
|
||||
{
|
||||
ondemand::object user = tweet["user"];
|
||||
screen_name = user["screen_name"];
|
||||
}
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
cout << screen_name << " (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
}
|
||||
```
|
||||
|
||||
Such code would be apply to a JSON document such as the following JSON mimicking a sample result from the Twitter API:
|
||||
|
||||
```json
|
||||
{
|
||||
"statuses": [{
|
||||
"text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
|
||||
"user": {
|
||||
"name": "AYUMI",
|
||||
"screen_name": "ayuu0123",
|
||||
"followers_count": 262,
|
||||
"friends_count": 252
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"favorite_count": 0
|
||||
},
|
||||
{
|
||||
"text": "RT @KATANA77: えっそれは・・・(一同) http://t.co/PkCJAcSuYK",
|
||||
"user": {
|
||||
"name": "RT&ファボ魔のむっつんさっm",
|
||||
"screen_name": "yuttari1998",
|
||||
"followers_count": 95,
|
||||
"friends_count": 158
|
||||
},
|
||||
"retweet_count": 82,
|
||||
"favorite_count": 42
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This streaming approach means that unused fields and values are not parsed or
|
||||
converted, thus saving space and time. In our example, the `"name"`, `"followers_count"`,
|
||||
and `"friends_count"` keys and matching values are skipped.
|
||||
|
||||
Further, the On Demand API does not parse a value *at all* until you try to convert it (e.g., to `double`,
|
||||
`int`, `string`, or `bool`). In our example, when accessing the key-value pair `"retweet_count": 82`, the parser
|
||||
may not convert the pair of characters `82` to the binary integer 82. Because the programmer specifies the data
|
||||
type, we avoid branch mispredictions related to data type determination and improve the performance.
|
||||
|
||||
|
||||
We expect users of an On Demand API to work in terms of a JSON dialect, which is a set of expectations and
|
||||
specifications that come in addition to the [JSON specification](https://www.rfc-editor.org/rfc/rfc8259.txt).
|
||||
The On Demand approach is designed around several principles:
|
||||
|
||||
* **Streaming (\*):** It avoids preparsing values, keeping the memory usage and the latency down.
|
||||
* **Forward-Only:** To prevent reiteration of the same values and to keep the number of variables down (literally), only a single index is maintained and everything uses it (even if you have nested for loops). This means when you are going through an array of arrays, for example, that the inner array loop will advance the index to the next comma, and the array can just pick it up and look at it.
|
||||
* **Natural Iteration:** A JSON array or object can be iterated with a normal C++ for loop. Nested arrays and objects are supported by nested for loops.
|
||||
* **Use-Specific Parsing:** Parsing is always specific to the type required by the programmer. For example, if the programmer asks for an unsigned integer, we just start parsing digits. If there were no digits, we toss an error. There are even different parsers for `double`, `uint64_t` and `int64_t` values. This use-specific parsing avoids the branchiness of a generic "type switch," and makes the code more inlineable and compact.
|
||||
* **Validate What You Use:** On Demand deliberately validates the values you use and the structure leading to it, but nothing else. The goal is a guarantee that the value you asked for is the correct one and is not malformed: there must be no confusion over whether you got the right value.
|
||||
|
||||
|
||||
|
||||
To understand why On Demand is different, it is helpful to review the major
|
||||
approaches to parsing and parser APIs in use today.
|
||||
|
||||
### DOM Parsers
|
||||
|
||||
Many of the most usable, popular JSON APIs (including simdjson) deserialize into a **DOM**: an intermediate tree of
|
||||
objects, arrays and values. In this model, we convert the input data all at once into a tree-like structure (the DOM).
|
||||
The DOM is then accessed by the programmer like any other in-memory data structure. The resulting API let
|
||||
you refer to each array or object separately, using familiar techniques like iteration (`for (auto value : array)`)
|
||||
or indexing (`object["key"]`). In some cases, the values are even deserialized directly into familiar C++ constructs like vectors and
|
||||
maps.
|
||||
|
||||
The DOM approach is conceptually simple and "programmer friendly". Using the
|
||||
DOM tree is often easy enough that many users use the DOM as-is instead of creating their own
|
||||
their own custom data structures.
|
||||
|
||||
The DOM approach was the only way to parse JSON documents up to version 0.6 of the simdjson library.
|
||||
Our DOM API looks similar to our On Demand example, except
|
||||
it calls `parse` instead of `iterate`:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
auto doc = parser.parse(json);
|
||||
for (auto tweet : doc["statuses"]) {
|
||||
std::string_view text = tweet["text"];
|
||||
std::string_view screen_name = tweet["user"]["screen_name"];
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
cout << screen_name << " (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
}
|
||||
```
|
||||
|
||||
Pros of the DOM approach:
|
||||
* Straightforward, programmer-friendly interface (arrays and objects).
|
||||
* Safe: all of the input data has been validated before it is accessed.
|
||||
* All of the JSON document is available at once to the programmer.
|
||||
|
||||
Cons of the DOM approach:
|
||||
* The memory usage scales linearly with the size of the input document.
|
||||
* Parses and stores everything, using memory and CPU cycles even on unused values.
|
||||
* Performance drain from [type blindness](#type-blindness).
|
||||
|
||||
|
||||
What the simdjson library demonstrates is that a DOM API may be quite fast indeed: we can parse files at speeds
|
||||
of several gigabytes per second. However, in some instances, it may be possible to achieve even higher speeds.
|
||||
|
||||
### Event-Based Parsers (SAX, SAJ, etc.)
|
||||
|
||||
|
||||
The event-based model (originally from the "Streaming API for XML") uses streaming to eliminate the cost of
|
||||
parsing and storing the entire JSON. In the event-based model, a core JSON engine parses the JSON document
|
||||
piece by piece, but instead of stuffing values in a DOM tree, it passes each value to a callback function,
|
||||
letting the user decide for themselves how to handle it. In such a model, the programmer may need to provide functions
|
||||
for all possible events (a number, a string, a new object, a new array, the array ends, the object ends, and so on).
|
||||
This allows programmers to work with much larger files without running out of memory.
|
||||
|
||||
The drawback is complexity: event-based APIs generally have you define a single callback for each type
|
||||
(e.g. `string_field(std::string_view key, std::string_view value)`). Because of this, the programmer suffers
|
||||
from context blindness: when they find a string they have to check where it is before they know what to
|
||||
do with it. Is this string the text of the tweet, the screen name, or something else? Are we even in
|
||||
a tweet right now, or is this from some other place in the document
|
||||
entirely? Though an event-based approach may allow superior performance, it is demanding of the programmer
|
||||
who must efficiently keep track of its current state within the JSON input.
|
||||
|
||||
The following is event-based example of the Twitter problem we have reviewed in the DOM and On Demand
|
||||
examples. To make it short enough to use as an example at all, it has heavily redacted: it only solves
|
||||
a part of the problem (does not get user.screen_name), it has bugs (it does not handle sub-objects
|
||||
in a tweet at all), and it uses a theoretical, simple event-based API that minimizes ceremony.
|
||||
|
||||
```c++
|
||||
struct twitter_callbacks {
|
||||
bool in_statuses;
|
||||
bool in_tweet;
|
||||
std::string_view text;
|
||||
uint64_t retweets;
|
||||
uint64_t favorites;
|
||||
void start_object_field(std::string_view key) {
|
||||
if (key == "statuses") { in_statuses = true; }
|
||||
}
|
||||
void start_object() {
|
||||
if (in_statuses) { in_tweet = true; }
|
||||
}
|
||||
void string_field(std::string_view key, std::string_view value) {
|
||||
if (in_tweet && key == "text") { text = value; }
|
||||
}
|
||||
void number_field(std::string_view key, uint64_t value) {
|
||||
if (in_tweet) {
|
||||
if (key == "retweet_count") { retweets = value; }
|
||||
if (key == "favorite_count") { favorites = value; }
|
||||
}
|
||||
}
|
||||
void end_object() {
|
||||
if (in_tweet) {
|
||||
cout << "[redacted] (" << retweets << " retweets / " << favorites << " favorites): " << text << endl;
|
||||
in_tweet = false;
|
||||
} else if (in_statuses) {
|
||||
in_statuses = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
sax::parser parser;
|
||||
parser.parse(twitter_callbacks());
|
||||
```
|
||||
|
||||
This is a large amount of code, requiring mental gymnastics even to read. An actual implementation is harder to write
|
||||
and to maintain.
|
||||
|
||||
|
||||
Pros of the event-based approach:
|
||||
* Speed and space benefits from low, predictable memory usage.
|
||||
* Parsing can be done more lazily: the API can delegate work to the programmer for better performance.
|
||||
* It is highly flexible: given enough effort, most tasks can be accomplished efficiently.
|
||||
|
||||
Cons of the event-based approach:
|
||||
* Performance drain from context blindness (e.g., switch statements for "where am I in the document")
|
||||
* Difficult to use (high code complexity, high maintenance, difficult to debug)
|
||||
* Lacks the safety of DOM: malformed documents could be ingested.
|
||||
|
||||
Though an event-based approach might have its niche uses, we believe that it is rarely ideally suited. We suspect that it is mostly used when performance and memory is a concern, and no other option (except DOM) is readily available.
|
||||
|
||||
### Schema-Based Parser Generators
|
||||
|
||||
|
||||
In a schema-based model, the programmer provides a description of a data structure, and the parser constructs the data structure in question during parsing. These parsers take a schema--a description of
|
||||
your JSON, with field names, types, everything--and generate classes/structs in your language of
|
||||
choice, as well as a parser to deserialize the JSON into those structs. Some such parsers let you
|
||||
define your own data structures (`struct`) and they let a preprocessor inspects it and generates a custom JSON parser for it.
|
||||
Though not all of these schema-based parser generators generate a parser or even optimize for
|
||||
streaming, but they are *able* to in principle. Unlike the DOM and the event-based models, a schema-based approach assumes
|
||||
that the structure of the document is known at compile-time.
|
||||
|
||||
|
||||
Pros of the schema-based approach:
|
||||
* Ease of Use is on par with DOM
|
||||
* Parsers that generate iterators and lazy values in structs can keep memory pressure down to event-based levels.
|
||||
* Type Blindness can be entirely solved with specific parsers for each type, saving many branches.
|
||||
* Context Blindness can be solved, especially if object fields are required and in order, saving even more branches.
|
||||
* Can be made a safe as DOM: the input can be entirely validated prior to ingestion.
|
||||
|
||||
Cons of the schema-based approach:
|
||||
* It is less flexible than the DOM or event-based approaches, sometimes limited to a deserialization-to-objects scenario.
|
||||
* The structure of the data must be fully known at compile-time.
|
||||
|
||||
|
||||
### Type Blindness and Branch Misprediction
|
||||
|
||||
The DOM and event-based parsing model suffer from **type
|
||||
blindness**: even when the programmer knows exactly what fields and what types are in the JSON document,
|
||||
the parser does not. This means it has to look at each value blind with a big "switch"
|
||||
statement, asking "is this a number? A string? A boolean? An array? An object?"
|
||||
|
||||
In modern processors, this kind of switch statement can make your program run slower
|
||||
than it needs to because of the high cost of branch misprediction. Indeed, modern processor
|
||||
cores rely on speculative execution for speed. They "read ahead" in your program, predicting
|
||||
which instructions to run as soon as the data is available. A single-threaded program can
|
||||
execute 2, 3 or even more instructions per cycle--largely because of speculative execution.
|
||||
|
||||
Unfortunately, when the processor mispredicts the instructions, typically due to a mispredicted
|
||||
branch, all of the work done from the misprediction has be discarded and started anew. The
|
||||
processor may have been executing 3 or 4 instructions per cycle, and consuming the corresponding
|
||||
power, but all of the work may have been wasteful.
|
||||
|
||||
Type blindness means that the processor has to guess, for every JSON value, whether it will be an array,
|
||||
an object, number, string or boolean since these correspond to distinct code paths.
|
||||
Though some JSON files have predictable content, we find in practice that many JSON files
|
||||
stress the branch prediction. Though branch predictors improve with each new generation of processors,
|
||||
the cost of branch mispredictions also tends to increase as pipelines expand, and the processors become
|
||||
able to schedule longer streams of instructions.
|
||||
|
||||
On Demand parsing is tailor-made to solve this problem at the source, parsing values only after the
|
||||
user declares their type by asking for a `double`, an `int`, a `string`, etc. It attempts to do so while
|
||||
preserving most of the flexibility of DOM parsing.
|
||||
|
||||
Algorithm
|
||||
---------
|
||||
|
||||
To help visualize the algorithm, we'll walk through the example C++ given at the top, for this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"statuses": [
|
||||
{ "id": 1, "text": "first!", "user": { "screen_name": "lemire", "name": "Daniel" }, "favorite_count": 100, "retweet_count": 40 },
|
||||
{ "id": 2, "text": "second!", "user": { "screen_name": "jkeiser2", "name": "John" }, "favorite_count": 2, "retweet_count": 3 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Starting the iteration
|
||||
|
||||
1. First, we declare a parser object that keeps internal buffers necessary for parsing. This can be
|
||||
reused to parse multiple JSON files, so you do not pay the high cost of allocating memory every
|
||||
time (and so it can stay in cache!).
|
||||
|
||||
This declaration does not allocate any memory; that will happen in the next step.
|
||||
|
||||
```c++
|
||||
ondemand::parser parser;
|
||||
```
|
||||
|
||||
2. We then start iterating the JSON document by allocating internal parser buffers, preprocessing
|
||||
the JSON, and initializing the iterator.
|
||||
|
||||
```c++
|
||||
auto doc = parser.iterate(json);
|
||||
```
|
||||
|
||||
Since this is the first time this parser has been used, `iterate()` first allocates internal
|
||||
parser buffers if this is the first time through. When reusing an existing parser, allocation
|
||||
only happens if the new document is bigger than internal buffers can handle. The On Demand
|
||||
API only ever allocates memory in the `iterate()` function call.
|
||||
|
||||
The simdjson library then preprocesses the JSON text at high speed, finding all tokens (i.e. the starting
|
||||
position of any JSON value, as well as any important operators like `,`, `:`, `]` or `}`).
|
||||
|
||||
Finally, a `document` iterator is created, initialized at the position of the first value in the
|
||||
`json` text input. The document iterator is bumped forward by array / object iterators and
|
||||
object[] lookup, and must be kept around until iteration is complete.
|
||||
|
||||
This operation can fail as this stage if the document in invalid! The result type is `simdjson_result<document>`.
|
||||
The simdjson library uses `simdjson_result` when a value needs to be returned by a function that can fail given improper inputs.
|
||||
The `simdjson_result` value contain an `error_code` and a `document`, and it was designed to allow you to use either error code
|
||||
checking or C++ exceptions via a direct cast `document(parser.iterate(json))` you can use `get()`
|
||||
to check the error and cast to a value, or cast directly to a value. However, the simdjson library
|
||||
rely on error chaining, so it is possible to delay error checks: we shall shortly explain error
|
||||
chaining more fully.
|
||||
|
||||
NOTE: You should always have such a `document` instance (here `doc`) and it should remain in scope for the duration
|
||||
of your parsing function. E.g., you should not use the returned document as a temporary (e.g., `auto x = parser.iterate(json).get_object();`)
|
||||
followed by other operations as the destruction of the `document` instance makes all of the derived instances
|
||||
ill-defined.
|
||||
|
||||
|
||||
3. We iterate over the "statuses" field using a typical C++ iterator, reading past the initial
|
||||
`{ "statuses": [ {`.
|
||||
|
||||
```c++
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
```
|
||||
This shorthand does much, and it is helpful to see what it expands to.
|
||||
Comments in front of each one explain what's going on:
|
||||
```c++
|
||||
// Validate that the top-level value is an object: check for {
|
||||
ondemand::object top = doc.get_object();
|
||||
|
||||
// Find the field statuses by:
|
||||
// 1. Check whether the object is empty (check for }). (We do not really need to do this unless the key lookup fails!)
|
||||
// 2. Check if we're at the field by looking for the string "statuses" using byte-by-byte comparison.
|
||||
// 3. Validate that there is a `:` after it.
|
||||
auto tweets_field = top["statuses"];
|
||||
|
||||
// Validate that the field value is an array: check for [
|
||||
// Also mark the array as finished if there is a ] next, which would cause the while () statement to exit immediately.
|
||||
ondemand::array tweets = tweets_field.get_array();
|
||||
// These three method calls do nothing substantial (the real checking happens in get_array() and ++)
|
||||
// != checks whether the array is marked as finished (if we have found a ]).
|
||||
ondemand::array_iterator tweets_iter = tweets.begin();
|
||||
while (tweets_iter != tweets.end()) {
|
||||
auto tweet_value = *tweets_iter;
|
||||
|
||||
// Validate that the array element is an object: check for {
|
||||
ondemand::object tweet = tweet_value.get_object();
|
||||
...
|
||||
}
|
||||
```
|
||||
What is not explained in this code expansion is *error chaining*.
|
||||
Generally, you can use `document` methods on a `simdjson_result<...>` value; any errors will
|
||||
just be passed down the chain. Many method calls
|
||||
can be chained in this manner. So `for (object tweet : doc["statuses"])`, which is the equivalent of
|
||||
`object tweet = *(doc.get_object()["statuses"].get_array().begin()).get_object()`, could fail in any of
|
||||
6 method calls, and the error will only be checked at the end,
|
||||
when you attempt to cast the final `simdjson_result<object>` to object. Upon casting, an exception is
|
||||
thrown if there was an error.
|
||||
|
||||
NOTE: while the document can be queried once for a key as if it were an object, it is not an actual object
|
||||
instance. If you need to treat it as an object (e.g., to query more than one keys), you can cast it as
|
||||
such `ondemand::object root_object = doc.get_object();`.
|
||||
|
||||
|
||||
4. We get the `"text"` field as a string.
|
||||
|
||||
```c++
|
||||
std::string_view text = tweet["text"];
|
||||
```
|
||||
|
||||
First, `["text"]` skips the `"id"` field because it does not match: skips the key, `:` and
|
||||
value (`1`). We then check whether there are more fields by looking for either `,`
|
||||
or `}`.
|
||||
|
||||
The second field is matched (`"text"`), so we validate the `:` and move to the actual value.
|
||||
|
||||
NOTE: `["text"]` does a *raw match*, comparing the key directly against the raw JSON. This means
|
||||
that keys with escapes in them may not be matched and the letter case must match exactly.
|
||||
|
||||
To convert to a string, we check for `"` and use simdjson's fast unescaping algorithm to copy
|
||||
`first!` (plus a terminating `\0`) into a buffer managed by the `document`. This buffer stores
|
||||
all strings from a single iteration. The next string will be written after the `\0`.
|
||||
|
||||
A `string_view` is returned which points to that buffer, and contains the length.
|
||||
|
||||
4. We get the `"screen_name"` from the `"user"` object.
|
||||
|
||||
```c++
|
||||
ondemand::object user = tweet["user"];
|
||||
screen_name = user["screen_name"];
|
||||
```
|
||||
|
||||
First, `["user"]` checks whether there are any more object fields by looking for either `,` or
|
||||
`}`. Then it matches `"user"` and validates the `:`.
|
||||
|
||||
`["screen_name"]` then converts to object, checking for `{`, and finds `"screen_name"`.
|
||||
|
||||
To convert the result to usable string (i.e., the screen name `lemire`), the characters are written to the document's
|
||||
string buffer (after possibly escaping them), which now has *two* string_views pointing into it, and looks like `first!\0lemire\0`.
|
||||
|
||||
Finally, the temporary user object is destroyed, causing it to skip the remainder of the object
|
||||
(`}`).
|
||||
|
||||
NOTE: You may only have one active array or object active at any given time. An array or an object becomes
|
||||
active when the `ondemand::object` or `ondemand::array` is created, and it releases its 'focus' when
|
||||
its destructor is called. If you create an array or an object located inside a parent object or array,
|
||||
the child array or object becomes active while the parent becomes temporarily inactive. If you access
|
||||
several sibling objects or arrays, you must ensure that the destructor is called by scoping each access
|
||||
(see Iteration Safety section below for further details).
|
||||
|
||||
5. We get `"retweet_count"` and `"favorite_count"` as unsigned integers.
|
||||
|
||||
```c++
|
||||
uint64_t retweets = tweet["retweet_count"];
|
||||
uint64_t favorites = tweet["favorite_count"];
|
||||
```
|
||||
|
||||
6. We loop to the next tweet.
|
||||
|
||||
```c++
|
||||
for (ondemand::object tweet : doc["statuses"]) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The relevant parts of the loop are:
|
||||
|
||||
```c++
|
||||
while (iter != statuses.end()) {
|
||||
ondemand::object tweet = *iter;
|
||||
...
|
||||
iter++;
|
||||
}
|
||||
```
|
||||
|
||||
First, the `tweet` destructor runs, skipping the remainder of the object which in this case is
|
||||
just `}`.
|
||||
|
||||
Next, `iter++` checks whether there are more values and finds `,`. The loop continues.
|
||||
|
||||
Finally, `ondemand::object tweet = *iter` checks for `{` and returns the object.
|
||||
|
||||
This tweet is processed just like the previous one.
|
||||
|
||||
7. We finish the last tweet.
|
||||
|
||||
At the end of the loop, the `tweet` is first destroyed, skipping the remainder of the tweet
|
||||
object (`}`).
|
||||
|
||||
The `iter++` instruction from `for (ondemand::object tweet : doc["statuses"])` then checks whether there are
|
||||
more values and finds that there are none (`]`). It marks the array iteration as finished and the for
|
||||
loop terminates.
|
||||
|
||||
Then the outer object is destroyed, skipping everything up to the `}`.
|
||||
|
||||
Design Features
|
||||
---------------
|
||||
|
||||
### String Parsing
|
||||
|
||||
When the user requests strings, we unescape them to a single string buffer much like the DOM parser
|
||||
so that users enjoy the same string performance as the core simdjson. We do not write the length to the
|
||||
string buffer, however; that is stored in the `string_view` instance we return to the user.
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
std::set<std::string_view> default_users;
|
||||
ondemand::array tweets = doc["statuses"].get_array();
|
||||
for (auto tweet_value : tweets) {
|
||||
auto tweet = tweet_value.get_object();
|
||||
ondemand::object user = tweet["user"].get_object();
|
||||
std::string_view screen_name = user["screen_name"].get_string();
|
||||
bool default_profile = user["default_profile"].get_bool();
|
||||
if (default_profile) { default_users.insert(screen_name); }
|
||||
}
|
||||
```
|
||||
|
||||
By using `string_view` instances, we avoid the high cost of allocating many small strings (as would be the
|
||||
case with `std::string`) but be mindful that the life cycle of these `string_view` instances is tied to the
|
||||
parser instance. If the parser instance is destroyed or reused for a new JSON document, these strings are no longer valid.
|
||||
|
||||
We iterate through object instances using `field` instances which represent key-value pairs. The value
|
||||
is accessible by the `value()` method whereas the key is accessible by the `key()` method.
|
||||
The keys are treated differently than values are made available as as special type `raw_json_string`
|
||||
which is a lightweight type that is meant to be used on a temporary basis, amost solely for
|
||||
direct raw ASCII comparisons (`field.key() == "mykey"`). If you occasionally need to access and store the
|
||||
unescaped key values, you may use the `unescaped_key()` method. Once you have called `unescaped_key()` method,
|
||||
neither the `key()` nor the `unescaped_key()` methods should be called: the current field instance
|
||||
has no longer a key (that is by design). Like other strings, the resulting `std::string_view` generated
|
||||
from the `unescaped_key()` method has a lifecycle tied to the `parser` instance: once the parser
|
||||
is destroyed or reused with another document, the `std::string_view` instance becomes invalid.
|
||||
|
||||
|
||||
```C++
|
||||
auto doc = parser.iterate(json);
|
||||
for(auto field : doc.get_object()) {
|
||||
std::string_view keyv = field.unescaped_key();
|
||||
}
|
||||
```
|
||||
|
||||
### Iteration Safety
|
||||
|
||||
The On Demand API is powerful. To compensate, we add some safeguards to ensure that it can be used without fear
|
||||
in production systems:
|
||||
|
||||
- If the value fails to be parsed as one type, the program can try to parse it as something else until the program succeeds. Thus
|
||||
the programmer can engineer fall back routines.
|
||||
- If the value succeeds in being parsed or converted to a type, the program cannot try again. An attempt to parse the same node twice will
|
||||
cause the program to abort. We put this safety measure in the API to prevent double iteration of an array which
|
||||
would cause inconsistent iterator state or double-unescaping a string which may cause memory
|
||||
overruns if done.
|
||||
- Guaranteed Iteration: If you discard a value without using it--perhaps you just wanted to know
|
||||
if it was `nullptr` but did not care what the actual value was--it will iterate. The destructor automates
|
||||
the iteration.
|
||||
|
||||
Some care is needed when using the On Demand API in scenarios where you need to access several sibling arrays or objects because
|
||||
only one object or array can be active at any one time. Let us consider the following example:
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::object parent = doc["parent"];
|
||||
// parent owns the focus
|
||||
ondemand::object c1 = parent["child1"];
|
||||
// c1 owns the focus
|
||||
//
|
||||
if(std::string_view(c1["name"]) != "John") { ... }
|
||||
// c2 attempts to grab the focus from parent but fails
|
||||
ondemand::object c2 = parent["child2"];
|
||||
// c2 is now in an unsafe state and the following line would be unsafe
|
||||
// if(std::string_view(c2["name"]) != "Daniel") { return false; }
|
||||
```
|
||||
|
||||
A correct usage is given by the following example:
|
||||
|
||||
```C++
|
||||
ondemand::parser parser;
|
||||
const padded_string json = R"({ "parent": {"child1": {"name": "John"} , "child2": {"name": "Daniel"}} })"_padded;
|
||||
auto doc = parser.iterate(json);
|
||||
ondemand::object parent = doc["parent"];
|
||||
// At this point, parent owns the focus
|
||||
{
|
||||
ondemand::object c1 = parent["child1"];
|
||||
// c1 grabbed the focus from parent
|
||||
if(std::string_view(c1["name"]) != "John") { return false; }
|
||||
}
|
||||
// c1 went out of scope, so its destructor was called and the focus
|
||||
// was handed back to parent.
|
||||
{
|
||||
ondemand::object c2 = parent["child2"];
|
||||
// c2 grabbed the focus from parent
|
||||
// the following is safe:
|
||||
if(std::string_view(c2["name"]) != "Daniel") { return false; }
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits of the On Demand Approach
|
||||
|
||||
We expect that the On Demand approach has many of the performance benefits of the schema-based approach, while providing a flexibility that is similar to that of the DOM-based approach.
|
||||
|
||||
* Faster than DOM in some cases. Reduced memory usage.
|
||||
* Straightforward, programmer-friendly interface (arrays and objects).
|
||||
* Highly expressive, beyond deserialization and pointer queries: many tasks can be accomplished with little code.
|
||||
|
||||
### Limitations of the On Demand Approach
|
||||
|
||||
The On Demand approach has some limitations:
|
||||
|
||||
* Because it operates in streaming mode, you only have access to the current element in the JSON document. Furthermore, the document is traversed in order so the code is sensitive to the order of the JSON nodes in the same manner as an event-based approach (e.g., SAX).
|
||||
* The On Demand approach is less safe than DOM: we only validate the components of the JSON document that are used and it is possible to begin ingesting an invalid document only to find out later that the document is invalid. Are you fine ingesting a large JSON document that starts with well formed JSON but ends with invalid JSON content?
|
||||
|
||||
There are currently additional technical limitations which we expect to resolve in future releases of the simdjson library:
|
||||
|
||||
* The simdjson library offers runtime dispatching which allows you to compile one binary and have it run at full speed on different processors, taking advantage of the specific features of the processor. The On Demand API does not have runtime dispatch support at this time. To benefit from the On Demand API, you must compile your code for a specific processor. E.g., if your processor supports AVX2 instructions, you should compile your binary executable with AVX2 instruction support (by using your compiler's commands). If you are sufficiently technically proficient, you can implement runtime dispatching within your application, by compiling your On Demand code for different processors.
|
||||
* There is an initial phase which scans the entire document quickly, irrespective of the size of the document. We plan to break this phase into distinct steps for large files in a future release as we have done with other components of our API (e.g., `parse_many`).
|
||||
* The On Demand API does not support JSON Pointer. This capability is currently limited to our core API.
|
||||
* You should be mindful that the though your software might write the keys in a consistent manner, the [JSON specification](https://www.rfc-editor.org/rfc/rfc8259.txt) states that "JSON parsing libraries have been observed to differ as to whether or not they make the ordering of object members visible". The On Demand API will help the programmer handle unexpected JSON dialects by throwing an exception when the unexpected occurs, but the programmer is responsible for handling such cases: e.g., by rejecting the JSON input that does not follow the expected JSON dialect. We intend to help users who wish to use the On Demand API but require support for order-insensitive semantics, but in our current implementation support for out-of-order keys (if needed) must be provided by the programmer. Currently, one might proceed in the following manner as a fallback measure if keys can appear in any order:
|
||||
```C++
|
||||
for (ondemand::object my_object : doc["mykey"]) {
|
||||
for (auto field : my_object) {
|
||||
if (field.key() == "key_value1") { process1(field.value()); }
|
||||
else if (field.key() == "key_value2") { process2(field.value()); }
|
||||
else if (field.key() == "key_value3") { process3(field.value()); }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Applicability of the On Demand Approach
|
||||
|
||||
At this time we recommend the On Demand API in the following cases:
|
||||
|
||||
1. The 64-bit hardware (CPU) used to run the software is known at compile time. If you need runtime dispatching because you cannot be certain of the hardware used to run your software, you will be better served with the core simdjson API. (This only applies to x64 (AMD/Intel). On 64-bit ARM hardware, runtime dispatching is unnecessary.)
|
||||
2. The used parts of JSON files do not need to be validated and the layout of the nodes follows a strict JSON dialect. If you are receiving JSON from other systems, you might be better served with core simdjson API as it fully validates the JSON inputs and allows you to navigate through the document at will.
|
||||
3. Speed and efficiency are of the utmost importance. Keep in mind that the core simdjson API is highly efficient so adopting the On Demand API is not necessary for high efficiency.
|
||||
4. As a developer, you value a clean, flexible and maintainable API.
|
||||
|
||||
Good applications for the On Demand API might be:
|
||||
|
||||
* You are working from pre-existing large JSON files that have been vetted. You expect them to be well formed according to a known JSON dialect and to have a consistent layout. For example, you might be doing biomedical research or machine learning on top of static data dumps in JSON.
|
||||
* You have a closed system on predetermined hardware. Both the generation and the consumption of JSON data is within your system. Your team controls both the software that produces the JSON and the software the parses it, your team knows and control the hardware. Thus you can fully test your system.
|
||||
* You are working with stable JSON APIs which have a consistent layout and JSON dialect.
|
||||
|
||||
## Checking Your CPU Selection
|
||||
|
||||
Given that the On Demand API does not offer runtime dispatching, your code is compiled against a specific CPU target. You should
|
||||
verify that the code is compiled against the target you expect: `haswell` (AVX2 x64 processors), `westmere` (SSE4 x64 processors), `arm64` (64-bit ARM), `fallback` (others). Under x64 processors, many programmers will want to target `haswell` whereas under ARM,
|
||||
most programmers will want to target `arm64`. The `fallback` is probably only good for testing purposes, not for deployment.
|
||||
|
||||
```C++
|
||||
std::cout << simdjson::builtin_implementation()->name() << std::endl;
|
||||
```
|
||||
|
||||
If you are using CMake for your C++ project, then you can pass compilation flags to your compiler by using
|
||||
the `CMAKE_CXX_FLAGS` variable:
|
||||
|
||||
```
|
||||
cmake -DCMAKE_CXX_FLAGS="-march=haswell" -B build_haswell
|
||||
cmake --build build_haswell
|
||||
```
|
||||
+9
-9
@@ -1,7 +1,7 @@
|
||||
parse_many
|
||||
==========
|
||||
|
||||
An interface providing features to work with files or streams containing multiple JSON documents.
|
||||
An interface providing features to work with files or streams containing multiple small JSON documents.
|
||||
As fast and convenient as possible.
|
||||
|
||||
Contents
|
||||
@@ -14,16 +14,16 @@ Contents
|
||||
- [API](#api)
|
||||
- [Use cases](#use-cases)
|
||||
|
||||
Motivations
|
||||
Motivation
|
||||
-----------
|
||||
|
||||
The main motivation for this piece of software is to achieve maximum speed and offer a
|
||||
better quality of life in parsing files containing multiple JSON documents.
|
||||
better quality of life in parsing files containing multiple small JSON documents.
|
||||
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a very handy
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a handy
|
||||
serialization format. However, when serializing a large sequence of
|
||||
values as an array, or a possibly indeterminate-length or never-
|
||||
ending sequence of values, JSON becomes difficult to work with.
|
||||
ending sequence of values, JSON may be inconvenient.
|
||||
|
||||
Consider a sequence of one million values, each possibly one kilobyte
|
||||
when encoded -- roughly one gigabyte. It is often desirable to process such a dataset incrementally
|
||||
@@ -32,9 +32,9 @@ without having to first read all of it before beginning to produce results.
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Here is a chart comparing the speed of the different alternatives to parse a multiline JSON.
|
||||
The simdjson library provides a threaded and non-threaded parse_many() implementation. As the
|
||||
figure below shows, if you can, use threads, but if you can't, it's still pretty fast!
|
||||
The following is a chart comparing the speed of the different alternatives to parse a multiline JSON.
|
||||
The simdjson library provides a threaded and non-threaded `parse_many()` implementation. As the
|
||||
figure below shows, if you can, use threads, but if you cannot, the unthreaded mode is still fast!
|
||||
[](/doc/Multiline_JSON_Parse_Competition.png)
|
||||
|
||||
How it works
|
||||
@@ -71,7 +71,7 @@ allocate enough memory so that all the documents can fit. This value is what we
|
||||
As of right now, we need to manually specify a value for this batch size, it has to be at least as
|
||||
big as the biggest document in your file, but not too big so that it submerges the cached memory.
|
||||
The bigger the batch size, the fewer we need to make allocations. We found that 1MB is somewhat a
|
||||
sweet spot for now.
|
||||
sweet spot.
|
||||
|
||||
1. When the user calls `parse_many`, we return a `document_stream` which the user can iterate over
|
||||
to receive parsed documents.
|
||||
|
||||
@@ -12,6 +12,8 @@ are still some scenarios where tuning can enhance performance.
|
||||
* [Visual Studio](#visual-studio)
|
||||
* [Downclocking](#downclocking)
|
||||
* [Best Use of the DOM API](#best-use-of-the-dom-api)
|
||||
* [Padding and Temporary Copies](#padding-and-temporary-copies)
|
||||
|
||||
|
||||
Reusing the parser for maximum efficiency
|
||||
-----------------------------------------
|
||||
@@ -174,3 +176,25 @@ Best Use of the DOM API
|
||||
|
||||
The simdjson API provides access to the JSON DOM (document-object-model) content as a tree of `dom::element` instances, each representing an object, an array or an atomic type (null, true, false, number). These `dom::element` instances are lightweight objects (e.g., spanning 16 bytes) and it might be advantageous to pass them by value, as opposed to passing them by reference or by pointer.
|
||||
|
||||
Padding and Temporary Copies
|
||||
--------------
|
||||
|
||||
The simdjson function `parser.parse` reads data from a padded buffer, containing SIMDJSON_PADDING extra bytes added at the end.
|
||||
If you are passing a `padded_string` to `parser.parse` or loading the JSON directly from
|
||||
disk (`parser.load`), padding is automatically handled.
|
||||
When calling `parser.parse` on a pointer (e.g., `parser.parse(my_char_pointer, my_length_in_bytes)`) a temporary copy is made by default with adequate padding and you, again, do not need to be concerned with padding.
|
||||
|
||||
Some users may not be able use our `padded_string` class or to load the data directly from disk (`parser.load`). They may need to pass data pointers to the library. If these users wish to avoid temporary copies and corresponding temporary memory allocations, they may want to call `parser.parse` with the `realloc_if_needed` parameter set to false (e.g., `parser.parse(my_char_pointer, my_length_in_bytes, false)`). In such cases, they need to ensure that there are at least SIMDJSON_PADDING extra bytes at the end that can be safely accessed and read. They do not need to initialize the padded bytes to any value in particular. The following example is safe:
|
||||
|
||||
|
||||
```C++
|
||||
const char *json = R"({"key":"value"})";
|
||||
const size_t json_len = std::strlen(json);
|
||||
std::unique_ptr<char[]> padded_json_copy{new char[json_len + SIMDJSON_PADDING]};
|
||||
memcpy(padded_json_copy.get(), json, json_len);
|
||||
memset(padded_json_copy.get() + json_len, 0, SIMDJSON_PADDING);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len, false);
|
||||
````
|
||||
|
||||
Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
|
||||
+14
-9
@@ -28,10 +28,6 @@ if(ENABLE_FUZZING)
|
||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
||||
set(SIMDJSON_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||
|
||||
add_custom_target(print_all_fuzz_targets
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ${SOURCES}
|
||||
)
|
||||
|
||||
# Fuzzer build flags and libraries
|
||||
add_library(simdjson-fuzzer INTERFACE)
|
||||
if (SIMDJSON_FUZZ_LINKMAIN)
|
||||
@@ -46,18 +42,27 @@ if(ENABLE_FUZZING)
|
||||
# Define the fuzzers
|
||||
add_custom_target(all_fuzzers)
|
||||
|
||||
set(fuzzernames)
|
||||
function(implement_fuzzer name)
|
||||
add_executable(${name} ${name}.cpp)
|
||||
target_link_libraries(${name} PRIVATE simdjson-fuzzer)
|
||||
add_dependencies(all_fuzzers ${name})
|
||||
add_test(${name} ${name})
|
||||
set_property(TEST ${name} APPEND PROPERTY LABELS fuzz)
|
||||
set(fuzzernames ${fuzzernames} ${name} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
implement_fuzzer(fuzz_parser)
|
||||
implement_fuzzer(fuzz_minify)
|
||||
implement_fuzzer(fuzz_atpointer)
|
||||
implement_fuzzer(fuzz_dump)
|
||||
implement_fuzzer(fuzz_print_json)
|
||||
implement_fuzzer(fuzz_dump_raw_tape)
|
||||
implement_fuzzer(fuzz_element)
|
||||
implement_fuzzer(fuzz_implementations) # parses and serializes again, compares across implementations
|
||||
implement_fuzzer(fuzz_minify) # minify *with* parsing
|
||||
implement_fuzzer(fuzz_minifyimpl) # minify *without* parsing, plus compare implementations
|
||||
implement_fuzzer(fuzz_parser)
|
||||
implement_fuzzer(fuzz_print_json)
|
||||
implement_fuzzer(fuzz_utf8) # utf8 verification, compares across implementations
|
||||
|
||||
# to be able to get a list of all fuzzers from within a script
|
||||
add_custom_target(print_all_fuzzernames
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ${fuzzernames})
|
||||
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#ifndef SIMDJSON_FUZZUTILS_H
|
||||
#define SIMDJSON_FUZZUTILS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <cstring> //memcpy
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
// view data as a char pointer
|
||||
template <typename T> inline const char* as_chars(const T* data) {
|
||||
return static_cast<const char*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Splits the input into strings, using a four byte separator which is human
|
||||
// readable. Makes for nicer debugging of fuzz data.
|
||||
// See https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#magic-separator
|
||||
// for background. Note: don't use memmem, it is not standard C++.
|
||||
inline std::vector<std::string_view> split(const char* Data, size_t Size) {
|
||||
|
||||
std::vector<std::string_view> ret;
|
||||
|
||||
using namespace std::literals;
|
||||
constexpr auto sep="\n~~\n"sv;
|
||||
|
||||
std::string_view all(Data,Size);
|
||||
auto pos=all.find(sep);
|
||||
while(pos!=std::string_view::npos) {
|
||||
ret.push_back(all.substr(0,pos));
|
||||
all=all.substr(pos+sep.size());
|
||||
pos=all.find(sep);
|
||||
}
|
||||
ret.push_back(all);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Generic helper to split fuzz data into usable parts, like ints etc.
|
||||
// Note that it does not throw, instead it sets the data pointer to null
|
||||
// if the input is exhausted.
|
||||
struct FuzzData {
|
||||
// data may not be null, even if size is zero.
|
||||
FuzzData(const uint8_t* data,
|
||||
size_t size) : Data(data),Size(size){}
|
||||
|
||||
///range is inclusive
|
||||
template<int Min, int Max>
|
||||
int getInt() {
|
||||
static_assert (Min<Max,"min must be <max");
|
||||
|
||||
// make this constexpr, can't overflow because that is UB and is forbidden
|
||||
// in constexpr evaluation
|
||||
constexpr int range=(Max-Min)+1;
|
||||
constexpr unsigned int urange=range;
|
||||
|
||||
// don't use std::uniform_int_distribution, we don't want to pay for
|
||||
// over consumption of random data. Accept the slightly non-uniform distribution.
|
||||
if(range<256)
|
||||
return Min+static_cast<int>(get<uint8_t>()%urange);
|
||||
if(range<65536)
|
||||
return Min+static_cast<int>(get<uint16_t>()%urange);
|
||||
|
||||
return Min+static_cast<int>(get<uint32_t>()%urange);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get() {
|
||||
const auto Nbytes=sizeof(T);
|
||||
T ret{};
|
||||
if(Size<Nbytes) {
|
||||
//don't throw, signal with null instead.
|
||||
Data=nullptr;
|
||||
Size=0;
|
||||
return ret;
|
||||
}
|
||||
std::memcpy(&ret,Data,Nbytes);
|
||||
Data+=Nbytes;
|
||||
Size-=Nbytes;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// gets a string view with length in [Min,Max]
|
||||
template<int Min, int Max>
|
||||
std::string_view get_stringview() {
|
||||
static_assert (Min>=0,"Min must be positive");
|
||||
const int len=getInt<Min,Max>();
|
||||
const unsigned int ulen=static_cast<unsigned int>(len);
|
||||
if(ulen<Size) {
|
||||
std::string_view ret(chardata(),ulen);
|
||||
Data+=len;
|
||||
Size-=ulen;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//mark that there is too little data to fulfill the request
|
||||
Data=nullptr;
|
||||
Size=0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// split the remainder of the data into string views,
|
||||
std::vector<std::string_view> splitIntoStrings() {
|
||||
std::vector<std::string_view> ret;
|
||||
if(Size>0) {
|
||||
ret=split(chardata(),Size);
|
||||
// all data consumed.
|
||||
Data+=Size;
|
||||
Size=0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//are we good?
|
||||
explicit operator bool() const { return Data!=nullptr;}
|
||||
|
||||
//we are a URBG
|
||||
// https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator
|
||||
//The type G satisfies UniformRandomBitGenerator if Given
|
||||
// T, the type named by G::result_type
|
||||
// g, a value of type G
|
||||
//
|
||||
// The following expressions must be valid and have their specified effects
|
||||
// Expression Return type Requirements
|
||||
// G::result_type T T is an unsigned integer type
|
||||
using result_type=uint8_t;
|
||||
// G::min() T Returns the smallest value that G's operator() may return. The value is strictly less than G::max(). The function must be constexpr.
|
||||
static constexpr result_type min() {return 0;}
|
||||
// G::max() T Returns the largest value that G's operator() may return. The value is strictly greater than G::min(). The function must be constexpr.
|
||||
static constexpr result_type max() {return 255;}
|
||||
// g() T Returns a value in the closed interval [G::min(), G::max()]. Has amortized constant complexity.
|
||||
result_type operator()() {
|
||||
if(Size==0) {
|
||||
// return something varying, otherwise uniform_int_distribution may get
|
||||
// stuck
|
||||
return failcount++;
|
||||
}
|
||||
const result_type ret=Data[0];
|
||||
Data++;
|
||||
Size--;
|
||||
return ret;
|
||||
}
|
||||
// returns a pointer to data as const char* to avoid those cstyle casts
|
||||
const char* chardata() const {return static_cast<const char*>(static_cast<const void*>(Data));}
|
||||
// members
|
||||
const uint8_t* Data;
|
||||
size_t Size;
|
||||
uint8_t failcount=0;
|
||||
};
|
||||
|
||||
|
||||
#endif // SIMDJSON_FUZZUTILS_H
|
||||
+45
-46
@@ -9,6 +9,43 @@
|
||||
|
||||
The simdjson library tries to follow [fuzzing best practises](https://google.github.io/oss-fuzz/advanced-topics/ideal-integration/#summary).
|
||||
|
||||
There is both "normal" fuzzers just feeding the api with fuzz data, as well as **differential** fuzzers. The differential fuzzers feed the same data to the multiple implementations (haswell, westmere and fallback) and ensure the same results are achieved. This makes sure the user will always get the same answer regardless of which implementation is in use.
|
||||
|
||||
The fuzzers are used in several ways.
|
||||
|
||||
* local fuzzing - for developers testing their changes before pushing and/or during development of the fuzzers themselves.
|
||||
* CI fuzzing - for weeding out those easy to find bugs in pull requests, before they are merged.
|
||||
* oss-fuzz - heavy duty 24/7 fuzzing provided by the google driven oss-fuzz project
|
||||
|
||||
## Local fuzzing
|
||||
Just invoke fuzz/quick_check.sh, it will download the latest corpus from bintray (kept up to date by the CI fuzzers) and run the fuzzers for a short time. In case you want to run the fuzzers for longer, modify the timeout value in the script or invoke the fuzzer directly.
|
||||
|
||||
This requires linux with clang and cmake installed (recent Debian and Ubuntu are known to work fine).
|
||||
|
||||
It is also possible to run the full oss-fuzz setup by following [these oss-fuzz instructions](https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally) with PROJECT_NAME set to simdjson. You will need rights to run docker.
|
||||
|
||||
## Fuzzing as a CI job - x64
|
||||
|
||||
There is a CI job which builds and runs the fuzzers. This is aimed to catch the "easy to fuzz" bugs quickly, without having to wait until pull requests are merged and eventually built and run by oss-fuzz.
|
||||
|
||||
The CI job does the following
|
||||
- builds a fast fuzzer, with full optimization but less checks which is good at rapidly exploring the input space
|
||||
- builds a heavily sanitized fuzzer, which is good at detecting errors
|
||||
- downloads the stored corpus
|
||||
- runs the fast fuzzer build for a while, to grow the corpus
|
||||
- runs the sanitizer fuzzer for a while, using the input found by the fast fuzzer
|
||||
- using a reproduce build (uninstrumented), executes a subset of the test cases in the corpus through valgrind
|
||||
- minimizes the corpus and uploads it (if on the master branch)
|
||||
- stores the corpus and valgrind output as artifacts
|
||||
|
||||
The job is available under the actions tab, here is a [direct link](https://github.com/simdjson/simdjson/actions?query=workflow%3A%22Fuzz+and+run+valgrind%22).
|
||||
|
||||
The corpus will grow over time and easy to find bugs will be detected already during the pull request stage. Also, it will keep the fuzzer builds from bit rot.
|
||||
|
||||
## Fuzzing as a CI job - arm64
|
||||
There is also a job running the fuzzers on arm64 (see .drone.yml) to make sure also the arm specific parts are fuzzed. This does not update the corpus, it just reuses what the x64 job finds.
|
||||
|
||||
## Fuzzing on oss-fuzz
|
||||
The simdjson library is continuously fuzzed on [oss-fuzz](https://github.com/google/oss-fuzz). In case a bug is found, the offending input is minimized and tested for reproducibility. A report with the details is automatically filed, and the contact persons at simdjson are notified via email. An issue is opened at the oss-fuzz bugtracker with restricted view access. When the bug is fixed, the issue is automatically closed.
|
||||
|
||||
Bugs are automatically made visible to the public after a period of time. An example of a bug that was found, fixed and closed can be seen here: [oss-fuzz 18714](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18714).
|
||||
@@ -16,8 +53,7 @@ Bugs are automatically made visible to the public after a period of time. An exa
|
||||
|
||||
## Currently open bugs
|
||||
|
||||
|
||||
You can find the currently opened bugs, if any at [bugs.chromium.org](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2): make sure not to miss the "Open Issues" selector. Bugs that are fixed by follow-up commits are automatically closed.
|
||||
You can find the currently open bugs (if any) at [bugs.chromium.org](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2): make sure not to miss the "Open Issues" selector. Bugs that are fixed by follow-up commits are automatically closed.
|
||||
|
||||
## Integration with oss-fuzz
|
||||
|
||||
@@ -26,24 +62,6 @@ Changes to the integration with oss-fuzz are made by making pull requests agains
|
||||
As little code as possible is kept at oss-fuzz since it is inconvenient to change. The [oss-fuzz build script](https://github.com/google/oss-fuzz/blob/b96dd54183f727a5d90c786e0fb01ec986c74d30/projects/simdjson/build.sh#L18) invokes [the script from the simdjson repo](https://github.com/simdjson/simdjson/blob/master/fuzz/ossfuzz.sh).
|
||||
|
||||
|
||||
|
||||
## Fuzzing as a CI job
|
||||
|
||||
There is a CI job which builds and runs the fuzzers. This is aimed to catch the "easy to fuzz" bugs quickly, without having to wait until pull requests are merged and eventually built and run by oss-fuzz.
|
||||
|
||||
The CI job does the following
|
||||
- builds several variants (with/without avx, with/without sanitizers, a fast fuzzer)
|
||||
- downloads the stored corpus
|
||||
- runs the fastest fuzzer build for 30 seconds, to grow the corpus
|
||||
- runs each build variant for 10 seconds on each fuzzer
|
||||
- using a reproduce build (uninstrumented), executes all the test cases in the corpus through valgrind
|
||||
- minimizes the corpus and upload it (if on the master branch)
|
||||
- store the corpus and valgrind output as artifacts
|
||||
|
||||
The job is available under the actions tab, here is a [direct link](https://github.com/simdjson/simdjson/actions?query=workflow%3A%22Run+fuzzers+on+stored+corpus+and+test+it+with+valgrind%22).
|
||||
|
||||
The corpus will grow over time and easy to find bugs will be detected already during the pull request stage. Also, it will keep the fuzzer builds from bit rot.
|
||||
|
||||
## Corpus
|
||||
|
||||
The simdjson library does not benefit from a corpus as much as other projects, because the library is very fast and explores the input space very well. With that said, it is still beneficial to have one. The CI job stores the corpus on bintray between runs, and is available at [bintray](https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar).
|
||||
@@ -55,32 +73,13 @@ One can also grab the corpus as an artifact from the github actions job. Pick a
|
||||
The code coverage from fuzzing is most easily viewed on the [oss-fuzz status panel](https://oss-fuzz.com/fuzzer-stats). Viewing the coverage does not require login, but the direct link is not easy to find. Substitute the date in the URL to get a more recent link:
|
||||
[https://storage.googleapis.com/oss-fuzz-coverage/simdjson/reports/20200411/linux/src/simdjson/report.html](https://storage.googleapis.com/oss-fuzz-coverage/simdjson/reports/20200411/linux/src/simdjson/report.html)
|
||||
|
||||
|
||||
## Running the fuzzers locally
|
||||
|
||||
This has only been tested on Linux (Debian and Ubuntu are known to work).
|
||||
|
||||
Make sure you have clang and cmake installed.
|
||||
The easiest way to get started is to run the following, standing in the root of the checked out repo:
|
||||
```
|
||||
fuzz/build_like_ossfuzz.sh
|
||||
```
|
||||
|
||||
Then invoke a fuzzer as shown by the following example:
|
||||
```
|
||||
mkdir -p out/parser
|
||||
build/fuzz/fuzz_parser out/parser/
|
||||
```
|
||||
|
||||
You can also use the more extensive fuzzer build script to get a variation of builds by using
|
||||
```
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
```
|
||||
|
||||
It is also possible to run the full oss-fuzz setup by following [these oss-fuzz instructions](https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally) with PROJECT_NAME set to simdjson. You will need rights to run docker.
|
||||
Keeping the coverage up is a never ending job. See [issue 368](https://github.com/simdjson/simdjson/issues/368)
|
||||
|
||||
## Reproducing
|
||||
To reproduce a test case, build the fuzzers, then invoke it with the testcase as a command line argument:
|
||||
```
|
||||
build/fuzz/fuzz_parser my_testcase.json
|
||||
To reproduce a test case, use the local build instruction. Then invoke the fuzzer (the fuzz_parser is shown as an example below) with the testcase as a command line argument:
|
||||
```shell
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
build-sanitizers/fuzz/fuzz_parser my_testcase.json
|
||||
```
|
||||
In case this does not reproduce the bug, you may want to proceed with reproducing using the oss-fuzz tools. See the instructions [here](https://google.github.io/oss-fuzz/advanced-topics/reproducing/).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Builds a corpus from all json files in the source directory.
|
||||
# Builds a corpus from all small json files in the source directory.
|
||||
# The files are renamed to the sha1 of their content, and suffixed
|
||||
# .json. The files are zipped into a flat file named corpus.zip
|
||||
|
||||
@@ -10,9 +10,9 @@ tmp=$(mktemp -d)
|
||||
|
||||
root=$(readlink -f "$(dirname "$0")/..")
|
||||
|
||||
find $root -type f -name "*.json" | while read -r json; do
|
||||
find $root -type f -size -4k -name "*.json" | while read -r json; do
|
||||
cp "$json" "$tmp"/$(sha1sum < "$json" |cut -f1 -d' ').json
|
||||
done
|
||||
|
||||
zip --junk-paths -r corpus.zip "$tmp"
|
||||
zip --quiet --junk-paths -r corpus.zip "$tmp"
|
||||
rm -rf "$tmp"
|
||||
|
||||
+21
-121
@@ -12,163 +12,63 @@ unset CXX CC CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
me=$(basename $0)
|
||||
|
||||
# A reproduce build, without avx but otherwise as plain
|
||||
# as it gets. No sanitizers or optimization.
|
||||
variant=plain-noavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
# common options
|
||||
CLANGVER=-9
|
||||
COMMON="-GNinja -DCMAKE_CXX_COMPILER=clang++$CLANGVER -DCMAKE_C_COMPILER=clang$CLANGVER -DSIMDJSON_BUILD_STATIC=Off -DENABLE_FUZZING=On -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_GIT=Off"
|
||||
|
||||
# A reproduce build as plain as it gets. Everythings tunable is
|
||||
# using the defaults.
|
||||
variant=plain-normal
|
||||
# A replay build, as plain as it gets. For use with valgrind/gdb.
|
||||
variant=replay
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
$COMMON \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# a fuzzer with sanitizers, built with avx disabled.
|
||||
variant=ossfuzz-noavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx "
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# A fuzzer with sanitizers. For improved capability to find bugs.
|
||||
variant=sanitizers
|
||||
|
||||
# a fuzzer with sanitizers, built with avx disabled.
|
||||
variant=ossfuzz-noavx9
|
||||
if which clang++-9 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang-9
|
||||
export CXX="clang++-9"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx "
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined -mno-avx2 -mno-avx"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
$COMMON \
|
||||
-DCMAKE_CXX_FLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined" \
|
||||
-DCMAKE_C_FLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined" \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
-DSIMDJSON_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-9 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
# a fuzzer with sanitizers, default built
|
||||
variant=ossfuzz-withavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -fno-sanitize-recover=undefined"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# a fast fuzzer, for fast exploration
|
||||
variant=ossfuzz-fast9
|
||||
if which clang++-9 >/dev/null 2>&1 ; then
|
||||
if [ ! -d build-$variant ] ; then
|
||||
export CC=clang-9
|
||||
export CXX="clang++-9"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link -O3 -g"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
# A fast fuzzer, for fast exploration rather than finding bugs.
|
||||
variant=fast
|
||||
if [ ! -d build-$variant ] ; then
|
||||
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE= \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
$COMMON \
|
||||
-DCMAKE_CXX_FLAGS="-fsanitize=fuzzer-no-link" \
|
||||
-DCMAKE_C_FLAGS="-fsanitize=fuzzer-no-link" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
-DSIMDJSON_FUZZ_LDFLAGS="-fsanitize=fuzzer"
|
||||
|
||||
ninja all_fuzzers
|
||||
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-9 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ export OUT=$(pwd)/ossfuzz-out
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined -O1"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
$ossfuzz
|
||||
|
||||
echo "look at the results in $OUT"
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "FuzzUtils.h"
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// Split data into two strings, json pointer and the document string.
|
||||
// Might end up with none, either or both being empty, important for
|
||||
// covering edge cases such as
|
||||
// https://github.com/simdjson/simdjson/issues/1142 Inputs missing the
|
||||
// separator line will get an empty json pointer but the all the input put in
|
||||
// the document string. This means test data from other fuzzers that take json
|
||||
// input works for this fuzzer as well.
|
||||
FuzzData fd(Data, Size);
|
||||
auto strings = fd.splitIntoStrings();
|
||||
while (strings.size() < 2) {
|
||||
strings.emplace_back();
|
||||
}
|
||||
assert(strings.size() >= 2);
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
|
||||
// parse without exceptions, for speed
|
||||
auto res = parser.parse(strings[0]);
|
||||
if (res.error())
|
||||
return 0;
|
||||
|
||||
simdjson::dom::element root;
|
||||
if (res.get(root))
|
||||
return 0;
|
||||
|
||||
auto maybe_leaf = root.at_pointer(strings[1]);
|
||||
if (maybe_leaf.error())
|
||||
return 0;
|
||||
|
||||
simdjson::dom::element leaf;
|
||||
if (maybe_leaf.get(leaf))
|
||||
return 0;
|
||||
|
||||
std::string_view sv;
|
||||
if (leaf.get_string().get(sv))
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
+1
-1
@@ -52,7 +52,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(Data, Size).get(elem);
|
||||
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
NulOStream os;
|
||||
//std::ostream& os(std::cout);
|
||||
print_json(os,elem);
|
||||
|
||||
@@ -10,9 +10,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(Data, Size).get(elem);
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
|
||||
NulOStream os;
|
||||
SIMDJSON_UNUSED auto dumpstatus = elem.dump_raw_tape(os);
|
||||
simdjson_unused auto dumpstatus = elem.dump_raw_tape(os);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "FuzzUtils.h"
|
||||
#include "NullBuffer.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
FuzzData fd(Data, Size);
|
||||
const int action = fd.getInt<0, 31>();
|
||||
|
||||
// there will be some templatized functions like is() which need to be tested
|
||||
// on a type. select one dynamically and create a function that will invoke
|
||||
// with that type
|
||||
const int selecttype=fd.getInt<0,7>();
|
||||
auto invoke_with_type=[selecttype](auto cb) {
|
||||
using constcharstar=const char*;
|
||||
switch(selecttype) {
|
||||
case 0: cb(bool{});break;
|
||||
case 1: cb(double{});break;
|
||||
case 2: cb(uint64_t{});break;
|
||||
case 3: cb(int64_t{});break;
|
||||
case 4: cb(std::string_view{});break;
|
||||
case 5: cb(constcharstar{});break;
|
||||
case 6: cb(simdjson::dom::array{});break;
|
||||
case 7: cb(simdjson::dom::object{});break;
|
||||
}
|
||||
};
|
||||
|
||||
const auto index = fd.get<size_t>();
|
||||
|
||||
// split the remainder of the document into strings
|
||||
auto strings = fd.splitIntoStrings();
|
||||
while (strings.size() < 2) {
|
||||
strings.emplace_back();
|
||||
}
|
||||
const auto str = strings[0];
|
||||
|
||||
// exit if there was too little data
|
||||
if (!fd)
|
||||
return 0;
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
simdjson_unused simdjson::dom::element elem;
|
||||
simdjson_unused auto error = parser.parse(strings[1]).get(elem);
|
||||
|
||||
if (error)
|
||||
return 0;
|
||||
|
||||
#define CASE(num, fun) \
|
||||
case num: { \
|
||||
simdjson_unused auto v = elem.fun(); \
|
||||
break; \
|
||||
}
|
||||
#define CASE2(num, fun) \
|
||||
case num: { \
|
||||
simdjson_unused auto v = elem fun; \
|
||||
break; \
|
||||
}
|
||||
try {
|
||||
|
||||
switch (action) {
|
||||
CASE(0, type);
|
||||
CASE(1, get_array);
|
||||
CASE(2, get_object);
|
||||
CASE(3, get_c_str);
|
||||
CASE(4, get_string_length);
|
||||
CASE(5, get_string);
|
||||
CASE(6, get_int64);
|
||||
CASE(7, get_uint64);
|
||||
CASE(8, get_double);
|
||||
CASE(9, get_bool);
|
||||
CASE(10, is_array);
|
||||
CASE(11, is_object);
|
||||
CASE(12, is_string);
|
||||
CASE(13, is_int64);
|
||||
CASE(14, is_uint64);
|
||||
CASE(15, is_double);
|
||||
CASE(16, is_number);
|
||||
CASE(17, is_bool);
|
||||
CASE(18, is_null);
|
||||
// element.is<>() :
|
||||
case 19: {
|
||||
invoke_with_type([&elem](auto t){ simdjson_unused auto v = elem.is<decltype (t)>(); });
|
||||
} break;
|
||||
|
||||
// CASE(xx,get);
|
||||
case 20: {
|
||||
invoke_with_type([&elem](auto t){ simdjson_unused auto v = elem.get<decltype (t)>(); });
|
||||
} break;
|
||||
|
||||
// CASE(xx,tie);
|
||||
case 21: {
|
||||
invoke_with_type([&elem](auto t){
|
||||
simdjson::error_code ec;
|
||||
simdjson::dom::element{elem}.tie(t,ec); });
|
||||
} break;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
// cast to type
|
||||
case 22: {
|
||||
invoke_with_type([&elem](auto t){
|
||||
using T=decltype(t);
|
||||
simdjson_unused auto v = static_cast<T>(elem); });
|
||||
} break;
|
||||
|
||||
CASE(23, begin);
|
||||
CASE(24, end);
|
||||
#endif
|
||||
CASE2(25, [str]);
|
||||
CASE2(26, .at_pointer(str));
|
||||
// CASE2(xx,at(str)); deprecated
|
||||
CASE2(28, .at(index));
|
||||
CASE2(29, .at_key(str));
|
||||
CASE2(30, .at_key_case_insensitive(str));
|
||||
case 31: { NulOStream os;
|
||||
simdjson_unused auto dumpstatus = elem.dump_raw_tape(os);} ;break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#undef CASE
|
||||
#undef CASE2
|
||||
|
||||
} catch (std::exception &) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200909 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
|
||||
// store each implementation along with it's intermediate results,
|
||||
// which would make things easier to debug in case this fuzzer ever
|
||||
// catches anything
|
||||
struct Impl {
|
||||
explicit Impl(const simdjson::implementation* im=nullptr) : impl(im),parser(),element(),error(),output(){}
|
||||
//silence -Weffc++
|
||||
Impl(const Impl&)=delete;
|
||||
Impl& operator=(const Impl&)=delete;
|
||||
|
||||
const simdjson::implementation* impl;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element element;
|
||||
simdjson::error_code error;
|
||||
std::string output;
|
||||
};
|
||||
|
||||
template<class Iterator>
|
||||
void showErrorAndAbort(Iterator first, Iterator last) {
|
||||
auto it=first;
|
||||
while(it!=last) {
|
||||
std::cerr<<"Implementation: "<<it->impl->name()<<"\tError:"<<it->error<<'\n';
|
||||
it++;
|
||||
}
|
||||
std::cerr.flush();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
template<class Iterator>
|
||||
void showOutputAndAbort(Iterator first, Iterator last) {
|
||||
|
||||
for(auto it=first;it!=last;++it) {
|
||||
std::cerr<<"Implementation: "<<it->impl->name()<<"\tOutput: "<<it->output<<'\n';
|
||||
}
|
||||
|
||||
// show the pairwise results
|
||||
for(auto it1=first; it1!=last; ++it1) {
|
||||
for(auto it2=it1; it2!=last; ++it2) {
|
||||
if(it1!=it2) {
|
||||
const bool matches=(it1->output==it2->output);
|
||||
std::cerr<<"Implementation "<<it1->impl->name()<<" and "<<it2->impl->name()<<(matches?" match.":" do NOT match.")<<'\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cerr.flush();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto supported_implementations=get_runtime_supported_implementations();
|
||||
|
||||
|
||||
// make this dynamic, so it works regardless of how it was compiled
|
||||
// or what hardware it runs on
|
||||
constexpr std::size_t Nimplementations_max=3;
|
||||
const std::size_t Nimplementations = supported_implementations.size();
|
||||
|
||||
if(Nimplementations>Nimplementations_max) {
|
||||
//there is another backend added, please bump Nimplementations_max!
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// get pointers to the backend implementation
|
||||
std::array<Impl,Nimplementations_max> implementations;
|
||||
{
|
||||
std::size_t i=0;
|
||||
for(auto& e: supported_implementations) {
|
||||
implementations[i++].impl=e;
|
||||
}
|
||||
}
|
||||
|
||||
// let each implementation parse and store the result
|
||||
std::size_t nerrors=0;
|
||||
for(std::size_t i=0; i<Nimplementations; ++i) {
|
||||
auto& e=implementations[i];
|
||||
simdjson::active_implementation=e.impl;
|
||||
e.error=e.parser.parse(Data,Size).get(e.element);
|
||||
if(e.error) {
|
||||
++nerrors;
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss<<e.element;
|
||||
e.output=oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
//we should either have no errors, or all should error
|
||||
if(nerrors!=0) {
|
||||
if(nerrors!=Nimplementations) {
|
||||
showErrorAndAbort(implementations.begin(),
|
||||
implementations.begin()+Nimplementations);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//parsing went well for all. compare the output against the first.
|
||||
const std::string& reference=implementations[0].output;
|
||||
for(std::size_t i=1; i<Nimplementations; ++i) {
|
||||
if(implementations[i].output!=reference) {
|
||||
showOutputAndAbort(implementations.begin(),
|
||||
implementations.begin()+Nimplementations);
|
||||
}
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
#include "simdjson.h"
|
||||
#include "FuzzUtils.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* Minifies by first parsing, then minifying.
|
||||
*/
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
auto begin = (const char *)Data;
|
||||
auto begin = as_chars(Data);
|
||||
auto end = begin + Size;
|
||||
|
||||
std::string str(begin, end);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(str).get(elem);
|
||||
if (error) { return 1; }
|
||||
if (error) { return 0; }
|
||||
|
||||
std::string minified=simdjson::minify(elem);
|
||||
(void)minified;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Minifies using the minify() function directly, without parsing.
|
||||
*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200912 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto implementations=get_runtime_supported_implementations();
|
||||
|
||||
using Buffer=std::vector<uint8_t>;
|
||||
auto minify=[Data,Size](const simdjson::implementation* impl) -> Buffer {
|
||||
Buffer ret(Size);
|
||||
std::size_t retsize=0;
|
||||
auto err=impl->minify(Data,Size,ret.data(),retsize);
|
||||
if(err) {
|
||||
std::string tmp = error_message(err);
|
||||
ret.assign(tmp.begin(),tmp.end());
|
||||
} else {
|
||||
assert(retsize<=Size && "size should not grow by minimize()!");
|
||||
ret.resize(retsize);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto const first = implementations.begin();
|
||||
auto const last = implementations.end();
|
||||
|
||||
const auto reference=minify(*first);
|
||||
|
||||
bool failed=false;
|
||||
for(auto it=first+1;it != last; ++it) {
|
||||
const auto current=minify(*it);
|
||||
if(current!=reference) {
|
||||
failed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if(failed) {
|
||||
std::cerr<<std::boolalpha<<"Mismatch between implementations of minify() found:\n";
|
||||
for(const auto& e:implementations) {
|
||||
const auto current=minify(e);
|
||||
std::string tmp(current.begin(),current.end());
|
||||
std::cerr<<e->name()<<" returns "<<tmp<<std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
SIMDJSON_UNUSED simdjson::dom::element elem;
|
||||
SIMDJSON_UNUSED auto error = parser.parse(Data, Size).get(elem);
|
||||
simdjson_unused simdjson::dom::element elem;
|
||||
simdjson_unused auto error = parser.parse(Data, Size).get(elem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* For fuzzing all of the implementations (haswell/fallback/westmere),
|
||||
* finding any difference between the output of each which would
|
||||
* indicate inconsistency. Also, it gets the non-default backend
|
||||
* some fuzzing love.
|
||||
*
|
||||
* Copyright Paul Dreik 20200912 for the simdjson project.
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include "supported_implementations.h"
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
// since this check is expensive, only do it once
|
||||
static const auto supported_implementations=get_runtime_supported_implementations();
|
||||
|
||||
|
||||
auto utf8verify=[Data,Size](const simdjson::implementation* impl) -> bool {
|
||||
return impl->validate_utf8((const char*)Data,Size);
|
||||
};
|
||||
|
||||
|
||||
auto first = supported_implementations.begin();
|
||||
auto last = supported_implementations.end();
|
||||
|
||||
|
||||
const bool reference=utf8verify(*first);
|
||||
|
||||
bool failed=false;
|
||||
for(auto it=first+1; it != last; ++it) {
|
||||
const bool current=utf8verify(*it);
|
||||
if(current!=reference) {
|
||||
failed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if(failed) {
|
||||
std::cerr<<std::boolalpha<<"Mismatch between implementations of validate_utf8() found:\n";
|
||||
for(const auto& e: supported_implementations) {
|
||||
if(!e->supported_by_runtime_system()) { continue; }
|
||||
const bool current=utf8verify(e);
|
||||
std::cerr<<e->name()<<" returns "<<current<<std::endl;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
//all is well
|
||||
return 0;
|
||||
}
|
||||
+1
-4
@@ -2,11 +2,8 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "FuzzUtils.h"
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size);
|
||||
|
||||
+6
-4
@@ -7,9 +7,7 @@
|
||||
# invoke it from the git root.
|
||||
|
||||
# make sure to exit on problems
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
set -eux
|
||||
|
||||
for prog in zip cmake ninja; do
|
||||
if ! which $prog >/dev/null; then
|
||||
@@ -32,13 +30,17 @@ cmake .. \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=Off \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_GIT=Off \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
cmake --build . --target all_fuzzers
|
||||
|
||||
cp fuzz/fuzz_* $OUT
|
||||
|
||||
# all corpora are equal, they all take json as input
|
||||
# all fuzzers but one (the tiny target for utf8 validation) takes json
|
||||
# as input, therefore use the same corpus of json files for all.
|
||||
for f in $(ls $OUT/fuzz* |grep -v '.zip$') ; do
|
||||
cp ../corpus.zip $OUT/$(basename $f).zip
|
||||
done
|
||||
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script is to make a quick check that the fuzzers work,
|
||||
# good when working locally developing the fuzzers or making
|
||||
# sure code changes still pass the fuzzers.
|
||||
#
|
||||
# It will download the corpus from bintray (kept up to date
|
||||
# by the crontab github actions) unless a local out/ directory
|
||||
# already exists.
|
||||
#
|
||||
# Run it standing in the root of the simdjson repository.
|
||||
#
|
||||
# By Paul Dreik 20201003
|
||||
|
||||
set -eu
|
||||
|
||||
for prog in wget tar cmake; do
|
||||
if ! which $prog >/dev/null; then
|
||||
echo please install $prog
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
#download the corpus if it does not already exist
|
||||
if [ ! -d out ] ; then
|
||||
wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
tar xf corpus.tar && rm corpus.tar
|
||||
fi
|
||||
|
||||
builddir=build-sanitizers
|
||||
|
||||
if [ ! -d $builddir ] ; then
|
||||
fuzz/build_fuzzer_variants.sh
|
||||
else
|
||||
cmake --build $builddir --target all_fuzzers
|
||||
fi
|
||||
|
||||
fuzzernames=$(cmake --build $builddir --target print_all_fuzzernames |tail -n1)
|
||||
|
||||
for fuzzer in $fuzzernames ; do
|
||||
exe=$builddir/fuzz/$fuzzer
|
||||
shortname=$(echo $fuzzer |cut -f2- -d_)
|
||||
echo found fuzzer $shortname with executable $exe
|
||||
mkdir -p out/$shortname
|
||||
others=$(find out -type d -not -name $shortname -not -name out -not -name cmin)
|
||||
$exe -max_total_time=20 -max_len=4000 out/$shortname $others
|
||||
echo "*************************************************************************"
|
||||
done
|
||||
echo "all is good, no errors found in any of these fuzzers: $fuzzernames"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief get_runtime_supported_implementations
|
||||
* Returns a vector of implementations, which both
|
||||
* have been compiled *and* are dynamically checked to
|
||||
* be supported at runtime.
|
||||
*
|
||||
* Aborts if no implementations are available (should not happen, fallback
|
||||
* should always be there for us!)
|
||||
* @return
|
||||
*/
|
||||
std::vector<const simdjson::implementation*>
|
||||
get_runtime_supported_implementations() {
|
||||
std::vector<const simdjson::implementation*> ret;
|
||||
for(auto& e: simdjson::available_implementations) {
|
||||
if(e->supported_by_runtime_system()) {
|
||||
ret.emplace_back(e);
|
||||
}
|
||||
}
|
||||
if(ret.empty()) {
|
||||
// No implementations available, not even fallback, weird.
|
||||
std::abort();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
// Public API
|
||||
#include "simdjson/simdjson_version.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/dom/array.h"
|
||||
@@ -51,6 +52,7 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#include "simdjson/dom/element.h"
|
||||
#include "simdjson/dom/object.h"
|
||||
#include "simdjson/dom/parser.h"
|
||||
#include "simdjson/dom/serialization.h"
|
||||
|
||||
// Deprecated API
|
||||
#include "simdjson/dom/jsonparser.h"
|
||||
@@ -68,6 +70,23 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#include "simdjson/dom/parsedjson_iterator-inl.h"
|
||||
#include "simdjson/dom/parser-inl.h"
|
||||
#include "simdjson/internal/tape_ref-inl.h"
|
||||
#include "simdjson/dom/serialization-inl.h"
|
||||
|
||||
// Implementation-internal files (must be included before the implementations themselves, to keep
|
||||
// amalgamation working--otherwise, the first time a file is included, it might be put inside the
|
||||
// #ifdef SIMDJSON_IMPLEMENTATION_ARM64/FALLBACK/etc., which means the other implementations can't
|
||||
// compile unless that implementation is turned on).
|
||||
#include "simdjson/internal/isadetection.h"
|
||||
#include "simdjson/internal/jsoncharutils_tables.h"
|
||||
#include "simdjson/internal/numberparsing_tables.h"
|
||||
#include "simdjson/internal/simdprune_tables.h"
|
||||
|
||||
// Implementations
|
||||
#include "simdjson/arm64.h"
|
||||
#include "simdjson/haswell.h"
|
||||
#include "simdjson/westmere.h"
|
||||
#include "simdjson/fallback.h"
|
||||
#include "simdjson/builtin.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef SIMDJSON_ARM64_H
|
||||
#define SIMDJSON_ARM64_H
|
||||
|
||||
#ifdef SIMDJSON_FALLBACK_H
|
||||
#error "arm64.h must be included before fallback.h"
|
||||
#endif
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#include "simdjson/internal/isadetection.h"
|
||||
#include "simdjson/internal/jsoncharutils_tables.h"
|
||||
#include "simdjson/internal/numberparsing_tables.h"
|
||||
#include "simdjson/internal/simdprune_tables.h"
|
||||
|
||||
#if SIMDJSON_IMPLEMENTATION_ARM64
|
||||
|
||||
namespace simdjson {
|
||||
/**
|
||||
* Implementation for NEON (ARMv8).
|
||||
*/
|
||||
namespace arm64 {
|
||||
} // namespace arm64
|
||||
} // namespace simdjson
|
||||
|
||||
#include "simdjson/arm64/implementation.h"
|
||||
|
||||
#include "simdjson/arm64/begin.h"
|
||||
|
||||
// Declarations
|
||||
#include "simdjson/generic/dom_parser_implementation.h"
|
||||
#include "simdjson/arm64/intrinsics.h"
|
||||
#include "simdjson/arm64/bitmanipulation.h"
|
||||
#include "simdjson/arm64/bitmask.h"
|
||||
#include "simdjson/arm64/simd.h"
|
||||
#include "simdjson/generic/jsoncharutils.h"
|
||||
#include "simdjson/generic/atomparsing.h"
|
||||
#include "simdjson/arm64/stringparsing.h"
|
||||
#include "simdjson/arm64/numberparsing.h"
|
||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||
#include "simdjson/generic/ondemand.h"
|
||||
|
||||
// Inline definitions
|
||||
#include "simdjson/generic/implementation_simdjson_result_base-inl.h"
|
||||
#include "simdjson/generic/ondemand-inl.h"
|
||||
#include "simdjson/arm64/end.h"
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_ARM64
|
||||
|
||||
#endif // SIMDJSON_ARM64_H
|
||||
@@ -0,0 +1 @@
|
||||
#define SIMDJSON_IMPLEMENTATION arm64
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
#define SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
namespace arm64 {
|
||||
|
||||
// We sometimes call trailing_zero on inputs that are zero,
|
||||
// but the algorithms do not end up using the returned value.
|
||||
@@ -55,7 +56,8 @@ simdjson_really_inline bool add_overflow(uint64_t value1, uint64_t value2, uint6
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ARM64_BITMANIPULATION_H
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef SIMDJSON_ARM64_BITMASK_H
|
||||
#define SIMDJSON_ARM64_BITMASK_H
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
namespace arm64 {
|
||||
|
||||
//
|
||||
// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered.
|
||||
@@ -32,8 +33,8 @@ simdjson_really_inline uint64_t prefix_xor(uint64_t bitmask) {
|
||||
return bitmask;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
} // namespace arm64
|
||||
} // namespace simdjson
|
||||
SIMDJSON_UNTARGET_REGION
|
||||
|
||||
#endif
|
||||
@@ -2,27 +2,29 @@
|
||||
#define SIMDJSON_ARM64_IMPLEMENTATION_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "isadetection.h"
|
||||
#include "simdjson/internal/isadetection.h"
|
||||
|
||||
namespace {
|
||||
namespace simdjson {
|
||||
namespace arm64 {
|
||||
|
||||
namespace {
|
||||
using namespace simdjson;
|
||||
using namespace simdjson::dom;
|
||||
}
|
||||
|
||||
class implementation final : public simdjson::implementation {
|
||||
public:
|
||||
simdjson_really_inline implementation() : simdjson::implementation("arm64", "ARM NEON", instruction_set::NEON) {}
|
||||
SIMDJSON_WARN_UNUSED error_code create_dom_parser_implementation(
|
||||
simdjson_really_inline implementation() : simdjson::implementation("arm64", "ARM NEON", internal::instruction_set::NEON) {}
|
||||
simdjson_warn_unused error_code create_dom_parser_implementation(
|
||||
size_t capacity,
|
||||
size_t max_length,
|
||||
std::unique_ptr<internal::dom_parser_implementation>& dst
|
||||
) const noexcept final;
|
||||
SIMDJSON_WARN_UNUSED error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final;
|
||||
SIMDJSON_WARN_UNUSED bool validate_utf8(const char *buf, size_t len) const noexcept final;
|
||||
simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final;
|
||||
simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final;
|
||||
};
|
||||
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ARM64_IMPLEMENTATION_H
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef SIMDJSON_ARM64_NUMBERPARSING_H
|
||||
#define SIMDJSON_ARM64_NUMBERPARSING_H
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
namespace arm64 {
|
||||
|
||||
// we don't have SSE, so let us use a scalar function
|
||||
// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
|
||||
@@ -14,11 +15,12 @@ static simdjson_really_inline uint32_t parse_eight_digits_unrolled(const uint8_t
|
||||
return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32);
|
||||
}
|
||||
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#define SWAR_NUMBER_PARSING
|
||||
|
||||
#include "generic/stage2/numberparsing.h"
|
||||
#include "simdjson/generic/numberparsing.h"
|
||||
|
||||
#endif // SIMDJSON_ARM64_NUMBERPARSING_H
|
||||
@@ -2,13 +2,14 @@
|
||||
#define SIMDJSON_ARM64_SIMD_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "simdprune_tables.h"
|
||||
#include "arm64/bitmanipulation.h"
|
||||
#include "simdjson/internal/simdprune_tables.h"
|
||||
#include "simdjson/arm64/bitmanipulation.h"
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
namespace arm64 {
|
||||
namespace simd {
|
||||
|
||||
#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
@@ -217,10 +218,10 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
simdjson_really_inline simd8<uint8_t>& operator-=(const simd8<uint8_t> other) { *this = *this - other; return *this; }
|
||||
|
||||
// Order-specific operations
|
||||
simdjson_really_inline uint8_t max() const { return vmaxvq_u8(*this); }
|
||||
simdjson_really_inline uint8_t min() const { return vminvq_u8(*this); }
|
||||
simdjson_really_inline simd8<uint8_t> max(const simd8<uint8_t> other) const { return vmaxq_u8(*this, other); }
|
||||
simdjson_really_inline simd8<uint8_t> min(const simd8<uint8_t> other) const { return vminq_u8(*this, other); }
|
||||
simdjson_really_inline uint8_t max_val() const { return vmaxvq_u8(*this); }
|
||||
simdjson_really_inline uint8_t min_val() const { return vminvq_u8(*this); }
|
||||
simdjson_really_inline simd8<uint8_t> max_val(const simd8<uint8_t> other) const { return vmaxq_u8(*this, other); }
|
||||
simdjson_really_inline simd8<uint8_t> min_val(const simd8<uint8_t> other) const { return vminq_u8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator<=(const simd8<uint8_t> other) const { return vcleq_u8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator>=(const simd8<uint8_t> other) const { return vcgeq_u8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator<(const simd8<uint8_t> other) const { return vcltq_u8(*this, other); }
|
||||
@@ -232,7 +233,7 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
|
||||
// Bit-specific operations
|
||||
simdjson_really_inline simd8<bool> any_bits_set(simd8<uint8_t> bits) const { return vtstq_u8(*this, bits); }
|
||||
simdjson_really_inline bool any_bits_set_anywhere() const { return this->max() != 0; }
|
||||
simdjson_really_inline bool any_bits_set_anywhere() const { return this->max_val() != 0; }
|
||||
simdjson_really_inline bool any_bits_set_anywhere(simd8<uint8_t> bits) const { return (*this & bits).any_bits_set_anywhere(); }
|
||||
template<int N>
|
||||
simdjson_really_inline simd8<uint8_t> shr() const { return vshrq_n_u8(*this, N); }
|
||||
@@ -255,6 +256,9 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
// sensible, but the AVX ISA makes this kind of approach difficult.
|
||||
template<typename L>
|
||||
simdjson_really_inline void compress(uint16_t mask, L * output) const {
|
||||
using internal::thintable_epi8;
|
||||
using internal::BitsSetTable256mul2;
|
||||
using internal::pshufb_combine_table;
|
||||
// this particular implementation was inspired by work done by @animetosho
|
||||
// we do it in two steps, first 8 bytes and then second 8 bytes
|
||||
uint8_t mask1 = uint8_t(mask); // least significant 8 bits
|
||||
@@ -374,8 +378,8 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
simdjson_really_inline simd8<int8_t>& operator-=(const simd8<int8_t> other) { *this = *this - other; return *this; }
|
||||
|
||||
// Order-sensitive comparisons
|
||||
simdjson_really_inline simd8<int8_t> max(const simd8<int8_t> other) const { return vmaxq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<int8_t> min(const simd8<int8_t> other) const { return vminq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<int8_t> max_val(const simd8<int8_t> other) const { return vmaxq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<int8_t> min_val(const simd8<int8_t> other) const { return vminq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator>(const simd8<int8_t> other) const { return vcgtq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator<(const simd8<int8_t> other) const { return vcltq_s8(*this, other); }
|
||||
simdjson_really_inline simd8<bool> operator==(const simd8<int8_t> other) const { return vceqq_s8(*this, other); }
|
||||
@@ -462,16 +466,6 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
return vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0);
|
||||
}
|
||||
|
||||
simdjson_really_inline simd8x64<T> bit_or(const T m) const {
|
||||
const simd8<T> mask = simd8<T>::splat(m);
|
||||
return simd8x64<T>(
|
||||
this->chunks[0] | mask,
|
||||
this->chunks[1] | mask,
|
||||
this->chunks[2] | mask,
|
||||
this->chunks[3] | mask
|
||||
);
|
||||
}
|
||||
|
||||
simdjson_really_inline uint64_t eq(const T m) const {
|
||||
const simd8<T> mask = simd8<T>::splat(m);
|
||||
return simd8x64<bool>(
|
||||
@@ -494,7 +488,8 @@ simdjson_really_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x
|
||||
}; // struct simd8x64<T>
|
||||
|
||||
} // namespace simd
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ARM64_SIMD_H
|
||||
@@ -2,11 +2,12 @@
|
||||
#define SIMDJSON_ARM64_STRINGPARSING_H
|
||||
|
||||
#include "simdjson.h"
|
||||
#include "arm64/simd.h"
|
||||
#include "arm64/bitmanipulation.h"
|
||||
#include "simdjson/arm64/simd.h"
|
||||
#include "simdjson/arm64/bitmanipulation.h"
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace {
|
||||
namespace arm64 {
|
||||
|
||||
using namespace simd;
|
||||
|
||||
@@ -43,9 +44,10 @@ simdjson_really_inline backslash_and_quote backslash_and_quote::copy_and_find(co
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace arm64
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#include "generic/stage2/stringparsing.h"
|
||||
#include "simdjson/generic/stringparsing.h"
|
||||
|
||||
#endif // SIMDJSON_ARM64_STRINGPARSING_H
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef SIMDJSON_BUILTIN_H
|
||||
#define SIMDJSON_BUILTIN_H
|
||||
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#ifndef SIMDJSON_BUILTIN_IMPLEMENTATION
|
||||
#if SIMDJSON_CAN_ALWAYS_RUN_HASWELL
|
||||
#define SIMDJSON_BUILTIN_IMPLEMENTATION haswell
|
||||
#elif SIMDJSON_CAN_ALWAYS_RUN_WESTMERE
|
||||
#define SIMDJSON_BUILTIN_IMPLEMENTATION westmere
|
||||
#elif SIMDJSON_CAN_ALWAYS_RUN_ARM64
|
||||
#define SIMDJSON_BUILTIN_IMPLEMENTATION arm64
|
||||
#elif SIMDJSON_CAN_ALWAYS_RUN_FALLBACK
|
||||
#define SIMDJSON_BUILTIN_IMPLEMENTATION fallback
|
||||
#else
|
||||
#error "All possible implementations (including fallback) have been disabled! simdjson will not run."
|
||||
#endif
|
||||
#endif // SIMDJSON_BUILTIN_IMPLEMENTATION
|
||||
|
||||
namespace simdjson {
|
||||
/**
|
||||
* Represents the best statically linked simdjson implementation that can be used by the compiling
|
||||
* program.
|
||||
*
|
||||
* Detects what options the program is compiled against, and picks the minimum implementation that
|
||||
* will work on any computer that can run the program. For example, if you compile with g++
|
||||
* -march=westmere, it will pick the westmere implementation. The haswell implementation will
|
||||
* still be available, and can be selected at runtime, but the builtin implementation (and any
|
||||
* code that uses it) will use westmere.
|
||||
*/
|
||||
namespace builtin = SIMDJSON_BUILTIN_IMPLEMENTATION;
|
||||
/**
|
||||
* Function which returns a pointer to an implementation matching the "builtin" implementation.
|
||||
* The builtin implementation is the best statically linked simdjson implementation that can be used by the compiling
|
||||
* program. If you compile with g++ -march=haswell, this will return the haswell implementation.
|
||||
* It is handy to be able to check what builtin was used: builtin_implementation()->name().
|
||||
*/
|
||||
const implementation * builtin_implementation();
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_BUILTIN_H
|
||||
@@ -6,6 +6,21 @@
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
namespace internal {
|
||||
/**
|
||||
* @private
|
||||
* Our own implementation of the C++17 to_chars function.
|
||||
* Defined in src/to_chars
|
||||
*/
|
||||
char *to_chars(char *first, const char *last, double value);
|
||||
/**
|
||||
* @private
|
||||
* A number parsing routine.
|
||||
* Defined in src/from_chars
|
||||
*/
|
||||
double from_chars(const char *first) noexcept;
|
||||
}
|
||||
|
||||
#ifndef SIMDJSON_EXCEPTIONS
|
||||
#if __cpp_exceptions
|
||||
#define SIMDJSON_EXCEPTIONS 1
|
||||
@@ -58,8 +73,8 @@ constexpr size_t DEFAULT_MAX_DEPTH = 1024;
|
||||
#define simdjson_really_inline __forceinline
|
||||
#define simdjson_never_inline __declspec(noinline)
|
||||
|
||||
#define SIMDJSON_UNUSED
|
||||
#define SIMDJSON_WARN_UNUSED
|
||||
#define simdjson_unused
|
||||
#define simdjson_warn_unused
|
||||
|
||||
#ifndef simdjson_likely
|
||||
#define simdjson_likely(x) x
|
||||
@@ -73,7 +88,7 @@ constexpr size_t DEFAULT_MAX_DEPTH = 1024;
|
||||
#define SIMDJSON_DISABLE_VS_WARNING(WARNING_NUMBER) __pragma(warning( disable : WARNING_NUMBER ))
|
||||
// Get rid of Intellisense-only warnings (Code Analysis)
|
||||
// Though __has_include is C++17, it is supported in Visual Studio 2017 or better (_MSC_VER>=1910).
|
||||
#if defined(_MSC_VER) && (_MSC_VER>=1910)
|
||||
#ifdef __has_include
|
||||
#if __has_include(<CppCoreCheck\Warnings.h>)
|
||||
#include <CppCoreCheck\Warnings.h>
|
||||
#define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS)
|
||||
@@ -92,8 +107,8 @@ constexpr size_t DEFAULT_MAX_DEPTH = 1024;
|
||||
#define simdjson_really_inline inline __attribute__((always_inline))
|
||||
#define simdjson_never_inline inline __attribute__((noinline))
|
||||
|
||||
#define SIMDJSON_UNUSED __attribute__((unused))
|
||||
#define SIMDJSON_WARN_UNUSED __attribute__((warn_unused_result))
|
||||
#define simdjson_unused __attribute__((unused))
|
||||
#define simdjson_warn_unused __attribute__((warn_unused_result))
|
||||
|
||||
#ifndef simdjson_likely
|
||||
#define simdjson_likely(x) __builtin_expect(!!(x), 1)
|
||||
@@ -196,4 +211,8 @@ namespace std {
|
||||
#endif // SIMDJSON_HAS_STRING_VIEW
|
||||
#undef SIMDJSON_HAS_STRING_VIEW // We are not going to need this macro anymore.
|
||||
|
||||
/// If EXPR is an error, returns it.
|
||||
#define SIMDJSON_TRY(EXPR) { auto _err = (EXPR); if (_err) { return _err; } }
|
||||
|
||||
|
||||
#endif // SIMDJSON_COMMON_DEFS_H
|
||||
|
||||
@@ -62,12 +62,10 @@ inline size_t array::size() const noexcept {
|
||||
return tape.scope_count();
|
||||
}
|
||||
inline simdjson_result<element> array::at_pointer(std::string_view json_pointer) const noexcept {
|
||||
if(json_pointer[0] != '/') {
|
||||
if(json_pointer.size() == 0) { // an empty string means that we return the current node
|
||||
if(json_pointer.empty()) { // an empty string means that we return the current node
|
||||
return element(this->tape); // copy the current node
|
||||
} else { // otherwise there is an error
|
||||
} else if(json_pointer[0] != '/') { // otherwise there is an error
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
}
|
||||
json_pointer = json_pointer.substr(1);
|
||||
// - means "the append position" or "the element after the end of the array"
|
||||
@@ -102,6 +100,7 @@ inline simdjson_result<element> array::at_pointer(std::string_view json_pointer)
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
inline simdjson_result<element> array::at(size_t index) const noexcept {
|
||||
size_t i=0;
|
||||
for (auto element : *this) {
|
||||
@@ -145,39 +144,9 @@ inline bool array::iterator::operator>=(const array::iterator& other) const noex
|
||||
inline bool array::iterator::operator>(const array::iterator& other) const noexcept {
|
||||
return tape.json_index > other.tape.json_index;
|
||||
}
|
||||
inline std::ostream& operator<<(std::ostream& out, const array &value) {
|
||||
return out << minify<array>(value);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
|
||||
template<>
|
||||
inline std::ostream& minifier<dom::array>::print(std::ostream& out) {
|
||||
out << '[';
|
||||
auto iter = value.begin();
|
||||
auto end = value.end();
|
||||
if (iter != end) {
|
||||
out << minify<dom::element>(*iter);
|
||||
for (++iter; iter != end; ++iter) {
|
||||
out << "," << minify<dom::element>(*iter);
|
||||
}
|
||||
}
|
||||
return out << ']';
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
template<>
|
||||
inline std::ostream& minifier<simdjson_result<dom::array>>::print(std::ostream& out) {
|
||||
if (value.error()) { throw simdjson_error(value.error()); }
|
||||
return out << minify<dom::array>(value.first);
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const simdjson_result<dom::array> &value) noexcept(false) {
|
||||
return out << minify<simdjson_result<dom::array>>(value);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user