mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
888 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88374a416f | |||
| 74870a8189 | |||
| 0ef4d90ad0 | |||
| 1a1e7edb02 | |||
| 51b835f71b | |||
| e38fe3d361 | |||
| cc042c9936 | |||
| 599e3bc937 | |||
| 1fa0d940bc | |||
| 7dc4a9525b | |||
| b6f1f4ef64 | |||
| 3faae67663 | |||
| ccc94c9b05 | |||
| 1fd30db726 | |||
| 0ba76ac066 | |||
| 8b661fe556 | |||
| 077907b7c3 | |||
| 172d669780 | |||
| 6b85b9a416 | |||
| 5d3001279c | |||
| 4582a13360 | |||
| 3a064535ae | |||
| 444ec4ad27 | |||
| 94e910586d | |||
| bb5ce007e6 | |||
| deaa74d378 | |||
| 13e1794e91 | |||
| 67b3595008 | |||
| 6c33f518a8 | |||
| 88da62ba09 | |||
| b6997a56df | |||
| 74178fd1c2 | |||
| 34c59bfa90 | |||
| 2956bce047 | |||
| 41f33ecbb9 | |||
| 86241e2871 | |||
| 1e32897d3e | |||
| 1b63a9a9b5 | |||
| 4c9f11b78a | |||
| 32348c2b0b | |||
| 5e690c5d04 | |||
| 4ec56484a1 | |||
| 8f2a5649fe | |||
| c3b25e12a5 | |||
| 6d3e33d440 | |||
| c11f7ce54f | |||
| 84806cc174 | |||
| 16f12f7d59 | |||
| 8609b8e589 | |||
| 29e744fdbb | |||
| 515b87bcbe | |||
| 5fa9faca20 | |||
| a4ebdfd47c | |||
| 188d8d4b64 | |||
| ce5581d428 | |||
| 5b4acf14ea | |||
| 5fc6cb15b8 | |||
| f6e9a8eee4 | |||
| 6c0950cb2e | |||
| cb8a9ef2c0 | |||
| 470cbbe9ff | |||
| e01f1434fb | |||
| 187084ce46 | |||
| b6f9382b5b | |||
| 57f68381ed | |||
| 3e35729eb6 | |||
| 544fa57641 | |||
| 7e94309046 | |||
| 21eff5b825 | |||
| b408d7c95e | |||
| d9929edbc1 | |||
| 843b73dedb | |||
| 02a8145b18 | |||
| cfcb315b14 | |||
| c8a70a0a73 | |||
| b84a3a0230 | |||
| 49d70232f8 | |||
| 8cc9f496ee | |||
| 1547f2ec80 | |||
| 42a8b40de0 | |||
| b4b968ff44 | |||
| 52e3c063c5 | |||
| 257089884f | |||
| c650ea9765 | |||
| e369d45b9c | |||
| 2d84b6f6d9 | |||
| eef1171944 | |||
| 12ccdcf858 | |||
| f1a03bfb04 | |||
| 696b0e29e4 | |||
| 5eb748ae17 | |||
| aa4340ef5c | |||
| 0062e54e93 | |||
| dada5090b0 | |||
| 1c4593c648 | |||
| e7004cef76 | |||
| 2bb101bd19 | |||
| 26baf70912 | |||
| 89c2582376 | |||
| 33e003616d | |||
| bf03d77ab9 | |||
| a6cbf1f922 | |||
| d6f056f266 | |||
| 69a247d500 | |||
| a76c67c19f | |||
| b836164a38 | |||
| 058507badf | |||
| ad40e90790 | |||
| 0c9dc11550 | |||
| 1ff55c2729 | |||
| 066269153e | |||
| 38bb08778a | |||
| 5dbcdf1484 | |||
| f03a6ab5a4 | |||
| 6fa5abcd7e | |||
| 5dc07ed295 | |||
| 064d4255d5 | |||
| 04139eb82e | |||
| 1b1a122b1f | |||
| ccb132320c | |||
| c0e7f824df | |||
| 05bba71eaf | |||
| 7ebe5c4bcf | |||
| ae1bd891e7 | |||
| 9899e5021d | |||
| 94440e0170 | |||
| c25928e44f | |||
| a7fc7d4ffb | |||
| f336103f63 | |||
| 56e2b38048 | |||
| a5ccff720a | |||
| 1d8c2d6c22 | |||
| 0b8c357eff | |||
| efc168f473 | |||
| d8428f98d9 | |||
| 60f17d26a3 | |||
| 05bc664c11 | |||
| 2cc84b6e51 | |||
| 5ccdbef7d5 | |||
| c13c2650a2 | |||
| ec6c998a3a | |||
| 2f6091419f | |||
| 2022dd7d74 | |||
| b8202dab3b | |||
| ef688a74fe | |||
| f632e7c043 | |||
| 04a19f9813 | |||
| 2cbc591c9d | |||
| 3f00e79bcb | |||
| 3586fc4910 | |||
| c9a6bbeb64 | |||
| 0655a135e6 | |||
| d3e8bb1889 | |||
| 14ceacac73 | |||
| e4f33b5970 | |||
| 4474f8ef18 | |||
| 76c9f4f5a6 | |||
| 942ef3b7f2 | |||
| 0b9df6d8c4 | |||
| b5ea504ad2 | |||
| 803b0c4bdb | |||
| 6537d0dc76 | |||
| f8f36c085c | |||
| 7339f67dd7 | |||
| 0d4e501239 | |||
| 8d609607e2 | |||
| 71a889ed73 | |||
| 27a75a9085 | |||
| 954d6c326d | |||
| 7ea05d038e | |||
| 33930ff046 | |||
| 16f41ea059 | |||
| 0a7270fc29 | |||
| 23fbd9d004 | |||
| 610c79fbf3 | |||
| fd44c2a2ff | |||
| a86a82b39c | |||
| 89b059b1ea | |||
| bd2d0f769f | |||
| d830422489 | |||
| d1a54249e7 | |||
| 4dfbf98e4e | |||
| 1b6258ec8c | |||
| be707dbb6f | |||
| 664b03bb13 | |||
| 7c6723d912 | |||
| 1febf2ec83 | |||
| fe69928764 | |||
| bbd61eb13f | |||
| e15e1e253d | |||
| 45e2178ada | |||
| a6e4933d93 | |||
| 98599e0972 | |||
| b4837f2e2f | |||
| ea08e7d192 | |||
| d178e089a6 | |||
| 5f00b37e21 | |||
| 8a8792d47f | |||
| 59d9bc9e48 | |||
| 8793dd3ceb | |||
| 48062380fa | |||
| 3636aa5522 | |||
| a1aea4588f | |||
| 1d4fffb799 | |||
| 6f90f5dc5f | |||
| 9dd6972d26 | |||
| d731a7d52c | |||
| 059468b74e | |||
| 5e69fb782a | |||
| a5beffda78 | |||
| 7de7ce5fdc | |||
| 383e8c7f68 | |||
| 0dbda65e44 | |||
| fe01da077e | |||
| d43a4e9df9 | |||
| 3e226795f0 | |||
| c4a0fe1606 | |||
| ef63a84a3e | |||
| 8c16ba372e | |||
| 9be4a17687 | |||
| 89332e1696 | |||
| 8a56129def | |||
| ed0c815735 | |||
| 7a69da16e4 | |||
| 351717414d | |||
| 52f44de257 | |||
| ae6dddfff4 | |||
| 539f555e23 | |||
| b75fa26dc1 | |||
| 3d22a2d845 | |||
| 1aab4752e2 | |||
| 86f8a4a9d2 | |||
| db2cb061cb | |||
| 6a71b24495 | |||
| 84712a8bbc | |||
| b86fb95306 | |||
| a3a9bde83e | |||
| 2fe2dd170b | |||
| f772bf4fbc | |||
| ac0c3093f4 | |||
| 12150baa5e | |||
| 219b02c1e5 | |||
| 4551e60f8b | |||
| ea842e78af | |||
| 5651fbedc4 | |||
| 2f87dfd4a9 | |||
| 332dd76867 | |||
| d2c9ea8a9a | |||
| 40d57da83c | |||
| 561813eb2a | |||
| e6c9dfbd91 | |||
| 603b6596af | |||
| 64abc3e86c | |||
| fac42fa3e8 | |||
| 7ad4020829 | |||
| 72ab0d11ff | |||
| 4ea866f050 | |||
| a476531524 | |||
| e7e6ac5bb3 | |||
| f346362b00 | |||
| d9ba455695 | |||
| 8927a0561f | |||
| e03c5e9f23 | |||
| 1f79200db8 | |||
| c009e4a57d | |||
| c615d52cf4 | |||
| dbb3316511 | |||
| cd6f204c77 | |||
| 269131ed21 | |||
| 65d784e88e | |||
| 35afb6cae0 | |||
| 27bce09be8 | |||
| 4f25b6ac0c | |||
| 3d5ed1a7e3 | |||
| a03115a4a6 | |||
| 7219d28a31 | |||
| 0875bce68f | |||
| 54fe302907 | |||
| edaa8f811f | |||
| 2c8fd109de | |||
| 07fe7ad1a2 | |||
| 16d88cc095 | |||
| 2a6e6b3dbd | |||
| 0c19848230 | |||
| 3c3a4db54e | |||
| 0e6bd2224f | |||
| a64d2f4673 | |||
| 1e8a54af0b | |||
| aa53d8708e | |||
| 8c600ca553 | |||
| 25fe6d7dde | |||
| afb369950c | |||
| d7f133c24c | |||
| 5312fd30e5 | |||
| 23dd0bdaa1 | |||
| 1d06624d38 | |||
| d40069a018 | |||
| 73e27bdd48 | |||
| 064eb0b24f | |||
| af968c5b44 | |||
| f93dbe51e2 | |||
| 1c34707925 | |||
| deaca58504 | |||
| 1d519aa9cb | |||
| 191faeae70 | |||
| 1153aaf55b | |||
| 292cb5a5af | |||
| 1e9488d4a6 | |||
| ff1d77ead9 | |||
| 977e1a94b2 | |||
| 60ee5fc844 | |||
| 940c8fcb5e | |||
| 71e0148eb4 | |||
| 293c104cc4 | |||
| 5a3035bb72 | |||
| e04cbd71d0 | |||
| fa4ce6a8bc | |||
| 9863f62321 | |||
| 7cd1f7dbd5 | |||
| 8c45a18524 | |||
| 8cb383ed45 | |||
| 7d1305b169 | |||
| 24ab1f32e4 | |||
| 073ad0dada | |||
| f43b6a0675 | |||
| fc1ddcd2f8 | |||
| e7f774f964 | |||
| cb49af1ea5 | |||
| 73d7d704c1 | |||
| 8b89232f12 | |||
| c3dec1a5ea | |||
| 44b06d70e8 | |||
| eee07e6cfd | |||
| 5051c27c3d | |||
| 76f3506ac5 | |||
| 5d7a84fad7 | |||
| dec161ed26 | |||
| 0f9dbf84b7 | |||
| f0d5337818 | |||
| 4cd9de5c37 | |||
| 76b0bfa7f5 | |||
| 32d6b0eed4 | |||
| 127d962271 | |||
| 92d7af0881 | |||
| af12066f77 | |||
| 2a1f8fa8f1 | |||
| 04e47bde84 | |||
| 49da7e74cd | |||
| 6ac47734c0 | |||
| 0e6ea76e88 | |||
| 0514588175 | |||
| 59d1212039 | |||
| d61cca6720 | |||
| 8c74e88f16 | |||
| 46cf512032 | |||
| 24a185d26b | |||
| b99a7344c9 | |||
| c6a4fb1e13 | |||
| e2718fe845 | |||
| 76314280cb | |||
| d6716218bd | |||
| 9371a92122 | |||
| e8b030ad17 | |||
| e0180b4849 | |||
| 3901bbb401 | |||
| 76bebfd798 | |||
| 3013166d8d | |||
| 8596e702ac | |||
| 414bf4a296 | |||
| 0daa01edef | |||
| 98abd96075 | |||
| 8e3fc826e2 | |||
| c750095241 | |||
| 2a0c0c0ad2 | |||
| 92f3bb89c3 | |||
| ac0e6c5e6e | |||
| f397b6fedf | |||
| 1d069e5077 | |||
| c5684a6278 | |||
| f3ac0be0e6 | |||
| 18c9468af5 | |||
| 32bc0da362 | |||
| c9a3800ce7 | |||
| d4239aaa8f | |||
| 4f72d5cfac | |||
| 66acab4130 | |||
| 9e9e3373e0 | |||
| 502fee1b45 | |||
| 4d0c7d706d | |||
| 6cd418b60a | |||
| a3b39dfd1a | |||
| 74da47e286 | |||
| 587ba9bec0 | |||
| 382392e03b | |||
| 409948a0f9 | |||
| 34919ca394 | |||
| 0d1c574cb1 | |||
| c564815931 | |||
| f6fb667ac1 | |||
| 832bbe734d | |||
| 7a2fda891c | |||
| e50c239a2e | |||
| cc7c8d92da | |||
| 87acab0846 | |||
| f43459d476 | |||
| e030f02776 | |||
| 10f2d01e7f | |||
| 80dbf9a32a | |||
| 185274e70f | |||
| f0ac55ec0c | |||
| 44544635dd | |||
| 3c594b1037 | |||
| ea7100e8c4 | |||
| a198abc485 | |||
| d4a37f6ef5 | |||
| a5c9c31231 | |||
| e3ec78a832 | |||
| a116e68a47 | |||
| e7084de166 | |||
| 398eda6365 | |||
| 349abf5ee6 | |||
| 4ce40b1975 | |||
| 536fe28f8f | |||
| 3e9e14f4d6 | |||
| c8140068ad | |||
| db314bc381 | |||
| 499a26b152 | |||
| a9cdb5be50 | |||
| 5f04208dbd | |||
| ffaa292006 | |||
| d3e44b1108 | |||
| fbf274a42b | |||
| d94cd65dfd | |||
| 3c1b403c4e | |||
| 75564453b3 | |||
| 3091e2dc0e | |||
| fc50a36cc5 | |||
| 9bf9fba2ec | |||
| 121615da70 | |||
| 53d28a713c | |||
| cf37704193 | |||
| 38289fe381 | |||
| f9337a1111 | |||
| 6d059b479f | |||
| b6e23b2d3e | |||
| e5e6a46c37 | |||
| 22b9a53bef | |||
| 3be81e3206 | |||
| ff09b6c824 | |||
| a8e892ba90 | |||
| 289cc3e7a0 | |||
| 7480b87e07 | |||
| befa6423be | |||
| fd418f568c | |||
| 09cf18a646 | |||
| 326c175dcb | |||
| 6d7c77ddc1 | |||
| efd706528b | |||
| b523c43927 | |||
| 75545ff70d | |||
| 3c6ef83046 | |||
| b9ac0a79f1 | |||
| 8539896f3d | |||
| a3b508ceff | |||
| 334a486737 | |||
| d7370cc916 | |||
| 92c34f7f38 | |||
| 93328c8d6d | |||
| 5710ec13d4 | |||
| fa637fcecb | |||
| 4af7d6f108 | |||
| 0fd159dadb | |||
| 1ff22c78b3 | |||
| ceb1def55c | |||
| 893a1d8306 | |||
| 3c91690e55 | |||
| 6835dd73bc | |||
| 7317fe1440 | |||
| 7b58fea911 | |||
| c1ff74c9a6 | |||
| 6dabfa176a | |||
| 0714f5fc67 | |||
| 3b1b1bfd48 | |||
| 218c867f46 | |||
| 2bc12f9730 | |||
| 5e564a8e0c | |||
| beaa6a9a7a | |||
| a9c8224f40 | |||
| 3dcc188d93 | |||
| 10b7556a37 | |||
| 54b7291c34 | |||
| 1e30b6e334 | |||
| 406240bae3 | |||
| 74d9b41b7d | |||
| ff0b0c54b7 | |||
| 6eec2d6b4f | |||
| 5731c5437a | |||
| 04f14ec026 | |||
| 12ed6336b1 | |||
| 3cb79e6977 | |||
| c5e21a2469 | |||
| 13aee51011 | |||
| 53fca1b5e6 | |||
| 7dad9fca0f | |||
| b249d7c76c | |||
| 4060f64232 | |||
| 5b2f7d3374 | |||
| 3116e29d16 | |||
| d2406f2a22 | |||
| a648318900 | |||
| 849f54e4f8 | |||
| 61009fea3f | |||
| 21dce6cca9 | |||
| 56bc8a778d | |||
| d93af1161d | |||
| 434776db1a | |||
| 6167e9cefc | |||
| dc918d764e | |||
| 9906887151 | |||
| b5a1017afa | |||
| 7badc230a4 | |||
| ab78482ee7 | |||
| 6369cf4dd9 | |||
| 7656bd50ee | |||
| 2115596ed3 | |||
| 0e3453f7c2 | |||
| 7ed65e42d7 | |||
| 835b640ebd | |||
| 6ee3318531 | |||
| ae24fe3850 | |||
| d4f4608dab | |||
| ea8a5020e2 | |||
| 622d9c9480 | |||
| bb0c9547be | |||
| 62da98aef6 | |||
| 03746b966b | |||
| de001da35b | |||
| 0da460ca13 | |||
| 450e19858b | |||
| 836e1fc330 | |||
| e836c28008 | |||
| fff4f921e4 | |||
| 9f265711a8 | |||
| 32afcd2e48 | |||
| 748df8d109 | |||
| 5ad405006c | |||
| 47859f3560 | |||
| 56d1b9a226 | |||
| 1b6a31b277 | |||
| 90a7503181 | |||
| e3efbcddc1 | |||
| c14b2fb36c | |||
| f0f111b387 | |||
| c95e45d283 | |||
| 52b8d50178 | |||
| f581627d10 | |||
| 6a8ec95a46 | |||
| b6c6680add | |||
| 5fb149f833 | |||
| abb0bf9247 | |||
| 8f3ddd3a73 | |||
| 8f34e6714a | |||
| 56841bcede | |||
| 006cc2ed60 | |||
| c79cf8d6bf | |||
| cf704cf81b | |||
| 265d474ec8 | |||
| 2e420169c3 | |||
| 5aec2671ea | |||
| ab0e22a316 | |||
| 06587824be | |||
| a0bce440a6 | |||
| 26b15251e2 | |||
| 1cf4fe405d | |||
| 6b8f5d3354 | |||
| d5af359365 | |||
| 8769e42a56 | |||
| 7cde65aa6e | |||
| e1b1500e3b | |||
| 2943a1c27f | |||
| b28cafc1d1 | |||
| 65f999b7b7 | |||
| 06d6636b97 | |||
| eb5a1ea113 | |||
| d84e70b6e5 | |||
| 7ff034504d | |||
| dedf0c6a8d | |||
| 5514ae3879 | |||
| 5af0dfb031 | |||
| 0bcda5e384 | |||
| d1eef242c6 | |||
| ceee00b276 | |||
| 6c2ab064cb | |||
| 772a5dc3d5 | |||
| 3e39a998ce | |||
| 2867dc50fa | |||
| c3c43769ae | |||
| 36ceaa4452 | |||
| c34b1a1b2a | |||
| e4df0ca368 | |||
| 8a91cecf41 | |||
| 04e8710cf5 | |||
| e8b3f9eaad | |||
| 23d6ec6cff | |||
| 0a6edae2dd | |||
| 0e45663ce8 | |||
| 80a9f4defd | |||
| 8f1e4018c0 | |||
| afe36d0b36 | |||
| 5d1e3efce8 | |||
| 293ec7aec5 | |||
| 6cefeb338b | |||
| f1744f5495 | |||
| 5750a173ff | |||
| e3a4fd9f93 | |||
| 5a071c1907 | |||
| 7cf3a7511b | |||
| af203aaf86 | |||
| 8e2c06cb0e | |||
| 1a5d8f1957 | |||
| 03c828c7ad | |||
| 758dc511fb | |||
| 0164723a8e | |||
| 032936a7b5 | |||
| da3e064fc7 | |||
| 317fc6ba0e | |||
| 1aaad223c0 | |||
| 81c86d7090 | |||
| d9a9fd387d | |||
| 0c190b165c | |||
| acc7bd79b0 | |||
| e4e89fe27a | |||
| 24551db0c8 | |||
| 12e6611ba4 | |||
| fb15886a1c | |||
| 06c1dc3a29 | |||
| 89d9de2353 | |||
| 12c85d3e23 | |||
| a5afec1f94 | |||
| ac0899c043 | |||
| 40c6213d7e | |||
| d140bc23f5 | |||
| 00f0859e1f | |||
| a0b2fab6fa | |||
| f669aafcf2 | |||
| 66a2807210 | |||
| c3009eb324 | |||
| 3bdfe167de | |||
| 31e8a12e88 | |||
| ebbfdcd35a | |||
| 9a7c8fb5be | |||
| cfef4ff2ad | |||
| b2220d6157 | |||
| 5ff941ae3d | |||
| 1c922d3b73 | |||
| b23dd28a06 | |||
| a55f41a24a | |||
| 5525c6f729 | |||
| eb147d9868 | |||
| f58a5d534e | |||
| b3ea8c406e | |||
| 99667f7c55 | |||
| 0b21203141 | |||
| 9a9ca974c2 | |||
| 140e4dde3d | |||
| 68670301e3 | |||
| a98d841983 | |||
| 332b764cc4 | |||
| 560f0742cc | |||
| 910f272467 | |||
| b6423a3426 | |||
| 4d2736ffa9 | |||
| 4dc2adf7f8 | |||
| da34f9a253 | |||
| 1f76737510 | |||
| bc8bc7d1a8 | |||
| 083569fca8 | |||
| a8903d9765 | |||
| 8e7d1a5f09 | |||
| c879b56f41 | |||
| 4518f1fba1 | |||
| 5c59b3a775 | |||
| 299dfcdd3c | |||
| 76c706644a | |||
| 0c8f2b9d85 | |||
| c924aaede9 | |||
| 28710f8ad5 | |||
| e695a19d11 | |||
| 6978a0b8d4 | |||
| 6784530b8b | |||
| 03d5fc33ca | |||
| ba14232628 | |||
| 1cdf5581f3 | |||
| 3488c49d0a | |||
| adaef43bc6 | |||
| ce8fe1bdf6 | |||
| fa04595d90 | |||
| aea79912ec | |||
| 80b4dd2e8a | |||
| 48530b89ea | |||
| a4025788ae | |||
| c6f2f60b03 | |||
| 33060738b6 | |||
| ab6d4871d8 | |||
| f87e64f988 | |||
| 27861f6358 | |||
| f611b65bc0 | |||
| 2dc61fbdc4 | |||
| 22be05400d | |||
| a9f501fe7d | |||
| e9077370ec | |||
| a804351a76 | |||
| f97b655f02 | |||
| 1498b78342 | |||
| 85e84fc1fa | |||
| 833e5d8bf1 | |||
| 773883c486 | |||
| 6e5e0278c2 | |||
| 951c4bedf8 | |||
| 9842e1f9d0 | |||
| 4c0c1c9830 | |||
| 6706d6053e | |||
| 0a874a5063 | |||
| 2caa6e3370 | |||
| a9e990251d | |||
| 7bde23590a | |||
| 5042dd52ce | |||
| 3b9e6bff3c | |||
| a2d05b21ff | |||
| f4f5f670a2 | |||
| 165e23773f | |||
| 8dbb598057 | |||
| ba9dc12164 | |||
| 399d08c86c | |||
| 6f799435b6 | |||
| 3d14154a29 | |||
| 7e331957c4 | |||
| 4da06830f1 | |||
| 27293cc1c1 | |||
| 2caac2b218 | |||
| 0dc80ccf21 | |||
| f2b48ede4c | |||
| 6cefdc2f5c | |||
| 29e78413fe | |||
| 8192e63a4b | |||
| b2ebdb0d07 | |||
| 9c3828fefe | |||
| 60916318f7 | |||
| d7c83397e4 | |||
| 1d621bba37 | |||
| e2f349e7bd | |||
| 102262c7ab | |||
| f02babe427 | |||
| fc6133b58f | |||
| 2bd65fa444 | |||
| d33208c7db | |||
| c9cd8e6211 | |||
| 74a96878bc | |||
| 7e28708e1d | |||
| 1211c01ca1 | |||
| f32b97733b | |||
| 4e1c90f76f | |||
| e63f258470 | |||
| ede9f9117f | |||
| 7c560fa137 | |||
| 178a0842fe | |||
| f345490cae | |||
| db141e82c9 | |||
| f163155929 | |||
| 9b6377fd80 | |||
| 7356b4532f | |||
| 7d7bec856d | |||
| c5504ef50b | |||
| 3658ff650d | |||
| 6d14afd80e | |||
| 6e5178efc4 | |||
| 29fc51522a | |||
| 6cd8fb7982 | |||
| ce824f8653 | |||
| 708f4a094d | |||
| 2704b73399 | |||
| 783ccd6c21 | |||
| 3fd1c3b64a | |||
| 58d249ca16 | |||
| bdc2b07339 | |||
| 6888ca709d | |||
| 8ae818e17c | |||
| c4f1baad31 | |||
| 3439ce19c9 | |||
| b7c18df540 | |||
| 74799134b1 | |||
| 3828e1e538 | |||
| d89046d515 | |||
| 4bc128f07e | |||
| e383b7a6ab | |||
| c89d6bf68b | |||
| 52640518d3 | |||
| cf493254b7 | |||
| c97eb41dc6 | |||
| b1224a77db | |||
| 17b777f751 | |||
| 9442c9e1f4 | |||
| 15740500af | |||
| 3484dda45e | |||
| 1ece6c0e2f | |||
| 59cad23aeb | |||
| da1c35d04b | |||
| c469aed047 | |||
| a065805b0f | |||
| f41a18b57d | |||
| 1257432df3 | |||
| d0c0e31220 | |||
| 9b7832c39a | |||
| e3e29b720d | |||
| 64872bddf4 | |||
| 81f2249575 | |||
| 9bbd6bd874 | |||
| 13e477ebfe | |||
| c284b54fc2 | |||
| 69caa477fb | |||
| 81f9aac13f | |||
| b2eff3c90c | |||
| aa45fe7359 | |||
| 253af0766c | |||
| cf9dbe583d | |||
| de8df0a05f | |||
| 53b6deaeae | |||
| 462858efa3 | |||
| f7e893667d | |||
| 5765c81f66 | |||
| 92334a8e28 | |||
| c4218c8e40 | |||
| c1f27fb848 | |||
| 6d0fd5bb93 | |||
| bd15d3ae24 | |||
| 7f249cd179 | |||
| aef3f4be99 | |||
| bf8083888d | |||
| f4fa5b7340 | |||
| 169568ca47 | |||
| 9cc4ddfc88 | |||
| 441963c84c | |||
| cf4ae61ac6 | |||
| da0f1cacea | |||
| 5e5592178d | |||
| b01222518d | |||
| 2060cf8a70 | |||
| a4bd87119b | |||
| 9f26355fe0 | |||
| f667d4965d | |||
| 585f84a734 | |||
| a1bff85263 | |||
| fb920bba62 | |||
| 58697f6f3b | |||
| 18c5b8d68a | |||
| 08cf140811 | |||
| 94673bcdf2 | |||
| aa15917c9d | |||
| 85fb37b6ea | |||
| ae3ae9a474 | |||
| e9be643db5 | |||
| b49eefbee6 | |||
| 640283fec6 | |||
| c8d50a6060 | |||
| 6a2728e730 | |||
| 1740d93420 | |||
| 0042d9b406 | |||
| 3bfa6097d5 | |||
| 237b8865f5 | |||
| 8f01cece3a | |||
| 2ca574d9e6 | |||
| 7f27e1e0e1 | |||
| 875e2f9d0d | |||
| 5c538dd9d6 | |||
| 3fb82502f7 | |||
| 7be2998cae | |||
| 9dfab9d9a4 | |||
| b63ae1f190 | |||
| 4fc796b387 | |||
| c3ff41dd84 | |||
| 0b927f059c | |||
| f3c3afd4cd | |||
| 1e26859bb7 | |||
| b1beacd1f3 | |||
| d9a0e2b8f4 | |||
| 4c7e95aac3 | |||
| c3310c6e8f | |||
| 2a24567370 |
+41
-15
@@ -1,22 +1,48 @@
|
||||
version: '{build}'
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
clone_folder: c:\projects\simdjson
|
||||
|
||||
platform:
|
||||
- x64
|
||||
branches: { only: [ master ] }
|
||||
configuration: Release
|
||||
image: Visual Studio 2019
|
||||
platform: x64
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- SIMDJSON_BUILD_STATIC: "OFF"
|
||||
- SIMDJSON_BUILD_STATIC: "ON"
|
||||
|
||||
- job_name: VS2019
|
||||
CMAKE_ARGS: -A %Platform%
|
||||
- job_name: VS2019CLANG
|
||||
CMAKE_ARGS: -A %Platform% -T ClangCL
|
||||
- job_name: VS2019ARM
|
||||
CMAKE_ARGS: -A ARM64 -DCMAKE_CROSSCOMPILING=1 -D SIMDJSON_GOOGLE_BENCHMARKS=OFF # Does Google Benchmark builds under VS ARM?
|
||||
- job_name: VS2017 (Static, No Threads)
|
||||
image: Visual Studio 2017
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF
|
||||
CTEST_ARGS: -E checkperf
|
||||
- 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
|
||||
- job_name: VS2015
|
||||
image: Visual Studio 2015
|
||||
CMAKE_ARGS: -A %Platform% -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF
|
||||
CTEST_ARGS: -E checkperf
|
||||
|
||||
build_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- ps: cmake -DSIMDJSON_BUILD_STATIC="$env:SIMDJSON_BUILD_STATIC" -DCMAKE_BUILD_TYPE=Release -DCMAKE_GENERATOR_PLATFORM=x64 ..
|
||||
- cmake --build .
|
||||
- ctest --verbose
|
||||
- cmake --version
|
||||
- cmake %CMAKE_ARGS% --parallel ..
|
||||
- cmake -LH ..
|
||||
- cmake --build . --config %Configuration% --verbose --parallel
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
except:
|
||||
- job_name: VS2019ARM
|
||||
|
||||
test_script:
|
||||
- ctest --output-on-failure -C %Configuration% --verbose %CTEST_ARGS% --parallel
|
||||
|
||||
clone_folder: c:\projects\simdjson
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
+238
-151
@@ -1,162 +1,249 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
# Reusable image / compiler definitions
|
||||
executors:
|
||||
gcc7:
|
||||
docker:
|
||||
- image: gcc:7
|
||||
environment:
|
||||
CXX: g++
|
||||
CC: gcc
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
gcc8:
|
||||
docker:
|
||||
- image: conanio/gcc8
|
||||
environment:
|
||||
CXX: g++-8
|
||||
CC: gcc-8
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
gcc9:
|
||||
docker:
|
||||
- image: conanio/gcc9
|
||||
environment:
|
||||
CXX: g++-9
|
||||
CC: gcc-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
gcc10:
|
||||
docker:
|
||||
- image: conanio/gcc10
|
||||
environment:
|
||||
CXX: g++-10
|
||||
CC: gcc-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
clang10:
|
||||
docker:
|
||||
- image: conanio/clang10
|
||||
environment:
|
||||
CXX: clang++-10
|
||||
CC: clang-10
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
clang9:
|
||||
docker:
|
||||
- image: conanio/clang9
|
||||
environment:
|
||||
CXX: clang++-9
|
||||
CC: clang-9
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
clang6:
|
||||
docker:
|
||||
- image: conanio/clang60
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
CC: clang-6.0
|
||||
BUILD_FLAGS: -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
|
||||
# Reusable test commands (and initializer for clang 6)
|
||||
commands:
|
||||
install_cmake:
|
||||
steps:
|
||||
- run: apt-get update -qq
|
||||
- run: apt-get install -y cmake
|
||||
|
||||
cmake_prep:
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir -p build
|
||||
|
||||
cmake_build:
|
||||
steps:
|
||||
- cmake_prep
|
||||
- run: |
|
||||
cd build &&
|
||||
cmake $CMAKE_FLAGS -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
make $BUILD_FLAGS all
|
||||
|
||||
cmake_test:
|
||||
steps:
|
||||
- cmake_build
|
||||
- run: |
|
||||
cd build && tools/json2json -h &&
|
||||
ctest $CTEST_FLAGS -L acceptance &&
|
||||
ctest $CTEST_FLAGS -LE acceptance -E checkperf
|
||||
|
||||
cmake_test_all:
|
||||
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 &&
|
||||
ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
|
||||
|
||||
# we not only want cmake to build and run tests, but we want also a successful installation from which we can build, link and run programs
|
||||
cmake_install_test: # this version builds, install, test and then verify from the installation
|
||||
steps:
|
||||
- run: cd build && make install
|
||||
- run: echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Ibuild/destination/include -Lbuild/destination/lib -std=c++17 -Wl,-rpath,build/destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json
|
||||
|
||||
jobs:
|
||||
"gcc":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: g++-7
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
g++-7
|
||||
git
|
||||
# static
|
||||
gcc7:
|
||||
description: Build and run tests on GCC 7 and AVX 2 with a cmake static build
|
||||
executor: gcc7
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ install_cmake, cmake_test, cmake_install_test ]
|
||||
justlib-gcc10:
|
||||
description: Build just the library, install it and do a basic test
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_JUST_LIBRARY=ON }
|
||||
steps: [ cmake_build, cmake_install_test ]
|
||||
gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake static build, this test performance regression
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test_all, cmake_install_test ]
|
||||
clang6:
|
||||
description: Build and run tests on clang 6 and AVX 2 with a cmake static build
|
||||
executor: clang6
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake static build
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
# libcpp
|
||||
libcpp-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake static build and libc++
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_USE_LIBCPP=ON -DSIMDJSON_BUILD_STATIC=ON }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
# sanitize
|
||||
sanitize-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake sanitize build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_SANITIZE=ON, BUILD_FLAGS: "", CTEST_FLAGS: -j4 --output-on-failure -E checkperf }
|
||||
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 }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
- run:
|
||||
name: Building (gcc)
|
||||
command: make
|
||||
# dynamic
|
||||
dynamic-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with a cmake dynamic build
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
dynamic-clang10:
|
||||
description: Build and run tests on clang 10 and AVX 2 with a cmake dynamic build
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc)
|
||||
command: make quiettest amalgamate
|
||||
# unthreaded
|
||||
unthreaded-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 *without* threads
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_ENABLE_THREADS=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
unthreaded-clang10:
|
||||
description: Build and run tests on Clang 10 and AVX 2 *without* threads
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_ENABLE_THREADS=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
|
||||
- run:
|
||||
name: Building (gcc, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
# noexcept
|
||||
noexcept-gcc10:
|
||||
description: Build and run tests on GCC 10 and AVX 2 with exceptions off
|
||||
executor: gcc10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_EXCEPTIONS=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
noexcept-clang10:
|
||||
description: Build and run tests on Clang 10 and AVX 2 with exceptions off
|
||||
executor: clang10
|
||||
environment: { CMAKE_FLAGS: -DSIMDJSON_EXCEPTIONS=OFF }
|
||||
steps: [ cmake_test, cmake_install_test ]
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
#
|
||||
# Misc.
|
||||
#
|
||||
|
||||
"gccnoavx":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: g++-7
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
g++-7
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (gcc)
|
||||
command: ARCHFLAGS="-march=nehalem" make
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc)
|
||||
command: ARCHFLAGS="-march=nehalem" make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (gcc, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (gcc, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
|
||||
"clang":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
clang-6.0
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (clang)
|
||||
command: make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang)
|
||||
command: make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (clang, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
|
||||
"clangnoavx":
|
||||
docker:
|
||||
- image: ubuntu:18.04
|
||||
environment:
|
||||
CXX: clang++-6.0
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: apt-get update -qq
|
||||
- run: >
|
||||
apt-get install -y
|
||||
build-essential
|
||||
cmake
|
||||
clang-6.0
|
||||
git
|
||||
|
||||
- run:
|
||||
name: Building (clang)
|
||||
command: ARCHFLAGS="-march=nehalem" make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang)
|
||||
command: ARCHFLAGS="-march=nehalem" make quiettest amalgamate
|
||||
|
||||
- run:
|
||||
name: Building (clang, cmake)
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
make
|
||||
|
||||
- run:
|
||||
name: Running tests (clang, cmake)
|
||||
command: |
|
||||
cd build
|
||||
make test
|
||||
# make (test and checkperf)
|
||||
arch-haswell-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 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
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -march=nehalem }
|
||||
steps: [ cmake_test ]
|
||||
no-computed-goto-gcc10:
|
||||
description: Build, run tests and check performance on GCC 7 with -DSIMDJSON_NO_COMPUTED_GOTO=true
|
||||
executor: gcc10
|
||||
environment: { CXXFLAGS: -DSIMDJSON_NO_COMPUTED_GOTO=true }
|
||||
steps: [ cmake_test ]
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
version: 2.1
|
||||
build_and_test:
|
||||
jobs:
|
||||
- "clang"
|
||||
- "gcc"
|
||||
- "clangnoavx"
|
||||
- "gccnoavx"
|
||||
# full multi-implementation tests
|
||||
- gcc7
|
||||
- gcc10 # do not delete this as it tests our performance
|
||||
- clang6
|
||||
#- clang10 # this gets tested a lot below
|
||||
|
||||
# libc++
|
||||
- libcpp-clang10
|
||||
|
||||
# full single-implementation tests
|
||||
- sanitize-gcc10
|
||||
- sanitize-clang10
|
||||
- dynamic-gcc10
|
||||
- dynamic-clang10
|
||||
- unthreaded-gcc10
|
||||
- unthreaded-clang10
|
||||
|
||||
# no exceptions
|
||||
- noexcept-gcc10
|
||||
- noexcept-clang10
|
||||
|
||||
# quicker make single-implementation tests
|
||||
- arch-haswell-gcc10
|
||||
- arch-nehalem-gcc10
|
||||
- no-computed-goto-gcc10
|
||||
|
||||
# testing "just the library"
|
||||
- justlib-gcc10
|
||||
|
||||
# TODO add windows: https://circleci.com/docs/2.0/configuration-reference/#windows
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
task:
|
||||
timeout_in: 120m
|
||||
freebsd_instance:
|
||||
matrix:
|
||||
- image_family: freebsd-13-0-snap
|
||||
|
||||
env:
|
||||
ASSUME_ALWAYS_YES: YES
|
||||
setup_script:
|
||||
- pkg update -f
|
||||
- pkg install bash
|
||||
- pkg install cmake
|
||||
- pkg install git
|
||||
build_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake ..
|
||||
- make -j4
|
||||
test_script:
|
||||
- cd build
|
||||
- ctest -j4 --output-on-failure -E checkperf
|
||||
@@ -0,0 +1,15 @@
|
||||
*
|
||||
!.git
|
||||
!Makefile
|
||||
!amalgamate.sh
|
||||
!benchmark
|
||||
!dependencies
|
||||
!include
|
||||
!jsonchecker
|
||||
!jsonexamples
|
||||
!scripts
|
||||
!singleheader
|
||||
!src
|
||||
!style
|
||||
!tests
|
||||
!tools
|
||||
+376
-217
@@ -1,40 +1,389 @@
|
||||
kind: pipeline
|
||||
name: x64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
name: i386-gcc # we do not support 32-bit systems, but we run tests
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: test
|
||||
image: gcc:8
|
||||
- name: Build and Test
|
||||
image: i386/ubuntu
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- make
|
||||
- make quiettest
|
||||
- make amalgamate
|
||||
- apt-get update -qq
|
||||
- apt-get install -y g++ cmake gcc
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
name: i386-clang # we do not support 32-bit systems, but we run tests
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: test
|
||||
image: gcc:8
|
||||
- name: Build and Test
|
||||
image: i386/ubuntu
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- make
|
||||
- make quiettest
|
||||
- make amalgamate
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang++-6.0 cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: gcc9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:9
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- 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
|
||||
- ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: clang6
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: conanio/clang60
|
||||
user: root
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- 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
|
||||
- ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: dynamic-gcc9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:9
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: dynamic-clang9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: conanio/clang9
|
||||
user: root
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: sanitize-gcc9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:9
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L acceptance -LE per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=haswell ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=westmere ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=fallback ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: sanitize-clang9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: conanio/clang9
|
||||
user: root
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L acceptance -LE per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=haswell ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=westmere ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=fallback ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-gcc8
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS -L acceptance -LE per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=arm64 ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=fallback ctest $CTEST_FLAGS -L per_implementation
|
||||
- ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-clang6
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang cmake git
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-dynamic-gcc8
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-dynamic-clang6
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=OFF
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang cmake git
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-sanitize-gcc8
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:8
|
||||
environment:
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake libstdc++6
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L acceptance -LE per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=arm64 ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=fallback ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64-sanitize-clang6
|
||||
platform: { os: linux, arch: arm64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
CMAKE_FLAGS: -DSIMDJSON_SANITIZE=ON
|
||||
BUILD_FLAGS: -- -j
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y clang cmake git
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L acceptance -LE per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=arm64 ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- SIMDJSON_FORCE_IMPLEMENTATION=fallback ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -L per_implementation
|
||||
- ASAN_OPTIONS="detect_leaks=0" ctest $CTEST_FLAGS -LE "acceptance|per_implementation" # Everything we haven't run yet, run now.
|
||||
---
|
||||
kind: pipeline
|
||||
name: ninja-clang9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: conanio/clang9
|
||||
user: root
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
BUILD_FLAGS: -- -j 4
|
||||
CMAKE_FLAGS: -GNinja -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure
|
||||
CXXFLAGS: -stdlib=libc++
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: libcpp-clang9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: conanio/clang9
|
||||
user: root
|
||||
environment:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_BUILD_STATIC=ON
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
CXXFLAGS: -stdlib=libc++
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: noexceptions-gcc9
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: gcc:9
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
BUILD_FLAGS: -- -j
|
||||
CMAKE_FLAGS: -DSIMDJSON_EXCEPTIONS=OFF
|
||||
CTEST_FLAGS: -j4 --output-on-failure -E checkperf
|
||||
commands:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- cmake --build . $BUILD_FLAGS
|
||||
- ctest $CTEST_FLAGS
|
||||
---
|
||||
kind: pipeline
|
||||
name: stylecheck
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
platform: { os: linux, arch: amd64 }
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
@@ -42,193 +391,3 @@ steps:
|
||||
- apt-get update -y
|
||||
- apt-get install -y python clang-format
|
||||
- ./style/run-clang-format.py -r include/ benchmark/ src/ tests/
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=OFF $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_clang_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=ON $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=ON $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: amd64_gcc_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=OFF $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=OFF $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_gcc_cmake_dynamic
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=OFF $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_clang_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=ON $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
---
|
||||
kind: pipeline
|
||||
name: arm64_gcc_cmake_static
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Build and Test
|
||||
image: ubuntu:18.04
|
||||
environment:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
commands:
|
||||
- apt-get update -y
|
||||
- apt-get install -y make $CC g++ cmake
|
||||
- $CC --version
|
||||
- mkdir build && cd build
|
||||
- cmake -DSIMDJSON_BUILD_STATIC=ON $CMAKE_FLAGS ..
|
||||
- make -j
|
||||
- ctest
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+113
-15
@@ -1,19 +1,117 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
# Uncomment next line to adjust line endings
|
||||
* text=auto
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.c text
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.java text
|
||||
*.xml text
|
||||
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.svg binary
|
||||
*.json binary
|
||||
# we don't want json files to be modified for this project
|
||||
*.json binary
|
||||
|
||||
|
||||
# Common settings that generally should always be used with your language specific settings
|
||||
|
||||
#
|
||||
# From generator:
|
||||
# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||
#
|
||||
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as an asset (binary) by default.
|
||||
*.svg text
|
||||
# If you want to treat it as binary,
|
||||
# use the following line instead.
|
||||
# *.svg binary
|
||||
*.eps binary
|
||||
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.sh text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
||||
# Serialisation
|
||||
#*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
*.zip binary
|
||||
|
||||
#
|
||||
# Exclude files from exporting
|
||||
#
|
||||
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
# Sources
|
||||
*.c text diff=c
|
||||
*.cc text diff=cpp
|
||||
*.cxx text diff=cpp
|
||||
*.cpp text diff=cpp
|
||||
*.c++ text diff=cpp
|
||||
*.hpp text diff=cpp
|
||||
*.h text diff=c
|
||||
*.h++ text diff=cpp
|
||||
*.hh text diff=cpp
|
||||
|
||||
# Compiled Object files
|
||||
*.slo binary
|
||||
*.lo binary
|
||||
*.o binary
|
||||
*.obj binary
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch binary
|
||||
*.pch binary
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so binary
|
||||
*.dylib binary
|
||||
*.dll binary
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai binary
|
||||
*.la binary
|
||||
*.a binary
|
||||
*.lib binary
|
||||
|
||||
# Executables
|
||||
*.exe binary
|
||||
*.out binary
|
||||
*.app binary
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour: provide a code sample if possible.
|
||||
|
||||
**Configuration (please complete the following information if relevant):**
|
||||
- OS: [e.g. Ubuntu 16.04.6 LTS]
|
||||
- Compiler [e.g. Apple clang version 11.0.3 (clang-1103.0.32.59) x86_64-apple-darwin19.4.0]
|
||||
- Version [e.g. 22]
|
||||
|
||||
|
||||
**Indicate whether you are willing or able to provide a bug fix as a pull request**
|
||||
|
||||
If you plan to contribute to simdjson, please read our
|
||||
* CONTRIBUTING guide: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md and our
|
||||
* HACKING guide: https://github.com/simdjson/simdjson/blob/master/HACKING.md
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
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 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.
|
||||
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
Please provide a clear rationale for the feature. Be advised that simdjson is a community-based project: you should consider providing help.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
** Are you willing to contribute code or documentation toward this new feature? **
|
||||
If you plan to contribute to simdjson, please read our
|
||||
* CONTRIBUTING guide: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md and our
|
||||
* HACKING guide: https://github.com/simdjson/simdjson/blob/master/HACKING.md
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Standard issue template
|
||||
about: Issue
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
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 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.
|
||||
|
||||
Is your issue:
|
||||
|
||||
1. A bug report? If so, please point at a reproducible test. Indicate whether you are willing or able to provide a bug fix as a pull request.
|
||||
|
||||
2. A build issue? If so, provide all possible details regarding your system configuration. If we cannot reproduce your issue, we cannot fix it.
|
||||
|
||||
3. A feature request? Please provide a clear rationale for the feature. Be advised that simdjson is a community-based project: you should consider providing help.
|
||||
|
||||
4. A documentation issue? Can you suggest an improvement?
|
||||
|
||||
|
||||
If you plan to contribute to simdjson, please read our
|
||||
* CONTRIBUTING guide: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md and our
|
||||
* HACKING guide: https://github.com/simdjson/simdjson/blob/master/HACKING.md
|
||||
@@ -0,0 +1,127 @@
|
||||
name: Run fuzzers on stored corpus and test it with valgrind
|
||||
|
||||
# In the case of a pull request happening at the same time as a cron
|
||||
# job, there is a risk two jobs run at the same time. Therefore,
|
||||
# the corpus is only uploaded for the master branch. Pull requests will
|
||||
# fuzz for a short while, but the results are not uploaded.
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: 23 */8 * * *
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
allfuzzers: parser dump dump_raw_tape print_json
|
||||
artifactsprefix: -artifact_prefix=fuzzfailure/
|
||||
steps:
|
||||
- name: Install packages necessary for building
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install --quiet ninja-build valgrind zip unzip
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 9
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
- name: Create and prepare the initial seed corpus
|
||||
run: |
|
||||
fuzz/build_corpus.sh
|
||||
mv corpus.zip seed_corpus.zip
|
||||
- name: Download the corpus from the last run
|
||||
run: |
|
||||
wget --quiet https://dl.bintray.com/pauldreik/simdjson-fuzz-corpus/corpus/corpus.tar
|
||||
tar xf corpus.tar
|
||||
rm corpus.tar
|
||||
- name: List clang versions
|
||||
run: |
|
||||
ls /usr/bin/clang*
|
||||
which clang++
|
||||
clang++ --version
|
||||
- name: Build all the variants
|
||||
run: fuzz/build_fuzzer_variants.sh
|
||||
- name: Verify that the oss-fuzz seed corpus passes without problems
|
||||
run: |
|
||||
mkdir seedcorpus
|
||||
unzip -q -d seedcorpus seed_corpus.zip
|
||||
for buildvariant in noavx withavx; do
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-$buildvariant/fuzz/fuzz_$fuzzer seedcorpus -max_total_time=1
|
||||
done
|
||||
done
|
||||
- name: Run the fastest fuzzer to explore fast
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
mkdir -p out/$fuzzer # in case this is a new fuzzer, or corpus.tar is broken
|
||||
build-ossfuzz-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
|
||||
done
|
||||
- name: Run the other fuzzer variants for $fuzzer, with sanitizers etc
|
||||
run: |
|
||||
set -x
|
||||
for fuzzer in $allfuzzers; do
|
||||
build-ossfuzz-withavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=20 $artifactsprefix || touch failed
|
||||
build-ossfuzz-noavx/fuzz/fuzz_$fuzzer out/$fuzzer -max_total_time=10 $artifactsprefix || touch failed
|
||||
build-ossfuzz-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
|
||||
echo now have $(ls out/$fuzzer |wc -l) files in corpus
|
||||
done
|
||||
- name: Minimize the corpus with the fast fuzzer
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
mkdir -p out/cmin/$fuzzer
|
||||
build-ossfuzz-fast9/fuzz/fuzz_$fuzzer -merge=1 out/cmin/$fuzzer out/$fuzzer
|
||||
rm -rf out/$fuzzer
|
||||
mv out/cmin/$fuzzer out/$fuzzer
|
||||
done
|
||||
- name: Package the corpus into an artifact
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
tar rf corpus.tar out/$fuzzer
|
||||
done
|
||||
- name: Save the corpus as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: corpus
|
||||
path: corpus.tar
|
||||
- name: Run the corpus through valgrind (normal build)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-noavx/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-noavx.txt
|
||||
done
|
||||
- name: Run the corpus through valgrind (noavx build)
|
||||
run: |
|
||||
for fuzzer in $allfuzzers; do
|
||||
find out/$fuzzer -type f |sort|xargs valgrind build-plain-normal/fuzz/fuzz_$fuzzer 2>&1|tee valgrind-$fuzzer-normal.txt
|
||||
done
|
||||
- name: Compress the valgrind output
|
||||
run: tar cf valgrind.tar valgrind-*.txt
|
||||
- name: Save valgrind output as a github artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: valgrindresults
|
||||
path: valgrind.tar
|
||||
- name: Upload the corpus and results to bintray if we are on master
|
||||
run: |
|
||||
if [ $(git rev-parse --verify HEAD) = $(git rev-parse --verify origin/master) ] ; then
|
||||
echo uploading each artifact twice, otherwise it will not be published
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T corpus.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/corpus.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
curl -T valgrind.tar -upauldreik:${{ secrets.bintrayApiKey }} https://api.bintray.com/content/pauldreik/simdjson-fuzz-corpus/corpus/0/corpus/valgrind.tar";publish=1;override=1"
|
||||
else
|
||||
echo "not on master, won't upload to bintray"
|
||||
fi
|
||||
@@ -0,0 +1,58 @@
|
||||
name: MinGW32-CI
|
||||
|
||||
on: push
|
||||
|
||||
|
||||
# Important: scoop will either install 32-bit GCC or 64-bit GCC, not both.
|
||||
|
||||
# It is important to build static libraries because cmake is not smart enough under Windows/mingw to take care of the path. So
|
||||
# with a dynamic library, you could get failures due to the fact that the EXE can't find its DLL.
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-gcc
|
||||
runs-on: windows-2016
|
||||
|
||||
env:
|
||||
CMAKE_GENERATOR: Ninja # This is critical, try ' cmake -GNinja-DSIMDJSON_BUILD_STATIC=ON .. ' if using the command line
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
|
||||
steps: # To reproduce what is below, start a powershell with administrative rights, using scoop *is* a good idea
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2 # we cache the scoop setup with 32-bit GCC
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
C:\ProgramData\scoop
|
||||
key: scoop32 # static key: should be good forever
|
||||
- name: Setup Windows # This should almost never run if the cache works.
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||
scoop install sudo --global
|
||||
sudo scoop install git --global
|
||||
sudo scoop install ninja --global
|
||||
sudo scoop install cmake --global
|
||||
sudo scoop install gcc --arch 32bit --global
|
||||
$env:path
|
||||
Write-Host 'Everything has been installed, you are good!'
|
||||
- name: Build and Test 32-bit x86
|
||||
shell: powershell
|
||||
run: |
|
||||
$ENV:PATH="C:\ProgramData\scoop\shims;C:\ProgramData\scoop\apps\gcc\current\bin;C:\ProgramData\scoop\apps\ninja\current;$ENV:PATH"
|
||||
g++ --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
git --version
|
||||
mkdir build32
|
||||
cd build32
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest . -R stringparsingcheck --output-on-failure
|
||||
ctest . -R numberparsingcheck --output-on-failure
|
||||
ctest . -R errortests --output-on-failure
|
||||
ctest . -R integer_tests --output-on-failure
|
||||
ctest . -R pointercheck --output-on-failure
|
||||
@@ -0,0 +1,60 @@
|
||||
name: MinGW64-CI
|
||||
|
||||
on: push
|
||||
|
||||
|
||||
# Important: scoop will either install 32-bit GCC or 64-bit GCC, not both.
|
||||
|
||||
# It is important to build static libraries because cmake is not smart enough under Windows/mingw to take care of the path. So
|
||||
# with a dynamic library, you could get failures due to the fact that the EXE can't find its DLL.
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: windows-gcc
|
||||
runs-on: windows-2016
|
||||
|
||||
env:
|
||||
CMAKE_GENERATOR: Ninja # This is critical, try ' cmake -GNinja-DSIMDJSON_BUILD_STATIC=ON .. ' if using the command line
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
|
||||
steps: # To reproduce what is below, start a powershell with administrative rights, using scoop *is* a good idea
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
- uses: actions/cache@v2 # we cache the scoop setup with 64-bit GCC
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
C:\ProgramData\scoop
|
||||
key: scoop64 # static key: should be good forever
|
||||
- name: Setup Windows # This should almost never run if the cache works.
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||
scoop install sudo --global
|
||||
sudo scoop install git --global
|
||||
sudo scoop install ninja --global
|
||||
sudo scoop install cmake --global
|
||||
sudo scoop install gcc --arch 64bit --global
|
||||
$env:path
|
||||
Write-Host 'Everything has been installed, you are good!'
|
||||
- name: Build and Test 64-bit x64
|
||||
shell: powershell
|
||||
run: |
|
||||
$ENV:PATH="C:\ProgramData\scoop\shims;C:\ProgramData\scoop\apps\gcc\current\bin;C:\ProgramData\scoop\apps\ninja\current;$ENV:PATH"
|
||||
g++ --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
git --version
|
||||
mkdir build64
|
||||
cd build64
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_COMPETITION=OFF -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_ENABLE_THREADS=OFF ..
|
||||
cmake --build . --target basictests numberparsingcheck stringparsingcheck errortests integer_tests pointercheck --verbose
|
||||
ctest . -R stringparsingcheck --output-on-failure
|
||||
ctest . -R numberparsingcheck --output-on-failure
|
||||
ctest . -R errortests --output-on-failure
|
||||
ctest . -R integer_tests --output-on-failure
|
||||
ctest . -R pointercheck --output-on-failure
|
||||
|
||||
+98
-1
@@ -1 +1,98 @@
|
||||
build/
|
||||
# eclipse project files
|
||||
.cproject
|
||||
.project
|
||||
.settings
|
||||
|
||||
# emacs temp files
|
||||
*~
|
||||
|
||||
# vim temp files
|
||||
.*.swp
|
||||
|
||||
# XCode
|
||||
^build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
*.DS_Store
|
||||
|
||||
# IDE specific folder for JetBrains IDEs
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Visual Studio Code artifacts
|
||||
.vscode/*
|
||||
.history/
|
||||
|
||||
# Visual Studio artifacts
|
||||
/VS/
|
||||
|
||||
# C/C++ build outputs
|
||||
.build/
|
||||
bins
|
||||
gens
|
||||
libs
|
||||
objs
|
||||
|
||||
# C++ ignore from https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
||||
# CMake files that may be specific to our installation
|
||||
|
||||
# Build outputs
|
||||
/build*/
|
||||
/visual_studio/
|
||||
|
||||
# Fuzzer outputs generated by instructions in fuzz/Fuzzing.md
|
||||
/corpus.zip
|
||||
/ossfuzz-out/
|
||||
/out/
|
||||
|
||||
# Generated docs
|
||||
/doc/api
|
||||
|
||||
@@ -28,3 +28,9 @@
|
||||
[submodule "dependencies/json"]
|
||||
path = dependencies/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "dependencies/benchmark"]
|
||||
path = dependencies/benchmark
|
||||
url = https://github.com/google/benchmark.git
|
||||
[submodule "dependencies/cxxopts"]
|
||||
path = dependencies/cxxopts
|
||||
url = https://github.com/jarro2783/cxxopts
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# List of authors for copyright purposes
|
||||
# List of authors for copyright purposes, in no particular order
|
||||
Daniel Lemire
|
||||
Geoff Langdale
|
||||
John Keiser
|
||||
|
||||
+68
-39
@@ -1,52 +1,81 @@
|
||||
cmake_minimum_required(VERSION 3.9) # CMP0069 NEW
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ltoresult)
|
||||
if(ltoresult)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
# usage: cmake -DSIMDJSON_DISABLE_AVX=on ..
|
||||
option(SIMDJSON_DISABLE_AVX "Forcefully disable AVX even if hardware supports it" OFF)
|
||||
project(simdjson
|
||||
DESCRIPTION "Parsing gigabytes of JSON per second"
|
||||
LANGUAGES CXX C
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MACOSX_RPATH OFF)
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "No build type selected, default to Release")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
project(simdjson)
|
||||
set(SIMDJSON_LIB_NAME simdjson)
|
||||
set(PROJECT_VERSION_MAJOR 0)
|
||||
set(PROJECT_VERSION_MINOR 2)
|
||||
set(PROJECT_VERSION_PATCH 1)
|
||||
set(SIMDJSON_LIB_VERSION "0.2.1" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "0" CACHE STRING "simdjson library soversion")
|
||||
set(PROJECT_VERSION_MINOR 4)
|
||||
set(PROJECT_VERSION_PATCH 7)
|
||||
set(SIMDJSON_SEMANTIC_VERSION "0.4.7" CACHE STRING "simdjson semantic version")
|
||||
set(SIMDJSON_LIB_VERSION "2.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "2" CACHE STRING "simdjson library soversion")
|
||||
set(SIMDJSON_GITHUB_REPOSITORY https://github.com/simdjson/simdjson)
|
||||
|
||||
if(NOT MSVC)
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" OFF) # turning it on disables the production of a dynamic library
|
||||
else()
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" ON) # turning it on disables the production of a dynamic library
|
||||
include(GNUInstallDirs)
|
||||
include(cmake/simdjson-flags.cmake)
|
||||
include(cmake/simdjson-user-cmakecache.cmake)
|
||||
|
||||
|
||||
|
||||
if(SIMDJSON_JUST_LIBRARY)
|
||||
message( STATUS "Building just the library, omitting all tests, tools and benchmarks." )
|
||||
endif()
|
||||
option(SIMDJSON_SANITIZE "Sanitize addresses" OFF)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake")
|
||||
#
|
||||
# Set up test data
|
||||
#
|
||||
if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
enable_testing()
|
||||
add_subdirectory(jsonchecker)
|
||||
add_subdirectory(jsonexamples)
|
||||
add_library(test-data INTERFACE)
|
||||
target_link_libraries(test-data INTERFACE jsonchecker-data jsonexamples-data)
|
||||
endif()
|
||||
|
||||
find_package(CTargets)
|
||||
find_package(Options)
|
||||
|
||||
install(DIRECTORY include/${SIMDJSON_LIB_NAME} DESTINATION include)
|
||||
set (TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/jsonchecker/")
|
||||
set (BENCHMARK_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/jsonexamples/")
|
||||
add_definitions(-DSIMDJSON_TEST_DATA_DIR="${TEST_DATA_DIR}")
|
||||
add_definitions(-DSIMDJSON_BENCHMARK_DATA_DIR="${TEST_DATA_DIR}")
|
||||
enable_testing()
|
||||
#
|
||||
# Create the top level simdjson library (must be done at this level to use both src/ and include/
|
||||
# directories) and tools
|
||||
#
|
||||
add_subdirectory(include)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(benchmark)
|
||||
add_subdirectory(windows)
|
||||
if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
add_subdirectory(dependencies) ## This needs to be before tools because of cxxopts
|
||||
add_subdirectory(tools) ## This needs to be before tests because of cxxopts
|
||||
add_subdirectory(singleheader)
|
||||
endif()
|
||||
install(FILES singleheader/simdjson.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
#
|
||||
# Compile tools / tests / benchmarks
|
||||
#
|
||||
if(NOT(SIMDJSON_JUST_LIBRARY))
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(examples)
|
||||
add_subdirectory(benchmark)
|
||||
add_subdirectory(fuzz)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Source files should be just ASCII
|
||||
#
|
||||
find_program(FIND find)
|
||||
find_program(FILE file)
|
||||
find_program(GREP grep)
|
||||
if((FIND) AND (FILE) AND (GREP))
|
||||
add_test(
|
||||
NAME "just_ascii"
|
||||
COMMAND sh -c "${FIND} include src windows tools singleheader tests examples benchmark -path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' -type f -exec ${FILE} '{}' \; |${GREP} -v ASCII || exit 0 && exit 1"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
#
|
||||
# CPack
|
||||
#
|
||||
set(CPACK_PACKAGE_VENDOR "Daniel Lemire")
|
||||
set(CPACK_PACKAGE_CONTACT "lemire@gmail.com")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Parsing gigabytes of JSON per second")
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
The simdjson library is an open project written in C++. Contributions are invited. Contributors
|
||||
agree to the project's license.
|
||||
|
||||
We have an extensive list of issues, and contributions toward any of these issues is invited.
|
||||
Contributions can take the form of code samples, better documentation or design ideas.
|
||||
|
||||
In particular, the following contributions are invited:
|
||||
|
||||
- The library is focused on performance. Well-documented performance optimization are invited.
|
||||
- Fixes to known or newly discovered bugs are always welcome. Typically, a bug fix should come with
|
||||
a test demonstrating that the bug has been fixed.
|
||||
- The simdjson library is advanced software and maintainability and flexibility are always a
|
||||
concern. Specific contributions to improve maintainability and flexibility are invited.
|
||||
|
||||
We discourage the following types of contributions:
|
||||
|
||||
- Code refactoring. We all have our preferences as to how code should be written, but unnecessary
|
||||
refactoring can waste time and introduce new bugs. If you believe that refactoring is needed, you
|
||||
first must explain how it helps in concrete terms. Does it improve the performance?
|
||||
- Applications of new language features for their own sake. Using advanced C++ language constructs
|
||||
is actually a negative as it may reduce portability (to old compilers, old standard libraries and
|
||||
systems) and reduce accessibility (to programmers that have not kept up), so it must be offsetted
|
||||
by clear gains like performance or maintainability. When in doubt, avoid advanced C++ features
|
||||
(beyond C++11).
|
||||
- Style formatting. In general, please abstain from reformatting code just to make it look prettier.
|
||||
Though code formatting is important, it can also be a waste of time if several contributors try to
|
||||
tweak the code base toward their own preference. Please do not introduce unneeded white-space
|
||||
changes.
|
||||
|
||||
In short, most code changes should either bring new features or better performance. We want to avoid unmotivated code changes.
|
||||
|
||||
|
||||
Specific rules
|
||||
----------
|
||||
|
||||
We have few hard rules, but we have some:
|
||||
|
||||
- Printing to standard output or standard error (`stderr`, `stdout`, `std::cerr`, `std::cout`) in the core library is forbidden. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Compiled code should not write to stdout or stderr".
|
||||
- Calls to `abort()` are forbidden in the core library. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Under no circumstances should your compiled code ever call abort or exit".
|
||||
- All source code files (.h, .cpp) must be ASCII.
|
||||
|
||||
Tools, tests and benchmarks are not held to these same strict rules.
|
||||
|
||||
General Guidelines
|
||||
----------
|
||||
|
||||
Contributors are encouraged to :
|
||||
|
||||
- Document their changes. Though we do not enforce a rule regarding code comments, we prefer that non-trivial algorithms and techniques be somewhat documented in the code.
|
||||
- Follow as much as possible the existing code style. We do not enforce a specific code style, but we prefer consistency.
|
||||
- Modify as few lines of code as possible when working on an issue. The more lines you modify, the harder it is for your fellow human beings to understand what is going on.
|
||||
- Tools may report "problems" with the code, but we never delegate programming to tools: if there is a problem with the code, we need to understand it. Thus we will not "fix" code merely to please a static analyzer if we do not understand.
|
||||
- Provide tests for any new feature. We will not merge a new feature without tests.
|
||||
|
||||
Pull Requests
|
||||
--------------
|
||||
|
||||
Pull requests are always invited. However, we ask that you follow these guidelines:
|
||||
|
||||
- It is wiser to discuss your ideas first as part of an issue before you start coding. If you omit this step and code first, be prepare to have your code receive scrutiny and be dropped.
|
||||
- Users should provide a rationale for their changes. Does it improve performance? Does it add a feature? Does it improve maintainability? Does fix a bug? This must be explicitly stated as part of the pull request. Do not propose changes based on taste or intuition. We do not delegate programming to tools: that some tool suggested a code change is not reason enough to change the code.
|
||||
1. When your code improves performance, please document the gains with a benchmark using hard numbers.
|
||||
2. If your code fixes a bug, please be either fix a failing test, or propose a new test.
|
||||
3. Other types of changes must be clearly motivated. We openly discourage changes with no identifiable benefits.
|
||||
- Changes should be focused and minimal. You should change as few lines of code as possible. Please do not reformat or touch files needlessly.
|
||||
- New features must be accompanied of new tests, in general.
|
||||
- Your code should pass our continuous-integration tests. It is your responsability to ensure that your proposal pass the tests. We do not merge pull requests that would break our build.
|
||||
|
||||
If the benefits of your proposed code remain unclear, we may choose to discard your code: that is not an insult, we frequently discard our own code. We may also consider various alternatives and choose another path. Again, that is not an insult or a sign that you have wasted your time.
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Though we do not have a formal code of conduct, we will not tolerate bullying, bigotry or
|
||||
intimidation. Everyone is welcome to contribute. If you have concerns, you can raise them privately with the core team members (e.g., D. Lemire, J. Keiser).
|
||||
|
||||
We welcome contributions from women and less represented groups. If you need help, please reach out.
|
||||
|
||||
Consider the following points when engaging with the project:
|
||||
|
||||
- We discourage arguments from authority: ideas are discusssed on their own merits and not based on who stated it.
|
||||
- Be mindful that what you may view as an aggression is maybe merely a difference of opinion or a misunderstanding.
|
||||
- Be mindful that a collection of small aggressions, even if mild in isolation, can become harmful.
|
||||
|
||||
Getting Started Hacking
|
||||
-----------------------
|
||||
|
||||
An overview of simdjson's directory structure, with pointers to architecture and design
|
||||
considerations and other helpful notes, can be found at [HACKING.md](HACKING.md).
|
||||
@@ -23,5 +23,17 @@ Chang Liu
|
||||
Sunny Gleason
|
||||
John Keiser
|
||||
Zach Bjornson
|
||||
Vitaly Baranov
|
||||
Juho Lauri
|
||||
Michael Eisel
|
||||
Io Daza Dillon
|
||||
Paul Dreik
|
||||
Jeremie Piotte
|
||||
Matthew Wilson
|
||||
Dušan Jovanović
|
||||
Matjaž Ostroveršnik
|
||||
Nong Li
|
||||
Furkan Taşkale
|
||||
Brendan Knapp
|
||||
# if you have contributed to the project and your name does not
|
||||
# appear in this list, please let us know!
|
||||
|
||||
+88
-10
@@ -1,10 +1,88 @@
|
||||
# docker build -t simdjson . && docker run --privileged -t simdjson
|
||||
FROM gcc:8.3
|
||||
COPY . /usr/src/
|
||||
WORKDIR /usr/src/
|
||||
RUN make clean
|
||||
RUN make amalgamate
|
||||
RUN make
|
||||
RUN make test
|
||||
RUN make parsingcompetition
|
||||
CMD ["bash", "scripts/selectparser.sh"]
|
||||
###
|
||||
#
|
||||
# Though simdjson requires only commonly available compilers and tools, it can
|
||||
# be convenient to build it and test it inside a docker container: it makes it
|
||||
# possible to test and benchmark simdjson under even relatively out-of-date
|
||||
# Linux servers. It should also work under macOS and Windows, though not
|
||||
# at native speeds, maybe.
|
||||
#
|
||||
# Assuming that you have a working docker server, this file
|
||||
# allows you to build, test and benchmark simdjson.
|
||||
#
|
||||
# We build the library and associated files in the dockerbuild subdirectory.
|
||||
# It may be necessary to delete it before creating the image:
|
||||
#
|
||||
# rm -r -f dockerbuild
|
||||
#
|
||||
# The need to delete the directory has nothing to do with docker per se: it is
|
||||
# simply cleaner in CMake to start from a fresh directory. This is important: if you
|
||||
# reuse the same directory with different configurations, you may get broken builds.
|
||||
#
|
||||
#
|
||||
# Then you can build the image as follows:
|
||||
#
|
||||
# docker build -t simdjson --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) .
|
||||
#
|
||||
# Please note that the image does not contain a copy of the code. However, the image will contain the
|
||||
# the compiler and the build system. This means that if you change the source code, after you have built
|
||||
# the image, you won't need to rebuild the image. In fact, unless you want to try a different compiler, you
|
||||
# do not need to ever rebuild the image, even if you do a lot of work on the source code.
|
||||
#
|
||||
# We specify the users to avoid having files owned by a privileged user (root) in our directory. Some
|
||||
# people like to run their machine as the "root" user. We do not think it is cool.
|
||||
#
|
||||
# Then you need to build the project:
|
||||
#
|
||||
# docker run -v $(pwd):/project:Z simdjson
|
||||
#
|
||||
# Should you change a source file, you may need to call this command again. Because the output
|
||||
# files are persistent between calls to this command (they reside in the dockerbuild directory),
|
||||
# this command can be fast.
|
||||
#
|
||||
# Next you can test it as follows:
|
||||
#
|
||||
# docker run -it -v $(pwd):/project:Z simdjson sh -c "cd dockerbuild && ctest . --output-on-failure -E checkperf"
|
||||
#
|
||||
# The run the complete tests requires you to have built all of simdjson.
|
||||
#
|
||||
# Building all of simdjson takes a long time. Instead, you can build just one target:
|
||||
#
|
||||
# docker run -it -v $(pwd):/project:Z simdjson sh -c "[ -d dockerbuild ] || mkdir dockerbuild && cd dockerbuild && cmake .. && cmake --build . --target parse"
|
||||
#
|
||||
# Note that it is safe to remove dockerbuild before call the previous command, as the repository gets rebuild. It is also possible, by changing the command, to use a different directory name.
|
||||
#
|
||||
# You can run performance tests:
|
||||
#
|
||||
# docker run -it --privileged -v $(pwd):/project:Z simdjson sh -c "cd dockerbuild && for i in ../jsonexamples/*.json; do echo \$i; ./benchmark/parse \$i; done"
|
||||
#
|
||||
# The "--privileged" is recommended so you can get performance counters under Linux.
|
||||
#
|
||||
# You can also grab a fresh copy of simdjson and rebuild it, to make comparisons:
|
||||
#
|
||||
# docker run -it -v $(pwd):/project:Z simdjson sh -c "git clone https://github.com/simdjson/simdjson.git && cd simdjson && mkdir build && cd build && cmake .. && cmake --build . --target parse "
|
||||
#
|
||||
# Then you can run comparisons:
|
||||
#
|
||||
# docker run -it --privileged -v $(pwd):/project:Z simdjson sh -c "for i in jsonexamples/*.json; do echo \$i; dockerbuild/benchmark/parse \$i| grep GB| head -n 1; simdjson/build/benchmark/parse \$i | grep GB |head -n 1; done"
|
||||
#
|
||||
####
|
||||
FROM ubuntu:20.10
|
||||
################
|
||||
# We would prefer to use the conan io images but they do not support 64-bit ARM? The small gcc images appear to
|
||||
# be broken on ARM.
|
||||
# Furthermore, we would not expect users to frequently rebuild the container, so using ubuntu is probably fine.
|
||||
###############
|
||||
ARG USER_ID
|
||||
ARG GROUP_ID
|
||||
RUN apt-get update -qq
|
||||
RUN DEBIAN_FRONTEND="noninteractive" apt-get -y install tzdata
|
||||
RUN apt-get install -y cmake g++ git
|
||||
RUN mkdir project
|
||||
|
||||
RUN addgroup --gid $GROUP_ID user; exit 0
|
||||
RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user; exit 0
|
||||
USER user
|
||||
RUN gcc --version
|
||||
WORKDIR /project
|
||||
|
||||
CMD ["sh","-c","[ -d dockerbuild ] || mkdir dockerbuild && cd dockerbuild && cmake .. && cmake --build . "]
|
||||
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
Hacking simdjson
|
||||
================
|
||||
|
||||
Here is wisdom about how to build, test and run simdjson from within the repository. *Users* of
|
||||
simdjson should use the released simdjson.h and simdjson.cpp files.
|
||||
|
||||
If you plan to contribute to simdjson, please read our [CONTRIBUTING](https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md) guide.
|
||||
|
||||
Directory Structure and Source
|
||||
------------------------------
|
||||
|
||||
simdjson's source structure, from the top level, looks like this:
|
||||
|
||||
* **CMakeLists.txt:** The main build system.
|
||||
* **include:** User-facing declarations and inline definitions (most user-facing functions are inlined).
|
||||
* simdjson.h: A "master include" that includes files from include/simdjson/. This is equivalent to
|
||||
the distributed simdjson.h.
|
||||
* simdjson/*.h: Declarations for public simdjson classes and functions.
|
||||
* simdjson/inline/*.h: Definitions for public simdjson classes and functions.
|
||||
* **src:** The source files for non-inlined functionality (e.g. the architecture-specific parser
|
||||
implementations).
|
||||
* simdjson.cpp: A "master source" that includes all implementation files from src/. This is
|
||||
equivalent to the distributed simdjson.cpp.
|
||||
* arm64/|fallback/|haswell/|westmere/: Architecture-specific implementations. All functions are
|
||||
Each architecture defines its own namespace, e.g. simdjson::haswell.
|
||||
* generic/: Generic implementations of the simdjson parser. These files may be included and
|
||||
compiled multiple times, from whichever architectures use them. They assume they are already
|
||||
enclosed in a namespace, e.g.:
|
||||
```c++
|
||||
namespace simdjson {
|
||||
namespace haswell {
|
||||
#include "generic/stage1/json_structural_indexer.h"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Other important files and directories:
|
||||
* **.drone.yml:** Definitions for Drone CI.
|
||||
* **.appveyor.yml:** Definitions for Appveyor CI (Windows).
|
||||
* **.circleci:** Definitions for Circle CI.
|
||||
* **amalgamate.sh:** Generates singleheader/simdjson.h and singleheader/simdjson.cpp for release.
|
||||
* **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the
|
||||
cardinal rule is don't regress performance without knowing exactly why, and what you're trading
|
||||
for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnnecessary system calls. We recommend checking the performance as follows:
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . --config Release
|
||||
benchmark/parse ../jsonexamples/twitter.json
|
||||
```
|
||||
The last line becomes `./benchmark/Release/parse.exe ../jsonexample/twitter.json` under Windows. You may also use Google Benchmark:
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . --target bench_parse_call --config Release
|
||||
./benchmark/bench_parse_call
|
||||
```
|
||||
The last line becomes `./benchmark/Release/bench_parse_call.exe` under Windows. Under Windows, you can also build with the clang compiler by adding `-T ClangCL` to the call to `cmake ..`: `cmake .. - TClangCL`.
|
||||
* **fuzz:** The source for fuzz testing. This lets us explore important edge and middle cases
|
||||
* **fuzz:** The source for fuzz testing. This lets us explore important edge and middle cases
|
||||
automatically, and is run in CI.
|
||||
* **jsonchecker:** A set of JSON files used to check different functionality of the parser.
|
||||
* **pass*.json:** Files that should pass validation.
|
||||
* **fail*.json:** Files that should fail validation.
|
||||
* **jsonexamples:** A wide spread of useful, real-world JSON files with different characteristics
|
||||
and sizes.
|
||||
* **singleheader:** Contains generated simdjson.h and simdjson.cpp that we release.
|
||||
* **test:** The tests are here. basictests.cpp and errortests.cpp are the primary ones.
|
||||
* **tools:** Source for executables that can be distributed with simdjson
|
||||
> **Don't modify the files in singleheader/ directly; these are automatically generated.**
|
||||
>
|
||||
> While we distribute those files on release, we *maintain* the files under include/ and src/.
|
||||
|
||||
While simdjson distributes just two files from the singleheader/ directory, we *maintain* the code in
|
||||
multiple files under include/ and src/. include/simdjson.h and src/simdjson.cpp are the "spine" for
|
||||
these, and you can include
|
||||
|
||||
|
||||
|
||||
Runtime Dispatching
|
||||
--------------------
|
||||
|
||||
A key feature of simdjson is the ability to compile different processing kernels, optimized for specific instruction sets, and to select
|
||||
the most appropriate kernel at runtime. This ensures that users get the very best performance while still enabling simdjson to run everywhere.
|
||||
This technique is frequently called runtime dispatching. The simdjson achieves runtime dispatching entirely in C++: we do not assume
|
||||
that the user is building the code using CMake, for example.
|
||||
|
||||
To make runtime dispatching work, it is critical that the code be compiled for the lowest supported processor. In particular, you should
|
||||
not use flags such as -mavx2, /arch:AVX2 and so forth while compiling simdjson. When you do so, you allow the compiler to use advanced
|
||||
instructions. In turn, these advanced instructions present in the code may cause a runtime failure if the runtime processor does not
|
||||
support them. Even a simple loop, compiled with these flags, might generate binary code that only run on advanced processors.
|
||||
|
||||
So we compile simdjson for a generic processor. Our users should do the same if they want simdjson's runtime dispatch to work. It is important
|
||||
to understand that if runtime dispatching does not work, then simdjson will cause crashes on older processors. Of course, if a user chooses
|
||||
to compile their code for a specific instruction set (e.g., AVX2), they are responsible for the failures if they later run their code
|
||||
on a processor that does not support AVX2. Yet, if we were to entice these users to do so, we would share the blame: thus we carefully instruct
|
||||
users to compile their code in a generic way without doing anything to enable advanced instructions.
|
||||
|
||||
|
||||
We only use runtime dispatching on x64 (AMD/Intel) platforms, at the moment. On ARM processors, we would need a standard way to query, at runtime,
|
||||
the processor for its supported features. We do not know how to do so on ARM systems in general. Thankfully it is not yet a concern: 64-bit ARM
|
||||
processors are fairly uniform as far as the instruction sets they support.
|
||||
|
||||
|
||||
In all cases, simdjson uses advanced instructions by relying on "intrinsic functions": we do not write assembly code. The intrinsic functions
|
||||
are special functions that the compiler might recognize and translate into fast code. To make runtime dispatching work, we rely on the fact that
|
||||
the header providing these instructions
|
||||
(intrin.h under Visual Studio, x86intrin.h elsewhere) defines all of the intrinsic functions, including those that are not supported
|
||||
processor.
|
||||
|
||||
At this point, we are require to use one of two main strategies.
|
||||
|
||||
1. On POSIX systems, the main compilers (LLVM clang, GNU gcc) allow us to use any intrinsic function after including the header, but they fail to inline the resulting instruction if the target processor does not support them. Because we compile for a generic processor, we would not be able to use most intrinsic functions. Thankfully, more recent versions of these compilers allow us to flag a region of code with a specific target, so that we can compile only some of the code with support for advanced instructions. Thus in our C++, one might notice macros like `TARGET_HASWELL`. It is then our responsability, at runtime, to only run the regions of code (that we call kernels) matching the properties of the runtime processor. The benefit of this approach is that the compiler not only let us use intrinsic functions, but it can also optimize the rest of the code in the kernel with advanced instructions we enabled.
|
||||
|
||||
2. Under Visual Studio, the problem is somewhat simpler. Visual Studio will not only provide the intrinsic functions, but it will also allow us to use them. They will compile just fine. It is at runtime that they may cause a crash. So we do not need to mark regions of code for compilation toward advanced processors (e.g., with `TARGET_HASWELL` macros). The downside of the Visual Studio approach is that the compiler is not allowed to use advanced instructions others than those we specify. In principle, this means that Visual Studio has weaker optimization opportunities.
|
||||
|
||||
|
||||
|
||||
We also handle the special case where a user is compiling using LLVM clang under Windows, [using the Visual Studio toolchain](https://devblogs.microsoft.com/cppblog/clang-llvm-support-in-visual-studio/). If you compile with LLVM clang under Visual Studio, then the header files (intrin.h or x86intrin.h) no longer provides the intrinsic functions that are unsupported by the processor. This appears to be deliberate on the part of the LLVM engineers. With a few lines of code, we handle this scenario just like LLVM clang under a POSIX system, but forcing the inclusion of the specific headers, and rolling our own intrinsic function as needed.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Regenerating Single Headers From Master
|
||||
---------------------------------------
|
||||
|
||||
simdjson.h and simdjson.cpp are not always up to date in master. To ensure you have the latest copy,
|
||||
you can regenerate them by running this at the top level:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . --target amalgamate
|
||||
```
|
||||
|
||||
The amalgamator is at `amalgamate.sh` at the top level. It generates singleheader/simdjson.h by
|
||||
reading through include/simdjson.h, copy/pasting each header file into the amalgamated file at the
|
||||
point it gets included (but only once per header). singleheader/simdjson.cpp is generated from
|
||||
src/simdjson.cpp the same way, except files under generic/ may be included and copy/pasted multiple
|
||||
times.
|
||||
|
||||
### Usage (CMake on 64-bit platforms like Linux, freeBSD or macOS)
|
||||
|
||||
Requirements: In addition to git, we require a recent version of CMake as well as bash.
|
||||
|
||||
1. On macOS, the easiest way to install cmake might be to use [brew](https://brew.sh) and then type
|
||||
```
|
||||
brew install cmake
|
||||
```
|
||||
2. Under Linux, you might be able to install CMake as follows:
|
||||
```
|
||||
apt-get update -qq
|
||||
apt-get install -y cmake
|
||||
```
|
||||
3. On freeBSD, you might be able to install bash and CMake as follows:
|
||||
```
|
||||
pkg update -f
|
||||
pkg install bash
|
||||
pkg install cmake
|
||||
```
|
||||
|
||||
You need a recent compiler like clang or gcc. We recommend at least GNU GCC/G++ 7 or LLVM clang 6.
|
||||
|
||||
|
||||
Building: While in the project repository, do the following:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
ctest
|
||||
```
|
||||
|
||||
CMake will build a library. By default, it builds a shared library (e.g., libsimdjson.so on Linux).
|
||||
|
||||
You can build a static library:
|
||||
|
||||
```
|
||||
mkdir buildstatic
|
||||
cd buildstatic
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON ..
|
||||
cmake --build .
|
||||
ctest
|
||||
```
|
||||
|
||||
In some cases, you may want to specify your compiler, especially if the default compiler on your system is too old. You need to tell cmake which compiler you wish to use by setting the CC and CXX variables. Under bash, you can do so with commands such as `export CC=gcc-7` and `export CXX=g++-7`. You can also do it as part of the `cmake` command: `cmake .. -DCMAKE_CXX_COMPILER=g++`. You may proceed as follows:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
mkdir build
|
||||
cd build
|
||||
export CXX=g++-8 CC=gcc-8
|
||||
cmake ..
|
||||
cmake --build .
|
||||
ctest
|
||||
```
|
||||
|
||||
If your compiler does not default on C++11 support or better you may get failing tests. If so, you may be able to exclude the failing tests by replacing `ctest` with `ctest -E "^quickstart$"`.
|
||||
|
||||
Note that the name of directory (`build`) is arbitrary, you can name it as you want (e.g., `buildgcc`) and you can have as many different such directories as you would like (one per configuration).
|
||||
|
||||
|
||||
|
||||
### Usage (CMake on 64-bit Windows using Visual Studio)
|
||||
|
||||
We assume you have a common 64-bit Windows PC with at least Visual Studio 2017 and an x64 processor with AVX2 support (2013 Intel Haswell or later) or SSE 4.2 + CLMUL (2010 Westmere or later).
|
||||
|
||||
- Grab the simdjson code from GitHub, e.g., by cloning it using [GitHub Desktop](https://desktop.github.com/).
|
||||
- Install [CMake](https://cmake.org/download/). When you install it, make sure to ask that `cmake` be made available from the command line. Please choose a recent version of cmake.
|
||||
- Create a subdirectory within simdjson, such as `build`.
|
||||
- Using a shell, go to this newly created directory. You can start a shell directly from GitHub Desktop (Repository > Open in Command Prompt).
|
||||
- Type `cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..` in the shell while in the `build` repository. (Alternatively, if you want to build a DLL, you may use the command line `cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF ..`.)
|
||||
- This last command (`cmake ...`) created a Visual Studio solution file in the newly created directory (e.g., `simdjson.sln`). Open this file in Visual Studio. You should now be able to build the project and run the tests. For example, in the `Solution Explorer` window (available from the `View` menu), right-click `ALL_BUILD` and select `Build`. To test the code, still in the `Solution Explorer` window, select `RUN_TESTS` and select `Build`.
|
||||
|
||||
|
||||
Though having Visual Studio installed is necessary, one can build simdjson using only cmake commands:
|
||||
|
||||
- `mkdir build`
|
||||
- `cd build`
|
||||
- `cmake ..`
|
||||
- `cmake --build . -config Release`
|
||||
|
||||
|
||||
Furthermore, if you have installed LLVM clang on Windows, for example as a component of Visual Studio 2019, you can configure and build simdjson using LLVM clang on Windows using cmake:
|
||||
|
||||
|
||||
- `mkdir build`
|
||||
- `cd build`
|
||||
- `cmake .. -T ClangCL`
|
||||
- `cmake --build . -config Release`
|
||||
|
||||
|
||||
### Usage (Using `vcpkg` on 64-bit Windows, Linux and macOS)
|
||||
|
||||
[vcpkg](https://github.com/Microsoft/vcpkg) users on Windows, Linux and macOS can download and install `simdjson` with one single command from their favorite shell.
|
||||
|
||||
On 64-bit Linux and macOS:
|
||||
|
||||
```
|
||||
$ ./vcpkg install simdjson
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
On Windows (64-bit):
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a shared library.
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows-static
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
These commands will also print out instructions on how to use the library from MSBuild or CMake-based projects.
|
||||
|
||||
If you find the version of `simdjson` shipped with `vcpkg` is out-of-date, feel free to report it to
|
||||
`vcpkg` community either by submitting an issue or by creating a PR.
|
||||
|
||||
### Usage (Docker)
|
||||
|
||||
One can run tests and benchmarks using docker. It especially makes sense under Linux. Privileged
|
||||
access may be needed to get performance counters.
|
||||
|
||||
```
|
||||
git clone https://github.com/simdjson/simdjson.git
|
||||
cd simdjson
|
||||
docker build -t simdjson .
|
||||
docker run --privileged -t simdjson
|
||||
```
|
||||
|
||||
## Architecture and Design Notes
|
||||
|
||||
### Requirements
|
||||
|
||||
- 64-bit platforms like Linux or macOS, as well as Windows through Visual Studio 2017 or later.
|
||||
- Any 64-bit processor:
|
||||
- AVX2 (i.e., Intel processors starting with the Haswell microarchitecture released 2013 and AMD
|
||||
processors starting with the Zen microarchitecture released 2017),
|
||||
- SSE 4.2 and CLMUL (i.e., Intel processors going back to Westmere released in 2010 or AMD
|
||||
processors starting with the Jaguar used in the PS4 and XBox One),
|
||||
- 64-bit ARM processor (ARMv8-A NEON): this covers a wide range of mobile processors, including
|
||||
all Apple processors currently available for sale, going as far back as the iPhone 5s (2013).
|
||||
- Any 64-bit processor (simdjson has a fallback generic 64-bit implementation that is still super
|
||||
fast).
|
||||
- A recent C++ compiler (e.g., GNU GCC or LLVM CLANG or Visual Studio 2017), we assume C++17. GNU
|
||||
GCC 7 or better or LLVM's clang 6 or better.
|
||||
- Some benchmark scripts assume bash and other common utilities, but they are optional.
|
||||
|
||||
### Scope
|
||||
|
||||
We provide a fast parser, that fully validates an input according to various specifications.
|
||||
The parser builds a useful immutable (read-only) DOM (document-object model) which can be later accessed.
|
||||
|
||||
To simplify the engineering, we make some assumptions.
|
||||
|
||||
- We support UTF-8 (and thus ASCII), nothing else (no Latin, no UTF-16). We do not believe this is a
|
||||
genuine limitation, because we do not think there is any serious application that needs to process
|
||||
JSON data without an ASCII or UTF-8 encoding. If the UTF-8 contains a leading BOM, it should be
|
||||
omitted: the user is responsible for detecting and skipping the BOM; UTF-8 BOMs are discouraged.
|
||||
- All strings in the JSON document may have up to 4294967295 bytes in UTF-8 (4GB). To enforce this
|
||||
constraint, we refuse to parse a document that contains more than 4294967295 bytes (4GB). This
|
||||
should accommodate most JSON documents.
|
||||
- As allowed by the specification, we allow repeated keys within an object (other parsers like
|
||||
sajson do the same).
|
||||
- [The simdjson library is fast for JSON documents spanning a few bytes up to many megabytes](https://github.com/lemire/simdjson/issues/312).
|
||||
|
||||
_We do not aim to provide a general-purpose JSON library._ A library like RapidJSON offers much more
|
||||
than just parsing, it helps you generate JSON and offers various other convenient functions. We
|
||||
merely parse the document. This may change in the future.
|
||||
|
||||
### Features
|
||||
|
||||
- The input string is unmodified. (Parsers like sajson and RapidJSON use the input string as a buffer.)
|
||||
- We parse integers and floating-point numbers as separate types which allows us to support large signed 64-bit integers in [-9223372036854775808,9223372036854775808), like a Java `long` or a C/C++ `long long` and large unsigned integers up to the value 18446744073709551615. Among the parsers that differentiate between integers and floating-point numbers, not all support 64-bit integers. (For example, sajson rejects JSON files with integers larger than or equal to 2147483648. RapidJSON will parse a file containing an overly long integer like 18446744073709551616 as a floating-point number.) When we cannot represent exactly an integer as a signed or unsigned 64-bit value, we reject the JSON document.
|
||||
- We support the full range of 64-bit floating-point numbers (binary64). The values range from ` std::numeric_limits<double>::lowest()` to `std::numeric_limits<double>::max()`, so from -1.7976e308 all the way to 1.7975e308. Extreme values (less or equal to -1e308, greater or equal to 1e308) are rejected: we refuse to parse the input document.
|
||||
- We test for accurate float parsing with a perfect accuracy (ULP 0). Many parsers offer only approximate floating parsing. For example, RapidJSON also offers the option of accurate float parsing (`kParseFullPrecisionFlag`) but it comes at a significant performance penalty compared to the default settings. By default, RapidJSON tolerates an error of 3 ULP.
|
||||
- We do full UTF-8 validation as part of the parsing. (Parsers like fastjson, gason and dropbox json11 do not do UTF-8 validation. The sajson parser does incomplete UTF-8 validation, accepting code point
|
||||
sequences like 0xb1 0x87.)
|
||||
- We fully validate the numbers. (Parsers like gason and ultranjson will accept `[0e+]` as valid JSON.)
|
||||
- We validate string content for unescaped characters. (Parsers like fastjson and ultrajson accept unescaped line breaks and tabs in strings.)
|
||||
- We fully validate the white-space characters outside of the strings. Parsers like RapidJSON will accept JSON documents with null characters outside of strings.
|
||||
|
||||
### Architecture
|
||||
|
||||
The parser works in two stages:
|
||||
|
||||
- Stage 1. (Find marks) Identifies quickly structure elements, strings, and so forth. We validate UTF-8 encoding at that stage.
|
||||
- Stage 2. (Structure building) Involves constructing a "tree" of sort (materialized as a tape) to navigate through the data. Strings and numbers are parsed at this stage.
|
||||
|
||||
### Remarks on JSON parsing
|
||||
|
||||
- The JSON spec defines what a JSON parser is:
|
||||
> A JSON parser transforms a JSON text into another representation. A JSON parser MUST accept all texts that conform to the JSON grammar. A JSON parser MAY accept non-JSON forms or extensions. An implementation may set limits on the size of texts that it accepts. An implementation may set limits on the maximum depth of nesting. An implementation may set limits on the range and precision of numbers. An implementation may set limits on the length and character contents of strings.
|
||||
|
||||
* JSON is not JavaScript:
|
||||
|
||||
> All JSON is Javascript but NOT all Javascript is JSON. So {property:1} is invalid because property does not have double quotes around it. {'property':1} is also invalid, because it's single quoted while the only thing that can placate the JSON specification is double quoting. JSON is even fussy enough that {"property":.1} is invalid too, because you should have of course written {"property":0.1}. Also, don't even think about having comments or semicolons, you guessed it: they're invalid. (credit:https://github.com/elzr/vim-json)
|
||||
|
||||
* The structural characters are:
|
||||
|
||||
begin-array = [ left square bracket
|
||||
begin-object = { left curly bracket
|
||||
end-array = ] right square bracket
|
||||
end-object = } right curly bracket
|
||||
name-separator = : colon
|
||||
value-separator = , comma
|
||||
|
||||
### Pseudo-structural elements
|
||||
|
||||
A character is pseudo-structural if and only if:
|
||||
|
||||
1. Not enclosed in quotes, AND
|
||||
2. Is a non-whitespace character, AND
|
||||
3. Its preceding character is either:
|
||||
(a) a structural character, OR
|
||||
(b) whitespace.
|
||||
|
||||
This helps as we redefine some new characters as pseudo-structural such as the characters 1, G, n in the following:
|
||||
|
||||
> { "foo" : 1.5, "bar" : 1.5 GEOFF_IS_A_DUMMY bla bla , "baz", null }
|
||||
|
||||
|
||||
|
||||
### UTF-8 validation (lookup2)
|
||||
|
||||
The simdjson library relies on the lookup2 algorithm for UTF-8 validation on x64 platforms.
|
||||
|
||||
This algorithm validate the length of multibyte characters (that each multibyte character has the right number of continuation characters, and that all continuation characters are part of a multibyte character).
|
||||
|
||||
#### Algorithm
|
||||
|
||||
This algorithm compares *expected* continuation characters with *actual* continuation bytes, and emits an error anytime there is a mismatch.
|
||||
|
||||
For example, in the string "𝄞₿֏ab", which has a 4-, 3-, 2- and 1-byte
|
||||
characters, the file will look like this:
|
||||
|
||||
| Character | 𝄞 | | | | ₿ | | | ֏ | | a | b |
|
||||
|-----------------------|----|----|----|----|----|----|----|----|----|----|----|
|
||||
| Character Length | 4 | | | | 3 | | | 2 | | 1 | 1 |
|
||||
| Byte | F0 | 9D | 84 | 9E | E2 | 82 | BF | D6 | 8F | 61 | 62 |
|
||||
| is_second_byte | | X | | | | X | | | X | | |
|
||||
| is_third_byte | | | X | | | | X | | | | |
|
||||
| is_fourth_byte | | | | X | | | | | | | |
|
||||
| expected_continuation | | X | X | X | | X | X | | X | | |
|
||||
| is_continuation | | X | X | X | | X | X | | X | | |
|
||||
|
||||
The errors here are basically (Second Byte OR Third Byte OR Fourth Byte == Continuation):
|
||||
|
||||
- **Extra Continuations:** Any continuation that is not a second, third or fourth byte is not
|
||||
part of a valid 2-, 3- or 4-byte character and is thus an error. It could be that it's just
|
||||
floating around extra outside of any character, or that there is an illegal 5-byte character,
|
||||
or maybe it's at the beginning of the file before any characters have started; but it's an
|
||||
error in all these cases.
|
||||
- **Missing Continuations:** Any second, third or fourth byte that *isn't* a continuation is an error, because that means
|
||||
we started a new character before we were finished with the current one.
|
||||
|
||||
#### Getting the Previous Bytes
|
||||
|
||||
Because we want to know if a byte is the *second* (or third, or fourth) byte of a multibyte
|
||||
character, we need to "shift the bytes" to find that out. This is what they mean:
|
||||
|
||||
- `is_continuation`: if the current byte is a continuation.
|
||||
- `is_second_byte`: if 1 byte back is the start of a 2-, 3- or 4-byte character.
|
||||
- `is_third_byte`: if 2 bytes back is the start of a 3- or 4-byte character.
|
||||
- `is_fourth_byte`: if 3 bytes back is the start of a 4-byte character.
|
||||
|
||||
We use shuffles to go n bytes back, selecting part of the current `input` and part of the
|
||||
`prev_input` (search for `.prev<1>`, `.prev<2>`, etc.). These are passed in by the caller
|
||||
function, because the 1-byte-back data is used by other checks as well.
|
||||
|
||||
#### Getting the Continuation Mask
|
||||
|
||||
Once we have the right bytes, we have to get the masks. To do this, we treat UTF-8 bytes as
|
||||
numbers, using signed `<` and `>` operations to check if they are continuations or leads.
|
||||
In fact, we treat the numbers as *signed*, partly because it helps us, and partly because
|
||||
Intel's SIMD presently only offers signed `<` and `>` operations (not unsigned ones).
|
||||
|
||||
In UTF-8, bytes that start with the bits 110, 1110 and 11110 are 2-, 3- and 4-byte "leads,"
|
||||
respectively, meaning they expect to have 1, 2 and 3 "continuation bytes" after them.
|
||||
Continuation bytes start with 10, and ASCII (1-byte characters) starts with 0.
|
||||
|
||||
When treated as signed numbers, they look like this:
|
||||
|
||||
| Type | High Bits | Binary Range | Signed |
|
||||
|--------------|------------|--------------|--------|
|
||||
| ASCII | `0` | `01111111` | 127 |
|
||||
| | | `00000000` | 0 |
|
||||
| 4+-Byte Lead | `1111` | `11111111` | -1 |
|
||||
| | | `11110000 | -16 |
|
||||
| 3-Byte Lead | `1110` | `11101111` | -17 |
|
||||
| | | `11100000 | -32 |
|
||||
| 2-Byte Lead | `110` | `11011111` | -33 |
|
||||
| | | `11000000 | -64 |
|
||||
| Continuation | `10` | `10111111` | -65 |
|
||||
| | | `10000000 | -128 |
|
||||
|
||||
This makes it pretty easy to get the continuation mask! It's just a single comparison:
|
||||
|
||||
```
|
||||
is_continuation = input < -64`
|
||||
```
|
||||
|
||||
We can do something similar for the others, but it takes two comparisons instead of one: "is
|
||||
the start of a 4-byte character" is `< -32` and `> -65`, for example. And 2+ bytes is `< 0` and
|
||||
`> -64`. Surely we can do better, they're right next to each other!
|
||||
|
||||
#### Getting the is_xxx Masks: Shifting the Range
|
||||
|
||||
Notice *why* continuations were a single comparison. The actual *range* would require two
|
||||
comparisons--`< -64` and `> -129`--but all characters are always greater than -128, so we get
|
||||
that for free. In fact, if we had *unsigned* comparisons, 2+, 3+ and 4+ comparisons would be
|
||||
just as easy: 4+ would be `> 239`, 3+ would be `> 223`, and 2+ would be `> 191`.
|
||||
|
||||
Instead, we add 128 to each byte, shifting the range up to make comparison easy. This wraps
|
||||
ASCII down into the negative, and puts 4+-Byte Lead at the top:
|
||||
|
||||
| Type | High Bits | Binary Range | Signed |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 4+-Byte Lead (+ 127) | `0111` | `01111111` | 127 |
|
||||
| | | `01110000 | 112 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 3-Byte Lead (+ 127) | `0110` | `01101111` | 111 |
|
||||
| | | `01100000 | 96 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| 2-Byte Lead (+ 127) | `010` | `01011111` | 95 |
|
||||
| | | `01000000 | 64 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| Continuation (+ 127) | `00` | `00111111` | 63 |
|
||||
| | | `00000000 | 0 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
| ASCII (+ 127) | `1` | `11111111` | -1 |
|
||||
| | | `10000000` | -128 |
|
||||
|----------------------|------------|--------------|-------|
|
||||
|
||||
*Now* we can use signed `>` on all of them:
|
||||
|
||||
```
|
||||
prev1 = input.prev<1>
|
||||
prev2 = input.prev<2>
|
||||
prev3 = input.prev<3>
|
||||
prev1_flipped = input.prev<1>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
prev2_flipped = input.prev<2>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
prev3_flipped = input.prev<3>(prev_input) ^ 0x80; // Same as `+ 128`
|
||||
is_second_byte = prev1_flipped > 63;2+-byte lead
|
||||
is_third_byte = prev2_flipped > 95;3+-byte lead
|
||||
is_fourth_byte = prev3_flipped > 111; // 4+-byte lead
|
||||
```
|
||||
|
||||
NOTE: we use `^ 0x80` instead of `+ 128` in the code, which accomplishes the same thing, and even takes the same number
|
||||
of cycles as `+`, but on many Intel architectures can be parallelized better (you can do 3
|
||||
`^`'s at a time on Haswell, but only 2 `+`'s).
|
||||
|
||||
That doesn't look like it saved us any instructions, did it? Well, because we're adding the
|
||||
same number to all of them, we can save one of those `+ 128` operations by assembling
|
||||
`prev2_flipped` out of prev 1 and prev 3 instead of assembling it from input and adding 128
|
||||
to it. One more instruction saved!
|
||||
|
||||
```
|
||||
prev1 = input.prev<1>
|
||||
prev3 = input.prev<3>
|
||||
prev1_flipped = prev1 ^ 0x80; // Same as `+ 128`
|
||||
prev3_flipped = prev3 ^ 0x80; // Same as `+ 128`
|
||||
prev2_flipped = prev1_flipped.concat<2>(prev3_flipped): // <shuffle: take the first 2 bytes from prev1 and the rest from prev3
|
||||
```
|
||||
|
||||
#### Bringing It All Together: Detecting the Errors
|
||||
|
||||
At this point, we have `is_continuation`, `is_first_byte`, `is_second_byte` and `is_third_byte`.
|
||||
All we have left to do is check if they match!
|
||||
|
||||
```
|
||||
return (is_second_byte | is_third_byte | is_fourth_byte) ^ is_continuation;
|
||||
```
|
||||
|
||||
But wait--there's more. The above statement is only 3 operations, but they *cannot be done in
|
||||
parallel*. You have to do 2 `|`'s and then 1 `&`. Haswell, at least, has 3 ports that can do
|
||||
bitwise operations, and we're only using 1!
|
||||
|
||||
#### Epilogue: Addition For Booleans
|
||||
|
||||
There is one big case the above code doesn't explicitly talk about--what if is_second_byte
|
||||
and is_third_byte are BOTH true? That means there is a 3-byte and 2-byte character right next
|
||||
to each other (or any combination), and the continuation could be part of either of them!
|
||||
Our algorithm using `&` and `|` won't detect that the continuation byte is problematic.
|
||||
|
||||
Never fear, though. If that situation occurs, we'll already have detected that the second
|
||||
leading byte was an error, because it was supposed to be a part of the preceding multibyte
|
||||
character, but it *wasn't a continuation*.
|
||||
|
||||
We could stop here, but it turns out that we can fix it using `+` and `-` instead of `|` and
|
||||
`&`, which is both interesting and possibly useful (even though we're not using it here). It
|
||||
exploits the fact that in SIMD, a *true* value is -1, and a *false* value is 0. So those
|
||||
comparisons were giving us numbers!
|
||||
|
||||
Given that, if you do `is_second_byte + is_third_byte + is_fourth_byte`, under normal
|
||||
circumstances you will either get 0 (0 + 0 + 0) or -1 (-1 + 0 + 0, etc.). Thus,
|
||||
`(is_second_byte + is_third_byte + is_fourth_byte) - is_continuation` will yield 0 only if
|
||||
*both* or *neither* are 0 (0-0 or -1 - -1). You'll get 1 or -1 if they are different. Because
|
||||
*any* nonzero value is treated as an error (not just -1), we're just fine here :)
|
||||
|
||||
Further, if *more than one* multibyte character overlaps,
|
||||
`is_second_byte + is_third_byte + is_fourth_byte` will be -2 or -3! Subtracting `is_continuation`
|
||||
from *that* is guaranteed to give you a nonzero value (-1, -2 or -3). So it'll always be
|
||||
considered an error.
|
||||
|
||||
One reason you might want to do this is parallelism. ^ and | are not associative, so
|
||||
(A | B | C) ^ D will always be three operations in a row: either you do A | B -> | C -> ^ D, or
|
||||
you do B | C -> | A -> ^ D. But addition and subtraction *are* associative: (A + B + C) - D can
|
||||
be written as `(A + B) + (C - D)`. This means you can do A + B and C - D at the same time, and
|
||||
then adds the result together. Same number of operations, but if the processor can run
|
||||
independent things in parallel (which most can), it runs faster.
|
||||
|
||||
This doesn't help us on Intel, but might help us elsewhere: on Haswell, at least, | and ^ have
|
||||
a super nice advantage in that more of them can be run at the same time (they can run on 3
|
||||
ports, while + and - can run on 2)! This means that we can do A | B while we're still doing C,
|
||||
saving us the cycle we would have earned by using +. Even more, using an instruction with a
|
||||
wider array of ports can help *other* code run ahead, too, since these instructions can "get
|
||||
out of the way," running on a port other instructions can't.
|
||||
|
||||
#### Epilogue II: One More Trick
|
||||
|
||||
There's one more relevant trick up our sleeve, it turns out: it turns out on Intel we can "pay
|
||||
for" the (prev<1> + 128) instruction, because it can be used to save an instruction in
|
||||
check_special_cases()--but we'll talk about that there :)
|
||||
|
||||
|
||||
|
||||
|
||||
## About the Project
|
||||
|
||||
### Bindings and Ports of simdjson
|
||||
|
||||
We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything).
|
||||
|
||||
|
||||
- [ZippyJSON](https://github.com/michaeleisel/zippyjson): Swift bindings for the simdjson project.
|
||||
- [pysimdjson](https://github.com/TkTech/pysimdjson): Python bindings for the simdjson project.
|
||||
- [simdjson-rs](https://github.com/Licenser/simdjson-rs): Rust port.
|
||||
- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings).
|
||||
- [SimdJsonSharp](https://github.com/EgorBo/SimdJsonSharp): C# version for .NET Core (bindings and full port).
|
||||
- [simdjson_nodejs](https://github.com/luizperes/simdjson_nodejs): Node.js bindings for the simdjson project.
|
||||
- [simdjson_php](https://github.com/crazyxman/simdjson_php): PHP bindings for the simdjson project.
|
||||
- [simdjson_ruby](https://github.com/saka1/simdjson_ruby): Ruby bindings for the simdjson project.
|
||||
- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly.
|
||||
- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings.
|
||||
|
||||
### Tools
|
||||
|
||||
- `json2json mydoc.json` parses the document, constructs a model and then dumps back the result to standard output.
|
||||
- `json2json -d mydoc.json` parses the document, constructs a model and then dumps model (as a tape) to standard output. The tape format is described in the accompanying file `tape.md`.
|
||||
- `minify mydoc.json` minifies the JSON document, outputting the result to standard output. Minifying means to remove the unneeded white space characters.
|
||||
- `jsonpointer mydoc.json <jsonpath> <jsonpath> ... <jsonpath>` parses the document, constructs a model and then processes a series of [JSON Pointer paths](https://tools.ietf.org/html/rfc6901). The result is itself a JSON document.
|
||||
|
||||
|
||||
### Various References
|
||||
|
||||
- [Google double-conv](https://github.com/google/double-conversion/)
|
||||
- [How to implement atoi using SIMD?](https://stackoverflow.com/questions/35127060/how-to-implement-atoi-using-simd)
|
||||
- [Parsing JSON is a Minefield 💣](http://seriot.ch/parsing_json.php)
|
||||
- https://tools.ietf.org/html/rfc7159
|
||||
- The Mison implementation in rust https://github.com/pikkr/pikkr
|
||||
- http://rapidjson.org/md_doc_sax.html
|
||||
- https://github.com/Geal/parser_benchmarks/tree/master/json
|
||||
- Gron: A command line tool that makes JSON greppable https://news.ycombinator.com/item?id=16727665
|
||||
- GoogleGson https://github.com/google/gson
|
||||
- Jackson https://github.com/FasterXML/jackson
|
||||
- https://www.yelp.com/dataset_challenge
|
||||
- RapidJSON. http://rapidjson.org/
|
||||
|
||||
Inspiring links:
|
||||
|
||||
- https://auth0.com/blog/beating-json-performance-with-protobuf/
|
||||
- https://gist.github.com/shijuvar/25ad7de9505232c87034b8359543404a
|
||||
- https://github.com/frankmcsherry/blog/blob/master/posts/2018-02-11.md
|
||||
|
||||
Validating UTF-8 takes no more than 0.7 cycles per byte:
|
||||
|
||||
- https://github.com/lemire/fastvalidate-utf-8 https://lemire.me/blog/2018/05/16/validating-utf-8-strings-using-as-little-as-0-7-cycles-per-byte/
|
||||
|
||||
### Academic References
|
||||
|
||||
- T.Mühlbauer, W.Rödiger, R.Seilbeck, A.Reiser, A.Kemper, and T.Neumann. Instant loading for main memory databases. PVLDB, 6(14):1702–1713, 2013. (SIMD-based CSV parsing)
|
||||
- Mytkowicz, Todd, Madanlal Musuvathi, and Wolfram Schulte. "Data-parallel finite-state machines." ACM SIGARCH Computer Architecture News. Vol. 42. No. 1. ACM, 2014.
|
||||
- Lu, Yifan, et al. "Tree structured data processing on GPUs." Cloud Computing, Data Science & Engineering-Confluence, 2017 7th International Conference on. IEEE, 2017.
|
||||
- Sidhu, Reetinder. "High throughput, tree automata based XML processing using FPGAs." Field-Programmable Technology (FPT), 2013 International Conference on. IEEE, 2013.
|
||||
- Dai, Zefu, Nick Ni, and Jianwen Zhu. "A 1 cycle-per-byte XML parsing accelerator." Proceedings of the 18th annual ACM/SIGDA international symposium on Field programmable gate arrays. ACM, 2010.
|
||||
- Lin, Dan, et al. "Parabix: Boosting the efficiency of text processing on commodity processors." High Performance Computer Architecture (HPCA), 2012 IEEE 18th International Symposium on. IEEE, 2012. http://parabix.costar.sfu.ca/export/1783/docs/HPCA2012/final_ieee/final.pdf
|
||||
- Deshmukh, V. M., and G. R. Bamnote. "An empirical evaluation of optimization parameters in XML parsing for performance enhancement." Computer, Communication and Control (IC4), 2015 International Conference on. IEEE, 2015.
|
||||
- Moussalli, Roger, et al. "Efficient XML Path Filtering Using GPUs." ADMS@ VLDB. 2011.
|
||||
- Jianliang, Ma, et al. "Parallel speculative dom-based XML parser." High Performance Computing and Communication & 2012 IEEE 9th International Conference on Embedded Software and Systems (HPCC-ICESS), 2012 IEEE 14th International Conference on. IEEE, 2012.
|
||||
- Li, Y., Katsipoulakis, N.R., Chandramouli, B., Goldstein, J. and Kossmann, D., 2017. Mison: a fast JSON parser for data analytics. Proceedings of the VLDB Endowment, 10(10), pp.1118-1129. http://www.vldb.org/pvldb/vol10/p1118-li.pdf
|
||||
- Cameron, Robert D., et al. "Parallel scanning with bitstream addition: An xml case study." European Conference on Parallel Processing. Springer, Berlin, Heidelberg, 2011.
|
||||
- Cameron, Robert D., Kenneth S. Herdy, and Dan Lin. "High performance XML parsing using parallel bit stream technology." Proceedings of the 2008 conference of the center for advanced studies on collaborative research: meeting of minds. ACM, 2008.
|
||||
- Shah, Bhavik, et al. "A data parallel algorithm for XML DOM parsing." International XML Database Symposium. Springer, Berlin, Heidelberg, 2009.
|
||||
- Cameron, Robert D., and Dan Lin. "Architectural support for SWAR text processing with parallel bit streams: the inductive doubling principle." ACM Sigplan Notices. Vol. 44. No. 3. ACM, 2009.
|
||||
- Amagasa, Toshiyuki, Mana Seino, and Hiroyuki Kitagawa. "Energy-Efficient XML Stream Processing through Element-Skipping Parsing." Database and Expert Systems Applications (DEXA), 2013 24th International Workshop on. IEEE, 2013.
|
||||
- Medforth, Nigel Woodland. "icXML: Accelerating Xerces-C 3.1. 1 using the Parabix Framework." (2013).
|
||||
- Zhang, Qiang Scott. Embedding Parallel Bit Stream Technology Into Expat. Diss. Simon Fraser University, 2010.
|
||||
- Cameron, Robert D., et al. "Fast Regular Expression Matching with Bit-parallel Data Streams."
|
||||
- Lin, Dan. Bits filter: a high-performance multiple string pattern matching algorithm for malware detection. Diss. School of Computing Science-Simon Fraser University, 2010.
|
||||
- Yang, Shiyang. Validation of XML Document Based on Parallel Bit Stream Technology. Diss. Applied Sciences: School of Computing Science, 2013.
|
||||
- N. Nakasato, "Implementation of a parallel tree method on a GPU", Journal of Computational Science, vol. 3, no. 3, pp. 132-141, 2012.
|
||||
@@ -1,203 +0,0 @@
|
||||
|
||||
.SUFFIXES:
|
||||
#
|
||||
.SUFFIXES: .cpp .o .c .h
|
||||
|
||||
|
||||
.PHONY: clean cleandist
|
||||
COREDEPSINCLUDE = -Idependencies/json/single_include -Idependencies/rapidjson/include -Idependencies/sajson/include -Idependencies/cJSON -Idependencies/jsmn
|
||||
EXTRADEPSINCLUDE = -Idependencies/jsoncppdist -Idependencies/json11 -Idependencies/fastjson/src -Idependencies/fastjson/include -Idependencies/gason/src -Idependencies/ujson4c/3rdparty -Idependencies/ujson4c/src
|
||||
# users can provide their own additional flags with make EXTRAFLAGS=something
|
||||
architecture:=$(shell arch)
|
||||
|
||||
####
|
||||
# If you want to specify your own target architecture,
|
||||
# then define ARCHFLAGS. Otherwise, we set good default.
|
||||
# E.g., type ' ARCHFLAGS="-march=nehalem" make parse '
|
||||
###
|
||||
ifeq ($(architecture),aarch64)
|
||||
ARCHFLAGS ?= -march=armv8-a+crc+crypto
|
||||
else
|
||||
ARCHFLAGS ?= -msse4.2 -mpclmul # lowest supported feature set?
|
||||
endif
|
||||
|
||||
CXXFLAGS = $(ARCHFLAGS) -std=c++17 -Wall -Wextra -Wshadow -Iinclude -Ibenchmark/linux $(EXTRAFLAGS)
|
||||
CFLAGS = $(ARCHFLAGS) -Idependencies/ujson4c/3rdparty -Idependencies/ujson4c/src $(EXTRAFLAGS)
|
||||
|
||||
|
||||
# This is a convenience flag
|
||||
ifdef SANITIZEGOLD
|
||||
SANITIZE = 1
|
||||
LINKER = gold
|
||||
endif
|
||||
|
||||
ifdef LINKER
|
||||
CXXFLAGS += -fuse-ld=$(LINKER)
|
||||
CFLAGS += -fuse-ld=$(LINKER)
|
||||
endif
|
||||
|
||||
|
||||
# SANITIZE *implies* DEBUG
|
||||
ifeq ($(MEMSANITIZE),1)
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=memory -fno-omit-frame-pointer -fsanitize=undefined
|
||||
else
|
||||
ifeq ($(SANITIZE),1)
|
||||
CXXFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
CFLAGS += -g3 -O0 -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined
|
||||
else
|
||||
ifeq ($(DEBUG),1)
|
||||
CXXFLAGS += -g3 -O0
|
||||
CFLAGS += -g3 -O0
|
||||
else
|
||||
# we opt for -O3 for regular builds
|
||||
CXXFLAGS += -O3
|
||||
CFLAGS += -O3
|
||||
endif # ifeq ($(DEBUG),1)
|
||||
endif # ifeq ($(SANITIZE),1)
|
||||
endif # ifeq ($(MEMSANITIZE),1)
|
||||
|
||||
MAINEXECUTABLES=parse minify json2json jsonstats statisticalmodel jsonpointer
|
||||
TESTEXECUTABLES=jsoncheck numberparsingcheck stringparsingcheck pointercheck
|
||||
COMPARISONEXECUTABLES=minifiercompetition parsingcompetition parseandstatcompetition distinctuseridcompetition allparserscheckfile allparsingcompetition
|
||||
SUPPLEMENTARYEXECUTABLES=parse_noutf8validation parse_nonumberparsing parse_nostringparsing
|
||||
|
||||
HEADERS= include/simdjson/simdutf8check_haswell.h include/simdjson/simdutf8check_westmere.h include/simdjson/simdutf8check_arm64.h include/simdjson/stringparsing.h include/simdjson/stringparsing_arm64.h include/simdjson/stringparsing_haswell.h include/simdjson/stringparsing_macros.h include/simdjson/stringparsing_westmere.h include/simdjson/numberparsing.h include/simdjson/jsonparser.h include/simdjson/common_defs.h include/simdjson/jsonioutil.h benchmark/benchmark.h benchmark/linux/linux-perf-events.h include/simdjson/parsedjson.h include/simdjson/stage1_find_marks.h include/simdjson/stage1_find_marks_arm64.h include/simdjson/stage1_find_marks_haswell.h include/simdjson/stage1_find_marks_westmere.h include/simdjson/stage1_find_marks_macros.h include/simdjson/stage2_build_tape.h include/simdjson/jsoncharutils.h include/simdjson/jsonformatutils.h include/simdjson/stage1_find_marks_flatten.h include/simdjson/stage1_find_marks_flatten_haswell.h
|
||||
LIBFILES=src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/stage2_build_tape.cpp src/parsedjson.cpp src/parsedjsoniterator.cpp
|
||||
MINIFIERHEADERS=include/simdjson/jsonminifier.h include/simdjson/simdprune_tables.h
|
||||
MINIFIERLIBFILES=src/jsonminifier.cpp
|
||||
|
||||
|
||||
RAPIDJSON_INCLUDE:=dependencies/rapidjson/include
|
||||
SAJSON_INCLUDE:=dependencies/sajson/include
|
||||
JSON11_INCLUDE:=dependencies/json11/json11.hpp
|
||||
FASTJSON_INCLUDE:=dependencies/include/fastjson/fastjson.h
|
||||
GASON_INCLUDE:=dependencies/gason/src/gason.h
|
||||
UJSON4C_INCLUDE:=dependencies/ujson4c/src/ujdecode.c
|
||||
CJSON_INCLUDE:=dependencies/cJSON/cJSON.h
|
||||
JSMN_INCLUDE:=dependencies/jsmn/jsmn.h
|
||||
JSON_INCLUDE:=dependencies/json/single_include/nlohmann/json.hpp
|
||||
|
||||
LIBS=$(RAPIDJSON_INCLUDE) $(JSON_INCLUDE) $(SAJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE)
|
||||
|
||||
EXTRAOBJECTS=ujdecode.o
|
||||
all: $(MAINEXECUTABLES)
|
||||
|
||||
competition: $(COMPARISONEXECUTABLES)
|
||||
|
||||
.PHONY: benchmark test
|
||||
|
||||
benchmark:
|
||||
bash ./scripts/parser.sh
|
||||
bash ./scripts/parseandstat.sh
|
||||
|
||||
test: jsoncheck numberparsingcheck stringparsingcheck basictests allparserscheckfile minify json2json pointercheck
|
||||
./basictests
|
||||
./numberparsingcheck
|
||||
./stringparsingcheck
|
||||
./jsoncheck
|
||||
./pointercheck
|
||||
./scripts/testjson2json.sh
|
||||
./scripts/issue150.sh
|
||||
@echo "It looks like the code is good!"
|
||||
|
||||
quiettest: jsoncheck numberparsingcheck stringparsingcheck basictests allparserscheckfile minify json2json pointercheck
|
||||
./basictests
|
||||
./numberparsingcheck
|
||||
./stringparsingcheck
|
||||
./jsoncheck
|
||||
./pointercheck
|
||||
./scripts/testjson2json.sh
|
||||
./scripts/issue150.sh
|
||||
|
||||
amalgamate:
|
||||
./amalgamation.sh
|
||||
$(CXX) $(CXXFLAGS) -o singleheader/demo ./singleheader/amalgamation_demo.cpp -Isingleheader
|
||||
|
||||
submodules:
|
||||
-git submodule update --init --recursive
|
||||
-touch submodules
|
||||
|
||||
$(JSON_INCLUDE) $(SAJSON_INCLUDE) $(RAPIDJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE) : submodules
|
||||
|
||||
parse: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
|
||||
statisticalmodel: benchmark/statisticalmodel.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o statisticalmodel $(LIBFILES) benchmark/statisticalmodel.cpp $(LIBFLAGS)
|
||||
|
||||
|
||||
parse_noutf8validation: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_noutf8validation -DSIMDJSON_SKIPUTF8VALIDATION $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
|
||||
parse_nonumberparsing: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nonumberparsing -DSIMDJSON_SKIPNUMBERPARSING $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
|
||||
parse_nostringparsing: benchmark/parse.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o parse_nostringparsing -DSIMDJSON_SKIPSTRINGPARSING $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS)
|
||||
|
||||
|
||||
jsoncheck:tests/jsoncheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsoncheck $(LIBFILES) tests/jsoncheck.cpp -I. $(LIBFLAGS)
|
||||
|
||||
basictests:tests/basictests.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o basictests $(LIBFILES) tests/basictests.cpp -I. $(LIBFLAGS)
|
||||
|
||||
|
||||
numberparsingcheck:tests/numberparsingcheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o numberparsingcheck tests/numberparsingcheck.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp -I. $(LIBFLAGS) -DJSON_TEST_NUMBERS
|
||||
|
||||
|
||||
stringparsingcheck:tests/stringparsingcheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o stringparsingcheck tests/stringparsingcheck.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp -I. $(LIBFLAGS) -DJSON_TEST_STRINGS
|
||||
|
||||
pointercheck:tests/pointercheck.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o pointercheck tests/pointercheck.cpp src/stage2_build_tape.cpp src/jsonioutil.cpp src/jsonparser.cpp src/simdjson.cpp src/stage1_find_marks.cpp src/parsedjson.cpp src/parsedjsoniterator.cpp -I. $(LIBFLAGS)
|
||||
|
||||
minifiercompetition: benchmark/minifiercompetition.cpp $(HEADERS) submodules $(MINIFIERHEADERS) $(LIBFILES) $(MINIFIERLIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minifiercompetition $(LIBFILES) $(MINIFIERLIBFILES) benchmark/minifiercompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
minify: tools/minify.cpp $(HEADERS) $(MINIFIERHEADERS) $(LIBFILES) $(MINIFIERLIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o minify $(MINIFIERLIBFILES) $(LIBFILES) tools/minify.cpp -I.
|
||||
|
||||
json2json: tools/json2json.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o json2json $ tools/json2json.cpp $(LIBFILES) -I.
|
||||
|
||||
jsonpointer: tools/jsonpointer.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsonpointer $ tools/jsonpointer.cpp $(LIBFILES) -I.
|
||||
|
||||
jsonstats: tools/jsonstats.cpp $(HEADERS) $(LIBFILES)
|
||||
$(CXX) $(CXXFLAGS) -o jsonstats $ tools/jsonstats.cpp $(LIBFILES) -I.
|
||||
|
||||
ujdecode.o: $(UJSON4C_INCLUDE)
|
||||
$(CC) $(CFLAGS) -c dependencies/ujson4c/src/ujdecode.c
|
||||
|
||||
parseandstatcompetition: benchmark/parseandstatcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
$(CXX) $(CXXFLAGS) -o parseandstatcompetition $(LIBFILES) benchmark/parseandstatcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
distinctuseridcompetition: benchmark/distinctuseridcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
$(CXX) $(CXXFLAGS) -o distinctuseridcompetition $(LIBFILES) benchmark/distinctuseridcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
parsingcompetition: benchmark/parsingcompetition.cpp $(HEADERS) $(LIBFILES) submodules
|
||||
@echo "In case of build error due to missing files, try 'make clean'"
|
||||
$(CXX) $(CXXFLAGS) -o parsingcompetition $(LIBFILES) benchmark/parsingcompetition.cpp -I. $(LIBFLAGS) $(COREDEPSINCLUDE)
|
||||
|
||||
allparsingcompetition: benchmark/parsingcompetition.cpp $(HEADERS) $(LIBFILES) $(EXTRAOBJECTS) submodules
|
||||
$(CXX) $(CXXFLAGS) -o allparsingcompetition $(LIBFILES) benchmark/parsingcompetition.cpp $(EXTRAOBJECTS) -I. $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE) -DALLPARSER
|
||||
|
||||
|
||||
allparserscheckfile: tests/allparserscheckfile.cpp $(HEADERS) $(LIBFILES) $(EXTRAOBJECTS) submodules
|
||||
$(CXX) $(CXXFLAGS) -o allparserscheckfile $(LIBFILES) tests/allparserscheckfile.cpp $(EXTRAOBJECTS) -I. $(LIBFLAGS) $(COREDEPSINCLUDE) $(EXTRADEPSINCLUDE)
|
||||
|
||||
.PHONY: clean cppcheck cleandist
|
||||
|
||||
cppcheck:
|
||||
cppcheck --enable=all src/*.cpp benchmarks/*.cpp tests/*.cpp -Iinclude -I. -Ibenchmark/linux
|
||||
|
||||
everything: $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
|
||||
clean:
|
||||
rm -f submodules $(EXTRAOBJECTS) $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
|
||||
cleandist:
|
||||
rm -f submodules $(EXTRAOBJECTS) $(MAINEXECUTABLES) $(EXTRA_EXECUTABLES) $(TESTEXECUTABLES) $(COMPARISONEXECUTABLES) $(SUPPLEMENTARYEXECUTABLES)
|
||||
@@ -1,85 +0,0 @@
|
||||
# Notes on simdjson
|
||||
|
||||
## Rationale:
|
||||
|
||||
The simdjson project serves two purposes:
|
||||
|
||||
1. It creates a useful library for parsing JSON data quickly.
|
||||
|
||||
2. It is a demonstration of the use of SIMD and pipelined programming techniques to perform a complex and irregular task.
|
||||
These techniques include the use of large registers and SIMD instructions to process large amounts of input data at once,
|
||||
to hold larger entities than can typically be held in a single General Purpose Register (GPR), and to perform operations
|
||||
that are not cheap to perform without use of a SIMD unit (for example table lookup using permute instructions).
|
||||
|
||||
The other key technique is that the system is designed to minimize the number of unpredictable branches that must be taken
|
||||
to perform the task. Modern architectures are both wide and deep (4-wide pipelines with ~14 stages are commonplace). A
|
||||
recent Intel Architecture processor, for example, can perform 3 256-bit SIMD operations or 2 512-bit SIMD operations per
|
||||
cycle as well as other operations on general purpose registers or with the load/store unit. An incorrectly predicted branch
|
||||
will clear this pipeline. While it is rare that a programmer can achieve the maximum throughput on a machine, a developer
|
||||
may be missing the opportunity to carry out 56 operations for each branch miss.
|
||||
|
||||
Many code-bases make use of SIMD and deeply pipelined, "non-branchy", processing for regular tasks. Numerical problems
|
||||
(e.g. "matrix multiply") or simple 'bulk search' tasks (e.g. "count all the occurrences of a given character in a text",
|
||||
"find the first occurrence of the string 'foo' in a text") frequently use this class of techniques. We are demonstrating
|
||||
that these techniques can be applied to much more complex and less regular tasks.
|
||||
|
||||
## Design:
|
||||
|
||||
### Stage 1: SIMD over bytes; bit vector processing over bytes.
|
||||
|
||||
The first stage of our processing must identify key points in our input: the 'structural characters' of JSON (curly and
|
||||
square braces, colon, and comma), the start and end of strings as delineated by double quote characters, other JSON 'atoms'
|
||||
that are not distinguishable by simple characters (constructs such as "true", "false", "null" and numbers), as well as
|
||||
discovering these characters and atoms in the presence of both quoting conventions and backslash escaping conventions.
|
||||
|
||||
As such we follow the broad outline of the construction of a structural index as set forth in the Mison paper [XXX]; first,
|
||||
the discovery of odd-length sequences of backslash characters (which will cause quote characters immediately following to
|
||||
be escaped and not serve their quoting role but instead be literal charaters), second, the discovery of quote pairs (which
|
||||
cause structural characters within the quote pairs to also be merely literal characters and have no function as structural
|
||||
characters), then finally the discovery of structural characters not contained without the quote pairs.
|
||||
|
||||
We depart from the Mison paper in terms of method and overall design. In terms of method, the Mison paper uses iteration
|
||||
over bit vectors to discover backslash sequences and quote pairs; we introduce branch-free techniques to discover both of
|
||||
these properties.
|
||||
|
||||
We also make use of our ability to quickly detect whitespace in this early stage. We can use another bit-vector based
|
||||
transformation to discover locations in our data that follow a structural character or quote or whitespace and are not whitespace. Excluding locations within strings, and the structural characters we have already discovered,
|
||||
these locations are the only place that we can expect to see the starts of the JSON 'atoms'. These locations are thus
|
||||
treated as 'structural' ('pseudo-structural characters').
|
||||
|
||||
This stage involves either SIMD processing over bytes or the manipulation of bit arrays that have 1 bit corresponding
|
||||
to 1 byte of input. As such, it can be quite inefficient for some inputs - it is possible to observe dozens of operations
|
||||
taking place to discover that there are in fact no odd-numbered sequences of backslashes or quotes in a given block of
|
||||
input. However, this inefficiency on such inputs is balanced by the fact that it costs no more to run this code over
|
||||
complex structured input, and the alternatives would generally involve running a number of unpredictable branches (for
|
||||
example, the loop branches in Mison that iterate over bit vectors).
|
||||
|
||||
### Stage 2: The transition from "SIMD over bytes" to "indices"
|
||||
|
||||
Our structural, pseudo-structural and other 'interesting' characters are relatively rare (TODO: quantify in detail -
|
||||
it's typically about 1 in 10). As such, continuing to process them as bit vectors will involve manipulating data structures
|
||||
that are relatively large as well as being fairly unpredictably spaced. We must transform these bitvectors of "interesting"
|
||||
locations into offsets.
|
||||
|
||||
Note that we can examine the character at the offset to discover what the original function of the item in the bitvector
|
||||
was. While the JSON structural characters and quotes are relatively self-explanatory (although working only with one offset
|
||||
at a time, we have lost the distinction between opening quotes and closing quotes, something that was available in Stage 1),
|
||||
it is a quirk of JSON that the legal atoms can all be distinguished from each other by their first character - 't' for
|
||||
'true', 'f' for 'false', 'n' for 'null' and the character class [0-9-] for numerical values.
|
||||
|
||||
Thus, the offset suffices, as long as we retain our original input.
|
||||
|
||||
Our current implementation involves a straightforward transformation of bitmaps to indices by use of the 'count trailing
|
||||
zeros' operation and the well-known operation to clear the lowest set bit. Note that this implementation introduces an
|
||||
unpredictable branch; unless there is a regular pattern in our bitmaps, we would expect to have at least one branch miss
|
||||
for each bitmap.
|
||||
|
||||
### Stage 3: Operation over indices
|
||||
|
||||
This now works over a dual structure.
|
||||
|
||||
1. The "state machine", whose role it is to validate the sequence of structural characters and ensure that the input is at least generally structured like valid JSON (after this stage, the only errors permissible should be malformed atoms and numbers). If and only if the "state machine" reached all accept states, then,
|
||||
|
||||
2. The "tape machine" will have produced valid output. The tape machine works blindly over characters writing records to tapes. These records create a lean but somewhat traversable linked structure that, for valid inputs, should represent what we need to know about the JSON input.
|
||||
|
||||
FIXME: a lot more detail is required on the operation of both these machines.
|
||||
@@ -1,581 +1,190 @@
|
||||
# simdjson : Parsing gigabytes of JSON per second
|
||||
[](https://cloud.drone.io/lemire/simdjson/)
|
||||
[](https://circleci.com/gh/lemire/simdjson)
|
||||
[](https://ci.appveyor.com/project/lemire/simdjson)
|
||||
[![][license img]][license]
|
||||
[](https://lgtm.com/projects/g/lemire/simdjson/context:cpp)
|
||||
[](https://cloud.drone.io/simdjson/simdjson)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Asimdjson&can=2)
|
||||
[](https://ci.appveyor.com/project/lemire/simdjson-jmmti/branch/master)
|
||||
[](https://circleci.com/gh/simdjson/simdjson)
|
||||
[](https://cirrus-ci.com/github/simdjson/simdjson)
|
||||

|
||||
[![][license img]][license] [](https://simdjson.org/api/0.4.0/index.html)
|
||||
|
||||
simdjson : Parsing gigabytes of JSON per second
|
||||
===============================================
|
||||
|
||||
## A C++ library to see how fast we can parse JSON with complete validation.
|
||||
<img src="images/logo.png" width="10%" style="float: right">
|
||||
JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh
|
||||
approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms
|
||||
to parse JSON 2.5x faster than anything else out there.
|
||||
|
||||
JSON documents are everywhere on the Internet. Servers spend a lot of time parsing these documents. We want to accelerate the parsing of JSON per se using commonly available SIMD instructions as much as possible while doing full validation (including character encoding).
|
||||
* **Fast:** Over 2.5x faster than other production-grade JSON parsers.
|
||||
* **Easy:** First-class, easy to use API.
|
||||
* **Strict:** Full JSON and UTF-8 validation, lossless parsing. Performance with no compromises.
|
||||
* **Automatic:** Selects a CPU-tailored parser at runtime. No configuration needed.
|
||||
* **Reliable:** From memory allocation to error handling, simdjson's design avoids surprises.
|
||||
|
||||
<img src="images/logo.png" width="10%">
|
||||
This library is part of the [Awesome Modern C++](https://awesomecpp.com) list.
|
||||
|
||||
Table of Contents
|
||||
-----------------
|
||||
|
||||
## Real-world usage
|
||||
* [Quick Start](#quick-start)
|
||||
* [Documentation](#documentation)
|
||||
* [Performance results](#performance-results)
|
||||
* [Real-world usage](#real-world-usage)
|
||||
* [Bindings and Ports of simdjson](#bindings-and-ports-of-simdjson)
|
||||
* [About simdjson](#about-simdjson)
|
||||
* [Funding](#funding)
|
||||
* [Contributing to simdjson](#contributing-to-simdjson)
|
||||
* [License](#license)
|
||||
|
||||
- [Microsoft FishStore](https://github.com/microsoft/FishStore)
|
||||
- [Yandex ClickHouse](https://github.com/yandex/ClickHouse)
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
## Paper
|
||||
|
||||
The simdjson library is easily consumable with a single .h and .cpp file.
|
||||
|
||||
A description of the design and implementation of simdjson appears at https://arxiv.org/abs/1902.08318 and an informal blog post providing some background and context is at https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/.
|
||||
0. Prerequisites: `g++` (version 7 or better) or `clang++` (version 6 or better), and a 64-bit system with a command-line shell (e.g., Linux, macOS, freeBSD). We also support programming environnements like Visual Studio and Xcode, but different steps are needed.
|
||||
1. Pull [simdjson.h](singleheader/simdjson.h) and [simdjson.cpp](singleheader/simdjson.cpp) into a directory, along with the sample file [twitter.json](jsonexamples/twitter.json).
|
||||
```
|
||||
wget https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.h https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.cpp https://raw.githubusercontent.com/simdjson/simdjson/master/jsonexamples/twitter.json
|
||||
```
|
||||
2. Create `quickstart.cpp`:
|
||||
|
||||
Some people [enjoy reading our paper](https://arxiv.org/abs/1902.08318):
|
||||
```c++
|
||||
#include "simdjson.h"
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets = parser.load("twitter.json");
|
||||
std::cout << tweets["search_metadata"]["count"] << " results." << std::endl;
|
||||
}
|
||||
```
|
||||
3. `c++ -o quickstart quickstart.cpp simdjson.cpp`
|
||||
4. `./quickstart`
|
||||
```
|
||||
100 results.
|
||||
```
|
||||
|
||||
[<img src="images/halvarflake.png" width="50%">](https://twitter.com/halvarflake/status/1118459536686362625)
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Usage documentation is available:
|
||||
|
||||
## Performance results
|
||||
* [Basics](doc/basics.md) is an overview of how to use simdjson and its APIs.
|
||||
* [Performance](doc/performance.md) shows some more advanced scenarios and how to tune for them.
|
||||
* [Implementation Selection](doc/implementation-selection.md) describes runtime CPU detection and
|
||||
how you can work with it.
|
||||
* [API](https://simdjson.org/api/0.3.1/annotated.html) contains the automatically generated API documentation.
|
||||
|
||||
simdjson uses three-quarters less instructions than state-of-the-art parser RapidJSON and fifty percent less than sajson. To our knowledge, simdjson is the first fully-validating JSON parser to run at gigabytes per second on commodity processors.
|
||||
Performance results
|
||||
-------------------
|
||||
|
||||
The simdjson library uses three-quarters less instructions than state-of-the-art parser [RapidJSON](https://rapidjson.org) and
|
||||
fifty percent less than sajson. To our knowledge, simdjson is the first fully-validating JSON parser
|
||||
to run at [gigabytes per second](https://en.wikipedia.org/wiki/Gigabyte) (GB/s) on commodity processors. It can parse millions of JSON documents per second on a single core.
|
||||
|
||||
The following figure represents parsing speed in GB/s for parsing various files
|
||||
on an Intel Skylake processor (3.4 GHz) using the GNU GCC 9 compiler (with the -O3 flag).
|
||||
We compare against the best and fastest C++ libraries.
|
||||
The simdjson library offers full unicode ([UTF-8](https://en.wikipedia.org/wiki/UTF-8)) validation and exact
|
||||
number parsing. The RapidJSON library is tested in two modes: fast and
|
||||
exact number parsing. The sajson library offers fast (but not exact)
|
||||
number parsing and partial unicode validation. In this data set, the file
|
||||
sizes range from 65KB (github_events) all the way to 3.3GB (gsoc-2018).
|
||||
Many files are mostly made of numbers: canada, mesh.pretty, mesh, random
|
||||
and numbers: in such instances, we see lower JSON parsing speeds due to the
|
||||
high cost of number parsing. The simdjson library uses exact number parsing which
|
||||
is particular taxing.
|
||||
|
||||
<img src="doc/gbps.png" width="90%">
|
||||
|
||||
On a Skylake processor, the parsing speeds (in GB/s) of various processors on the twitter.json file are as follows.
|
||||
On a Skylake processor, the parsing speeds (in GB/s) of various processors on the twitter.json file are as follows, using again GNU GCC 9.1 (with the -O3 flag). The popular JSON for Modern C++ library is particularly slow: it obviously trades parsing speed for other desirable features.
|
||||
|
||||
| parser | GB/s |
|
||||
| ------------------------------------- | ---- |
|
||||
| simdjson | 2.2 |
|
||||
| RapidJSON encoding-validation | 0.51 |
|
||||
| RapidJSON encoding-validation, insitu | 0.71 |
|
||||
| sajson (insitu, dynamic) | 0.70 |
|
||||
| sajson (insitu, static) | 0.97 |
|
||||
| dropbox | 0.14 |
|
||||
| fastjson | 0.26 |
|
||||
| gason | 0.85 |
|
||||
| ultrajson | 0.42 |
|
||||
| jsmn | 0.28 |
|
||||
| cJSON | 0.34 |
|
||||
| JSON for Modern C++ (nlohmann/json) | 0.10 |
|
||||
| simdjson | 2.5 |
|
||||
| RapidJSON UTF8-validation | 0.29 |
|
||||
| RapidJSON UTF8-valid., exact numbers | 0.28 |
|
||||
| RapidJSON insitu, UTF8-validation | 0.41 |
|
||||
| RapidJSON insitu, UTF8-valid., exact | 0.39 |
|
||||
| sajson (insitu, dynamic) | 0.62 |
|
||||
| sajson (insitu, static) | 0.88 |
|
||||
| dropbox | 0.13 |
|
||||
| fastjson | 0.27 |
|
||||
| gason | 0.59 |
|
||||
| ultrajson | 0.34 |
|
||||
| jsmn | 0.25 |
|
||||
| cJSON | 0.31 |
|
||||
| JSON for Modern C++ (nlohmann/json) | 0.11 |
|
||||
|
||||
## Requirements
|
||||
|
||||
- We support platforms like Linux or macOS, as well as Windows through Visual Studio 2017 or later.
|
||||
- A processor with
|
||||
- AVX2 (i.e., Intel processors starting with the Haswell microarchitecture released 2013 and AMD processors starting with the Zen microarchitecture released 2017),
|
||||
- or SSE 4.2 and CLMUL (i.e., Intel processors going back to Westmere released in 2010 or AMD processors starting with the Jaguar used in the PS4 and XBox One)
|
||||
- or a 64-bit ARM processor (ARMv8-A): this covers a wide range of mobile processors, including all Apple processors currently available for sale, going back as far back as the iPhone 5s (2013).
|
||||
- A recent C++ compiler (e.g., GNU GCC or LLVM CLANG or Visual Studio 2017), we assume C++17. GNU GCC 7 or better or LLVM's clang 6 or better.
|
||||
- Some benchmark scripts assume bash and other common utilities, but they are optional.
|
||||
The simdjson library offers high speed whether it processes tiny files (e.g., 300 bytes)
|
||||
or larger files (e.g., 3MB). The following plot presents parsing
|
||||
speed for [synthetic files over various sizes generated with a script](https://github.com/simdjson/simdjson_experiments_vldb2019/blob/master/experiments/growing/gen.py) on a 3.4 GHz Skylake processor (GNU GCC 9, -O3).
|
||||
<img src="doc/growing.png" width="90%">
|
||||
|
||||
## License
|
||||
|
||||
This code is made available under the Apache License 2.0.
|
||||
[All our experiments are reproducible](https://github.com/simdjson/simdjson_experiments_vldb2019).
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it under the liberal (business-friendly) MIT license.
|
||||
Real-world usage
|
||||
----------------
|
||||
|
||||
## Code usage and example
|
||||
- [Microsoft FishStore](https://github.com/microsoft/FishStore)
|
||||
- [Yandex ClickHouse](https://github.com/yandex/ClickHouse)
|
||||
- [Clang Build Analyzer](https://github.com/aras-p/ClangBuildAnalyzer)
|
||||
|
||||
The main API involves populating a `ParsedJson` object which hosts a fully navigable document-object-model (DOM) view of the JSON document. The DOM can be accessed using [JSON Pointer](https://tools.ietf.org/html/rfc6901) paths, for example. The main function is `json_parse` which takes a string containing the JSON document as well as a reference to pre-allocated `ParsedJson` object (which can be reused multiple time). Once you have populated the `ParsedJson` object you can navigate through the DOM with an iterator (e.g., created by `ParsedJson::Iterator pjh(pj)`, see 'Navigating the parsed document').
|
||||
If you are planning to use simdjson in a product, please work from one of our releases.
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
const char * filename = ... //
|
||||
|
||||
// use whatever means you want to get a string (UTF-8) of your JSON document
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj;
|
||||
pj.allocate_capacity(p.size()); // allocate memory for parsing up to p.size() bytes
|
||||
const int res = json_parse(p, pj); // do the parsing, return 0 on success
|
||||
// parsing is done!
|
||||
if (res != 0) {
|
||||
// You can use the "simdjson/simdjson.h" header to access the error message
|
||||
std::cout << "Error parsing:" << simdjson::error_message(res) << std::endl;
|
||||
}
|
||||
// the ParsedJson document can be used here
|
||||
// pj can be reused with other json_parse calls.
|
||||
```
|
||||
|
||||
It is also possible to use a simpler API if you do not mind having the overhead
|
||||
of memory allocation with each new JSON document:
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
const char * filename = ... //
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj = build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
// something went wrong
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Though the `padded_string` class is recommended for best performance, you can call `json_parse` and `build_parsed_json`, passing a standard `std::string` object.
|
||||
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
std::string mystring = ... //
|
||||
ParsedJson pj;
|
||||
pj.allocate_capacity(mystring.size()); // allocate memory for parsing up to p.size() bytes
|
||||
// std::string may not overallocate so a copy will be needed
|
||||
const int res = json_parse(mystring, pj); // do the parsing, return 0 on success
|
||||
// parsing is done!
|
||||
if (res != 0) {
|
||||
// You can use the "simdjson/simdjson.h" header to access the error message
|
||||
std::cout << "Error parsing:" << simdjson::error_message(res) << std::endl;
|
||||
}
|
||||
// pj can be reused with other json_parse calls.
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```C
|
||||
#include "simdjson/jsonparser.h"
|
||||
using namespace simdjson;
|
||||
|
||||
/...
|
||||
|
||||
std::string mystring = ... //
|
||||
// std::string may not overallocate so a copy will be needed
|
||||
ParsedJson pj = build_parsed_json(mystring); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
// something went wrong
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
As needed, the `json_parse` and `build_parsed_json` functions copy the input data to a temporary buffer readable up to SIMDJSON_PADDING bytes beyond the end of the data.
|
||||
|
||||
## Usage: easy single-header version
|
||||
|
||||
See the "singleheader" repository for a single header version. See the included
|
||||
file "amalgamation_demo.cpp" for usage. This requires no specific build system: just
|
||||
copy the files in your project in your include path. You can then include them quite simply:
|
||||
|
||||
```C
|
||||
#include <iostream>
|
||||
#include "simdjson.h"
|
||||
#include "simdjson.cpp"
|
||||
using namespace simdjson;
|
||||
int main(int argc, char *argv[]) {
|
||||
const char * filename = argv[1];
|
||||
padded_string p = get_corpus(filename);
|
||||
ParsedJson pj = build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
std::cout << "not valid" << std::endl;
|
||||
std::cout << pj.get_error_message() << std::endl;
|
||||
} else {
|
||||
std::cout << "valid" << std::endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Note: In some settings, it might be desirable to precompile `simdjson.cpp` instead of including it.
|
||||
|
||||
## Runtime dispatch
|
||||
|
||||
On Intel and AMD processors, we get best performance by using the hardware support for AVX2 instructions. However, simdjson also
|
||||
runs on older Intel and AMD processors. We require a minimum feature support of SSE 4.2 and CLMUL (2010 Intel Westmere or better).
|
||||
The code automatically detects the feature set of your processor and switches to the right function at runtime (a technical
|
||||
sometimes called runtime dispatch).
|
||||
|
||||
|
||||
We also support 64-bit ARM. We assume NEON support, and if the cryptographic extension is available, we leverage it, at compile-time.
|
||||
There is no runtime dispatch on ARM.
|
||||
|
||||
## Thread safety
|
||||
|
||||
The simdjson library is single-threaded and thread safety is the responsability of the caller. If you are on an x64 processor, the runtime dispatching assigns the right code path the firs time that parsing is attempted. For safety, you should always call json_parse at least once in a single-threaded context.
|
||||
|
||||
|
||||
## Usage (old-school Makefile on platforms like Linux or macOS)
|
||||
|
||||
Requirements: recent clang or gcc, and make. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. A system like Linux or macOS is expected.
|
||||
|
||||
To test:
|
||||
|
||||
```
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
To run benchmarks:
|
||||
|
||||
```
|
||||
make parse
|
||||
./parse jsonexamples/twitter.json
|
||||
```
|
||||
|
||||
Under Linux, the `parse` command gives a detailed analysis of the performance counters.
|
||||
|
||||
To run comparative benchmarks (with other parsers):
|
||||
|
||||
```
|
||||
make benchmark
|
||||
```
|
||||
|
||||
## Usage (CMake on platforms like Linux or macOS)
|
||||
|
||||
Requirements: We require a recent version of cmake. On macOS, the easiest way to install cmake might be to use [brew](https://brew.sh) and then type
|
||||
|
||||
```
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
There is an [equivalent brew on Linux which works the same way as well](https://linuxbrew.sh).
|
||||
|
||||
You need a recent compiler like clang or gcc. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. For example, you can install a recent compiler with brew:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
```
|
||||
|
||||
Optional: You need to tell cmake which compiler you wish to use by setting the CC and CXX variables. Under bash, you can do so with commands such as `export CC=gcc-7` and `export CXX=g++-7`.
|
||||
|
||||
Building: While in the project repository, do the following:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
CMake will build a library. By default, it builds a shared library (e.g., libsimdjson.so on Linux).
|
||||
|
||||
You can build a static library:
|
||||
|
||||
```
|
||||
mkdir buildstatic
|
||||
cd buildstatic
|
||||
cmake -DSIMDJSON_BUILD_STATIC=ON ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
In some cases, you may want to specify your compiler, especially if the default compiler on your system is too old. You may proceed as follows:
|
||||
|
||||
```
|
||||
brew install gcc@8
|
||||
mkdir build
|
||||
cd build
|
||||
export CXX=g++-8 CC=gcc-8
|
||||
cmake ..
|
||||
make
|
||||
make test
|
||||
```
|
||||
|
||||
## Usage (CMake on Windows using Visual Studio)
|
||||
|
||||
We assume you have a common Windows PC with at least Visual Studio 2017 and an x64 processor with AVX2 support (2013 Intel Haswell or later) or SSE 4.2 + CLMUL (2010 Westmere or later).
|
||||
|
||||
- Grab the simdjson code from GitHub, e.g., by cloning it using [GitHub Desktop](https://desktop.github.com/).
|
||||
- Install [CMake](https://cmake.org/download/). When you install it, make sure to ask that `cmake` be made available from the command line. Please choose a recent version of cmake.
|
||||
- Create a subdirectory within simdjson, such as `VisualStudio`.
|
||||
- Using a shell, go to this newly created directory.
|
||||
- Type `cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..` in the shell while in the `VisualStudio` repository. (Alternatively, if you want to build a DLL, you may use the command line `cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF ..`.)
|
||||
- This last command (`cmake ...`) created a Visual Studio solution file in the newly created directory (e.g., `simdjson.sln`). Open this file in Visual Studio. You should now be able to build the project and run the tests. For example, in the `Solution Explorer` window (available from the `View` menu), right-click `ALL_BUILD` and select `Build`. To test the code, still in the `Solution Explorer` window, select `RUN_TESTS` and select `Build`.
|
||||
|
||||
|
||||
|
||||
## Usage (Using `vcpkg` on Windows, Linux and MacOS)
|
||||
|
||||
[vcpkg](https://github.com/Microsoft/vcpkg) users on Windows, Linux and MacOS can download and install `simdjson` with one single command from their favorite shell.
|
||||
|
||||
On Linux and MacOS:
|
||||
|
||||
```
|
||||
$ ./vcpkg install simdjson
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
On Windows (64-bit):
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a shared library.
|
||||
|
||||
```
|
||||
.\vcpkg.exe install simdjson:x64-windows-static
|
||||
```
|
||||
|
||||
will build and install `simdjson` as a static library.
|
||||
|
||||
These commands will also print out instructions on how to use the library from MSBuild or CMake-based projects.
|
||||
|
||||
If you find the version of `simdjson` shipped with `vcpkg` is out-of-date, feel free to report it to `vcpkg` community either by submiting an issue or by creating a PR.
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
- `json2json mydoc.json` parses the document, constructs a model and then dumps back the result to standard output.
|
||||
- `json2json -d mydoc.json` parses the document, constructs a model and then dumps model (as a tape) to standard output. The tape format is described in the accompanying file `tape.md`.
|
||||
- `minify mydoc.json` minifies the JSON document, outputting the result to standard output. Minifying means to remove the unneeded white space characters.
|
||||
- `jsonpointer mydoc.json <jsonpath> <jsonpath> ... <jsonpath>` parses the document, constructs a model and then processes a series of [JSON Pointer paths](https://tools.ietf.org/html/rfc6901). The result is itself a JSON document.
|
||||
|
||||
## Scope
|
||||
|
||||
We provide a fast parser, that fully validates an input according to various specifications.
|
||||
The parser builds a useful immutable (read-only) DOM (document-object model) which can be later accessed.
|
||||
|
||||
To simplify the engineering, we make some assumptions.
|
||||
|
||||
- We support UTF-8 (and thus ASCII), nothing else (no Latin, no UTF-16). We do not believe this is a genuine limitation, because we do not think there is any serious application that needs to process JSON data without an ASCII or UTF-8 encoding. If the UTF-8 contains a leading BOM, it should be omitted: the user is responsible for detecting and skipping the BOM; UTF-8 BOMs are discouraged.
|
||||
- All strings in the JSON document may have up to 4294967295 bytes in UTF-8 (4GB). To enforce this constraint, we refuse to parse a document that contains more than 4294967295 bytes (4GB). This should accommodate most JSON documents.
|
||||
- As allowed by the specification, we allow repeated keys within an object (other parsers like sajson do the same).
|
||||
- Performance is optimized for JSON documents spanning at least a tens kilobytes up to many megabytes: the performance issues with having to parse many tiny JSON documents or one truly enormous JSON document are different.
|
||||
|
||||
_We do not aim to provide a general-purpose JSON library._ A library like RapidJSON offers much more than just parsing, it helps you generate JSON and offers various other convenient functions. We merely parse the document.
|
||||
|
||||
## Features
|
||||
|
||||
- The input string is unmodified. (Parsers like sajson and RapidJSON use the input string as a buffer.)
|
||||
- We parse integers and floating-point numbers as separate types which allows us to support large 64-bit integers in [-9223372036854775808,9223372036854775808), like a Java `long` or a C/C++ `long long`. Among the parsers that differentiate between integers and floating-point numbers, not all support 64-bit integers. (For example, sajson rejects JSON files with integers larger than or equal to 2147483648. RapidJSON will parse a file containing an overly long integer like 18446744073709551616 as a floating-point number.) When we cannot represent exactly an integer as a signed 64-bit value, we reject the JSON document.
|
||||
- We support the full range of 64-bit floating-point numbers (binary64). The values range from ` std::numeric_limits<double>::lowest()` to `std::numeric_limits<double>::max()`, so from -1.7976e308 all the way to 1.7975e308. Extreme values (less or equal to -1e308, greater or equal to 1e308) are rejected: we refuse to parse the input document.
|
||||
- We test for accurate float parsing with a bound on the [unit of least precision (ULP)](https://en.wikipedia.org/wiki/Unit_in_the_last_place) of one. Practically speaking, this implies 15 digits of accuracy or better.
|
||||
- We do full UTF-8 validation as part of the parsing. (Parsers like fastjson, gason and dropbox json11 do not do UTF-8 validation. The sajson parser does incomplete UTF-8 validation, accepting code point
|
||||
sequences like 0xb1 0x87.)
|
||||
- We fully validate the numbers. (Parsers like gason and ultranjson will accept `[0e+]` as valid JSON.)
|
||||
- We validate string content for unescaped characters. (Parsers like fastjson and ultrajson accept unescaped line breaks and tabs in strings.)
|
||||
- We fully validate the white-space characters outside of the strings. Parsers like RapidJSON will accept JSON documents with null characters outside of strings.
|
||||
|
||||
## Architecture
|
||||
|
||||
The parser works in two stages:
|
||||
|
||||
- Stage 1. (Find marks) Identifies quickly structure elements, strings, and so forth. We validate UTF-8 encoding at that stage.
|
||||
- Stage 2. (Structure building) Involves constructing a "tree" of sort (materialized as a tape) to navigate through the data. Strings and numbers are parsed at this stage.
|
||||
|
||||
## JSON Pointer
|
||||
|
||||
We can navigate the parsed JSON using JSON Pointers as per the [RFC6901 standard](https://tools.ietf.org/html/rfc6901).
|
||||
|
||||
You can build a tool (jsonpointer) to parse a JSON document and then issue an array of JSON Pointer queries:
|
||||
|
||||
```
|
||||
make jsonpointer
|
||||
./jsonpointer jsonexamples/small/demo.json /Image/Width /Image/Height /Image/IDs/2
|
||||
./jsonpointer jsonexamples/twitter.json /statuses/0/id /statuses/1/id /statuses/2/id /statuses/3/id /statuses/4/id /statuses/5/id
|
||||
```
|
||||
|
||||
In C++, given a `ParsedJson`, we can move to a node with the `move_to` method, passing a `std::string` representing the JSON Pointer query.
|
||||
|
||||
## Navigating the parsed document
|
||||
|
||||
Here is a code sample to dump back the parsed JSON to a string:
|
||||
|
||||
```c
|
||||
ParsedJson::Iterator pjh(pj);
|
||||
if (!pjh.is_ok()) {
|
||||
std::cerr << " Could not iterate parsed result. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
compute_dump(pj);
|
||||
//
|
||||
// where compute_dump is :
|
||||
|
||||
void compute_dump(ParsedJson::Iterator &pjh) {
|
||||
if (pjh.is_object()) {
|
||||
std::cout << "{";
|
||||
if (pjh.down()) {
|
||||
pjh.print(std::cout); // must be a string
|
||||
std::cout << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
std::cout << ",";
|
||||
pjh.print(std::cout);
|
||||
std::cout << ":";
|
||||
pjh.next();
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
std::cout << "}";
|
||||
} else if (pjh.is_array()) {
|
||||
std::cout << "[";
|
||||
if (pjh.down()) {
|
||||
compute_dump(pjh); // let us recurse
|
||||
while (pjh.next()) {
|
||||
std::cout << ",";
|
||||
compute_dump(pjh); // let us recurse
|
||||
}
|
||||
pjh.up();
|
||||
}
|
||||
std::cout << "]";
|
||||
} else {
|
||||
pjh.print(std::cout); // just print the lone value
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following function will find all user.id integers:
|
||||
|
||||
```C
|
||||
void simdjson_scan(std::vector<int64_t> &answer, ParsedJson::Iterator &i) {
|
||||
while(i.move_forward()) {
|
||||
if(i.get_scope_type() == '{') {
|
||||
bool found_user = (i.get_string_length() == 4) && (memcmp(i.get_string(), "user", 4) == 0);
|
||||
i.move_to_value();
|
||||
if(found_user) {
|
||||
if(i.is_object() && i.move_to_key("id",2)) {
|
||||
if (i.is_integer()) {
|
||||
answer.push_back(i.get_integer());
|
||||
}
|
||||
i.up();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## In-depth comparisons
|
||||
|
||||
If you want to see how a wide range of parsers validate a given JSON file:
|
||||
|
||||
```
|
||||
make allparserscheckfile
|
||||
./allparserscheckfile myfile.json
|
||||
```
|
||||
|
||||
For performance comparisons:
|
||||
|
||||
```
|
||||
make parsingcompetition
|
||||
./parsingcompetition myfile.json
|
||||
```
|
||||
|
||||
For broader comparisons:
|
||||
|
||||
```
|
||||
make allparsingcompetition
|
||||
./allparsingcompetition myfile.json
|
||||
```
|
||||
|
||||
Both the `parsingcompetition` and `allparsingcompetition` tools take a `-t` flag which produces
|
||||
a table-oriented output that can be conventiently parsed by other tools.
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
One can run tests and benchmarks using docker. It especially makes sense under Linux. A privileged access may be needed to get performance counters.
|
||||
|
||||
```
|
||||
git clone https://github.com/lemire/simdjson.git
|
||||
cd simdjson
|
||||
docker build -t simdjson .
|
||||
docker run --privileged -t simdjson
|
||||
```
|
||||
|
||||
## Other programming languages
|
||||
Bindings and Ports of simdjson
|
||||
------------------------------
|
||||
|
||||
We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything).
|
||||
|
||||
- [ZippyJSON](https://github.com/michaeleisel/zippyjson): Swift bindings for the simdjson project.
|
||||
- [pysimdjson](https://github.com/TkTech/pysimdjson): Python bindings for the simdjson project.
|
||||
- [simdjson-rs](https://github.com/Licenser/simdjson-rs): Rust port
|
||||
- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings)
|
||||
- [SimdJsonSharp](https://github.com/EgorBo/SimdJsonSharp): C# version for .NET Core (bindings and full port)
|
||||
- [simdjson-rs](https://github.com/simd-lite): Rust port.
|
||||
- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings).
|
||||
- [SimdJsonSharp](https://github.com/EgorBo/SimdJsonSharp): C# version for .NET Core (bindings and full port).
|
||||
- [simdjson_nodejs](https://github.com/luizperes/simdjson_nodejs): Node.js bindings for the simdjson project.
|
||||
- [simdjson_php](https://github.com/crazyxman/simdjson_php): PHP bindings for the simdjson project.
|
||||
- [simdjson_ruby](https://github.com/saka1/simdjson_ruby): Ruby bindings for the simdjson project.
|
||||
- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly.
|
||||
- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings.
|
||||
|
||||
## Various References
|
||||
About simdjson
|
||||
--------------
|
||||
|
||||
- [Google double-conv](https://github.com/google/double-conversion/)
|
||||
- [How to implement atoi using SIMD?](https://stackoverflow.com/questions/35127060/how-to-implement-atoi-using-simd)
|
||||
- [Parsing JSON is a Minefield 💣](http://seriot.ch/parsing_json.php)
|
||||
- https://tools.ietf.org/html/rfc7159
|
||||
- The Mison implementation in rust https://github.com/pikkr/pikkr
|
||||
- http://rapidjson.org/md_doc_sax.html
|
||||
- https://github.com/Geal/parser_benchmarks/tree/master/json
|
||||
- Gron: A command line tool that makes JSON greppable https://news.ycombinator.com/item?id=16727665
|
||||
- GoogleGson https://github.com/google/gson
|
||||
- Jackson https://github.com/FasterXML/jackson
|
||||
- https://www.yelp.com/dataset_challenge
|
||||
- RapidJSON. http://rapidjson.org/
|
||||
The simdjson library takes advantage of modern microarchitectures, parallelizing with SIMD vector
|
||||
instructions, reducing branch misprediction, and reducing data dependency to take advantage of each
|
||||
CPU's multiple execution cores.
|
||||
|
||||
Inspiring links:
|
||||
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.
|
||||
|
||||
- https://auth0.com/blog/beating-json-performance-with-protobuf/
|
||||
- https://gist.github.com/shijuvar/25ad7de9505232c87034b8359543404a
|
||||
- https://github.com/frankmcsherry/blog/blob/master/posts/2018-02-11.md
|
||||
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/).
|
||||
|
||||
Validating UTF-8 takes no more than 0.7 cycles per byte:
|
||||
For the video inclined, <br />
|
||||
[](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
(it was the best voted talk, we're kinda proud of it).
|
||||
|
||||
- https://github.com/lemire/fastvalidate-utf-8 https://lemire.me/blog/2018/05/16/validating-utf-8-strings-using-as-little-as-0-7-cycles-per-byte/
|
||||
|
||||
## Remarks on JSON parsing
|
||||
|
||||
- The JSON spec defines what a JSON parser is:
|
||||
> A JSON parser transforms a JSON text into another representation. A JSON parser MUST accept all texts that conform to the JSON grammar. A JSON parser MAY accept non-JSON forms or extensions. An implementation may set limits on the size of texts that it accepts. An implementation may set limits on the maximum depth of nesting. An implementation may set limits on the range and precision of numbers. An implementation may set limits on the length and character contents of strings.
|
||||
|
||||
* JSON is not JavaScript:
|
||||
|
||||
> All JSON is Javascript but NOT all Javascript is JSON. So {property:1} is invalid because property does not have double quotes around it. {'property':1} is also invalid, because it's single quoted while the only thing that can placate the JSON specification is double quoting. JSON is even fussy enough that {"property":.1} is invalid too, because you should have of course written {"property":0.1}. Also, don't even think about having comments or semicolons, you guessed it: they're invalid. (credit:https://github.com/elzr/vim-json)
|
||||
|
||||
* The structural characters are:
|
||||
|
||||
|
||||
begin-array = [ left square bracket
|
||||
begin-object = { left curly bracket
|
||||
end-array = ] right square bracket
|
||||
end-object = } right curly bracket
|
||||
name-separator = : colon
|
||||
value-separator = , comma
|
||||
|
||||
### Pseudo-structural elements
|
||||
|
||||
A character is pseudo-structural if and only if:
|
||||
|
||||
1. Not enclosed in quotes, AND
|
||||
2. Is a non-whitespace character, AND
|
||||
3. Its preceding character is either:
|
||||
(a) a structural character, OR
|
||||
(b) whitespace.
|
||||
|
||||
This helps as we redefine some new characters as pseudo-structural such as the characters 1, G, n in the following:
|
||||
|
||||
> { "foo" : 1.5, "bar" : 1.5 GEOFF_IS_A_DUMMY bla bla , "baz", null }
|
||||
|
||||
## Academic References
|
||||
|
||||
- T.Mühlbauer, W.Rödiger, R.Seilbeck, A.Reiser, A.Kemper, and T.Neumann. Instant loading for main memory databases. PVLDB, 6(14):1702–1713, 2013. (SIMD-based CSV parsing)
|
||||
- Mytkowicz, Todd, Madanlal Musuvathi, and Wolfram Schulte. "Data-parallel finite-state machines." ACM SIGARCH Computer Architecture News. Vol. 42. No. 1. ACM, 2014.
|
||||
- Lu, Yifan, et al. "Tree structured data processing on GPUs." Cloud Computing, Data Science & Engineering-Confluence, 2017 7th International Conference on. IEEE, 2017.
|
||||
- Sidhu, Reetinder. "High throughput, tree automata based XML processing using FPGAs." Field-Programmable Technology (FPT), 2013 International Conference on. IEEE, 2013.
|
||||
- Dai, Zefu, Nick Ni, and Jianwen Zhu. "A 1 cycle-per-byte XML parsing accelerator." Proceedings of the 18th annual ACM/SIGDA international symposium on Field programmable gate arrays. ACM, 2010.
|
||||
- Lin, Dan, et al. "Parabix: Boosting the efficiency of text processing on commodity processors." High Performance Computer Architecture (HPCA), 2012 IEEE 18th International Symposium on. IEEE, 2012. http://parabix.costar.sfu.ca/export/1783/docs/HPCA2012/final_ieee/final.pdf
|
||||
- Deshmukh, V. M., and G. R. Bamnote. "An empirical evaluation of optimization parameters in XML parsing for performance enhancement." Computer, Communication and Control (IC4), 2015 International Conference on. IEEE, 2015.
|
||||
- Moussalli, Roger, et al. "Efficient XML Path Filtering Using GPUs." ADMS@ VLDB. 2011.
|
||||
- Jianliang, Ma, et al. "Parallel speculative dom-based XML parser." High Performance Computing and Communication & 2012 IEEE 9th International Conference on Embedded Software and Systems (HPCC-ICESS), 2012 IEEE 14th International Conference on. IEEE, 2012.
|
||||
- Li, Y., Katsipoulakis, N.R., Chandramouli, B., Goldstein, J. and Kossmann, D., 2017. Mison: a fast JSON parser for data analytics. Proceedings of the VLDB Endowment, 10(10), pp.1118-1129. http://www.vldb.org/pvldb/vol10/p1118-li.pdf
|
||||
- Cameron, Robert D., et al. "Parallel scanning with bitstream addition: An xml case study." European Conference on Parallel Processing. Springer, Berlin, Heidelberg, 2011.
|
||||
- Cameron, Robert D., Kenneth S. Herdy, and Dan Lin. "High performance XML parsing using parallel bit stream technology." Proceedings of the 2008 conference of the center for advanced studies on collaborative research: meeting of minds. ACM, 2008.
|
||||
- Shah, Bhavik, et al. "A data parallel algorithm for XML DOM parsing." International XML Database Symposium. Springer, Berlin, Heidelberg, 2009.
|
||||
- Cameron, Robert D., and Dan Lin. "Architectural support for SWAR text processing with parallel bit streams: the inductive doubling principle." ACM Sigplan Notices. Vol. 44. No. 3. ACM, 2009.
|
||||
- Amagasa, Toshiyuki, Mana Seino, and Hiroyuki Kitagawa. "Energy-Efficient XML Stream Processing through Element-Skipping Parsing." Database and Expert Systems Applications (DEXA), 2013 24th International Workshop on. IEEE, 2013.
|
||||
- Medforth, Nigel Woodland. "icXML: Accelerating Xerces-C 3.1. 1 using the Parabix Framework." (2013).
|
||||
- Zhang, Qiang Scott. Embedding Parallel Bit Stream Technology Into Expat. Diss. Simon Fraser University, 2010.
|
||||
- Cameron, Robert D., et al. "Fast Regular Expression Matching with Bit-parallel Data Streams."
|
||||
- Lin, Dan. Bits filter: a high-performance multiple string pattern matching algorithm for malware detection. Diss. School of Computing Science-Simon Fraser University, 2010.
|
||||
- Yang, Shiyang. Validation of XML Document Based on Parallel Bit Stream Technology. Diss. Applied Sciences: School of Computing Science, 2013.
|
||||
- N. Nakasato, "Implementation of a parallel tree method on a GPU", Journal of Computational Science, vol. 3, no. 3, pp. 132-141, 2012.
|
||||
|
||||
|
||||
## Funding
|
||||
|
||||
The work is supported by the Natural Sciences and Engineering Research Council of Canada under grant number RGPIN-2017-03910.
|
||||
Funding
|
||||
-------
|
||||
|
||||
The work is supported by the Natural Sciences and Engineering Research Council of Canada under grant
|
||||
number RGPIN-2017-03910.
|
||||
|
||||
[license]: LICENSE
|
||||
[license img]: https://img.shields.io/badge/License-Apache%202-blue.svg
|
||||
|
||||
Contributing to simdjson
|
||||
------------------------
|
||||
|
||||
Head over to [CONTRIBUTING.md](CONTRIBUTING.md) for information on contributing to simdjson, and
|
||||
[HACKING.md](HACKING.md) for information on source, building, and architecture/design.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This code is made available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it under the liberal (business-friendly) MIT license.
|
||||
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the Boost license (http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# 0.3
|
||||
|
||||
## Highlights
|
||||
|
||||
- Test coverage has been greatly improved and we have resolved many static-analysis warnings on different systems.
|
||||
- We added a fast (8GB/s) minifier that works directly on JSON strings.
|
||||
- We added fast (10GB/s) UTF-8 validator that works directly on strings (any strings, including non-JSON).
|
||||
- The array and object elements have a constant-time size() method.
|
||||
- Performance improvements to the API (type(), get<>()).
|
||||
- The parse_many function (ndjson) has been entirely reworked. It now uses a single secondary thread instead of several new threads.
|
||||
- We have introduced a faster UTF-8 validation algorithm (lookup3) for all kernels (ARM, x64 SSE, x64 AVX).
|
||||
- C++11 support for older compilers and systems.
|
||||
- FreeBSD support (and tests).
|
||||
- We support the clang front-end compiler (clangcl) under Visual Studio.
|
||||
- It is now possible to target ARM platforms under Visual Studio.
|
||||
- The simdjson library will never abort or print to standard output/error.
|
||||
|
||||
# 0.3
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Multi-Document Parsing:** Read a bundle of JSON documents (ndjson) 2-4x faster than doing it
|
||||
individually. [API docs](https://github.com/simdjson/simdjson/blob/master/doc/basics.md#newline-delimited-json-ndjson-and-json-lines) / [Design Details](https://github.com/simdjson/simdjson/blob/master/doc/parse_many.md)
|
||||
- **Simplified API:** The API has been completely revamped for ease of use, including a new JSON
|
||||
navigation API and fluent support for error code *and* exception styles of error handling with a
|
||||
single API. [Docs](https://github.com/simdjson/simdjson/blob/master/doc/basics.md#the-basics-loading-and-parsing-json-documents)
|
||||
- **Exact Float Parsing:** Now simdjson parses floats flawlessly *without* any performance loss,
|
||||
thanks to [great work by @michaeleisel and @lemire](https://github.com/simdjson/simdjson/pull/558).
|
||||
[Blog Post](https://lemire.me/blog/2020/03/10/fast-float-parsing-in-practice/)
|
||||
- **Even Faster:** The fastest parser got faster! With a [shiny new UTF-8 validator](https://github.com/simdjson/simdjson/pull/387)
|
||||
and meticulously refactored SIMD core, simdjson 0.3 is 15% faster than before, running at 2.5 GB/s
|
||||
(where 0.2 ran at 2.2 GB/s).
|
||||
|
||||
## Minor Highlights
|
||||
|
||||
- Fallback implementation: simdjson now has a non-SIMD fallback implementation, and can run even on
|
||||
very old 64-bit machines.
|
||||
- Automatic allocation: as part of API simplification, the parser no longer has to be preallocated--
|
||||
it will adjust automatically when it encounters larger files.
|
||||
- Runtime selection API: We've exposed simdjson's runtime CPU detection and implementation selection
|
||||
as an API, so you can tell what implementation we detected and test with other implementations.
|
||||
- Error handling your way: Whether you use exceptions or check error codes, simdjson lets you handle
|
||||
errors in your style. APIs that can fail return simdjson_result<T>, letting you check the error
|
||||
code before using the result. But if you are more comfortable with exceptions, skip the error code
|
||||
and cast straight to T, and exceptions will be thrown automatically if an error happens. Use the
|
||||
same API either way!
|
||||
- Error chaining: We also worked to keep non-exception error-handling short and sweet. Instead of
|
||||
having to check the error code after every single operation, now you can *chain* JSON navigation
|
||||
calls like looking up an object field or array element, or casting to a string, so that you only
|
||||
have to check the error code once at the very end.
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
#!/bin/bash
|
||||
########################################################################
|
||||
# Generates an "amalgamation build" for roaring. Inspired by similar
|
||||
# script used by whefs.
|
||||
########################################################################
|
||||
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
|
||||
|
||||
echo "We are about to amalgamate all simdjson files into one source file. "
|
||||
echo "See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale. "
|
||||
|
||||
AMAL_H="simdjson.h"
|
||||
AMAL_C="simdjson.cpp"
|
||||
|
||||
# order does not matter
|
||||
ALLCFILES="
|
||||
$SCRIPTPATH/src/simdjson.cpp
|
||||
$SCRIPTPATH/src/jsonioutil.cpp
|
||||
$SCRIPTPATH/src/jsonminifier.cpp
|
||||
$SCRIPTPATH/src/jsonparser.cpp
|
||||
$SCRIPTPATH/src/stage1_find_marks.cpp
|
||||
$SCRIPTPATH/src/stage2_build_tape.cpp
|
||||
$SCRIPTPATH/src/parsedjson.cpp
|
||||
$SCRIPTPATH/src/parsedjsoniterator.cpp
|
||||
"
|
||||
|
||||
# order matters
|
||||
ALLCHEADERS="
|
||||
$SCRIPTPATH/include/simdjson/simdjson_version.h
|
||||
$SCRIPTPATH/include/simdjson/portability.h
|
||||
$SCRIPTPATH/include/simdjson/isadetection.h
|
||||
$SCRIPTPATH/include/simdjson/simdjson.h
|
||||
$SCRIPTPATH/include/simdjson/common_defs.h
|
||||
$SCRIPTPATH/include/simdjson/padded_string.h
|
||||
$SCRIPTPATH/include/simdjson/jsoncharutils.h
|
||||
$SCRIPTPATH/include/simdjson/jsonformatutils.h
|
||||
$SCRIPTPATH/include/simdjson/jsonioutil.h
|
||||
$SCRIPTPATH/include/simdjson/simdprune_tables.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/simdutf8check_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/jsonminifier.h
|
||||
$SCRIPTPATH/include/simdjson/parsedjson.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_flatten.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_flatten_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_macros.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stage1_find_marks_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_macros.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_westmere.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_haswell.h
|
||||
$SCRIPTPATH/include/simdjson/stringparsing_arm64.h
|
||||
$SCRIPTPATH/include/simdjson/numberparsing.h
|
||||
$SCRIPTPATH/include/simdjson/stage2_build_tape.h
|
||||
$SCRIPTPATH/include/simdjson/jsonparser.h
|
||||
"
|
||||
|
||||
for i in ${ALLCHEADERS} ${ALLCFILES}; do
|
||||
test -e $i && continue
|
||||
echo "FATAL: source file [$i] not found."
|
||||
exit 127
|
||||
done
|
||||
|
||||
|
||||
function stripinc()
|
||||
{
|
||||
sed -e '/# *include *"/d' -e '/# *include *<simdjson\//d'
|
||||
}
|
||||
function dofile()
|
||||
{
|
||||
RELFILE=${1#"$SCRIPTPATH/"}
|
||||
echo "/* begin file $RELFILE */"
|
||||
# echo "#line 8 \"$1\"" ## redefining the line/file is not nearly as useful as it sounds for debugging. It breaks IDEs.
|
||||
stripinc < $1
|
||||
echo "/* end file $RELFILE */"
|
||||
}
|
||||
|
||||
timestamp=$(date)
|
||||
echo "Creating ${AMAL_H}..."
|
||||
echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${AMAL_H}"
|
||||
{
|
||||
for h in ${ALLCHEADERS}; do
|
||||
dofile $h
|
||||
done
|
||||
} >> "${AMAL_H}"
|
||||
|
||||
|
||||
echo "Creating ${AMAL_C}..."
|
||||
echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${AMAL_C}"
|
||||
{
|
||||
echo "#include \"${AMAL_H}\""
|
||||
|
||||
echo ""
|
||||
echo "/* used for http://dmalloc.com/ Dmalloc - Debug Malloc Library */"
|
||||
echo "#ifdef DMALLOC"
|
||||
echo "#include \"dmalloc.h\""
|
||||
echo "#endif"
|
||||
echo ""
|
||||
|
||||
for h in ${ALLCFILES}; do
|
||||
dofile $h
|
||||
done
|
||||
} >> "${AMAL_C}"
|
||||
|
||||
|
||||
|
||||
DEMOCPP="amalgamation_demo.cpp"
|
||||
echo "Creating ${DEMOCPP}..."
|
||||
echo "/* auto-generated on ${timestamp}. Do not edit! */" > "${DEMOCPP}"
|
||||
cat <<< '
|
||||
#include <iostream>
|
||||
#include "simdjson.h"
|
||||
#include "simdjson.cpp"
|
||||
int main(int argc, char *argv[]) {
|
||||
if(argc < 2) {
|
||||
std::cerr << "Please specify a filename " << std::endl;
|
||||
}
|
||||
const char * filename = argv[1];
|
||||
simdjson::padded_string p = simdjson::get_corpus(filename);
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p); // do the parsing
|
||||
if( ! pj.is_valid() ) {
|
||||
std::cout << "not valid" << std::endl;
|
||||
} else {
|
||||
std::cout << "valid" << std::endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
' >> "${DEMOCPP}"
|
||||
|
||||
echo "Done with all files generation. "
|
||||
|
||||
echo "Files have been written to directory: $PWD "
|
||||
ls -la ${AMAL_C} ${AMAL_H} ${DEMOCPP}
|
||||
|
||||
echo "Giving final instructions:"
|
||||
|
||||
|
||||
CPPBIN=${DEMOCPP%%.*}
|
||||
|
||||
echo "Try :"
|
||||
echo "c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json "
|
||||
|
||||
SINGLEHDR=$SCRIPTPATH/singleheader
|
||||
echo "Copying files to $SCRIPTPATH/singleheader "
|
||||
mkdir -p $SINGLEHDR
|
||||
echo "c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json " > $SINGLEHDR/README.md
|
||||
cp ${AMAL_C} ${AMAL_H} ${DEMOCPP} $SINGLEHDR
|
||||
ls $SINGLEHDR
|
||||
|
||||
cd $SINGLEHDR && c++ -O3 -std=c++17 -o ${CPPBIN} ${DEMOCPP} && ./${CPPBIN} ../jsonexamples/twitter.json
|
||||
|
||||
lowercase(){
|
||||
echo "$1" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
OS=`lowercase \`uname\``
|
||||
@@ -1,8 +1,37 @@
|
||||
target_include_directories(${SIMDJSON_LIB_NAME}
|
||||
INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmark>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmark/linux>
|
||||
)
|
||||
include_directories( . linux )
|
||||
link_libraries(simdjson simdjson-flags simdjson-windows-headers test-data)
|
||||
add_executable(benchfeatures benchfeatures.cpp)
|
||||
add_executable(get_corpus_benchmark get_corpus_benchmark.cpp)
|
||||
add_executable(perfdiff perfdiff.cpp)
|
||||
add_executable(parse parse.cpp)
|
||||
add_executable(parse_stream parse_stream.cpp)
|
||||
add_executable(statisticalmodel statisticalmodel.cpp)
|
||||
|
||||
add_cpp_benchmark(parse)
|
||||
add_cpp_benchmark(statisticalmodel)
|
||||
add_executable(parse_noutf8validation parse.cpp)
|
||||
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
|
||||
add_executable(parse_nonumberparsing parse.cpp)
|
||||
target_compile_definitions(parse_nonumberparsing PRIVATE SIMDJSON_SKIPNUMBERPARSING)
|
||||
add_executable(parse_nostringparsing parse.cpp)
|
||||
target_compile_definitions(parse_nostringparsing PRIVATE SIMDJSON_SKIPSTRINGPARSING)
|
||||
|
||||
if (TARGET benchmark::benchmark)
|
||||
link_libraries(benchmark::benchmark)
|
||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||
endif()
|
||||
|
||||
if (TARGET competition-all)
|
||||
add_executable(distinctuseridcompetition distinctuseridcompetition.cpp)
|
||||
target_link_libraries(distinctuseridcompetition competition-core)
|
||||
add_executable(minifiercompetition minifiercompetition.cpp)
|
||||
target_link_libraries(minifiercompetition competition-core)
|
||||
add_executable(parseandstatcompetition parseandstatcompetition.cpp)
|
||||
target_link_libraries(parseandstatcompetition competition-core)
|
||||
add_executable(parsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(parsingcompetition competition-core)
|
||||
add_executable(allparsingcompetition parsingcompetition.cpp)
|
||||
target_link_libraries(allparsingcompetition competition-all)
|
||||
target_compile_definitions(allparsingcompetition PRIVATE ALLPARSER)
|
||||
endif()
|
||||
|
||||
include(checkperf.cmake)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# From the ROOT, run:
|
||||
# docker build -t simdjsonbench -f benchmark/Dockerfile . && docker run --privileged -t simdjsonbench
|
||||
FROM gcc:8.3
|
||||
|
||||
# # Build latest
|
||||
# ENV latest_release=v0.2.1
|
||||
# WORKDIR /usr/src/$latest_release/
|
||||
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ -b $latest_release .
|
||||
# RUN make parse
|
||||
|
||||
# # Build master
|
||||
# WORKDIR /usr/src/master/
|
||||
# RUN git clone --depth 1 https://github.com/lemire/simdjson/ .
|
||||
# RUN make parse
|
||||
|
||||
# Build the current source
|
||||
COPY . /usr/src/current/
|
||||
WORKDIR /usr/src/current/
|
||||
RUN make checkperf
|
||||
@@ -0,0 +1,552 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include "simdjson.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace simdjson;
|
||||
using namespace benchmark;
|
||||
using namespace std;
|
||||
|
||||
const padded_string EMPTY_ARRAY("[]", 2);
|
||||
|
||||
const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
const char *NUMBERS_JSON = SIMDJSON_BENCHMARK_DATA_DIR "numbers.json";
|
||||
|
||||
|
||||
|
||||
static void numbers_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
if ((error = e.get(x))) { cerr << "found a node that is not an number: " << error << endl; break;}
|
||||
container.push_back(x);
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_scan);
|
||||
|
||||
static void numbers_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
if ((error = e.get(x))) { cerr << "found a node that is not an number: " << error << endl; break;}
|
||||
container[pos++] = x;
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_size_scan);
|
||||
|
||||
|
||||
static void numbers_type_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array" << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
if(actual_type != dom::element_type::DOUBLE) {
|
||||
cerr << "found a node that is not an number?" << endl; break;
|
||||
}
|
||||
double x;
|
||||
error = e.get(x);
|
||||
container.push_back(x);
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_type_scan);
|
||||
|
||||
static void numbers_type_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
if ((error = parser.load(NUMBERS_JSON).get(arr))) {
|
||||
cerr << "could not read " << NUMBERS_JSON << " as an array: " << error << endl;
|
||||
return;
|
||||
}
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
if(actual_type != dom::element_type::DOUBLE) {
|
||||
cerr << "found a node that is not an number?" << endl; break;
|
||||
}
|
||||
double x;
|
||||
error = e.get(x);
|
||||
container[pos++] = x;
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_type_size_scan);
|
||||
|
||||
static void numbers_load_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (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;
|
||||
break;
|
||||
}
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
if ((error = e.get(x))) { cerr << "found a node that is not an number: " << error << endl; break;}
|
||||
container.push_back(x);
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_load_scan);
|
||||
|
||||
static void numbers_load_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr;
|
||||
simdjson::error_code error;
|
||||
for (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;
|
||||
break;
|
||||
}
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (auto e : arr) {
|
||||
double x;
|
||||
if ((error = e.get(x))) { cerr << "found a node that is not an number?" << endl; break;}
|
||||
container[pos++] = x;
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_load_size_scan);
|
||||
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
|
||||
static void numbers_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (double x : arr) {
|
||||
container.push_back(x);
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_exceptions_scan);
|
||||
|
||||
static void numbers_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (auto e : arr) {
|
||||
container[pos++] = double(e);
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_exceptions_size_scan);
|
||||
|
||||
|
||||
|
||||
static void numbers_type_exceptions_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
if(actual_type != dom::element_type::DOUBLE) {
|
||||
cerr << "found a node that is not an number?" << endl; break;
|
||||
}
|
||||
container.push_back(double(e));
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_type_exceptions_scan);
|
||||
|
||||
static void numbers_type_exceptions_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (auto e : arr) {
|
||||
dom::element_type actual_type = e.type();
|
||||
if(actual_type != dom::element_type::DOUBLE) {
|
||||
cerr << "found a node that is not an number?" << endl; break;
|
||||
}
|
||||
container[pos++] = double(e);
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_type_exceptions_size_scan);
|
||||
|
||||
static void numbers_exceptions_load_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (UNUSED auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
for (double x : arr) {
|
||||
container.push_back(x);
|
||||
}
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_exceptions_load_scan);
|
||||
|
||||
static void numbers_exceptions_load_size_scan(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
for (UNUSED auto _ : state) {
|
||||
// this may hit the disk, but probably just once
|
||||
dom::array arr = parser.load(NUMBERS_JSON);
|
||||
std::vector<double> container;
|
||||
container.resize(arr.size());
|
||||
size_t pos = 0;
|
||||
for (double x : arr) {
|
||||
container[pos++] = x;
|
||||
}
|
||||
if(pos != container.size()) { cerr << "bad count" << endl; }
|
||||
benchmark::DoNotOptimize(container.data());
|
||||
benchmark::ClobberMemory();
|
||||
}
|
||||
}
|
||||
BENCHMARK(numbers_exceptions_load_size_scan);
|
||||
|
||||
|
||||
static void twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_count);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_count(State& state) {
|
||||
// Prints the number of results in twitter.json
|
||||
padded_string json = padded_string::load(TWITTER_JSON);
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
ParsedJson::Iterator iter(pj);
|
||||
// uint64_t result_count = doc["search_metadata"]["count"];
|
||||
if (!iter.move_to_key("search_metadata")) { return; }
|
||||
if (!iter.move_to_key("count")) { return; }
|
||||
if (!iter.is_integer()) { return; }
|
||||
int64_t result_count = iter.get_integer();
|
||||
|
||||
if (result_count != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(iterator_twitter_count);
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
static void twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
for (UNUSED auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::object user = tweet["user"];
|
||||
if (user["default_profile"]) {
|
||||
default_users.insert(user["screen_name"]);
|
||||
}
|
||||
}
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_default_profile);
|
||||
|
||||
static void twitter_image_sizes(State& state) {
|
||||
// Count unique image sizes
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(TWITTER_JSON);
|
||||
simdjson::error_code error;
|
||||
for (UNUSED auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
for (dom::object tweet : doc["statuses"]) {
|
||||
dom::array media;
|
||||
if (not (error = tweet["entities"]["media"].get(media))) {
|
||||
for (dom::object image : media) {
|
||||
for (auto size : image["sizes"].get<dom::object>()) {
|
||||
image_sizes.insert({ size.value["w"], size.value["h"] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(twitter_image_sizes);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void error_code_twitter_count(State& state) noexcept {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
uint64_t value;
|
||||
if ((error = doc["search_metadata"]["count"].get(value))) { return; }
|
||||
if (value != 100) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_count);
|
||||
|
||||
static void error_code_twitter_default_profile(State& state) noexcept {
|
||||
// Count unique users with a default profile.
|
||||
dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
|
||||
dom::array tweets;
|
||||
if ((error = doc["statuses"].get(tweets))) { return; }
|
||||
for (dom::element tweet : tweets) {
|
||||
dom::object user;
|
||||
if ((error = tweet["user"].get(user))) { return; }
|
||||
bool default_profile;
|
||||
if ((error = user["default_profile"].get(default_profile))) { return; }
|
||||
if (default_profile) {
|
||||
std::string_view screen_name;
|
||||
if ((error = user["screen_name"].get(screen_name))) { return; }
|
||||
default_users.insert(screen_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_default_profile);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_default_profile(State& state) {
|
||||
// Count unique users with a default profile.
|
||||
padded_string json;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
set<string_view> default_users;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
// for (dom::object tweet : doc["statuses"]) {
|
||||
if (!(iter.move_to_key("statuses") && iter.is_array())) { return; }
|
||||
if (iter.down()) { // first status
|
||||
do {
|
||||
|
||||
// dom::object user = tweet["user"];
|
||||
if (!(iter.move_to_key("user") && iter.is_object())) { return; }
|
||||
|
||||
// if (user["default_profile"]) {
|
||||
if (iter.move_to_key("default_profile")) {
|
||||
if (iter.is_true()) {
|
||||
if (!iter.up()) { return; } // back to user
|
||||
|
||||
// default_users.insert(user["screen_name"]);
|
||||
if (!(iter.move_to_key("screen_name") && iter.is_string())) { return; }
|
||||
default_users.insert(string_view(iter.get_string(), iter.get_string_length()));
|
||||
}
|
||||
if (!iter.up()) { return; } // back to user
|
||||
}
|
||||
|
||||
if (!iter.up()) { return; } // back to status
|
||||
|
||||
} while (iter.next()); // next status
|
||||
}
|
||||
|
||||
if (default_users.size() != 86) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
BENCHMARK(iterator_twitter_default_profile);
|
||||
|
||||
static void error_code_twitter_image_sizes(State& state) noexcept {
|
||||
// Count unique image sizes
|
||||
dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
dom::element doc;
|
||||
if ((error = parser.load(TWITTER_JSON).get(doc))) { std::cerr << error << std::endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
dom::array statuses;
|
||||
if ((error = doc["statuses"].get(statuses))) { return; }
|
||||
for (dom::element tweet : statuses) {
|
||||
dom::array images;
|
||||
if (not (error = tweet["entities"]["media"].get(images))) {
|
||||
for (dom::element image : images) {
|
||||
dom::object sizes;
|
||||
if ((error = image["sizes"].get(sizes))) { return; }
|
||||
for (auto size : sizes) {
|
||||
uint64_t width, height;
|
||||
if ((error = size.value["w"].get(width))) { return; }
|
||||
if ((error = size.value["h"].get(height))) { return; }
|
||||
image_sizes.insert({ width, height });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(error_code_twitter_image_sizes);
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void iterator_twitter_image_sizes(State& state) {
|
||||
// Count unique image sizes
|
||||
padded_string json;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
ParsedJson pj = build_parsed_json(json);
|
||||
for (UNUSED auto _ : state) {
|
||||
set<tuple<uint64_t, uint64_t>> image_sizes;
|
||||
ParsedJson::Iterator iter(pj);
|
||||
|
||||
// for (dom::object tweet : doc["statuses"]) {
|
||||
if (!(iter.move_to_key("statuses") && iter.is_array())) { return; }
|
||||
if (iter.down()) { // first status
|
||||
do {
|
||||
|
||||
// dom::object media;
|
||||
// not_found = tweet["entities"]["media"].get(media);
|
||||
// if (!not_found) {
|
||||
if (iter.move_to_key("entities")) {
|
||||
if (!iter.is_object()) { return; }
|
||||
if (iter.move_to_key("media")) {
|
||||
if (!iter.is_array()) { return; }
|
||||
|
||||
// for (dom::object image : media) {
|
||||
if (iter.down()) { // first media
|
||||
do {
|
||||
|
||||
// for (auto [key, size] : dom::object(image["sizes"])) {
|
||||
if (!(iter.move_to_key("sizes") && iter.is_object())) { return; }
|
||||
if (iter.down()) { // first size
|
||||
do {
|
||||
iter.move_to_value();
|
||||
|
||||
// image_sizes.insert({ size["w"], size["h"] });
|
||||
if (!(iter.move_to_key("w")) && !iter.is_integer()) { return; }
|
||||
uint64_t width = iter.get_integer();
|
||||
if (!iter.up()) { return; } // back to size
|
||||
if (!(iter.move_to_key("h")) && !iter.is_integer()) { return; }
|
||||
uint64_t height = iter.get_integer();
|
||||
if (!iter.up()) { return; } // back to size
|
||||
image_sizes.insert({ width, height });
|
||||
|
||||
} while (iter.next()); // next size
|
||||
if (!iter.up()) { return; } // back to sizes
|
||||
}
|
||||
if (!iter.up()) { return; } // back to image
|
||||
} while (iter.next()); // next image
|
||||
if (!iter.up()) { return; } // back to media
|
||||
}
|
||||
if (!iter.up()) { return; } // back to entities
|
||||
}
|
||||
if (!iter.up()) { return; } // back to status
|
||||
}
|
||||
} while (iter.next()); // next status
|
||||
}
|
||||
|
||||
if (image_sizes.size() != 15) { return; };
|
||||
}
|
||||
}
|
||||
BENCHMARK(iterator_twitter_image_sizes);
|
||||
|
||||
static void print_json(State& state) noexcept {
|
||||
// Prints the number of results in twitter.json
|
||||
dom::parser parser;
|
||||
|
||||
padded_string json;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(json);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
|
||||
int code = json_parse(json, parser);
|
||||
if (code) { cerr << error_message(code) << endl; return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
std::stringstream s;
|
||||
if (!parser.print_json(s)) { cerr << "print_json failed" << endl; return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(print_json);
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,162 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
using namespace benchmark;
|
||||
using namespace std;
|
||||
|
||||
const padded_string EMPTY_ARRAY("[]", 2);
|
||||
const char *TWITTER_JSON = SIMDJSON_BENCHMARK_DATA_DIR "twitter.json";
|
||||
const char *GSOC_JSON = SIMDJSON_BENCHMARK_DATA_DIR "gsoc-2018.json";
|
||||
|
||||
|
||||
|
||||
static void parse_twitter(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(TWITTER_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
error = parser.allocate(docdata.size());
|
||||
if(error) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (UNUSED auto _ : state) {
|
||||
dom::element doc;
|
||||
bytes += docdata.size();
|
||||
;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse twitter.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
benchmark::DoNotOptimize(doc);
|
||||
}
|
||||
// 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(parse_twitter)->Repetitions(10)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
static void parse_gsoc(State& state) {
|
||||
dom::parser parser;
|
||||
padded_string docdata;
|
||||
auto error = padded_string::load(GSOC_JSON).get(docdata);
|
||||
if(error) {
|
||||
cerr << "could not parse gsoc-2018.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
// we do not want mem. alloc. in the loop.
|
||||
error = parser.allocate(docdata.size());
|
||||
if(error) {
|
||||
cout << error << endl;
|
||||
return;
|
||||
}
|
||||
size_t bytes = 0;
|
||||
for (UNUSED auto _ : state) {
|
||||
bytes += docdata.size();
|
||||
dom::element doc;
|
||||
if ((error = parser.parse(docdata).get(doc))) {
|
||||
cerr << "could not parse gsoc-2018.json" << error << endl;
|
||||
return;
|
||||
}
|
||||
benchmark::DoNotOptimize(doc);
|
||||
}
|
||||
// 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(parse_gsoc)->Repetitions(10)->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
|
||||
return *(std::max_element(std::begin(v), std::end(v)));
|
||||
})->DisplayAggregatesOnly(true);
|
||||
|
||||
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void json_parse(State& state) {
|
||||
ParsedJson pj;
|
||||
if (!pj.allocate_capacity(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
auto error = json_parse(EMPTY_ARRAY, pj);
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
BENCHMARK(json_parse);
|
||||
static void parser_parse_error_code(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(parser_parse_error_code);
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void parser_parse_exception(State& state) {
|
||||
dom::parser parser;
|
||||
if (parser.allocate(EMPTY_ARRAY.length())) { return; }
|
||||
for (UNUSED auto _ : state) {
|
||||
try {
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK(parser_parse_exception);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
static void build_parsed_json(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
dom::parser parser = simdjson::build_parsed_json(EMPTY_ARRAY);
|
||||
if (!parser.valid) { return; }
|
||||
}
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
BENCHMARK(build_parsed_json);
|
||||
static void document_parse_error_code(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
dom::parser parser;
|
||||
auto error = parser.parse(EMPTY_ARRAY).error();
|
||||
if (error) { return; }
|
||||
}
|
||||
}
|
||||
BENCHMARK(document_parse_error_code);
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
static void document_parse_exception(State& state) {
|
||||
for (UNUSED auto _ : state) {
|
||||
try {
|
||||
dom::parser parser;
|
||||
UNUSED dom::element doc = parser.parse(EMPTY_ARRAY);
|
||||
} catch(simdjson_error &j) {
|
||||
cout << j.what() << endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK(document_parse_exception);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,466 @@
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <cinttypes>
|
||||
#include <initializer_list>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "benchmarker.h"
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
|
||||
// Stash the exe_name in main() for functions to use
|
||||
char* exe_name;
|
||||
|
||||
void print_usage(ostream& out) {
|
||||
out << "Usage: " << exe_name << " [-v] [-n #] [-s STAGE] [-a ARCH]" << endl;
|
||||
out << endl;
|
||||
out << "Runs the parser against jsonexamples/generated json files in a loop, measuring speed and other statistics." << endl;
|
||||
out << endl;
|
||||
out << "Options:" << endl;
|
||||
out << endl;
|
||||
out << "-n # - Number of iterations per file. Default: 400" << endl;
|
||||
out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl;
|
||||
out << "-v - Verbose output." << endl;
|
||||
out << "-s STAGE - Stop after the given stage." << endl;
|
||||
out << " -s stage1 - Stop after find_structural_bits." << endl;
|
||||
out << " -s all - Run all stages." << endl;
|
||||
out << "-a ARCH - Use the parser with the designated architecture (HASWELL, WESTMERE" << endl;
|
||||
out << " or ARM64). By default, detects best supported architecture." << endl;
|
||||
}
|
||||
|
||||
void exit_usage(string message) {
|
||||
cerr << message << endl;
|
||||
cerr << endl;
|
||||
print_usage(cerr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct option_struct {
|
||||
bool stage1_only = false;
|
||||
|
||||
int32_t iterations = 400;
|
||||
int32_t iteration_step = 50;
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
option_struct(int argc, char **argv) {
|
||||
int c;
|
||||
|
||||
while ((c = getopt(argc, argv, "vtn:i:a:s:")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
iterations = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
iteration_step = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a':
|
||||
simdjson::active_implementation = simdjson::available_implementations[optarg];
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
stage1_only = true;
|
||||
} else if (!strcmp(optarg, "all")) {
|
||||
stage1_only = false;
|
||||
} else {
|
||||
exit_usage(string("Unsupported option value -s ") + optarg + ": expected -s stage1 or all");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
exit_error(string("Unexpected argument ") + std::string(1,static_cast<char>(c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void each_stage(const F& f) const {
|
||||
f(BenchmarkStage::STAGE1);
|
||||
if (!this->stage1_only) {
|
||||
f(BenchmarkStage::STAGE2);
|
||||
f(BenchmarkStage::ALL);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct feature_benchmarker {
|
||||
benchmarker utf8;
|
||||
benchmarker utf8_miss;
|
||||
benchmarker escape;
|
||||
benchmarker escape_miss;
|
||||
benchmarker empty;
|
||||
benchmarker empty_miss;
|
||||
benchmarker struct7;
|
||||
benchmarker struct7_miss;
|
||||
benchmarker struct7_full;
|
||||
benchmarker struct15;
|
||||
benchmarker struct15_miss;
|
||||
benchmarker struct23;
|
||||
benchmarker struct23_miss;
|
||||
|
||||
feature_benchmarker(event_collector& collector) :
|
||||
utf8 (SIMDJSON_BENCHMARK_DATA_DIR "generated/utf-8.json", collector),
|
||||
utf8_miss (SIMDJSON_BENCHMARK_DATA_DIR "generated/utf-8-miss.json", collector),
|
||||
escape (SIMDJSON_BENCHMARK_DATA_DIR "generated/escape.json", collector),
|
||||
escape_miss (SIMDJSON_BENCHMARK_DATA_DIR "generated/escape-miss.json", collector),
|
||||
empty (SIMDJSON_BENCHMARK_DATA_DIR "generated/0-structurals.json", collector),
|
||||
empty_miss (SIMDJSON_BENCHMARK_DATA_DIR "generated/0-structurals-miss.json", collector),
|
||||
struct7 (SIMDJSON_BENCHMARK_DATA_DIR "generated/7-structurals.json", collector),
|
||||
struct7_miss (SIMDJSON_BENCHMARK_DATA_DIR "generated/7-structurals-miss.json", collector),
|
||||
struct7_full (SIMDJSON_BENCHMARK_DATA_DIR "generated/7-structurals-full.json", collector),
|
||||
struct15 (SIMDJSON_BENCHMARK_DATA_DIR "generated/15-structurals.json", collector),
|
||||
struct15_miss(SIMDJSON_BENCHMARK_DATA_DIR "generated/15-structurals-miss.json", collector),
|
||||
struct23 (SIMDJSON_BENCHMARK_DATA_DIR "generated/23-structurals.json", collector),
|
||||
struct23_miss(SIMDJSON_BENCHMARK_DATA_DIR "generated/23-structurals-miss.json", collector)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
really_inline void run_iterations(size_t iterations, bool stage1_only=false) {
|
||||
struct7.run_iterations(iterations, stage1_only);
|
||||
struct7_miss.run_iterations(iterations, stage1_only);
|
||||
struct7_full.run_iterations(iterations, stage1_only);
|
||||
utf8.run_iterations(iterations, stage1_only);
|
||||
utf8_miss.run_iterations(iterations, stage1_only);
|
||||
escape.run_iterations(iterations, stage1_only);
|
||||
escape_miss.run_iterations(iterations, stage1_only);
|
||||
empty.run_iterations(iterations, stage1_only);
|
||||
empty_miss.run_iterations(iterations, stage1_only);
|
||||
struct15.run_iterations(iterations, stage1_only);
|
||||
struct15_miss.run_iterations(iterations, stage1_only);
|
||||
struct23.run_iterations(iterations, stage1_only);
|
||||
struct23_miss.run_iterations(iterations, stage1_only);
|
||||
}
|
||||
|
||||
double cost_per_block(BenchmarkStage stage, const benchmarker& feature, size_t feature_blocks, const benchmarker& base) const {
|
||||
return (feature[stage].best.elapsed_ns() - base[stage].best.elapsed_ns()) / double(feature_blocks);
|
||||
}
|
||||
|
||||
// Whether we're recording cache miss and branch miss events
|
||||
bool has_events() const {
|
||||
return empty.collector.has_events();
|
||||
}
|
||||
|
||||
// Base cost of any block (including empty ones)
|
||||
double base_cost(BenchmarkStage stage) const {
|
||||
return (empty[stage].best.elapsed_ns() / double(empty.stats->blocks));
|
||||
}
|
||||
|
||||
// Extra cost of a 1-7 structural block over an empty block
|
||||
double struct1_7_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct7, struct7.stats->blocks_with_1_structural, empty);
|
||||
}
|
||||
// Extra cost of an 1-7-structural miss
|
||||
double struct1_7_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct7_miss, struct7_miss.stats->blocks_with_1_structural, struct7);
|
||||
}
|
||||
// Rate of 1-7-structural misses per 8-structural flip
|
||||
double struct1_7_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
|
||||
}
|
||||
|
||||
// Extra cost of an 8-15 structural block over a 1-7 structural block
|
||||
double struct8_15_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct15, struct15.stats->blocks_with_8_structurals, struct7);
|
||||
}
|
||||
// Extra cost of an 8-15-structural miss over a 1-7 miss
|
||||
double struct8_15_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct15_miss, struct15_miss.stats->blocks_with_8_structurals_flipped, struct15);
|
||||
}
|
||||
// Rate of 8-15-structural misses per 8-structural flip
|
||||
double struct8_15_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
|
||||
}
|
||||
|
||||
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
|
||||
double struct16_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct23, struct23.stats->blocks_with_16_structurals, struct15);
|
||||
}
|
||||
// Extra cost of a 16-structural miss over an 8-15 miss
|
||||
double struct16_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, struct23_miss, struct23_miss.stats->blocks_with_16_structurals_flipped, struct23);
|
||||
}
|
||||
// Rate of 16-structural misses per 16-structural flip
|
||||
double struct16_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
|
||||
}
|
||||
|
||||
// Extra cost of having UTF-8 in a block
|
||||
double utf8_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, utf8, utf8.stats->blocks_with_utf8, struct7_full);
|
||||
}
|
||||
// Extra cost of a UTF-8 miss
|
||||
double utf8_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, utf8_miss, utf8_miss.stats->blocks_with_utf8_flipped, utf8);
|
||||
}
|
||||
// Rate of UTF-8 misses per UTF-8 flip
|
||||
double utf8_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
|
||||
}
|
||||
|
||||
// Extra cost of having escapes in a block
|
||||
double escape_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, escape, escape.stats->blocks_with_escapes, struct7_full);
|
||||
}
|
||||
// Extra cost of an escape miss
|
||||
double escape_miss_cost(BenchmarkStage stage) const {
|
||||
return cost_per_block(stage, escape_miss, escape_miss.stats->blocks_with_escapes_flipped, escape);
|
||||
}
|
||||
// Rate of escape misses per escape flip
|
||||
double escape_miss_rate(BenchmarkStage stage) const {
|
||||
if (!has_events()) { return 1; }
|
||||
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
|
||||
}
|
||||
|
||||
double calc_expected_feature_cost(BenchmarkStage stage, const benchmarker& file) const {
|
||||
// Expected base ns/block (empty)
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = base_cost(stage) * double(stats.blocks);
|
||||
expected += struct1_7_cost(stage) * double(stats.blocks_with_1_structural);
|
||||
expected += utf8_cost(stage) * double(stats.blocks_with_utf8);
|
||||
expected += escape_cost(stage) * double(stats.blocks_with_escapes);
|
||||
expected += struct8_15_cost(stage) * double(stats.blocks_with_8_structurals);
|
||||
expected += struct16_cost(stage) * double(stats.blocks_with_16_structurals);
|
||||
return expected / double(stats.blocks);
|
||||
}
|
||||
|
||||
double calc_expected_miss_cost(BenchmarkStage stage, const benchmarker& file) const {
|
||||
// Expected base ns/block (empty)
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = struct1_7_miss_cost(stage) * double(stats.blocks_with_1_structural_flipped) * struct1_7_miss_rate(stage);
|
||||
expected += utf8_miss_cost(stage) * double(stats.blocks_with_utf8_flipped) * utf8_miss_rate(stage);
|
||||
expected += escape_miss_cost(stage) * double(stats.blocks_with_escapes_flipped) * escape_miss_rate(stage);
|
||||
expected += struct8_15_miss_cost(stage) * double(stats.blocks_with_8_structurals_flipped) * struct8_15_miss_rate(stage);
|
||||
expected += struct16_miss_cost(stage) * double(stats.blocks_with_16_structurals_flipped) * struct16_miss_rate(stage);
|
||||
return expected / double(stats.blocks);
|
||||
}
|
||||
|
||||
double calc_expected_misses(BenchmarkStage stage, const benchmarker& file) const {
|
||||
json_stats& stats = *file.stats;
|
||||
double expected = double(stats.blocks_with_1_structural_flipped) * struct1_7_miss_rate(stage);
|
||||
expected += double(stats.blocks_with_utf8_flipped) * utf8_miss_rate(stage);
|
||||
expected += double(stats.blocks_with_escapes_flipped) * escape_miss_rate(stage);
|
||||
expected += double(stats.blocks_with_8_structurals_flipped) * struct8_15_miss_rate(stage);
|
||||
expected += double(stats.blocks_with_16_structurals_flipped) * struct16_miss_rate(stage);
|
||||
return expected;
|
||||
}
|
||||
|
||||
double calc_expected(BenchmarkStage stage, const benchmarker& file) const {
|
||||
return calc_expected_feature_cost(stage, file) + calc_expected_miss_cost(stage, file);
|
||||
}
|
||||
|
||||
void print(const option_struct& options) const {
|
||||
printf("\n");
|
||||
printf("Features in ns/block (64 bytes):\n");
|
||||
printf("\n");
|
||||
printf("| %-8s ", "Stage");
|
||||
printf("| %8s ", "Base");
|
||||
printf("| %8s ", "7 Struct");
|
||||
printf("| %8s ", "UTF-8");
|
||||
printf("| %8s ", "Escape");
|
||||
printf("| %8s ", "15 Str.");
|
||||
printf("| %8s ", "16+ Str.");
|
||||
printf("| %15s ", "7 Struct Miss");
|
||||
printf("| %15s ", "UTF-8 Miss");
|
||||
printf("| %15s ", "Escape Miss");
|
||||
printf("| %15s ", "15 Str. Miss");
|
||||
printf("| %15s ", "16+ Str. Miss");
|
||||
printf("|\n");
|
||||
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|\n");
|
||||
|
||||
options.each_stage([&](auto stage) {
|
||||
printf("| %-8s ", benchmark_stage_name(stage));
|
||||
printf("| %8.3g ", base_cost(stage));
|
||||
printf("| %8.3g ", struct1_7_cost(stage));
|
||||
printf("| %8.3g ", utf8_cost(stage));
|
||||
printf("| %8.3g ", escape_cost(stage));
|
||||
printf("| %8.3g ", struct8_15_cost(stage));
|
||||
printf("| %8.3g ", struct16_cost(stage));
|
||||
if (has_events()) {
|
||||
printf("| %8.3g (%3d%%) ", struct1_7_miss_cost(stage), int(struct1_7_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", utf8_miss_cost(stage), int(utf8_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", escape_miss_cost(stage), int(escape_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", struct8_15_miss_cost(stage), int(struct8_15_miss_rate(stage)*100));
|
||||
printf("| %8.3g (%3d%%) ", struct16_miss_cost(stage), int(struct16_miss_rate(stage)*100));
|
||||
} else {
|
||||
printf("| %8.3g ", struct1_7_miss_cost(stage));
|
||||
printf("| %8.3g ", utf8_miss_cost(stage));
|
||||
printf("| %8.3g ", escape_miss_cost(stage));
|
||||
printf("| %8.3g ", struct8_15_miss_cost(stage));
|
||||
printf("| %8.3g ", struct16_miss_cost(stage));
|
||||
}
|
||||
printf("|\n");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
|
||||
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
|
||||
double calc = features.calc_expected(stage, results);
|
||||
double actual_misses = results[stage].best.branch_misses();
|
||||
double calc_misses = features.calc_expected_misses(stage, results);
|
||||
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
|
||||
printf(" | %-8s ", benchmark_stage_name(stage));
|
||||
printf("| %-15s ", filename);
|
||||
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
|
||||
printf("| %8.3g ", calc_miss_cost);
|
||||
printf("| %8.3g ", calc);
|
||||
printf("| %8.3g ", actual);
|
||||
printf("| %+8.3g ", actual - calc);
|
||||
printf("| %13llu ", (long long unsigned)(calc_misses));
|
||||
if (features.has_events()) {
|
||||
printf("| %13llu ", (long long unsigned)(actual_misses));
|
||||
printf("| %+13lld ", (long long int)(actual_misses - calc_misses));
|
||||
double miss_adjustment = calc_miss_cost * (double(int64_t(actual_misses - calc_misses)) / calc_misses);
|
||||
printf("| %8.3g ", calc_miss_cost + miss_adjustment);
|
||||
printf("| %+8.3g ", actual - (calc + miss_adjustment));
|
||||
}
|
||||
printf("|\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Read options
|
||||
exe_name = argv[0];
|
||||
option_struct options(argc, argv);
|
||||
if (options.verbose) {
|
||||
verbose_stream = &cout;
|
||||
}
|
||||
|
||||
// Initialize the event collector. We put this early so if it prints an error message, it's the
|
||||
// first thing printed.
|
||||
event_collector collector;
|
||||
|
||||
// Set up benchmarkers by reading all files
|
||||
feature_benchmarker features(collector);
|
||||
benchmarker gsoc_2018(SIMDJSON_BENCHMARK_DATA_DIR "gsoc-2018.json", collector);
|
||||
benchmarker twitter(SIMDJSON_BENCHMARK_DATA_DIR "twitter.json", collector);
|
||||
benchmarker random(SIMDJSON_BENCHMARK_DATA_DIR "random.json", collector);
|
||||
|
||||
// Run the benchmarks
|
||||
progress_bar progress(options.iterations, 100);
|
||||
// Put the if (options.stage1_only) *outside* the loop so that run_iterations will be optimized
|
||||
if (options.stage1_only) {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
features.run_iterations(options.iteration_step, true);
|
||||
gsoc_2018.run_iterations(options.iteration_step, true);
|
||||
twitter.run_iterations(options.iteration_step, true);
|
||||
random.run_iterations(options.iteration_step, true);
|
||||
}
|
||||
} else {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
features.run_iterations(options.iteration_step, false);
|
||||
gsoc_2018.run_iterations(options.iteration_step, false);
|
||||
twitter.run_iterations(options.iteration_step, false);
|
||||
random.run_iterations(options.iteration_step, false);
|
||||
}
|
||||
}
|
||||
if (!options.verbose) { progress.erase(); }
|
||||
|
||||
features.print(options);
|
||||
|
||||
// Gauge effectiveness
|
||||
if (options.verbose) {
|
||||
printf("\n");
|
||||
printf(" Effectiveness Check: Estimated vs. Actual ns/block for real files:\n");
|
||||
printf("\n");
|
||||
printf(" | %8s ", "Stage");
|
||||
printf("| %-15s ", "File");
|
||||
printf("| %11s ", "Est. (Base)");
|
||||
printf("| %11s ", "Est. (Miss)");
|
||||
printf("| %8s ", "Est.");
|
||||
printf("| %8s ", "Actual");
|
||||
printf("| %8s ", "Diff");
|
||||
printf("| %13s ", "Est. Misses");
|
||||
if (features.has_events()) {
|
||||
printf("| %13s ", "Actual Misses");
|
||||
printf("| %13s ", "Diff (Misses)");
|
||||
printf("| %13s ", "Adjusted Miss");
|
||||
printf("| %13s ", "Adjusted Diff");
|
||||
}
|
||||
printf("|\n");
|
||||
printf(" |%.10s", "---------------------------------------");
|
||||
printf("|%.17s", "---------------------------------------");
|
||||
printf("|%.13s", "---------------------------------------");
|
||||
printf("|%.13s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.10s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
if (features.has_events()) {
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
printf("|%.15s", "---------------------------------------");
|
||||
}
|
||||
printf("|\n");
|
||||
|
||||
options.each_stage([&](auto stage) {
|
||||
print_file_effectiveness(stage, "gsoc-2018.json", gsoc_2018, features);
|
||||
print_file_effectiveness(stage, "twitter.json", twitter, features);
|
||||
print_file_effectiveness(stage, "random.json", random, features);
|
||||
});
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
+62
-179
@@ -1,82 +1,7 @@
|
||||
#ifndef _BENCHMARK_H_
|
||||
#define _BENCHMARK_H_
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#ifdef __x86_64__
|
||||
|
||||
const char *unitname = "cycles";
|
||||
|
||||
#define RDTSC_START(cycles) \
|
||||
do { \
|
||||
uint32_t cyc_high, cyc_low; \
|
||||
__asm volatile("cpuid\n" \
|
||||
"rdtsc\n" \
|
||||
"mov %%edx, %0\n" \
|
||||
"mov %%eax, %1" \
|
||||
: "=r"(cyc_high), "=r"(cyc_low) \
|
||||
: \
|
||||
: /* no read only */ \
|
||||
"%rax", "%rbx", "%rcx", "%rdx" /* clobbers */ \
|
||||
); \
|
||||
(cycles) = ((uint64_t)cyc_high << 32) | cyc_low; \
|
||||
} while (0)
|
||||
|
||||
#define RDTSC_STOP(cycles) \
|
||||
do { \
|
||||
uint32_t cyc_high, cyc_low; \
|
||||
__asm volatile("rdtscp\n" \
|
||||
"mov %%edx, %0\n" \
|
||||
"mov %%eax, %1\n" \
|
||||
"cpuid" \
|
||||
: "=r"(cyc_high), "=r"(cyc_low) \
|
||||
: /* no read only registers */ \
|
||||
: "%rax", "%rbx", "%rcx", "%rdx" /* clobbers */ \
|
||||
); \
|
||||
(cycles) = ((uint64_t)cyc_high << 32) | cyc_low; \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
const char *unitname = " (clock units) ";
|
||||
|
||||
#define RDTSC_START(cycles) \
|
||||
do { \
|
||||
cycles = clock(); \
|
||||
} while (0)
|
||||
|
||||
#define RDTSC_STOP(cycles) \
|
||||
do { \
|
||||
cycles = clock(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
static __attribute__((noinline)) uint64_t rdtsc_overhead_func(uint64_t dummy) {
|
||||
return dummy;
|
||||
}
|
||||
|
||||
uint64_t global_rdtsc_overhead = (uint64_t)UINT64_MAX;
|
||||
|
||||
#define RDTSC_SET_OVERHEAD(test, repeat) \
|
||||
do { \
|
||||
uint64_t cycles_start, cycles_final, cycles_diff; \
|
||||
uint64_t min_diff = UINT64_MAX; \
|
||||
for (int i = 0; i < repeat; i++) { \
|
||||
__asm volatile("" ::: /* pretend to clobber */ "memory"); \
|
||||
RDTSC_START(cycles_start); \
|
||||
test; \
|
||||
RDTSC_STOP(cycles_final); \
|
||||
cycles_diff = (cycles_final - cycles_start); \
|
||||
if (cycles_diff < min_diff) \
|
||||
min_diff = cycles_diff; \
|
||||
} \
|
||||
global_rdtsc_overhead = min_diff; \
|
||||
} while (0)
|
||||
|
||||
double diff(timespec start, timespec end) {
|
||||
return ((end.tv_nsec + 1000000000 * end.tv_sec) -
|
||||
(start.tv_nsec + 1000000000 * start.tv_sec)) /
|
||||
1000000000.0;
|
||||
}
|
||||
#include "event_counter.h"
|
||||
|
||||
/*
|
||||
* Prints the best number of operations per cycle where
|
||||
@@ -86,58 +11,48 @@ double diff(timespec start, timespec end) {
|
||||
*/
|
||||
#define BEST_TIME(name, test, expected, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (global_rdtsc_overhead == UINT64_MAX) { \
|
||||
RDTSC_SET_OVERHEAD(rdtsc_overhead_func(1), repeat); \
|
||||
} \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
uint64_t cycles_start, cycles_final, cycles_diff; \
|
||||
uint64_t min_diff = (uint64_t)-1; \
|
||||
double min_sumclockdiff = DBL_MAX; \
|
||||
uint64_t sum_diff = 0; \
|
||||
double sumclockdiff = 0; \
|
||||
struct timespec time1, time2; \
|
||||
for (int i = 0; i < repeat; i++) { \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) { \
|
||||
pre; \
|
||||
__asm volatile("" ::: /* pretend to clobber */ "memory"); \
|
||||
clock_gettime(CLOCK_REALTIME, &time1); \
|
||||
RDTSC_START(cycles_start); \
|
||||
std::atomic_thread_fence(std::memory_order_acquire); \
|
||||
collector.start(); \
|
||||
if (test != expected) { \
|
||||
fprintf(stderr, "not expected (%d , %d )", (int)test, (int)expected); \
|
||||
break; \
|
||||
} \
|
||||
RDTSC_STOP(cycles_final); \
|
||||
clock_gettime(CLOCK_REALTIME, &time2); \
|
||||
double thistiming = diff(time1, time2); \
|
||||
sumclockdiff += thistiming; \
|
||||
if (thistiming < min_sumclockdiff) \
|
||||
min_sumclockdiff = thistiming; \
|
||||
cycles_diff = (cycles_final - cycles_start - global_rdtsc_overhead); \
|
||||
if (cycles_diff < min_diff) \
|
||||
min_diff = cycles_diff; \
|
||||
sum_diff += cycles_diff; \
|
||||
std::atomic_thread_fence(std::memory_order_release); \
|
||||
event_count allocate_count = collector.end(); \
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
uint64_t S = size; \
|
||||
float cycle_per_op = (min_diff) / (double)S; \
|
||||
float avg_cycle_per_op = (sum_diff) / ((double)S * repeat); \
|
||||
double avg_gb_per_s = \
|
||||
((double)S * repeat) / ((sumclockdiff)*1000.0 * 1000.0 * 1000.0); \
|
||||
double max_gb_per_s = \
|
||||
((double)S) / ((min_sumclockdiff)*1000.0 * 1000.0 * 1000.0); \
|
||||
if (verbose) \
|
||||
printf(" %7.3f %s per input byte (best) ", cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf(" %7.3f %s per input byte (avg) ", avg_cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf(" %7.3f GB/s (error margin: %.3f GB/s)", max_gb_per_s, \
|
||||
-avg_gb_per_s + max_gb_per_s); \
|
||||
if (!verbose) \
|
||||
printf(" %20.3f %20.3f %20.3f %20.3f ", cycle_per_op, \
|
||||
avg_cycle_per_op - cycle_per_op, max_gb_per_s, \
|
||||
-avg_gb_per_s + max_gb_per_s); \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
} \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
} while (0)
|
||||
@@ -145,78 +60,46 @@ double diff(timespec start, timespec end) {
|
||||
// like BEST_TIME, but no check
|
||||
#define BEST_TIME_NOCHECK(name, test, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (global_rdtsc_overhead == UINT64_MAX) { \
|
||||
RDTSC_SET_OVERHEAD(rdtsc_overhead_func(1), repeat); \
|
||||
} \
|
||||
if (verbose) \
|
||||
printf("%-40s\t: ", name); \
|
||||
else \
|
||||
printf("\"%-40s\"", name); \
|
||||
fflush(NULL); \
|
||||
uint64_t cycles_start, cycles_final, cycles_diff; \
|
||||
uint64_t min_diff = (uint64_t)-1; \
|
||||
uint64_t sum_diff = 0; \
|
||||
for (int i = 0; i < repeat; i++) { \
|
||||
event_collector collector; \
|
||||
event_aggregate aggregate{}; \
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) { \
|
||||
pre; \
|
||||
__asm volatile("" ::: /* pretend to clobber */ "memory"); \
|
||||
RDTSC_START(cycles_start); \
|
||||
std::atomic_thread_fence(std::memory_order_acquire); \
|
||||
collector.start(); \
|
||||
test; \
|
||||
RDTSC_STOP(cycles_final); \
|
||||
cycles_diff = (cycles_final - cycles_start - global_rdtsc_overhead); \
|
||||
if (cycles_diff < min_diff) \
|
||||
min_diff = cycles_diff; \
|
||||
sum_diff += cycles_diff; \
|
||||
std::atomic_thread_fence(std::memory_order_release); \
|
||||
event_count allocate_count = collector.end(); \
|
||||
aggregate << allocate_count; \
|
||||
} \
|
||||
uint64_t S = size; \
|
||||
float cycle_per_op = (min_diff) / (double)S; \
|
||||
float avg_cycle_per_op = (sum_diff) / ((double)S * repeat); \
|
||||
if (verbose) \
|
||||
printf(" %.3f %s per input byte (best) ", cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf(" %.3f %s per input byte (avg) ", avg_cycle_per_op, unitname); \
|
||||
if (verbose) \
|
||||
printf("\n"); \
|
||||
if (!verbose) \
|
||||
printf(" %.3f ", cycle_per_op); \
|
||||
fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
// like BEST_TIME except that we run a function to check the result
|
||||
#define BEST_TIME_CHECK(test, check, pre, repeat, size, verbose) \
|
||||
do { \
|
||||
if (global_rdtsc_overhead == UINT64_MAX) { \
|
||||
RDTSC_SET_OVERHEAD(rdtsc_overhead_func(1), repeat); \
|
||||
} \
|
||||
if (verbose) \
|
||||
printf("%-60s\t:\n", #test); \
|
||||
fflush(NULL); \
|
||||
uint64_t cycles_start, cycles_final, cycles_diff; \
|
||||
uint64_t min_diff = (uint64_t)-1; \
|
||||
uint64_t sum_diff = 0; \
|
||||
for (int i = 0; i < repeat; i++) { \
|
||||
pre; \
|
||||
__asm volatile("" ::: /* pretend to clobber */ "memory"); \
|
||||
RDTSC_START(cycles_start); \
|
||||
test; \
|
||||
RDTSC_STOP(cycles_final); \
|
||||
if (!check) { \
|
||||
printf("error"); \
|
||||
break; \
|
||||
if (collector.has_events()) { \
|
||||
printf("%7.3f", aggregate.best.cycles() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" cycles/byte "); \
|
||||
} \
|
||||
cycles_diff = (cycles_final - cycles_start - global_rdtsc_overhead); \
|
||||
if (cycles_diff < min_diff) \
|
||||
min_diff = cycles_diff; \
|
||||
sum_diff += cycles_diff; \
|
||||
printf("\t"); \
|
||||
printf("%7.3f", \
|
||||
aggregate.best.instructions() / static_cast<double>(size)); \
|
||||
if (verbose) { \
|
||||
printf(" instructions/byte "); \
|
||||
} \
|
||||
printf("\t"); \
|
||||
} \
|
||||
uint64_t S = size; \
|
||||
float cycle_per_op = (min_diff) / (double)S; \
|
||||
float avg_cycle_per_op = (sum_diff) / ((double)S * repeat); \
|
||||
if (verbose) \
|
||||
printf(" %.3f cycles per operation (best) ", cycle_per_op); \
|
||||
if (verbose) \
|
||||
printf("\t%.3f cycles per operation (avg) ", avg_cycle_per_op); \
|
||||
if (verbose) \
|
||||
printf("\n"); \
|
||||
if (!verbose) \
|
||||
printf(" %.3f ", cycle_per_op); \
|
||||
double gb = static_cast<double>(size) / 1000000000.0; \
|
||||
printf("%7.3f", gb / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" GB/s "); \
|
||||
} \
|
||||
printf("%7.3f", 1.0 / aggregate.best.elapsed_sec()); \
|
||||
if (verbose) { \
|
||||
printf(" documents/s "); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
fflush(NULL); \
|
||||
} while (0)
|
||||
|
||||
|
||||
@@ -0,0 +1,522 @@
|
||||
#ifndef __BENCHMARKER_H
|
||||
#define __BENCHMARKER_H
|
||||
|
||||
#include "event_counter.h"
|
||||
#include "simdjson.h" // For SIMDJSON_DISABLE_DEPRECATED_WARNINGS
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
using std::min;
|
||||
using std::max;
|
||||
|
||||
// Initialize "verbose" to go nowhere. We'll read options in main() and set to cout if verbose is true.
|
||||
std::ofstream dev_null;
|
||||
ostream *verbose_stream = &dev_null;
|
||||
const size_t BYTES_PER_BLOCK = 64;
|
||||
|
||||
ostream& verbose() {
|
||||
return *verbose_stream;
|
||||
}
|
||||
|
||||
void exit_error(string message) {
|
||||
cerr << message << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct json_stats {
|
||||
size_t bytes = 0;
|
||||
size_t blocks = 0;
|
||||
size_t structurals = 0;
|
||||
size_t blocks_with_utf8 = 0;
|
||||
size_t blocks_with_utf8_flipped = 0;
|
||||
size_t blocks_with_escapes = 0;
|
||||
size_t blocks_with_escapes_flipped = 0;
|
||||
size_t blocks_with_0_structurals = 0;
|
||||
size_t blocks_with_0_structurals_flipped = 0;
|
||||
size_t blocks_with_1_structural = 0;
|
||||
size_t blocks_with_1_structural_flipped = 0;
|
||||
size_t blocks_with_8_structurals = 0;
|
||||
size_t blocks_with_8_structurals_flipped = 0;
|
||||
size_t blocks_with_16_structurals = 0;
|
||||
size_t blocks_with_16_structurals_flipped = 0;
|
||||
|
||||
json_stats(const padded_string& json, const dom::parser& parser) {
|
||||
bytes = json.size();
|
||||
blocks = bytes / BYTES_PER_BLOCK;
|
||||
if (bytes % BYTES_PER_BLOCK > 0) { blocks++; } // Account for remainder block
|
||||
structurals = parser.implementation->n_structural_indexes-1;
|
||||
|
||||
// Calculate stats on blocks that will trigger utf-8 if statements / mispredictions
|
||||
bool last_block_has_utf8 = false;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Find utf-8 in the block
|
||||
size_t block_start = block*BYTES_PER_BLOCK;
|
||||
size_t block_end = block_start+BYTES_PER_BLOCK;
|
||||
if (block_end > json.size()) { block_end = json.size(); }
|
||||
bool block_has_utf8 = false;
|
||||
for (size_t i=block_start; i<block_end; i++) {
|
||||
if (json.data()[i] & 0x80) {
|
||||
block_has_utf8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block_has_utf8) {
|
||||
blocks_with_utf8++;
|
||||
}
|
||||
if (block > 0 && last_block_has_utf8 != block_has_utf8) {
|
||||
blocks_with_utf8_flipped++;
|
||||
}
|
||||
last_block_has_utf8 = block_has_utf8;
|
||||
}
|
||||
|
||||
// Calculate stats on blocks that will trigger escape if statements / mispredictions
|
||||
bool last_block_has_escapes = false;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Find utf-8 in the block
|
||||
size_t block_start = block*BYTES_PER_BLOCK;
|
||||
size_t block_end = block_start+BYTES_PER_BLOCK;
|
||||
if (block_end > json.size()) { block_end = json.size(); }
|
||||
bool block_has_escapes = false;
|
||||
for (size_t i=block_start; i<block_end; i++) {
|
||||
if (json.data()[i] == '\\') {
|
||||
block_has_escapes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block_has_escapes) {
|
||||
blocks_with_escapes++;
|
||||
}
|
||||
if (block > 0 && last_block_has_escapes != block_has_escapes) {
|
||||
blocks_with_escapes_flipped++;
|
||||
}
|
||||
last_block_has_escapes = block_has_escapes;
|
||||
}
|
||||
|
||||
// Calculate stats on blocks that will trigger structural count if statements / mispredictions
|
||||
bool last_block_has_0_structurals = false;
|
||||
bool last_block_has_1_structural = false;
|
||||
bool last_block_has_8_structurals = false;
|
||||
bool last_block_has_16_structurals = false;
|
||||
size_t structural=0;
|
||||
for (size_t block=0; block<blocks; block++) {
|
||||
// Count structurals in the block
|
||||
int block_structurals=0;
|
||||
while (structural < parser.implementation->n_structural_indexes && parser.implementation->structural_indexes[structural] < (block+1)*BYTES_PER_BLOCK) {
|
||||
block_structurals++;
|
||||
structural++;
|
||||
}
|
||||
|
||||
bool block_has_0_structurals = block_structurals == 0;
|
||||
if (block_has_0_structurals) {
|
||||
blocks_with_0_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_0_structurals != block_has_0_structurals) {
|
||||
blocks_with_0_structurals_flipped++;
|
||||
}
|
||||
last_block_has_0_structurals = block_has_0_structurals;
|
||||
|
||||
bool block_has_1_structural = block_structurals >= 1;
|
||||
if (block_has_1_structural) {
|
||||
blocks_with_1_structural++;
|
||||
}
|
||||
if (block > 0 && last_block_has_1_structural != block_has_1_structural) {
|
||||
blocks_with_1_structural_flipped++;
|
||||
}
|
||||
last_block_has_1_structural = block_has_1_structural;
|
||||
|
||||
bool block_has_8_structurals = block_structurals >= 8;
|
||||
if (block_has_8_structurals) {
|
||||
blocks_with_8_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_8_structurals != block_has_8_structurals) {
|
||||
blocks_with_8_structurals_flipped++;
|
||||
}
|
||||
last_block_has_8_structurals = block_has_8_structurals;
|
||||
|
||||
bool block_has_16_structurals = block_structurals >= 16;
|
||||
if (block_has_16_structurals) {
|
||||
blocks_with_16_structurals++;
|
||||
}
|
||||
if (block > 0 && last_block_has_16_structurals != block_has_16_structurals) {
|
||||
blocks_with_16_structurals_flipped++;
|
||||
}
|
||||
last_block_has_16_structurals = block_has_16_structurals;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct progress_bar {
|
||||
int max_value;
|
||||
int total_ticks;
|
||||
double ticks_per_value;
|
||||
int next_tick;
|
||||
progress_bar(int _max_value, int _total_ticks) : max_value(_max_value), total_ticks(_total_ticks), ticks_per_value(double(_total_ticks)/_max_value), next_tick(0) {
|
||||
fprintf(stderr, "[");
|
||||
for (int i=0;i<total_ticks;i++) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
fprintf(stderr, "]");
|
||||
for (int i=0;i<total_ticks+1;i++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
}
|
||||
|
||||
void print(int value) {
|
||||
double ticks = value*ticks_per_value;
|
||||
if (ticks >= total_ticks) {
|
||||
ticks = total_ticks-1;
|
||||
}
|
||||
int tick;
|
||||
for (tick=next_tick; tick <= ticks && tick <= total_ticks; tick++) {
|
||||
fprintf(stderr, "=");
|
||||
}
|
||||
next_tick = tick;
|
||||
}
|
||||
void erase() const {
|
||||
for (int i=0;i<next_tick+1;i++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
for (int tick=0; tick<=total_ticks+2; tick++) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
for (int tick=0; tick<=total_ticks+2; tick++) {
|
||||
fprintf(stderr, "\b");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class BenchmarkStage {
|
||||
ALL,
|
||||
ALLOCATE,
|
||||
STAGE1,
|
||||
STAGE2
|
||||
};
|
||||
|
||||
const char* benchmark_stage_name(BenchmarkStage stage) {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return "All";
|
||||
case BenchmarkStage::ALLOCATE: return "Allocate";
|
||||
case BenchmarkStage::STAGE1: return "Stage 1";
|
||||
case BenchmarkStage::STAGE2: return "Stage 2";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct benchmarker {
|
||||
// JSON text from loading the file. Owns the memory.
|
||||
padded_string json{};
|
||||
// JSON filename
|
||||
const char *filename;
|
||||
// Event collector that can be turned on to measure cycles, missed branches, etc.
|
||||
event_collector& collector;
|
||||
|
||||
// Statistics about the JSON file independent of its speed (amount of utf-8, structurals, etc.).
|
||||
// Loaded on first parse.
|
||||
json_stats* stats;
|
||||
// Speed and event summary for full parse (including allocation, stage 1 and stage 2)
|
||||
event_aggregate all_stages{};
|
||||
// Speed and event summary for stage 1
|
||||
event_aggregate stage1{};
|
||||
// Speed and event summary for stage 2
|
||||
event_aggregate stage2{};
|
||||
// Speed and event summary for allocation
|
||||
event_aggregate allocate_stage{};
|
||||
// Speed and event summary for the repeatly-parsing mode
|
||||
event_aggregate loop{};
|
||||
|
||||
benchmarker(const char *_filename, event_collector& _collector)
|
||||
: filename(_filename), collector(_collector), stats(NULL) {
|
||||
verbose() << "[verbose] loading " << filename << endl;
|
||||
auto error = padded_string::load(filename).get(json);
|
||||
if (error) {
|
||||
exit_error(string("Could not load the file ") + filename);
|
||||
}
|
||||
verbose() << "[verbose] loaded " << filename << endl;
|
||||
}
|
||||
|
||||
~benchmarker() {
|
||||
if (stats) {
|
||||
delete stats;
|
||||
}
|
||||
}
|
||||
|
||||
benchmarker(const benchmarker&) = delete;
|
||||
benchmarker& operator=(const benchmarker&) = delete;
|
||||
|
||||
const event_aggregate& operator[](BenchmarkStage stage) const {
|
||||
switch (stage) {
|
||||
case BenchmarkStage::ALL: return this->all_stages;
|
||||
case BenchmarkStage::STAGE1: return this->stage1;
|
||||
case BenchmarkStage::STAGE2: return this->stage2;
|
||||
case BenchmarkStage::ALLOCATE: return this->allocate_stage;
|
||||
default: exit_error("Unknown stage"); return this->all_stages;
|
||||
}
|
||||
}
|
||||
|
||||
int iterations() const {
|
||||
return all_stages.iterations;
|
||||
}
|
||||
|
||||
really_inline void run_iteration(bool stage1_only, bool hotbuffers=false) {
|
||||
// Allocate dom::parser
|
||||
collector.start();
|
||||
dom::parser parser;
|
||||
error_code error = parser.allocate(json.size());
|
||||
if (error) {
|
||||
exit_error(string("Unable to allocate_stage ") + to_string(json.size()) + " bytes for the JSON result: " + error_message(error));
|
||||
}
|
||||
event_count allocate_count = collector.end();
|
||||
allocate_stage << allocate_count;
|
||||
// Run it once to get hot buffers
|
||||
if(hotbuffers) {
|
||||
auto result = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (result.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(result.error()));
|
||||
}
|
||||
}
|
||||
|
||||
verbose() << "[verbose] allocated memory for parsed JSON " << endl;
|
||||
|
||||
// Stage 1 (find structurals)
|
||||
collector.start();
|
||||
error = parser.implementation->stage1((const uint8_t *)json.data(), json.size(), false);
|
||||
event_count stage1_count = collector.end();
|
||||
stage1 << stage1_count;
|
||||
if (error) {
|
||||
exit_error(string("Failed to parse ") + filename + " during stage 1: " + error_message(error));
|
||||
}
|
||||
|
||||
// Stage 2 (unified machine) and the rest
|
||||
|
||||
if (stage1_only) {
|
||||
all_stages << stage1_count;
|
||||
} else {
|
||||
event_count stage2_count;
|
||||
collector.start();
|
||||
error = parser.implementation->stage2(parser.doc);
|
||||
if (error) {
|
||||
exit_error(string("Failed to parse ") + filename + " during stage 2 parsing " + error_message(error));
|
||||
}
|
||||
stage2_count = collector.end();
|
||||
stage2 << stage2_count;
|
||||
all_stages << allocate_count + stage1_count + stage2_count;
|
||||
}
|
||||
// Calculate stats the first time we parse
|
||||
if (stats == NULL) {
|
||||
if (stage1_only) { // we need stage 2 once
|
||||
error = parser.implementation->stage2(parser.doc);
|
||||
if (error) {
|
||||
printf("Warning: failed to parse during stage 2. Unable to acquire statistics.\n");
|
||||
}
|
||||
}
|
||||
stats = new json_stats(json, parser);
|
||||
}
|
||||
}
|
||||
|
||||
void run_loop(size_t iterations) {
|
||||
dom::parser parser;
|
||||
auto firstresult = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (firstresult.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(firstresult.error()));
|
||||
}
|
||||
|
||||
collector.start();
|
||||
// some users want something closer to "number of documents per second"
|
||||
for(size_t i = 0; i < iterations; i++) {
|
||||
auto result = parser.parse((const uint8_t *)json.data(), json.size());
|
||||
if (result.error()) {
|
||||
exit_error(string("Failed to parse ") + filename + string(":") + error_message(result.error()));
|
||||
}
|
||||
}
|
||||
event_count all_loop_count = collector.end();
|
||||
loop << all_loop_count;
|
||||
}
|
||||
|
||||
really_inline void run_iterations(size_t iterations, bool stage1_only, bool hotbuffers=false) {
|
||||
for (size_t i = 0; i<iterations; i++) {
|
||||
run_iteration(stage1_only, hotbuffers);
|
||||
}
|
||||
run_loop(iterations);
|
||||
}
|
||||
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
template<typename T>
|
||||
void print_aggregate(const char* prefix, const T& stage) const {
|
||||
printf("%s%-13s: %8.4f ns per block (%6.2f%%) - %8.4f ns per byte - %8.4f ns per structural - %8.4f GB/s\n",
|
||||
prefix,
|
||||
"Speed",
|
||||
stage.elapsed_ns() / static_cast<double>(stats->blocks), // per block
|
||||
percent(stage.elapsed_sec(), all_stages.elapsed_sec()), // %
|
||||
stage.elapsed_ns() / static_cast<double>(stats->bytes), // per byte
|
||||
stage.elapsed_ns() / static_cast<double>(stats->structurals), // per structural
|
||||
(static_cast<double>(json.size()) / 1000000000.0) / stage.elapsed_sec() // GB/s
|
||||
);
|
||||
|
||||
if (collector.has_events()) {
|
||||
printf("%s%-13s: %8.4f per block (%6.2f%%) - %8.4f per byte - %8.4f per structural - %8.3f GHz est. frequency\n",
|
||||
prefix,
|
||||
"Cycles",
|
||||
stage.cycles() / static_cast<double>(stats->blocks),
|
||||
percent(stage.cycles(), all_stages.cycles()),
|
||||
stage.cycles() / static_cast<double>(stats->bytes),
|
||||
stage.cycles() / static_cast<double>(stats->structurals),
|
||||
(stage.cycles() / stage.elapsed_sec()) / 1000000000.0
|
||||
);
|
||||
printf("%s%-13s: %8.4f per block (%6.2f%%) - %8.4f per byte - %8.4f per structural - %8.3f per cycle\n",
|
||||
prefix,
|
||||
"Instructions",
|
||||
stage.instructions() / static_cast<double>(stats->blocks),
|
||||
percent(stage.instructions(), all_stages.instructions()),
|
||||
stage.instructions() / static_cast<double>(stats->bytes),
|
||||
stage.instructions() / static_cast<double>(stats->structurals),
|
||||
stage.instructions() / static_cast<double>(stage.cycles())
|
||||
);
|
||||
|
||||
// NOTE: removed cycles/miss because it is a somewhat misleading stat
|
||||
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
|
||||
prefix,
|
||||
"Misses",
|
||||
stage.branch_misses(),
|
||||
percent(stage.branch_misses(), all_stages.branch_misses()),
|
||||
stage.cache_misses(),
|
||||
percent(stage.cache_misses(), all_stages.cache_misses()),
|
||||
stage.cache_references()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static double percent(size_t a, size_t b) {
|
||||
return 100.0 * static_cast<double>(a) / static_cast<double>(b);
|
||||
}
|
||||
static double percent(double a, double b) {
|
||||
return 100.0 * a / b;
|
||||
}
|
||||
|
||||
void print(bool tabbed_output) const {
|
||||
if (tabbed_output) {
|
||||
char* filename_copy = (char*)malloc(strlen(filename)+1);
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING // Validated CRT_SECURE safe here
|
||||
strcpy(filename_copy, filename);
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#if defined(__linux__)
|
||||
char* base = ::basename(filename_copy);
|
||||
#else
|
||||
char* base = filename_copy;
|
||||
#endif
|
||||
if (strlen(base) >= 5 && !strcmp(base+strlen(base)-5, ".json")) {
|
||||
base[strlen(base)-5] = '\0';
|
||||
}
|
||||
|
||||
double gb = static_cast<double>(json.size()) / 1000000000.0;
|
||||
if (collector.has_events()) {
|
||||
printf("\"%s\"\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n",
|
||||
base,
|
||||
allocate_stage.best.cycles() / static_cast<double>(json.size()),
|
||||
stage1.best.cycles() / static_cast<double>(json.size()),
|
||||
stage2.best.cycles() / static_cast<double>(json.size()),
|
||||
all_stages.best.cycles() / static_cast<double>(json.size()),
|
||||
gb / all_stages.best.elapsed_sec(),
|
||||
gb / stage1.best.elapsed_sec(),
|
||||
gb / stage2.best.elapsed_sec());
|
||||
} else {
|
||||
printf("\"%s\"\t\t\t\t\t%f\t%f\t%f\n",
|
||||
base,
|
||||
gb / all_stages.best.elapsed_sec(),
|
||||
gb / stage1.best.elapsed_sec(),
|
||||
gb / stage2.best.elapsed_sec());
|
||||
}
|
||||
free(filename_copy);
|
||||
} else {
|
||||
printf("\n");
|
||||
printf("%s\n", filename);
|
||||
printf("%s\n", string(strlen(filename), '=').c_str());
|
||||
printf("%9zu blocks - %10zu bytes - %5zu structurals (%5.1f %%)\n", stats->bytes / BYTES_PER_BLOCK, stats->bytes, stats->structurals, percent(stats->structurals, stats->bytes));
|
||||
if (stats) {
|
||||
printf("special blocks with: utf8 %9zu (%5.1f %%) - escape %9zu (%5.1f %%) - 0 structurals %9zu (%5.1f %%) - 1+ structurals %9zu (%5.1f %%) - 8+ structurals %9zu (%5.1f %%) - 16+ structurals %9zu (%5.1f %%)\n",
|
||||
stats->blocks_with_utf8, percent(stats->blocks_with_utf8, stats->blocks),
|
||||
stats->blocks_with_escapes, percent(stats->blocks_with_escapes, stats->blocks),
|
||||
stats->blocks_with_0_structurals, percent(stats->blocks_with_0_structurals, stats->blocks),
|
||||
stats->blocks_with_1_structural, percent(stats->blocks_with_1_structural, stats->blocks),
|
||||
stats->blocks_with_8_structurals, percent(stats->blocks_with_8_structurals, stats->blocks),
|
||||
stats->blocks_with_16_structurals, percent(stats->blocks_with_16_structurals, stats->blocks));
|
||||
printf("special block flips: utf8 %9zu (%5.1f %%) - escape %9zu (%5.1f %%) - 0 structurals %9zu (%5.1f %%) - 1+ structurals %9zu (%5.1f %%) - 8+ structurals %9zu (%5.1f %%) - 16+ structurals %9zu (%5.1f %%)\n",
|
||||
stats->blocks_with_utf8_flipped, percent(stats->blocks_with_utf8_flipped, stats->blocks),
|
||||
stats->blocks_with_escapes_flipped, percent(stats->blocks_with_escapes_flipped, stats->blocks),
|
||||
stats->blocks_with_0_structurals_flipped, percent(stats->blocks_with_0_structurals_flipped, stats->blocks),
|
||||
stats->blocks_with_1_structural_flipped, percent(stats->blocks_with_1_structural_flipped, stats->blocks),
|
||||
stats->blocks_with_8_structurals_flipped, percent(stats->blocks_with_8_structurals_flipped, stats->blocks),
|
||||
stats->blocks_with_16_structurals_flipped, percent(stats->blocks_with_16_structurals_flipped, stats->blocks));
|
||||
}
|
||||
printf("\n");
|
||||
printf("All Stages\n");
|
||||
print_aggregate("| " , all_stages.best);
|
||||
// frequently, allocation is a tiny fraction of the running time so we omit it
|
||||
if(allocate_stage.best.elapsed_sec() > 0.01 * all_stages.best.elapsed_sec()) {
|
||||
printf("|- Allocation\n");
|
||||
print_aggregate("| ", allocate_stage.best);
|
||||
}
|
||||
printf("|- Stage 1\n");
|
||||
print_aggregate("| ", stage1.best);
|
||||
printf("|- Stage 2\n");
|
||||
print_aggregate("| ", stage2.best);
|
||||
if (collector.has_events()) {
|
||||
double freq1 = (stage1.best.cycles() / stage1.best.elapsed_sec()) / 1000000000.0;
|
||||
double freq2 = (stage2.best.cycles() / stage2.best.elapsed_sec()) / 1000000000.0;
|
||||
double freqall = (all_stages.best.cycles() / all_stages.best.elapsed_sec()) / 1000000000.0;
|
||||
double freqmin = min(freq1, freq2);
|
||||
double freqmax = max(freq1, freq2);
|
||||
if((freqall < 0.95 * freqmin) or (freqall > 1.05 * freqmax)) {
|
||||
printf("\nWarning: The processor frequency fluctuates in an expected way!!!\n"
|
||||
"Expect the overall speed not to match stage 1 and stage 2 speeds.\n"
|
||||
"Range for stage 1 and stage 2 : [%.3f GHz, %.3f GHz], overall: %.3f GHz.\n",
|
||||
freqmin, freqmax, freqall);
|
||||
}
|
||||
}
|
||||
printf("\n%.1f documents parsed per second (best)\n", 1.0/static_cast<double>(all_stages.best.elapsed_sec()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
# Relevant targets:
|
||||
# checkperf-parse: builds the reference checkperf-parse, syncing reference repository if needed
|
||||
# checkperf: builds the targets needed for checkperf (parse, perfdiff, checkperf-parse)
|
||||
# update-checkperf-repo: updates the reference repository we're checking performance against
|
||||
# checkperf-repo: initialize and sync reference repository (first time only)
|
||||
# TEST checkperf: runs the actual checkperf test
|
||||
|
||||
# Clone the repository if it's not there
|
||||
find_package(Git QUIET)
|
||||
if (SIMDJSON_IS_UNDER_GIT AND Git_FOUND AND (GIT_VERSION_STRING VERSION_GREATER "2.1.4") AND (NOT CMAKE_GENERATOR MATCHES Ninja) ) # We use "-C" which requires a recent git
|
||||
message(STATUS "Git is available and it is recent. We are enabling checkperf targets.")
|
||||
# sync_git_repository(myrepo ...) creates two targets:
|
||||
# myrepo - if the repo does not exist, creates and syncs it against the origin branch
|
||||
# update_myrepo - will update the repo against the origin branch (and create if needed)
|
||||
function(sync_git_repository name dir remote branch url)
|
||||
# This conditionally creates the git repository
|
||||
add_custom_command(
|
||||
OUTPUT ${dir}/.git/config
|
||||
COMMAND ${GIT_EXECUTABLE} init ${dir}
|
||||
COMMAND ${GIT_EXECUTABLE} -C ${dir} remote add ${remote} ${url}
|
||||
)
|
||||
add_custom_target(init-${name} DEPENDS ${dir}/.git/config)
|
||||
# This conditionally syncs the git repository, first time only
|
||||
add_custom_command(
|
||||
OUTPUT ${dir}/.git/FETCH_HEAD
|
||||
COMMAND ${GIT_EXECUTABLE} remote set-url ${remote} ${url}
|
||||
COMMAND ${GIT_EXECUTABLE} fetch --depth=1 ${remote} ${branch}
|
||||
COMMAND ${GIT_EXECUTABLE} reset --hard ${remote}/${branch}
|
||||
WORKING_DIRECTORY ${dir}
|
||||
DEPENDS init-${name}
|
||||
)
|
||||
# This is the ${name} target, which will create and sync the repo first time only
|
||||
add_custom_target(${name} DEPENDS ${dir}/.git/FETCH_HEAD)
|
||||
# This is the update-${name} target, which will sync the repo (creating it if needed)
|
||||
add_custom_target(
|
||||
update-${name}
|
||||
COMMAND ${GIT_EXECUTABLE} remote set-url ${remote} ${url}
|
||||
COMMAND ${GIT_EXECUTABLE} fetch --depth=1 ${remote} ${branch}
|
||||
COMMAND ${GIT_EXECUTABLE} reset --hard ${remote}/${branch}
|
||||
WORKING_DIRECTORY ${dir}
|
||||
DEPENDS init-${name}
|
||||
)
|
||||
endfunction(sync_git_repository)
|
||||
|
||||
set(SIMDJSON_CHECKPERF_REMOTE origin CACHE STRING "Remote repository to compare performance against")
|
||||
set(SIMDJSON_CHECKPERF_BRANCH master CACHE STRING "Branch to compare performance against")
|
||||
set(SIMDJSON_CHECKPERF_DIR ${CMAKE_CURRENT_BINARY_DIR}/checkperf-reference/${SIMDJSON_CHECKPERF_BRANCH} CACHE STRING "Location to put checkperf performance comparison repository")
|
||||
set(SIMDJSON_CHECKPERF_ARGS ${EXAMPLE_JSON} CACHE STRING "Arguments to pass to parse during checkperf")
|
||||
sync_git_repository(checkperf-repo ${SIMDJSON_CHECKPERF_DIR} ${SIMDJSON_CHECKPERF_REMOTE} ${SIMDJSON_CHECKPERF_BRANCH} ${SIMDJSON_GITHUB_REPOSITORY})
|
||||
|
||||
# Commands to cause cmake on benchmark/checkperf-master/build/
|
||||
# - first, copy CMakeCache.txt
|
||||
add_custom_command(
|
||||
OUTPUT ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${SIMDJSON_CHECKPERF_DIR}/build
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SIMDJSON_USER_CMAKECACHE} ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
|
||||
DEPENDS checkperf-repo simdjson-user-cmakecache
|
||||
)
|
||||
# - second, cmake ..
|
||||
add_custom_command(
|
||||
OUTPUT ${SIMDJSON_CHECKPERF_DIR}/build/cmake_install.cmake # We make many things but this seems the most cross-platform one we can depend on
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env CXX=${CMAKE_CXX_COMPILER} CC=${CMAKE_C_COMPILER}
|
||||
${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_COMPETITION=OFF -G ${CMAKE_GENERATOR} ..
|
||||
WORKING_DIRECTORY ${SIMDJSON_CHECKPERF_DIR}/build
|
||||
DEPENDS ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
|
||||
)
|
||||
|
||||
# - third, build parse.
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(CHECKPERF_PARSE ${SIMDJSON_CHECKPERF_DIR}/build/benchmark/$<CONFIGURATION>/parse)
|
||||
else()
|
||||
set(CHECKPERF_PARSE ${SIMDJSON_CHECKPERF_DIR}/build/benchmark/parse)
|
||||
endif()
|
||||
add_custom_target(
|
||||
checkperf-parse ALL # TODO is ALL necessary?
|
||||
# Build parse
|
||||
COMMAND ${CMAKE_COMMAND} --build . --target parse --config $<CONFIGURATION>
|
||||
WORKING_DIRECTORY ${SIMDJSON_CHECKPERF_DIR}/build
|
||||
DEPENDS ${SIMDJSON_CHECKPERF_DIR}/build/cmake_install.cmake # We make many things but this seems the most cross-platform one we can depend on
|
||||
)
|
||||
|
||||
# Target to build everything needed for the checkperf test
|
||||
add_custom_target(checkperf DEPENDS parse perfdiff checkperf-parse)
|
||||
|
||||
# Add the actual checkperf test
|
||||
add_test(
|
||||
NAME checkperf
|
||||
# COMMAND ECHO $<TARGET_FILE:perfdiff> \"$<TARGET_FILE:parse> -t ${SIMDJSON_CHECKPERF_ARGS}\" \"${CHECKPERF_PARSE} -t ${SIMDJSON_CHECKPERF_ARGS}\" }
|
||||
COMMAND $<TARGET_FILE:perfdiff> $<TARGET_FILE:parse> ${CHECKPERF_PARSE} -H -t ${SIMDJSON_CHECKPERF_ARGS}
|
||||
)
|
||||
set_property(TEST checkperf APPEND PROPERTY LABELS per_implementation)
|
||||
set_property(TEST checkperf APPEND PROPERTY DEPENDS parse perfdiff ${SIMDJSON_USER_CMAKECACHE})
|
||||
set_property(TEST checkperf PROPERTY RUN_SERIAL TRUE)
|
||||
else()
|
||||
if (CMAKE_GENERATOR MATCHES Ninja)
|
||||
message(STATUS "We disable the checkperf targets under Ninja.")
|
||||
else()
|
||||
message(STATUS "Either git is unavailable or else it is too old. We are disabling checkperf targets.")
|
||||
endif()
|
||||
endif ()
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -13,6 +17,8 @@
|
||||
|
||||
#include "sajson.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
bool equals(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
|
||||
@@ -30,52 +36,103 @@ void print_vec(const std::vector<int64_t> &v) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void simdjson_scan(std::vector<int64_t> &answer,
|
||||
simdjson::ParsedJson::Iterator &i) {
|
||||
while (i.move_forward()) {
|
||||
if (i.get_scope_type() == '{') {
|
||||
bool found_user = (i.get_string_length() == 4) &&
|
||||
(memcmp(i.get_string(), "user", 4) == 0);
|
||||
i.move_to_value();
|
||||
if (found_user) {
|
||||
if (i.is_object() && i.move_to_key("id", 2)) {
|
||||
if (i.is_integer()) {
|
||||
answer.push_back(i.get_integer());
|
||||
}
|
||||
i.up();
|
||||
}
|
||||
// clang-format off
|
||||
|
||||
// simdjson_recurse below come be implemented like so but it is slow:
|
||||
/*void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
error_code error;
|
||||
if (element.is_array()) {
|
||||
dom::array array;
|
||||
error = element.get(array);
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is_object()) {
|
||||
int64_t id;
|
||||
error = element["user"]["id"].get(id);
|
||||
if(!error) {
|
||||
v.push_back(id);
|
||||
}
|
||||
for (auto [key, value] : object) {
|
||||
if (value.is<simdjson::dom::array>() || value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(v, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// clang-format on
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
really_inline void simdjson_recurse(std::vector<int64_t> & v, simdjson::dom::element element) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
simdjson_just_dom(simdjson::ParsedJson &pj) {
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_just_dom(simdjson::dom::element doc) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson::ParsedJson::Iterator i(pj);
|
||||
simdjson_scan(answer, i);
|
||||
simdjson_recurse(answer, doc);
|
||||
remove_duplicates(answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
really_inline std::vector<int64_t>
|
||||
simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
if (!pj.is_valid()) {
|
||||
return answer;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (!error) {
|
||||
simdjson_recurse(answer, doc);
|
||||
remove_duplicates(answer);
|
||||
}
|
||||
simdjson::ParsedJson::Iterator i(pj);
|
||||
simdjson_scan(answer, i);
|
||||
remove_duplicates(answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool
|
||||
really_inline simdjson::error_code
|
||||
simdjson_just_parse(const simdjson::padded_string &p) {
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
bool answer = !pj.is_valid();
|
||||
return answer;
|
||||
simdjson::dom::parser parser;
|
||||
return parser.parse(p).error();
|
||||
}
|
||||
|
||||
void sajson_traverse(std::vector<int64_t> &answer, const sajson::value &node) {
|
||||
@@ -130,7 +187,7 @@ void sajson_traverse(std::vector<int64_t> &answer, const sajson::value &node) {
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
really_inline std::vector<int64_t>
|
||||
sasjon_just_dom(sajson::document &d) {
|
||||
std::vector<int64_t> answer;
|
||||
sajson_traverse(answer, d.get_root());
|
||||
@@ -138,7 +195,7 @@ sasjon_just_dom(sajson::document &d) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
really_inline std::vector<int64_t>
|
||||
sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
@@ -155,7 +212,7 @@ sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool
|
||||
really_inline bool
|
||||
sasjon_just_parse(const simdjson::padded_string &p) {
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
@@ -206,7 +263,7 @@ void rapid_traverse(std::vector<int64_t> &answer, const rapidjson::Value &v) {
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
really_inline std::vector<int64_t>
|
||||
rapid_just_dom(rapidjson::Document &d) {
|
||||
std::vector<int64_t> answer;
|
||||
rapid_traverse(answer, d);
|
||||
@@ -214,7 +271,7 @@ rapid_just_dom(rapidjson::Document &d) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) std::vector<int64_t>
|
||||
really_inline std::vector<int64_t>
|
||||
rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
std::vector<int64_t> answer;
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
@@ -232,7 +289,7 @@ rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool
|
||||
really_inline bool
|
||||
rapid_just_parse(const simdjson::padded_string &p) {
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
@@ -275,19 +332,18 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input has ";
|
||||
if (p.size() > 1024 * 1024)
|
||||
std::cout << p.size() / (1024 * 1024) << " MB ";
|
||||
else if (p.size() > 1024)
|
||||
std::cout << p.size() / 1024 << " KB ";
|
||||
if (p.size() > 1000 * 1000)
|
||||
std::cout << p.size() / (1000 * 1000) << " MB ";
|
||||
else if (p.size() > 1000)
|
||||
std::cout << p.size() / 1000 << " KB ";
|
||||
else
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
@@ -312,27 +368,30 @@ int main(int argc, char *argv[]) {
|
||||
size_t size = s1.size();
|
||||
|
||||
int repeat = 500;
|
||||
int volume = p.size();
|
||||
size_t volume = p.size();
|
||||
if (just_data) {
|
||||
printf(
|
||||
"name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
|
||||
}
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).size(), size, , repeat,
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).size(), size, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("rapid ", rapid_compute_stats(p).size(), size, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).size(), size, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson (just parse) ", simdjson_just_parse(p), false, , repeat,
|
||||
BEST_TIME("simdjson (just parse) ", simdjson_just_parse(p), simdjson::error_code::SUCCESS, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("rapid (just parse) ", rapid_just_parse(p), false, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("sasjon (just parse) ", sasjon_just_parse(p), false, , repeat,
|
||||
volume, !just_data);
|
||||
simdjson::ParsedJson dsimdjson = simdjson::build_parsed_json(p);
|
||||
BEST_TIME("simdjson (just dom) ", simdjson_just_dom(dsimdjson).size(), size,
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
error = parser.parse(p).get(doc);
|
||||
BEST_TIME("simdjson (just dom) ", simdjson_just_dom(doc).size(), size,
|
||||
, repeat, volume, !just_data);
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
buffer[p.size()] = '\0';
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
rapidjson::Document drapid;
|
||||
drapid.ParseInsitu<kParseValidateEncodingFlag>(buffer);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#ifndef __EVENT_COUNTER_H
|
||||
#define __EVENT_COUNTER_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::chrono::steady_clock;
|
||||
using std::chrono::time_point;
|
||||
using std::chrono::duration;
|
||||
|
||||
struct event_count {
|
||||
duration<double> elapsed;
|
||||
vector<unsigned long long> event_counts;
|
||||
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
|
||||
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
|
||||
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
|
||||
|
||||
// The types of counters (so we can read the getter more easily)
|
||||
enum event_counter_types {
|
||||
CPU_CYCLES,
|
||||
INSTRUCTIONS,
|
||||
BRANCH_MISSES,
|
||||
CACHE_REFERENCES,
|
||||
CACHE_MISSES
|
||||
};
|
||||
|
||||
double elapsed_sec() const { return duration<double>(elapsed).count(); }
|
||||
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
|
||||
double cycles() const { return static_cast<double>(event_counts[CPU_CYCLES]); }
|
||||
double instructions() const { return static_cast<double>(event_counts[INSTRUCTIONS]); }
|
||||
double branch_misses() const { return static_cast<double>(event_counts[BRANCH_MISSES]); }
|
||||
double cache_references() const { return static_cast<double>(event_counts[CACHE_REFERENCES]); }
|
||||
double cache_misses() const { return static_cast<double>(event_counts[CACHE_MISSES]); }
|
||||
|
||||
event_count& operator=(const event_count& other) {
|
||||
this->elapsed = other.elapsed;
|
||||
this->event_counts = other.event_counts;
|
||||
return *this;
|
||||
}
|
||||
event_count operator+(const event_count& other) const {
|
||||
return event_count(elapsed+other.elapsed, {
|
||||
event_counts[0]+other.event_counts[0],
|
||||
event_counts[1]+other.event_counts[1],
|
||||
event_counts[2]+other.event_counts[2],
|
||||
event_counts[3]+other.event_counts[3],
|
||||
event_counts[4]+other.event_counts[4],
|
||||
});
|
||||
}
|
||||
|
||||
void operator+=(const event_count& other) {
|
||||
*this = *this + other;
|
||||
}
|
||||
};
|
||||
|
||||
struct event_aggregate {
|
||||
int iterations = 0;
|
||||
event_count total{};
|
||||
event_count best{};
|
||||
event_count worst{};
|
||||
|
||||
event_aggregate() {}
|
||||
|
||||
void operator<<(const event_count& other) {
|
||||
if (iterations == 0 || other.elapsed < best.elapsed) {
|
||||
best = other;
|
||||
}
|
||||
if (iterations == 0 || other.elapsed > worst.elapsed) {
|
||||
worst = other;
|
||||
}
|
||||
iterations++;
|
||||
total += other;
|
||||
}
|
||||
|
||||
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
|
||||
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
|
||||
double cycles() const { return total.cycles() / iterations; }
|
||||
double instructions() const { return total.instructions() / iterations; }
|
||||
double branch_misses() const { return total.branch_misses() / iterations; }
|
||||
double cache_references() const { return total.cache_references() / iterations; }
|
||||
double cache_misses() const { return total.cache_misses() / iterations; }
|
||||
};
|
||||
|
||||
struct event_collector {
|
||||
event_count count{};
|
||||
time_point<steady_clock> start_clock{};
|
||||
|
||||
#if defined(__linux__)
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
|
||||
event_collector() : linux_events(vector<int>{
|
||||
PERF_COUNT_HW_CPU_CYCLES,
|
||||
PERF_COUNT_HW_INSTRUCTIONS,
|
||||
PERF_COUNT_HW_BRANCH_MISSES,
|
||||
PERF_COUNT_HW_CACHE_REFERENCES,
|
||||
PERF_COUNT_HW_CACHE_MISSES
|
||||
}) {}
|
||||
bool has_events() {
|
||||
return linux_events.is_working();
|
||||
}
|
||||
#else
|
||||
event_collector() {}
|
||||
bool has_events() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
really_inline void start() {
|
||||
#if defined(__linux)
|
||||
linux_events.start();
|
||||
#endif
|
||||
start_clock = steady_clock::now();
|
||||
}
|
||||
really_inline event_count& end() {
|
||||
time_point<steady_clock> end_clock = steady_clock::now();
|
||||
#if defined(__linux)
|
||||
linux_events.end(count.event_counts);
|
||||
#endif
|
||||
count.elapsed = end_clock - start_clock;
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
never_inline
|
||||
double bench(std::string filename, simdjson::padded_string& p) {
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_clock =
|
||||
std::chrono::steady_clock::now();
|
||||
simdjson::padded_string::load(filename).first.swap(p);
|
||||
std::chrono::time_point<std::chrono::steady_clock> end_clock =
|
||||
std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> elapsed = end_clock - start_clock;
|
||||
return (static_cast<double>(p.size()) / (1000000000.)) / elapsed.count();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int optind = 1;
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Reads document as far as possible. " << std::endl;
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
bench(filename, p);
|
||||
double meanval = 0;
|
||||
double maxval = 0;
|
||||
double minval = 10000;
|
||||
std::cout << "file size: "<< (static_cast<double>(p.size()) / (1000000000.)) << " GB" <<std::endl;
|
||||
size_t times = p.size() > 1000000000 ? 5 : 50;
|
||||
#if __cpp_exceptions
|
||||
try {
|
||||
#endif
|
||||
for(size_t i = 0; i < times; i++) {
|
||||
double tval = bench(filename, p);
|
||||
if(maxval < tval) maxval = tval;
|
||||
if(minval > tval) minval = tval;
|
||||
meanval += tval;
|
||||
}
|
||||
#if __cpp_exceptions
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
std::cout << "average speed: " << meanval / static_cast<double>(times) << " GB/s"<< std::endl;
|
||||
std::cout << "min speed : " << minval << " GB/s" << std::endl;
|
||||
std::cout << "max speed : " << maxval << " GB/s" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -17,10 +17,10 @@
|
||||
template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
|
||||
int fd;
|
||||
bool working;
|
||||
perf_event_attr attribs;
|
||||
int num_events;
|
||||
std::vector<uint64_t> temp_result_vec;
|
||||
std::vector<uint64_t> ids;
|
||||
perf_event_attr attribs{};
|
||||
size_t num_events{};
|
||||
std::vector<uint64_t> temp_result_vec{};
|
||||
std::vector<uint64_t> ids{};
|
||||
|
||||
public:
|
||||
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
uint32_t i = 0;
|
||||
for (auto config : config_vec) {
|
||||
attribs.config = config;
|
||||
fd = syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags);
|
||||
fd = static_cast<int>(syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
|
||||
if (fd == -1) {
|
||||
report_error("perf_event_open");
|
||||
}
|
||||
@@ -56,25 +56,29 @@ public:
|
||||
temp_result_vec.resize(num_events * 2 + 1);
|
||||
}
|
||||
|
||||
~LinuxEvents() { close(fd); }
|
||||
~LinuxEvents() { if (fd != -1) { close(fd); } }
|
||||
|
||||
inline void start() {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_RESET)");
|
||||
}
|
||||
if (fd != -1) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_RESET)");
|
||||
}
|
||||
|
||||
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
|
||||
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void end(std::vector<unsigned long long> &results) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
|
||||
}
|
||||
if (fd != -1) {
|
||||
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
|
||||
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
|
||||
}
|
||||
|
||||
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
|
||||
report_error("read");
|
||||
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
|
||||
report_error("read");
|
||||
}
|
||||
}
|
||||
// our actual results are in slots 1,3,5, ... of this structure
|
||||
// we really should be checking our ids obtained earlier to be safe
|
||||
@@ -83,6 +87,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool is_working() {
|
||||
return working;
|
||||
}
|
||||
|
||||
private:
|
||||
void report_error(const std::string &context) {
|
||||
if (working)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonminifier.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad
|
||||
// #define RAPIDJSON_SSE42 // bad
|
||||
@@ -14,8 +14,10 @@
|
||||
#include "rapidjson/writer.h"
|
||||
#include "sajson.h"
|
||||
|
||||
using namespace simdjson;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
using namespace simdjson;
|
||||
|
||||
std::string rapid_stringme_insitu(char *json) {
|
||||
Document d;
|
||||
@@ -43,6 +45,15 @@ std::string rapid_stringme(char *json) {
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
std::string simdjson_stringme(simdjson::padded_string & json) {
|
||||
std::stringstream ss;
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse(json);
|
||||
ss << simdjson::minify(doc);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int c;
|
||||
bool verbose = false;
|
||||
@@ -65,28 +76,28 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input has ";
|
||||
if (p.size() > 1024 * 1024)
|
||||
std::cout << p.size() / (1024 * 1024) << " MB ";
|
||||
else if (p.size() > 1024)
|
||||
std::cout << p.size() / 1024 << " KB ";
|
||||
if (p.size() > 1000 * 1000)
|
||||
std::cout << p.size() / (1000 * 1000) << " MB ";
|
||||
else if (p.size() > 1000)
|
||||
std::cout << p.size() / 1000 << " KB ";
|
||||
else
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
char *buffer = simdjson::allocate_padded_buffer(p.size() + 1);
|
||||
char *buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
|
||||
int repeat = 50;
|
||||
int volume = p.size();
|
||||
size_t volume = p.size();
|
||||
if (just_data) {
|
||||
printf(
|
||||
"name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
|
||||
@@ -101,20 +112,23 @@ int main(int argc, char *argv[]) {
|
||||
BEST_TIME_NOCHECK(
|
||||
"despacing with RapidJSON Insitu", rapid_stringme_insitu((char *)buffer),
|
||||
memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
|
||||
BEST_TIME_NOCHECK(
|
||||
"despacing with std::minify", simdjson_stringme(p),, repeat, volume, !just_data);
|
||||
|
||||
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
|
||||
size_t outlength = simdjson::json_minify((const uint8_t *)buffer, p.size(),
|
||||
(uint8_t *)buffer);
|
||||
if (verbose)
|
||||
std::cout << "json_minify length is " << outlength << std::endl;
|
||||
|
||||
size_t outlength;
|
||||
uint8_t *cbuffer = (uint8_t *)buffer;
|
||||
BEST_TIME("json_minify", simdjson::json_minify(cbuffer, p.size(), cbuffer),
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
BEST_TIME((std::string("simdjson->minify+")+imple->name()).c_str(), (imple->minify(cbuffer, p.size(), cbuffer, outlength) == 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 "
|
||||
"of original) \n",
|
||||
outlength, p.size(), outlength * 100.0 / p.size());
|
||||
outlength, p.size(), static_cast<double>(outlength) * 100.0 / static_cast<double>(p.size()));
|
||||
|
||||
/***
|
||||
* Is it worth it to minify before parsing?
|
||||
@@ -124,9 +138,10 @@ int main(int argc, char *argv[]) {
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
char *mini_buffer = simdjson::allocate_padded_buffer(p.size() + 1);
|
||||
size_t minisize = simdjson::json_minify((const uint8_t *)p.data(), p.size(),
|
||||
(uint8_t *)mini_buffer);
|
||||
char *mini_buffer = simdjson::internal::allocate_padded_buffer(p.size() + 1);
|
||||
size_t minisize;
|
||||
auto minierror = minify(p.data(), p.size(),mini_buffer, minisize);
|
||||
if (!minierror) { std::cerr << minierror << std::endl; exit(1); }
|
||||
mini_buffer[minisize] = '\0';
|
||||
|
||||
BEST_TIME("RapidJSON Insitu despaced", d.ParseInsitu(buffer).HasParseError(),
|
||||
@@ -150,31 +165,19 @@ int main(int argc, char *argv[]) {
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, mini_buffer, p.size()), repeat, volume, !just_data);
|
||||
|
||||
simdjson::ParsedJson pj;
|
||||
bool is_alloc_ok = pj.allocate_capacity(p.size(), 1024);
|
||||
if (!is_alloc_ok) {
|
||||
fprintf(stderr, "failed to allocate memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
simdjson::dom::parser parser;
|
||||
bool automated_reallocation = false;
|
||||
BEST_TIME("simdjson orig",
|
||||
simdjson::json_parse((const uint8_t *)buffer, p.size(), pj,
|
||||
automated_reallocation),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
parser.parse((const uint8_t *)buffer, p.size(),
|
||||
automated_reallocation).error(),
|
||||
simdjson::SUCCESS, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson despaced",
|
||||
parser.parse((const uint8_t *)buffer, minisize,
|
||||
automated_reallocation).error(),
|
||||
simdjson::SUCCESS, memcpy(buffer, mini_buffer, p.size()), repeat, volume,
|
||||
!just_data);
|
||||
|
||||
simdjson::ParsedJson pj2;
|
||||
bool is_alloc_ok2 = pj2.allocate_capacity(p.size(), 1024);
|
||||
if (!is_alloc_ok2) {
|
||||
fprintf(stderr, "failed to allocate memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
automated_reallocation = false;
|
||||
BEST_TIME("simdjson despaced",
|
||||
simdjson::json_parse((const uint8_t *)buffer, minisize, pj2,
|
||||
automated_reallocation),
|
||||
true, memcpy(buffer, mini_buffer, p.size()), repeat, volume,
|
||||
!just_data);
|
||||
free(buffer);
|
||||
free(ast_buffer);
|
||||
free(mini_buffer);
|
||||
|
||||
+177
-348
@@ -1,9 +1,11 @@
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#ifndef _MSC_VER
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include <cstdio>
|
||||
@@ -26,368 +28,195 @@
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
//#define DEBUG
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/isadetection.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson/parsedjson.h"
|
||||
#include "simdjson/stage1_find_marks.h"
|
||||
#include "simdjson/stage2_build_tape.h"
|
||||
namespace simdjson {
|
||||
Architecture _find_best_supported_implementation() {
|
||||
constexpr uint32_t haswell_flags =
|
||||
instruction_set::AVX2 | instruction_set::PCLMULQDQ |
|
||||
instruction_set::BMI1 | instruction_set::BMI2;
|
||||
constexpr uint32_t westmere_flags =
|
||||
instruction_set::SSE42 | instruction_set::PCLMULQDQ;
|
||||
uint32_t supports = detect_supported_architectures();
|
||||
// Order from best to worst (within architecture)
|
||||
if ((haswell_flags & supports) == haswell_flags) {
|
||||
return Architecture::HASWELL;
|
||||
}
|
||||
if ((westmere_flags & supports) == westmere_flags) {
|
||||
return Architecture::WESTMERE;
|
||||
}
|
||||
if (instruction_set::NEON)
|
||||
return Architecture::ARM64;
|
||||
|
||||
return Architecture::NONE;
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "benchmarker.h"
|
||||
|
||||
using namespace simdjson;
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
using std::vector;
|
||||
using std::ostream;
|
||||
using std::ofstream;
|
||||
using std::exception;
|
||||
|
||||
// Stash the exe_name in main() for functions to use
|
||||
char* exe_name;
|
||||
|
||||
void print_usage(ostream& out) {
|
||||
out << "Usage: " << exe_name << " [-vt] [-n #] [-s STAGE] [-a ARCH] <jsonfile> ..." << endl;
|
||||
out << endl;
|
||||
out << "Runs the parser against the given json files in a loop, measuring speed and other statistics." << endl;
|
||||
out << endl;
|
||||
out << "Options:" << endl;
|
||||
out << endl;
|
||||
out << "-n # - Number of iterations per file. Default: 200" << endl;
|
||||
out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl;
|
||||
out << "-t - Tabbed data output" << endl;
|
||||
out << "-v - Verbose output." << endl;
|
||||
out << "-s stage1 - Stop after find_structural_bits." << endl;
|
||||
out << "-s all - Run all stages." << endl;
|
||||
out << "-C - Leave the buffers cold (includes page allocation and related OS tasks during parsing, speed tied to OS performance)" << endl;
|
||||
out << "-H - Make the buffers hot (reduce page allocation and related OS tasks during parsing) [default]" << endl;
|
||||
out << "-a IMPL - Use the given parser implementation. By default, detects the most advanced" << endl;
|
||||
out << " implementation supported on the host machine." << endl;
|
||||
for (auto impl : simdjson::available_implementations) {
|
||||
out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
using unified_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
|
||||
using stage1_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj);
|
||||
|
||||
extern unified_functype *unified_ptr;
|
||||
|
||||
extern stage1_functype *stage1_ptr;
|
||||
|
||||
int unified_machine_dispatch(const uint8_t *buf, size_t len, ParsedJson &pj) {
|
||||
Architecture best_implementation = _find_best_supported_implementation();
|
||||
// Selecting the best implementation
|
||||
switch (best_implementation) {
|
||||
#ifdef IS_X86_64
|
||||
case Architecture::HASWELL:
|
||||
unified_ptr = &unified_machine<Architecture::HASWELL>;
|
||||
break;
|
||||
case Architecture::WESTMERE:
|
||||
unified_ptr = &unified_machine<Architecture::WESTMERE>;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IS_ARM64
|
||||
case Architecture::ARM64:
|
||||
unified_ptr = &unified_machine<Architecture::ARM64>;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
std::cerr << "The processor is not supported by simdjson." << std::endl;
|
||||
return simdjson::UNEXPECTED_ERROR;
|
||||
}
|
||||
|
||||
return unified_ptr(buf, len, pj);
|
||||
void exit_usage(string message) {
|
||||
cerr << message << endl;
|
||||
cerr << endl;
|
||||
print_usage(cerr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Responsible to select the best json_parse implementation
|
||||
int find_structural_bits_dispatch(const uint8_t *buf, size_t len,
|
||||
ParsedJson &pj) {
|
||||
Architecture best_implementation = _find_best_supported_implementation();
|
||||
// Selecting the best implementation
|
||||
switch (best_implementation) {
|
||||
#ifdef IS_X86_64
|
||||
case Architecture::HASWELL:
|
||||
stage1_ptr = &find_structural_bits<Architecture::HASWELL>;
|
||||
break;
|
||||
case Architecture::WESTMERE:
|
||||
stage1_ptr = &find_structural_bits<Architecture::WESTMERE>;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IS_ARM64
|
||||
case Architecture::ARM64:
|
||||
stage1_ptr = &find_structural_bits<Architecture::ARM64>;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
std::cerr << "The processor is not supported by simdjson." << std::endl;
|
||||
return simdjson::UNEXPECTED_ERROR;
|
||||
}
|
||||
struct option_struct {
|
||||
vector<char*> files{};
|
||||
bool stage1_only = false;
|
||||
|
||||
return stage1_ptr(buf, len, pj);
|
||||
}
|
||||
int32_t iterations = 200;
|
||||
int32_t iteration_step = -1;
|
||||
|
||||
stage1_functype *stage1_ptr = &find_structural_bits_dispatch;
|
||||
unified_functype *unified_ptr = &unified_machine_dispatch;
|
||||
} // namespace simdjson
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool dump = false;
|
||||
bool json_output = false;
|
||||
bool force_one_iteration = false;
|
||||
bool just_data = false;
|
||||
#ifndef _MSC_VER
|
||||
int c;
|
||||
bool tabbed_output = false;
|
||||
/**
|
||||
* Benchmarking on a cold parser instance means that the parsing may include
|
||||
* memory allocation at the OS level. This may lead to apparently odd results
|
||||
* such that higher speed under the Windows Subsystem for Linux than under the
|
||||
* regular Windows, for the same machine. It is arguably misleading to benchmark
|
||||
* how the OS allocates memory, when we really want to just benchmark simdjson.
|
||||
*/
|
||||
bool hotbuffers = true;
|
||||
|
||||
while ((c = getopt(argc, argv, "1vdt")) != -1) {
|
||||
switch (c) {
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'd':
|
||||
dump = true;
|
||||
break;
|
||||
case 'j':
|
||||
json_output = true;
|
||||
break;
|
||||
case '1':
|
||||
force_one_iteration = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#else
|
||||
int optind = 1;
|
||||
#endif
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] loading " << filename << std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] loaded " << filename << " (" << p.size()
|
||||
<< " bytes)" << std::endl;
|
||||
}
|
||||
#if defined(DEBUG)
|
||||
const uint32_t iterations = 1;
|
||||
#else
|
||||
const uint32_t iterations =
|
||||
force_one_iteration ? 1 : (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
||||
#endif
|
||||
std::vector<double> res;
|
||||
res.resize(iterations);
|
||||
if (!just_data)
|
||||
printf("number of iterations %u \n", iterations);
|
||||
#if !defined(__linux__)
|
||||
#define SQUASH_COUNTERS
|
||||
if (just_data) {
|
||||
printf("just_data (-t) flag only works under linux.\n");
|
||||
}
|
||||
#endif
|
||||
{ // practice run
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (allocok) {
|
||||
simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj);
|
||||
simdjson::unified_ptr(
|
||||
(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t
|
||||
*)(const uint8_t *)
|
||||
p.data(),
|
||||
p.size(), pj);
|
||||
}
|
||||
}
|
||||
#ifndef SQUASH_COUNTERS
|
||||
std::vector<int> evts;
|
||||
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
|
||||
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
|
||||
evts.push_back(PERF_COUNT_HW_BRANCH_MISSES);
|
||||
evts.push_back(PERF_COUNT_HW_CACHE_REFERENCES);
|
||||
evts.push_back(PERF_COUNT_HW_CACHE_MISSES);
|
||||
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
|
||||
std::vector<unsigned long long> results;
|
||||
results.resize(evts.size());
|
||||
unsigned long cy0 = 0, cy1 = 0, cy2 = 0;
|
||||
unsigned long cl0 = 0, cl1 = 0, cl2 = 0;
|
||||
unsigned long mis0 = 0, mis1 = 0, mis2 = 0;
|
||||
unsigned long cref0 = 0, cref1 = 0, cref2 = 0;
|
||||
unsigned long cmis0 = 0, cmis1 = 0, cmis2 = 0;
|
||||
#endif
|
||||
bool isok = true;
|
||||
#ifndef SQUASH_COUNTERS
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||
}
|
||||
unified.start();
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
unified.end(results);
|
||||
cy0 += results[0];
|
||||
cl0 += results[1];
|
||||
mis0 += results[2];
|
||||
cref0 += results[3];
|
||||
cmis0 += results[4];
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] allocated memory for parsed JSON " << std::endl;
|
||||
}
|
||||
unified.start();
|
||||
isok = (simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
cy1 += results[0];
|
||||
cl1 += results[1];
|
||||
mis1 += results[2];
|
||||
cref1 += results[3];
|
||||
cmis1 += results[4];
|
||||
if (!isok) {
|
||||
std::cout << "Failed during stage 1" << std::endl;
|
||||
break;
|
||||
}
|
||||
unified.start();
|
||||
isok = isok &&
|
||||
(simdjson::SUCCESS ==
|
||||
simdjson::unified_ptr((const uint8_t *)p.data(), p.size(), pj));
|
||||
unified.end(results);
|
||||
cy2 += results[0];
|
||||
cl2 += results[1];
|
||||
mis2 += results[2];
|
||||
cref2 += results[3];
|
||||
cmis2 += results[4];
|
||||
if (!isok) {
|
||||
std::cout << "Failed during stage 2" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// we do it again, this time just measuring the elapsed time
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] iteration # " << i << std::endl;
|
||||
}
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "[verbose] allocated memory for parsed JSON " << std::endl;
|
||||
}
|
||||
option_struct(int argc, char **argv) {
|
||||
int c;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
isok = (simdjson::stage1_ptr((const uint8_t *)p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
isok = isok &&
|
||||
(simdjson::SUCCESS ==
|
||||
simdjson::unified_ptr((const uint8_t *)p.data(), p.size(), pj));
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
res[i] = secs.count();
|
||||
if (!isok) {
|
||||
std::cerr << pj.get_error_message() << std::endl;
|
||||
std::cerr << "Could not parse. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
simdjson::ParsedJson pj =
|
||||
build_parsed_json(p); // do the parsing again to get the stats
|
||||
if (!pj.is_valid()) {
|
||||
std::cerr << pj.get_error_message() << std::endl;
|
||||
std::cerr << "Could not parse. " << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
double min_result = *min_element(res.begin(), res.end());
|
||||
double speedinGBs = (p.size()) / (min_result * 1000000000.0);
|
||||
#ifndef SQUASH_COUNTERS
|
||||
unsigned long total = cy0 + cy1 + cy2;
|
||||
if (just_data) {
|
||||
float cpb0 = (double)cy0 / (iterations * p.size());
|
||||
float cpb1 = (double)cy1 / (iterations * p.size());
|
||||
float cpb2 = (double)cy2 / (iterations * p.size());
|
||||
float cpbtotal = (double)total / (iterations * p.size());
|
||||
char *newfile = (char *)malloc(strlen(filename) + 1);
|
||||
if (newfile == NULL) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
::strcpy(newfile, filename);
|
||||
char *snewfile = ::basename(newfile);
|
||||
size_t nl = strlen(snewfile);
|
||||
for (size_t j = nl - 1; j > 0; j--) {
|
||||
if (snewfile[j] == '.') {
|
||||
snewfile[j] = '\0';
|
||||
while ((c = getopt(argc, argv, "vtn:i:a:s:HC")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
iterations = atoi(optarg);
|
||||
break;
|
||||
case 'i':
|
||||
iteration_step = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
tabbed_output = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a': {
|
||||
const implementation *impl = simdjson::available_implementations[optarg];
|
||||
if (!impl) {
|
||||
std::string exit_message = string("Unsupported option value -a ") + optarg + ": expected -a with one of ";
|
||||
for (auto imple : simdjson::available_implementations) {
|
||||
exit_message += imple->name();
|
||||
exit_message += " ";
|
||||
}
|
||||
exit_usage(exit_message);
|
||||
}
|
||||
simdjson::active_implementation = impl;
|
||||
break;
|
||||
}
|
||||
case 'C':
|
||||
hotbuffers = false;
|
||||
break;
|
||||
case 'H':
|
||||
hotbuffers = true;
|
||||
break;
|
||||
case 's':
|
||||
if (!strcmp(optarg, "stage1")) {
|
||||
stage1_only = true;
|
||||
} else if (!strcmp(optarg, "all")) {
|
||||
stage1_only = false;
|
||||
} else {
|
||||
exit_usage(string("Unsupported option value -s ") + optarg + ": expected -s stage1 or all");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// reaching here means an argument was given to getopt() which did not have a case label
|
||||
exit_usage("Unexpected argument - missing case for option "+
|
||||
std::string(1,static_cast<char>(c))+
|
||||
" (programming error)");
|
||||
}
|
||||
}
|
||||
|
||||
if (iteration_step == -1) {
|
||||
iteration_step = iterations / 50;
|
||||
if (iteration_step < 200) { iteration_step = 200; }
|
||||
if (iteration_step > iterations) { iteration_step = iterations; }
|
||||
}
|
||||
|
||||
// All remaining arguments are considered to be files
|
||||
for (int i=optind; i<argc; i++) {
|
||||
files.push_back(argv[i]);
|
||||
}
|
||||
if (files.empty()) {
|
||||
exit_usage("No files specified");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Read options
|
||||
exe_name = argv[0];
|
||||
option_struct options(argc, argv);
|
||||
if (options.verbose) {
|
||||
verbose_stream = &cout;
|
||||
verbose() << "Implementation: " << simdjson::active_implementation->name() << endl;
|
||||
}
|
||||
|
||||
// Start collecting events. We put this early so if it prints an error message, it's the
|
||||
// first thing printed.
|
||||
event_collector collector;
|
||||
|
||||
// Print preamble
|
||||
if (!options.tabbed_output) {
|
||||
printf("number of iterations %u \n", options.iterations);
|
||||
}
|
||||
|
||||
// Set up benchmarkers by reading all files
|
||||
vector<benchmarker*> benchmarkers;
|
||||
for (size_t i=0; i<options.files.size(); i++) {
|
||||
benchmarkers.push_back(new benchmarker(options.files[i], collector));
|
||||
}
|
||||
|
||||
// Run the benchmarks
|
||||
progress_bar progress(options.iterations, 50);
|
||||
// Put the if (options.stage1_only) *outside* the loop so that run_iterations will be optimized
|
||||
if (options.stage1_only) {
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
// Benchmark each file once per iteration
|
||||
for (size_t f=0; f<options.files.size(); f++) {
|
||||
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
|
||||
benchmarkers[f]->run_iterations(options.iteration_step, true, options.hotbuffers);
|
||||
}
|
||||
}
|
||||
printf("\"%s\"\t%f\t%f\t%f\t%f\t%f\n", snewfile, cpb0, cpb1, cpb2, cpbtotal,
|
||||
speedinGBs);
|
||||
free(newfile);
|
||||
} else {
|
||||
printf("number of bytes %ld number of structural chars %u ratio %.3f\n",
|
||||
p.size(), pj.n_structural_indexes,
|
||||
(double)pj.n_structural_indexes / p.size());
|
||||
printf("mem alloc instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache accesses: "
|
||||
"%10lu (failure %10lu)\n",
|
||||
cl0 / iterations, cy0 / iterations, 100. * cy0 / total,
|
||||
(double)cl0 / cy0, mis0 / iterations, (double)cy0 / mis0,
|
||||
cref1 / iterations, cmis0 / iterations);
|
||||
printf(" mem alloc runs at %.2f cycles per input byte.\n",
|
||||
(double)cy0 / (iterations * p.size()));
|
||||
printf("stage 1 instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache accesses: "
|
||||
"%10lu (failure %10lu)\n",
|
||||
cl1 / iterations, cy1 / iterations, 100. * cy1 / total,
|
||||
(double)cl1 / cy1, mis1 / iterations, (double)cy1 / mis1,
|
||||
cref1 / iterations, cmis1 / iterations);
|
||||
printf(" stage 1 runs at %.2f cycles per input byte.\n",
|
||||
(double)cy1 / (iterations * p.size()));
|
||||
for (int iteration = 0; iteration < options.iterations; iteration += options.iteration_step) {
|
||||
if (!options.verbose) { progress.print(iteration); }
|
||||
// Benchmark each file once per iteration
|
||||
for (size_t f=0; f<options.files.size(); f++) {
|
||||
verbose() << "[verbose] " << benchmarkers[f]->filename << " iterations #" << iteration << "-" << (iteration+options.iteration_step-1) << endl;
|
||||
benchmarkers[f]->run_iterations(options.iteration_step, false, options.hotbuffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!options.verbose) { progress.erase(); }
|
||||
|
||||
printf("stage 2 instructions: %10lu cycles: %10lu (%.2f %%) ins/cycles: "
|
||||
"%.2f mis. branches: %10lu (cycles/mis.branch %.2f) cache "
|
||||
"accesses: %10lu (failure %10lu)\n",
|
||||
cl2 / iterations, cy2 / iterations, 100. * cy2 / total,
|
||||
(double)cl2 / cy2, mis2 / iterations, (double)cy2 / mis2,
|
||||
cref2 / iterations, cmis2 / iterations);
|
||||
printf(" stage 2 runs at %.2f cycles per input byte and ",
|
||||
(double)cy2 / (iterations * p.size()));
|
||||
printf("%.2f cycles per structural character.\n",
|
||||
(double)cy2 / (iterations * pj.n_structural_indexes));
|
||||
for (size_t i=0; i<options.files.size(); i++) {
|
||||
benchmarkers[i]->print(options.tabbed_output);
|
||||
delete benchmarkers[i];
|
||||
}
|
||||
|
||||
printf(" all stages: %.2f cycles per input byte.\n",
|
||||
(double)total / (iterations * p.size()));
|
||||
printf("Estimated average frequency: %.3f GHz.\n",
|
||||
(double)total / (iterations * min_result * 1000000000.0));
|
||||
}
|
||||
#endif
|
||||
if (!just_data) {
|
||||
std::cout << "Min: " << min_result << " bytes read: " << p.size()
|
||||
<< " Gigabytes/second: " << speedinGBs << std::endl;
|
||||
}
|
||||
if (json_output) {
|
||||
isok = isok && pj.print_json(std::cout);
|
||||
}
|
||||
if (dump) {
|
||||
isok = isok && pj.dump_raw_tape(std::cout);
|
||||
}
|
||||
if (!isok) {
|
||||
fprintf(stderr, " Parsing failed. \n ");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "simdjson.h"
|
||||
|
||||
#define NB_ITERATION 20
|
||||
#define MIN_BATCH_SIZE 10000
|
||||
#define MAX_BATCH_SIZE 10000000
|
||||
|
||||
bool test_baseline = false;
|
||||
bool test_per_batch = true;
|
||||
bool test_best_batch = false;
|
||||
|
||||
bool compare(std::pair<size_t, double> i, std::pair<size_t, double> j) {
|
||||
return i.second > j.second;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc <= 1) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[1];
|
||||
auto[p, err] = simdjson::padded_string::load(filename);
|
||||
if (err) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (test_baseline) {
|
||||
std::wclog << "Baseline: Getline + normal parse... " << std::endl;
|
||||
std::cout << "Gigabytes/second\t"
|
||||
<< "Nb of documents parsed" << std::endl;
|
||||
for (auto i = 0; i < 3; i++) {
|
||||
// Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code alloc_error = parser.allocate(p.size());
|
||||
if (alloc_error) {
|
||||
std::cerr << alloc_error << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::istringstream ss(std::string(p.data(), p.size()));
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int count = 0;
|
||||
std::string line;
|
||||
int parse_res = simdjson::SUCCESS;
|
||||
while (getline(ss, line)) {
|
||||
// TODO we're likely triggering simdjson's padding reallocation here. Is
|
||||
// that intentional?
|
||||
parser.parse(line);
|
||||
count++;
|
||||
}
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
double speedinGBs = static_cast<double>(p.size()) /
|
||||
(static_cast<double>(secs.count()) * 1000000000.0);
|
||||
std::cout << speedinGBs << "\t\t\t\t" << count << std::endl;
|
||||
|
||||
if (parse_res != simdjson::SUCCESS) {
|
||||
std::cerr << "Parsing failed" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<size_t, double> batch_size_res;
|
||||
if (test_per_batch) {
|
||||
std::wclog << "parse_many: Speed per batch_size... from " << MIN_BATCH_SIZE
|
||||
<< " bytes to " << MAX_BATCH_SIZE << " bytes..." << std::endl;
|
||||
std::cout << "Batch Size\t"
|
||||
<< "Gigabytes/second\t"
|
||||
<< "Nb of documents parsed" << std::endl;
|
||||
for (size_t i = MIN_BATCH_SIZE; i <= MAX_BATCH_SIZE;
|
||||
i += (MAX_BATCH_SIZE - MIN_BATCH_SIZE) / 100) {
|
||||
batch_size_res.insert(std::pair<size_t, double>(i, 0));
|
||||
int count;
|
||||
for (size_t j = 0; j < 5; j++) {
|
||||
// Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
count = 0;
|
||||
simdjson::dom::document_stream docs;
|
||||
if ((error = parser.parse_many(p, i).get(docs))) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
for (auto result : docs) {
|
||||
error = result.error();
|
||||
if (error) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
double speedinGBs = static_cast<double>(p.size()) /
|
||||
(static_cast<double>(secs.count()) * 1000000000.0);
|
||||
if (speedinGBs > batch_size_res.at(i))
|
||||
batch_size_res[i] = speedinGBs;
|
||||
}
|
||||
std::cout << i << "\t\t" << std::fixed << std::setprecision(3)
|
||||
<< batch_size_res.at(i) << "\t\t\t\t" << count << std::endl;
|
||||
}
|
||||
}
|
||||
size_t optimal_batch_size{};
|
||||
double best_speed{};
|
||||
if (test_per_batch) {
|
||||
std::pair<size_t, double> best_results;
|
||||
best_results =
|
||||
(*min_element(batch_size_res.begin(), batch_size_res.end(), compare));
|
||||
optimal_batch_size = best_results.first;
|
||||
best_speed = best_results.second;
|
||||
} else {
|
||||
optimal_batch_size = MIN_BATCH_SIZE;
|
||||
}
|
||||
std::wclog << "Seemingly optimal batch_size: " << optimal_batch_size << "..."
|
||||
<< std::endl;
|
||||
std::wclog << "Best speed: " << best_speed << "..." << std::endl;
|
||||
|
||||
if (test_best_batch) {
|
||||
std::wclog << "Starting speed test... Best of " << NB_ITERATION
|
||||
<< " iterations..." << std::endl;
|
||||
std::vector<double> res;
|
||||
for (int i = 0; i < NB_ITERATION; i++) {
|
||||
|
||||
// Actual test
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code error;
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
// This includes allocation of the parser
|
||||
simdjson::dom::document_stream docs;
|
||||
if ((error = parser.parse_many(p, optimal_batch_size).get(docs))) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
for (auto result : docs) {
|
||||
error = result.error();
|
||||
if (error) {
|
||||
std::wcerr << "Parsing failed with: " << error << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> secs = end - start;
|
||||
res.push_back(secs.count());
|
||||
}
|
||||
|
||||
double min_result = *min_element(res.begin(), res.end());
|
||||
double speedinGBs =
|
||||
static_cast<double>(p.size()) / (min_result * 1000000000.0);
|
||||
|
||||
std::cout << "Min: " << min_result << " bytes read: " << p.size()
|
||||
<< " Gigabytes/second: " << speedinGBs << std::endl;
|
||||
}
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
// Multithreading probably does not help matters for small files (less than 10
|
||||
// MB).
|
||||
if (p.size() < 10000000) {
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "Warning: your file is small and the performance results are "
|
||||
"probably meaningless"
|
||||
<< std::endl;
|
||||
std::cout << "as far as multithreaded performance goes." << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout
|
||||
<< "Try to concatenate the file with itself to generate a large one."
|
||||
<< std::endl;
|
||||
std::cout << "In bash: " << std::endl;
|
||||
std::cout << "for i in {1..1000}; do cat '" << filename
|
||||
<< "' >> bar.ndjson; done" << std::endl;
|
||||
std::cout << argv[0] << " bar.ndjson" << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#include <unistd.h>
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -11,6 +14,8 @@
|
||||
|
||||
#include "sajson.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
using namespace simdjson;
|
||||
struct stat_s {
|
||||
@@ -43,63 +48,180 @@ void print_stat(const stat_t &s) {
|
||||
s.true_count, s.false_count);
|
||||
}
|
||||
|
||||
__attribute__((noinline)) stat_t
|
||||
simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
simdjson::ParsedJson pj = build_parsed_json(p);
|
||||
answer.valid = pj.is_valid();
|
||||
if (!answer.valid) {
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
size_t tape_idx = 0;
|
||||
uint64_t tape_val = pj.tape[tape_idx++];
|
||||
uint8_t type = (tape_val >> 56);
|
||||
size_t how_many = 0;
|
||||
assert(type == 'r');
|
||||
how_many = tape_val & JSON_VALUE_MASK;
|
||||
for (; tape_idx < how_many; tape_idx++) {
|
||||
tape_val = pj.tape[tape_idx];
|
||||
// uint64_t payload = tape_val & JSON_VALUE_MASK;
|
||||
type = (tape_val >> 56);
|
||||
switch (type) {
|
||||
case 'l': // we have a long int
|
||||
answer.number_count++;
|
||||
tape_idx++; // skipping the integer
|
||||
break;
|
||||
case 'd': // we have a double
|
||||
answer.number_count++;
|
||||
tape_idx++; // skipping the double
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
answer.null_count++;
|
||||
break;
|
||||
case 't': // we have a true
|
||||
answer.true_count++;
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
answer.false_count++;
|
||||
break;
|
||||
case '{': // we have an object
|
||||
answer.object_count++;
|
||||
break;
|
||||
case '}': // we end an object
|
||||
break;
|
||||
case '[': // we start an array
|
||||
answer.array_count++;
|
||||
break;
|
||||
case ']': // we end an array
|
||||
break;
|
||||
default:
|
||||
break; // ignore
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<double>()) {
|
||||
s.number_count++;
|
||||
} else if (element.is<bool>()) {
|
||||
simdjson::error_code error;
|
||||
bool v;
|
||||
if (not (error = element.get(v)) && v) {
|
||||
s.true_count++;
|
||||
} else {
|
||||
s.false_count++;
|
||||
}
|
||||
} else if (element.is_null()) {
|
||||
s.null_count++;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
|
||||
error_code error;
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
s.array_count++;
|
||||
dom::array array;
|
||||
if ((error = element.get(array))) {
|
||||
std::cerr << error << std::endl;
|
||||
abort();
|
||||
}
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() ||
|
||||
child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, child);
|
||||
} else {
|
||||
simdjson_process_atom(s, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
s.object_count++;
|
||||
dom::object object;
|
||||
if ((error = element.get(object))) {
|
||||
std::cerr << error << std::endl;
|
||||
abort();
|
||||
}
|
||||
for (auto field : object) {
|
||||
if (field.value.is<simdjson::dom::array>() ||
|
||||
field.value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, field.value);
|
||||
} else {
|
||||
simdjson_process_atom(s, field.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
simdjson_process_atom(s, element);
|
||||
}
|
||||
}
|
||||
|
||||
never_inline stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t s{};
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (error) {
|
||||
s.valid = false;
|
||||
return s;
|
||||
}
|
||||
s.valid = true;
|
||||
simdjson_recurse(s, doc);
|
||||
return s;
|
||||
}
|
||||
|
||||
///
|
||||
struct Stat {
|
||||
size_t objectCount;
|
||||
size_t arrayCount;
|
||||
size_t numberCount;
|
||||
size_t stringCount;
|
||||
size_t trueCount;
|
||||
size_t falseCount;
|
||||
size_t nullCount;
|
||||
|
||||
size_t memberCount; // Number of members in all objects
|
||||
size_t elementCount; // Number of elements in all arrays
|
||||
size_t stringLength; // Number of code units in all strings
|
||||
};
|
||||
|
||||
static void GenStatPlus(Stat &stat, const dom::element &v) {
|
||||
switch (v.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
for (dom::element child : dom::array(v)) {
|
||||
GenStatPlus(stat, child);
|
||||
stat.elementCount++;
|
||||
}
|
||||
stat.arrayCount++;
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
for (dom::key_value_pair kv : dom::object(v)) {
|
||||
GenStatPlus(stat, dom::element(kv.value));
|
||||
stat.memberCount++;
|
||||
stat.stringCount++;
|
||||
}
|
||||
stat.objectCount++;
|
||||
break;
|
||||
case dom::element_type::INT64:
|
||||
case dom::element_type::UINT64:
|
||||
case dom::element_type::DOUBLE:
|
||||
stat.numberCount++;
|
||||
break;
|
||||
case dom::element_type::STRING: {
|
||||
stat.stringCount++;
|
||||
auto sv = std::string_view(v);
|
||||
stat.stringLength += sv.size();
|
||||
} break;
|
||||
case dom::element_type::BOOL:
|
||||
if (bool(v)) {
|
||||
stat.trueCount++;
|
||||
} else {
|
||||
stat.falseCount++;
|
||||
}
|
||||
break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
++stat.nullCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void RapidGenStat(Stat &stat, const rapidjson::Value &v) {
|
||||
switch (v.GetType()) {
|
||||
case kNullType:
|
||||
stat.nullCount++;
|
||||
break;
|
||||
case kFalseType:
|
||||
stat.falseCount++;
|
||||
break;
|
||||
case kTrueType:
|
||||
stat.trueCount++;
|
||||
break;
|
||||
|
||||
case kObjectType:
|
||||
for (Value::ConstMemberIterator m = v.MemberBegin(); m != v.MemberEnd();
|
||||
++m) {
|
||||
stat.stringLength += m->name.GetStringLength();
|
||||
RapidGenStat(stat, m->value);
|
||||
}
|
||||
stat.objectCount++;
|
||||
stat.memberCount += (v.MemberEnd() - v.MemberBegin());
|
||||
stat.stringCount += (v.MemberEnd() - v.MemberBegin()); // Key
|
||||
break;
|
||||
|
||||
case kArrayType:
|
||||
for (Value::ConstValueIterator i = v.Begin(); i != v.End(); ++i)
|
||||
RapidGenStat(stat, *i);
|
||||
stat.arrayCount++;
|
||||
stat.elementCount += v.Size();
|
||||
break;
|
||||
|
||||
case kStringType:
|
||||
stat.stringCount++;
|
||||
stat.stringLength += v.GetStringLength();
|
||||
break;
|
||||
|
||||
case kNumberType:
|
||||
stat.numberCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
never_inline Stat rapidjson_compute_stats_ref(const rapidjson::Value &doc) {
|
||||
Stat s{};
|
||||
RapidGenStat(s, doc);
|
||||
return s;
|
||||
}
|
||||
|
||||
never_inline Stat
|
||||
simdjson_compute_stats_refplus(const simdjson::dom::element &doc) {
|
||||
Stat s{};
|
||||
GenStatPlus(s, doc);
|
||||
return s;
|
||||
}
|
||||
|
||||
// see
|
||||
@@ -145,15 +267,18 @@ void sajson_traverse(stat_t &stats, const sajson::value &node) {
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) stat_t
|
||||
sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
never_inline stat_t sasjon_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size());
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
auto d = sajson::parse(sajson::dynamic_allocation(),
|
||||
sajson::mutable_string_view(p.size(), buffer));
|
||||
answer.valid = d.is_valid();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
@@ -203,16 +328,19 @@ void rapid_traverse(stat_t &stats, const rapidjson::Value &v) {
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noinline)) stat_t
|
||||
rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
never_inline stat_t rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
rapidjson::Document d;
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
|
||||
answer.valid = !d.HasParseError();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
@@ -226,6 +354,32 @@ rapid_compute_stats(const simdjson::padded_string &p) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
never_inline stat_t
|
||||
rapid_accurate_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer{};
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
if (buffer == nullptr) {
|
||||
return answer;
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
rapidjson::Document d;
|
||||
d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(buffer);
|
||||
answer.valid = !d.HasParseError();
|
||||
if (!answer.valid) {
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
answer.number_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
rapid_traverse(answer, d);
|
||||
free(buffer);
|
||||
return answer;
|
||||
}
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
@@ -257,19 +411,18 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input has ";
|
||||
if (p.size() > 1024 * 1024)
|
||||
std::cout << p.size() / (1024 * 1024) << " MB ";
|
||||
else if (p.size() > 1024)
|
||||
std::cout << p.size() / 1024 << " KB ";
|
||||
if (p.size() > 1000 * 1000)
|
||||
std::cout << p.size() / (1000 * 1000) << " MB ";
|
||||
else if (p.size() > 1000)
|
||||
std::cout << p.size() / 1000 << " KB ";
|
||||
else
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
@@ -284,6 +437,11 @@ int main(int argc, char *argv[]) {
|
||||
printf("rapid: ");
|
||||
print_stat(s2);
|
||||
}
|
||||
stat_t s2a = rapid_accurate_compute_stats(p);
|
||||
if (verbose) {
|
||||
printf("rapid full: ");
|
||||
print_stat(s2a);
|
||||
}
|
||||
stat_t s3 = sasjon_compute_stats(p);
|
||||
if (verbose) {
|
||||
printf("sasjon: ");
|
||||
@@ -292,14 +450,39 @@ int main(int argc, char *argv[]) {
|
||||
assert(stat_equal(s1, s2));
|
||||
assert(stat_equal(s1, s3));
|
||||
int repeat = 50;
|
||||
int volume = p.size();
|
||||
size_t volume = p.size();
|
||||
if (just_data) {
|
||||
printf("name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
|
||||
}
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).valid, true, , repeat,
|
||||
volume, !just_data);
|
||||
BEST_TIME("RapidJSON ", rapid_compute_stats(p).valid, true, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).valid, true, , repeat, volume,
|
||||
!just_data);
|
||||
BEST_TIME("simdjson ", simdjson_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON ", rapid_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON (precise) ", rapid_accurate_compute_stats(p).valid, true,
|
||||
, repeat, volume, !just_data);
|
||||
BEST_TIME("sasjon ", sasjon_compute_stats(p).valid, true, ,
|
||||
repeat, volume, !just_data);
|
||||
if (!just_data) {
|
||||
printf("API traversal tests\n");
|
||||
printf("Based on https://github.com/miloyip/nativejson-benchmark\n");
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (error) {
|
||||
std::cerr << error << std::endl;
|
||||
}
|
||||
size_t refval = simdjson_compute_stats_refplus(doc).objectCount;
|
||||
|
||||
BEST_TIME("simdjson ",
|
||||
simdjson_compute_stats_refplus(doc).objectCount, refval, , repeat,
|
||||
volume, !just_data);
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
rapidjson::Document d;
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
|
||||
BEST_TIME("rapid ", rapidjson_compute_stats_ref(d).objectCount,
|
||||
refval, , repeat, volume, !just_data);
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#ifndef _MSC_VER
|
||||
#include "linux-perf-events.h"
|
||||
#include <unistd.h>
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif //__linux__
|
||||
@@ -11,6 +12,8 @@
|
||||
|
||||
#include "benchmark.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
|
||||
// #define RAPIDJSON_SSE2 // bad for performance
|
||||
// #define RAPIDJSON_SSE42 // bad for performance
|
||||
#include "rapidjson/document.h"
|
||||
@@ -44,11 +47,13 @@ extern "C" {
|
||||
|
||||
#endif
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
#ifdef ALLPARSER
|
||||
// fastjson has a tricky interface
|
||||
void on_json_error(void *, const fastjson::ErrorContext &ec) {
|
||||
void on_json_error(void *, UNUSED const fastjson::ErrorContext &ec) {
|
||||
// std::cerr<<"ERROR: "<<ec.mesg<<std::endl;
|
||||
}
|
||||
bool fastjson_parse(const char *input) {
|
||||
@@ -60,70 +65,70 @@ bool fastjson_parse(const char *input) {
|
||||
// end of fastjson stuff
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "vt")) != -1)
|
||||
switch (c) {
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
|
||||
std::cerr << "The '-t' flag outputs a table. " << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
const char *filename = argv[optind];
|
||||
if (optind + 1 < argc) {
|
||||
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
|
||||
<< std::endl;
|
||||
never_inline size_t sum_line_lengths(std::stringstream & is) {
|
||||
std::string line;
|
||||
size_t sumofalllinelengths{0};
|
||||
while(std::getline(is, line)) {
|
||||
sumofalllinelengths += line.size();
|
||||
}
|
||||
return sumofalllinelengths;
|
||||
}
|
||||
|
||||
inline void reset_stream(std::stringstream & is) {
|
||||
is.clear();
|
||||
is.seekg(0,std::ios::beg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool bench(const char *filename, bool verbose, bool just_data, double repeat_multiplier) {
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &e) { // caught by reference to base
|
||||
std::cout << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
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; }
|
||||
// Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
|
||||
if (verbose) {
|
||||
std::cout << "Input has ";
|
||||
if (p.size() > 1024 * 1024)
|
||||
std::cout << p.size() / (1024 * 1024) << " MB ";
|
||||
else if (p.size() > 1024)
|
||||
std::cout << p.size() / 1024 << " KB ";
|
||||
std::cout << "Input " << filename << " has ";
|
||||
if (p.size() > 1000 * 1000)
|
||||
std::cout << p.size() / (1000 * 1000) << " MB";
|
||||
else if (p.size() > 1000)
|
||||
std::cout << p.size() / 1000 << " KB";
|
||||
else
|
||||
std::cout << p.size() << " B ";
|
||||
std::cout << std::endl;
|
||||
std::cout << p.size() << " B";
|
||||
std::cout << ": will run " << repeat << " iterations." << std::endl;
|
||||
}
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size(), 1024);
|
||||
|
||||
if (!allocok) {
|
||||
std::cerr << "can't allocate memory" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
int repeat = (p.size() < 1 * 1000 * 1000 ? 1000 : 10);
|
||||
int volume = p.size();
|
||||
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");
|
||||
}
|
||||
if (!just_data)
|
||||
BEST_TIME("simdjson (dynamic mem) ", build_parsed_json(p).is_valid(), true,
|
||||
, repeat, volume, !just_data);
|
||||
// (static alloc)
|
||||
BEST_TIME("simdjson ", json_parse(p, pj), simdjson::SUCCESS, , repeat, volume,
|
||||
!just_data);
|
||||
if (!just_data) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (!just_data) {
|
||||
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);
|
||||
}
|
||||
// (static alloc)
|
||||
simdjson::dom::parser parser;
|
||||
BEST_TIME("simdjson ", parser.parse(p).error(), simdjson::SUCCESS, , repeat, volume,
|
||||
!just_data);
|
||||
|
||||
rapidjson::Document d;
|
||||
|
||||
char *buffer = (char *)malloc(p.size() + 1);
|
||||
@@ -132,16 +137,35 @@ int main(int argc, char *argv[]) {
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON ",
|
||||
d.Parse<kParseValidateEncodingFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
{
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
BEST_TIME("RapidJSON (accurate number parsing) ",
|
||||
d.Parse<kParseValidateEncodingFlag|kParseFullPrecisionFlag>((const char *)buffer)
|
||||
.HasParseError(),
|
||||
false, , repeat, volume,
|
||||
!just_data);
|
||||
}
|
||||
BEST_TIME("RapidJSON (insitu)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag>(buffer).HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
BEST_TIME("RapidJSON (insitu, accurate number parsing)",
|
||||
d.ParseInsitu<kParseValidateEncodingFlag|kParseFullPrecisionFlag>(buffer).HasParseError(),
|
||||
false,
|
||||
memcpy(buffer, p.data(), p.size()) && (buffer[p.size()] = '\0'),
|
||||
repeat, volume, !just_data);
|
||||
#ifndef ALLPARSER
|
||||
if (!just_data)
|
||||
#endif
|
||||
@@ -161,9 +185,11 @@ int main(int argc, char *argv[]) {
|
||||
sajson::mutable_string_view(p.size(), buffer))
|
||||
.is_valid(),
|
||||
true, memcpy(buffer, p.data(), p.size()), repeat, volume, !just_data);
|
||||
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
size_t expected = json::parse(p.data(), p.data() + p.size()).size();
|
||||
BEST_TIME("nlohmann-json", json::parse(buffer, buffer + p.size()).size(),
|
||||
expected, memcpy(buffer, p.data(), p.size()), repeat, volume,
|
||||
expected, , repeat, volume,
|
||||
!just_data);
|
||||
|
||||
#ifdef ALLPARSER
|
||||
@@ -190,14 +216,14 @@ int main(int argc, char *argv[]) {
|
||||
{
|
||||
std::unique_ptr<jsmntok_t[]> tokens =
|
||||
std::make_unique<jsmntok_t[]>(p.size());
|
||||
jsmn_parser parser;
|
||||
jsmn_init(&parser);
|
||||
jsmn_parser jparser;
|
||||
jsmn_init(&jparser);
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
BEST_TIME(
|
||||
"jsmn ",
|
||||
(jsmn_parse(&parser, buffer, p.size(), tokens.get(), p.size()) > 0),
|
||||
true, jsmn_init(&parser), repeat, volume, !just_data);
|
||||
(jsmn_parse(&jparser, buffer, p.size(), tokens.get(), static_cast<unsigned int>(p.size())) > 0),
|
||||
true, jsmn_init(&jparser), repeat, volume, !just_data);
|
||||
}
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
@@ -235,9 +261,10 @@ int main(int argc, char *argv[]) {
|
||||
results.resize(evts.size());
|
||||
stats.resize(evts.size());
|
||||
std::fill(stats.begin(), stats.end(), 0); // unnecessary
|
||||
for (int i = 0; i < repeat; i++) {
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
unified.start();
|
||||
if (json_parse(p, pj) != simdjson::SUCCESS)
|
||||
auto parse_error = parser.parse(p).error();
|
||||
if (parse_error)
|
||||
printf("bug\n");
|
||||
unified.end(results);
|
||||
std::transform(stats.begin(), stats.end(), results.begin(), stats.begin(),
|
||||
@@ -246,13 +273,13 @@ int main(int argc, char *argv[]) {
|
||||
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",
|
||||
stats[0] * 1.0 / repeat, stats[1] * 1.0 / repeat,
|
||||
stats[2] * 1.0 / repeat, stats[3] * 1.0 / repeat,
|
||||
stats[4] * 1.0 / repeat, volume * repeat * 1.0 / stats[2],
|
||||
stats[1] * 1.0 / stats[0], stats[1] * 1.0 / (volume * repeat));
|
||||
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 (int i = 0; i < repeat; i++) {
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
buffer[p.size()] = '\0';
|
||||
unified.start();
|
||||
@@ -266,13 +293,13 @@ int main(int argc, char *argv[]) {
|
||||
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",
|
||||
stats[0] * 1.0 / repeat, stats[1] * 1.0 / repeat,
|
||||
stats[2] * 1.0 / repeat, stats[3] * 1.0 / repeat,
|
||||
stats[4] * 1.0 / repeat, volume * repeat * 1.0 / stats[2],
|
||||
stats[1] * 1.0 / stats[0], stats[1] * 1.0 / (volume * repeat));
|
||||
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 (int i = 0; i < repeat; i++) {
|
||||
for (decltype(repeat) i = 0; i < repeat; i++) {
|
||||
memcpy(buffer, p.data(), p.size());
|
||||
unified.start();
|
||||
if (sajson::parse(sajson::bounded_allocation(ast_buffer, ast_buffer_size),
|
||||
@@ -286,13 +313,49 @@ int main(int argc, char *argv[]) {
|
||||
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",
|
||||
stats[0] * 1.0 / repeat, stats[1] * 1.0 / repeat,
|
||||
stats[2] * 1.0 / repeat, stats[3] * 1.0 / repeat,
|
||||
stats[4] * 1.0 / repeat, volume * repeat * 1.0 / stats[2],
|
||||
stats[1] * 1.0 / stats[0], stats[1] * 1.0 / (volume * repeat));
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool verbose = false;
|
||||
bool just_data = false;
|
||||
double repeat_multiplier = 1;
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "r:vt")) != -1)
|
||||
switch (c) {
|
||||
case 'r':
|
||||
repeat_multiplier = atof(optarg);
|
||||
break;
|
||||
case 't':
|
||||
just_data = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
if (optind >= argc) {
|
||||
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
|
||||
std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
|
||||
std::cerr << "The '-t' flag outputs a table." << std::endl;
|
||||
std::cerr << "The '-r <N>' flag sets the repeat multiplier: set it above 1 to do more iterations, and below 1 to do fewer." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
int result = EXIT_SUCCESS;
|
||||
for (int fileind = optind; fileind < argc; fileind++) {
|
||||
if (!bench(argv[fileind], verbose, just_data, repeat_multiplier)) { result = EXIT_FAILURE; }
|
||||
printf("\n\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
|
||||
int closepipe(FILE *pipe) {
|
||||
int exit_code = pclose(pipe);
|
||||
if (exit_code != EXIT_SUCCESS) {
|
||||
std::cerr << "Error " << exit_code << " running benchmark command!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
std::string exec(const char* cmd) {
|
||||
std::cerr << cmd << std::endl;
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
std::unique_ptr<FILE, decltype(&closepipe)> pipe(popen(cmd, "r"), closepipe);
|
||||
if (!pipe) {
|
||||
std::cerr << "popen() failed!" << std::endl;
|
||||
abort();
|
||||
}
|
||||
while (fgets(buffer.data(), int(buffer.size()), pipe.get()) != nullptr) {
|
||||
result += buffer.data();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double readThroughput(std::string parseOutput) {
|
||||
std::istringstream output(parseOutput);
|
||||
std::string line;
|
||||
double result = 0;
|
||||
int numResults = 0;
|
||||
while (std::getline(output, line)) {
|
||||
std::string::size_type pos = 0;
|
||||
for (int i=0; i<5; i++) {
|
||||
pos = line.find('\t', pos);
|
||||
if (pos == std::string::npos) {
|
||||
std::cerr << "Command printed out a line with less than 5 fields in it:\n" << line << std::endl;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
result += std::stod(line.substr(pos));
|
||||
numResults++;
|
||||
}
|
||||
if (numResults == 0) {
|
||||
std::cerr << "No results returned from benchmark command!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return result / numResults;
|
||||
}
|
||||
|
||||
const double INTERLEAVED_ATTEMPTS = 7;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <old parse exe> <new parse exe> [<parse arguments>]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string newCommand = argv[1];
|
||||
std::string refCommand = argv[2];
|
||||
for (int i=3; i<argc; i++) {
|
||||
newCommand += " ";
|
||||
newCommand += argv[i];
|
||||
refCommand += " ";
|
||||
refCommand += argv[i];
|
||||
}
|
||||
|
||||
std::vector<double> ref;
|
||||
std::vector<double> newcode;
|
||||
for (int attempt=0; attempt < INTERLEAVED_ATTEMPTS; attempt++) {
|
||||
std::cout << "Attempt #" << (attempt+1) << " of " << INTERLEAVED_ATTEMPTS << std::endl;
|
||||
|
||||
// Read new throughput
|
||||
double newThroughput = readThroughput(exec(newCommand.c_str()));
|
||||
std::cout << "New throughput: " << newThroughput << std::endl;
|
||||
newcode.push_back(newThroughput);
|
||||
|
||||
// Read reference throughput
|
||||
double referenceThroughput = readThroughput(exec(refCommand.c_str()));
|
||||
std::cout << "Ref throughput: " << referenceThroughput << std::endl;
|
||||
ref.push_back(referenceThroughput);
|
||||
}
|
||||
// we check if the maximum of newcode is lower than minimum of ref, if so we have a problem so fail!
|
||||
double worseref = *std::min_element(ref.begin(), ref.end());
|
||||
double bestnewcode = *std::max_element(newcode.begin(), newcode.end());
|
||||
double bestref = *std::max_element(ref.begin(), ref.end());
|
||||
double worsenewcode = *std::min_element(newcode.begin(), newcode.end());
|
||||
std::cout << "The new code has a throughput in " << worsenewcode << " -- " << bestnewcode << std::endl;
|
||||
std::cout << "The reference code has a throughput in " << worseref << " -- " << bestref << std::endl;
|
||||
if(bestnewcode < worseref) {
|
||||
std::cerr << "You probably have a performance degradation." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(bestnewcode < worseref) {
|
||||
std::cout << "You probably have a performance gain." << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
std::cout << "There is no obvious performance difference. A manual check might be needed." << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <iostream>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "simdjson/jsonioutil.h"
|
||||
#include "simdjson/jsonparser.h"
|
||||
#include "simdjson.h"
|
||||
#ifdef __linux__
|
||||
#include "linux-perf-events.h"
|
||||
#endif
|
||||
@@ -42,72 +39,77 @@ struct stat_s {
|
||||
|
||||
using stat_t = struct stat_s;
|
||||
|
||||
|
||||
|
||||
really_inline void simdjson_process_atom(stat_t &s,
|
||||
simdjson::dom::element element) {
|
||||
if (element.is<int64_t>()) {
|
||||
s.integer_count++;
|
||||
} else if(element.is<std::string_view>()) {
|
||||
s.string_count++;
|
||||
} else if(element.is<double>()) {
|
||||
s.float_count++;
|
||||
} else if (element.is<bool>()) {
|
||||
simdjson::error_code err;
|
||||
bool v;
|
||||
err = element.get(v);
|
||||
if (v) {
|
||||
s.true_count++;
|
||||
} else {
|
||||
s.false_count++;
|
||||
}
|
||||
} else if (element.is_null()) {
|
||||
s.null_count++;
|
||||
}
|
||||
}
|
||||
|
||||
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
|
||||
simdjson::error_code error;
|
||||
if (element.is<simdjson::dom::array>()) {
|
||||
s.array_count++;
|
||||
simdjson::dom::array array;
|
||||
if ((error = element.get(array))) { std::cerr << error << std::endl; abort(); }
|
||||
for (auto child : array) {
|
||||
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, child);
|
||||
} else {
|
||||
simdjson_process_atom(s, child);
|
||||
}
|
||||
}
|
||||
} else if (element.is<simdjson::dom::object>()) {
|
||||
s.object_count++;
|
||||
simdjson::dom::object object;
|
||||
if ((error = element.get(object))) { std::cerr << error << std::endl; abort(); }
|
||||
for (auto field : object) {
|
||||
s.string_count++; // for key
|
||||
if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
|
||||
simdjson_recurse(s, field.value);
|
||||
} else {
|
||||
simdjson_process_atom(s, field.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
simdjson_process_atom(s, element);
|
||||
}
|
||||
}
|
||||
|
||||
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
|
||||
stat_t answer;
|
||||
simdjson::ParsedJson pj = simdjson::build_parsed_json(p);
|
||||
answer.valid = pj.is_valid();
|
||||
if (!answer.valid) {
|
||||
stat_t answer{};
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse(p).get(doc);
|
||||
if (error) {
|
||||
answer.valid = false;
|
||||
return answer;
|
||||
}
|
||||
answer.valid = true;
|
||||
answer.backslash_count =
|
||||
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
|
||||
answer.non_ascii_byte_count = count_nonasciibytes(
|
||||
reinterpret_cast<const uint8_t *>(p.data()), p.size());
|
||||
answer.byte_count = p.size();
|
||||
answer.integer_count = 0;
|
||||
answer.float_count = 0;
|
||||
answer.object_count = 0;
|
||||
answer.array_count = 0;
|
||||
answer.null_count = 0;
|
||||
answer.true_count = 0;
|
||||
answer.false_count = 0;
|
||||
answer.string_count = 0;
|
||||
answer.structural_indexes_count = pj.n_structural_indexes;
|
||||
size_t tape_idx = 0;
|
||||
uint64_t tape_val = pj.tape[tape_idx++];
|
||||
uint8_t type = (tape_val >> 56);
|
||||
size_t how_many = 0;
|
||||
assert(type == 'r');
|
||||
how_many = tape_val & JSON_VALUE_MASK;
|
||||
for (; tape_idx < how_many; tape_idx++) {
|
||||
tape_val = pj.tape[tape_idx];
|
||||
// uint64_t payload = tape_val & JSON_VALUE_MASK;
|
||||
type = (tape_val >> 56);
|
||||
switch (type) {
|
||||
case 'l': // we have a long int
|
||||
answer.integer_count++;
|
||||
tape_idx++; // skipping the integer
|
||||
break;
|
||||
case 'd': // we have a double
|
||||
answer.float_count++;
|
||||
tape_idx++; // skipping the double
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
answer.null_count++;
|
||||
break;
|
||||
case 't': // we have a true
|
||||
answer.true_count++;
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
answer.false_count++;
|
||||
break;
|
||||
case '{': // we have an object
|
||||
answer.object_count++;
|
||||
break;
|
||||
case '}': // we end an object
|
||||
break;
|
||||
case '[': // we start an array
|
||||
answer.array_count++;
|
||||
break;
|
||||
case ']': // we end an array
|
||||
break;
|
||||
case '"': // we have a string
|
||||
answer.string_count++;
|
||||
break;
|
||||
default:
|
||||
break; // ignore
|
||||
}
|
||||
}
|
||||
answer.structural_indexes_count = parser.implementation->n_structural_indexes;
|
||||
simdjson_recurse(answer, doc);
|
||||
return answer;
|
||||
}
|
||||
|
||||
@@ -136,9 +138,8 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
}
|
||||
simdjson::padded_string p;
|
||||
try {
|
||||
simdjson::get_corpus(filename).swap(p);
|
||||
} catch (const std::exception &) { // caught by reference to base
|
||||
auto error = simdjson::padded_string::load(filename).get(p);
|
||||
if (error) {
|
||||
std::cerr << "Could not load the file " << filename << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -164,10 +165,10 @@ int main(int argc, char *argv[]) {
|
||||
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
|
||||
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
|
||||
#ifdef __linux__
|
||||
simdjson::ParsedJson pj;
|
||||
bool allocok = pj.allocate_capacity(p.size());
|
||||
if (!allocok) {
|
||||
std::cerr << "failed to allocate memory" << std::endl;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::error_code alloc_error = parser.allocate(p.size());
|
||||
if (alloc_error) {
|
||||
std::cerr << alloc_error << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
|
||||
@@ -181,17 +182,15 @@ int main(int argc, char *argv[]) {
|
||||
results.resize(evts.size());
|
||||
for (uint32_t i = 0; i < iterations; i++) {
|
||||
unified.start();
|
||||
// The default template is simdjson::Architecture::NATIVE.
|
||||
bool isok = (simdjson::find_structural_bits<>(p.data(), p.size(), pj) ==
|
||||
simdjson::SUCCESS);
|
||||
// The default template is simdjson::architecture::NATIVE.
|
||||
bool isok = (parser.implementation->stage1((const uint8_t *)p.data(), p.size(), false) == simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
|
||||
cy1 += results[0];
|
||||
cl1 += results[1];
|
||||
|
||||
unified.start();
|
||||
isok =
|
||||
isok && (simdjson::SUCCESS == unified_machine(p.data(), p.size(), pj));
|
||||
isok = isok && (parser.implementation->stage2(parser.doc) == simdjson::SUCCESS);
|
||||
unified.end(results);
|
||||
|
||||
cy2 += results[0];
|
||||
@@ -200,8 +199,8 @@ int main(int argc, char *argv[]) {
|
||||
std::cerr << "failure?" << std::endl;
|
||||
}
|
||||
}
|
||||
printf("%f %f %f %f ", cy1 * 1.0 / iterations, cl1 * 1.0 / iterations,
|
||||
cy2 * 1.0 / iterations, cl2 * 1.0 / iterations);
|
||||
printf("%f %f %f %f ", static_cast<double>(cy1) / static_cast<double>(iterations), static_cast<double>(cl1) / static_cast<double>(iterations),
|
||||
static_cast<double>(cy2) / static_cast<double>(iterations), static_cast<double>(cl2) / static_cast<double>(iterations));
|
||||
#endif // __linux__
|
||||
printf("\n");
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Helper so we don't have to repeat ourselves so much
|
||||
# Usage: add_cpp_test(testname [COMPILE_ONLY] [SOURCES a.cpp b.cpp ...] [LABELS acceptance per_implementation ...])
|
||||
# SOURCES defaults to testname.cpp if not specified.
|
||||
function(add_cpp_test TEST_NAME)
|
||||
# Parse arguments
|
||||
cmake_parse_arguments(PARSE_ARGV 1 ARGS "COMPILE_ONLY;LIBRARY;WILL_FAIL" "" "SOURCES;LABELS")
|
||||
if (NOT ARGS_SOURCES)
|
||||
list(APPEND ARGS_SOURCES ${TEST_NAME}.cpp)
|
||||
endif()
|
||||
if (ARGS_COMPILE_ONLY)
|
||||
list(APPEND ${ARGS_LABELS} compile)
|
||||
endif()
|
||||
|
||||
# Add the compile target
|
||||
if (ARGS_LIBRARY)
|
||||
add_library(${TEST_NAME} STATIC ${ARGS_SOURCES})
|
||||
else(ARGS_LIBRARY)
|
||||
add_executable(${TEST_NAME} ${ARGS_SOURCES})
|
||||
endif(ARGS_LIBRARY)
|
||||
|
||||
# Add test
|
||||
if (ARGS_COMPILE_ONLY OR ARGS_LIBRARY)
|
||||
add_test(
|
||||
NAME ${TEST_NAME}
|
||||
COMMAND ${CMAKE_COMMAND} --build . --target ${TEST_NAME} --config $<CONFIGURATION>
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
)
|
||||
set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE)
|
||||
else()
|
||||
add_test(${TEST_NAME} ${TEST_NAME})
|
||||
endif()
|
||||
|
||||
if (ARGS_LABELS)
|
||||
set_property(TEST ${TEST_NAME} APPEND PROPERTY LABELS ${ARGS_LABELS})
|
||||
endif()
|
||||
|
||||
if (ARGS_WILL_FAIL)
|
||||
set_property(TEST ${TEST_NAME} PROPERTY WILL_FAIL TRUE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_compile_only_test TEST_NAME)
|
||||
add_test(
|
||||
NAME ${TEST_NAME}
|
||||
COMMAND ${CMAKE_COMMAND} --build . --target ${TEST_NAME} --config $<CONFIGURATION>
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
)
|
||||
set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
message (STATUS "The simdjson repository appears to be standalone.")
|
||||
option(SIMDJSON_JUST_LIBRARY "Build just the library, omit tests, tools and benchmarks" OFF)
|
||||
message (STATUS "By default, we attempt to build everything.")
|
||||
else()
|
||||
message (STATUS "The simdjson repository appears to be used as a subdirectory.")
|
||||
option(SIMDJSON_JUST_LIBRARY "Build just the library, omit tests, tools and benchmarks" ON)
|
||||
message (STATUS "By default, we just build the library.")
|
||||
endif()
|
||||
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git)
|
||||
set(SIMDJSON_IS_UNDER_GIT ON CACHE BOOL "Whether cmake is under git control")
|
||||
message( STATUS "The simdjson repository appears to be under git." )
|
||||
else()
|
||||
set(SIMDJSON_IS_UNDER_GIT OFF CACHE BOOL "Whether cmake is under git control")
|
||||
message( STATUS "The simdjson repository does not appear to be under git." )
|
||||
endif()
|
||||
|
||||
#
|
||||
# Flags used by exes and by the simdjson library (project-wide flags)
|
||||
#
|
||||
add_library(simdjson-flags INTERFACE)
|
||||
add_library(simdjson-internal-flags INTERFACE)
|
||||
target_link_libraries(simdjson-internal-flags INTERFACE simdjson-flags)
|
||||
|
||||
option(SIMDJSON_SANITIZE "Sanitize addresses" OFF)
|
||||
if(SIMDJSON_SANITIZE)
|
||||
target_compile_options(simdjson-flags INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
|
||||
target_link_libraries(simdjson-flags INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
|
||||
|
||||
# Ubuntu bug for GCC 5.0+ (safe for all versions)
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
target_link_libraries(simdjson-flags INTERFACE -fuse-ld=gold)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "No build type selected, default to Release")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
||||
if(SIMDJSON_SANITIZE)
|
||||
message(WARNING "No build type selected and you have enabled the sanitizer. Consider setting CMAKE_BUILD_TYPE to Debug to help identify the eventual problems.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" ON) # turning it on disables the production of a dynamic library
|
||||
else()
|
||||
option(SIMDJSON_BUILD_STATIC "Build a static library" OFF) # turning it on disables the production of a dynamic library
|
||||
option(SIMDJSON_USE_LIBCPP "Use the libc++ library" OFF)
|
||||
endif()
|
||||
option(SIMDJSON_COMPETITION "Compile competitive benchmarks" ON)
|
||||
|
||||
option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark benchmarks" ON)
|
||||
if(SIMDJSON_COMPETITION)
|
||||
message(STATUS "Using SIMDJSON_GOOGLE_BENCHMARKS")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake")
|
||||
|
||||
# We compile tools, tests, etc. with C++ 17. Override yourself if you need on a target.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_MACOSX_RPATH OFF)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD ON)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
|
||||
# LTO seems to create all sorts of fun problems. Let us
|
||||
# disable temporarily.
|
||||
#include(CheckIPOSupported)
|
||||
#check_ipo_supported(RESULT ltoresult)
|
||||
#if(ltoresult)
|
||||
# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
#endif()
|
||||
|
||||
|
||||
if(MSVC)
|
||||
if("${MSVC_TOOLSET_VERSION}" STREQUAL "140")
|
||||
# Visual Studio 2015 issues warnings and we tolerate it, cmake -G"Visual Studio 14" ..
|
||||
target_compile_options(simdjson-internal-flags INTERFACE /W0 /sdl)
|
||||
else()
|
||||
# Recent version of Visual Studio expected (2017, 2019...). Prior versions are unsupported.
|
||||
target_compile_options(simdjson-internal-flags INTERFACE /WX /W3 /sdl)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -fPIC)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Werror -Wall -Wextra -Weffc++)
|
||||
target_compile_options(simdjson-internal-flags INTERFACE -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion)
|
||||
endif()
|
||||
|
||||
# Optional flags
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
endif()
|
||||
|
||||
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)
|
||||
endif()
|
||||
|
||||
option(SIMDJSON_ENABLE_THREADS "Link with thread support" ON)
|
||||
if(SIMDJSON_ENABLE_THREADS)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(simdjson-flags INTERFACE Threads::Threads)
|
||||
target_link_libraries(simdjson-flags INTERFACE ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_compile_options(simdjson-flags INTERFACE ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_THREADS_ENABLED=1) # This will be set in the code automatically.
|
||||
endif()
|
||||
|
||||
# Some users compile simdjson with thread support but still do not want simdjson to use threads.
|
||||
#
|
||||
# Important : Expect this option to disappear in the future.
|
||||
#
|
||||
option(SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT "Whether we enabled thread support or not (SIMDJSON_ENABLE_THREADS), do not use threads.\
|
||||
This option does nothing when thread support is not enabled. We reserve the right to remove this option in a future release in\
|
||||
favor of a runtime approach." OFF)
|
||||
if(SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT)
|
||||
target_compile_definitions(simdjson-flags INTERFACE SIMDJSON_DO_NOT_USE_THREADS_NO_MATTER_WHAT=1)
|
||||
endif()
|
||||
|
||||
if(SIMDJSON_USE_LIBCPP)
|
||||
target_link_libraries(simdjson-flags INTERFACE -stdlib=libc++ -lc++abi)
|
||||
# instead of the above line, we could have used
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi")
|
||||
# The next line is needed empirically.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
# we update CMAKE_SHARED_LINKER_FLAGS, this gets updated later as well
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lc++abi")
|
||||
endif(SIMDJSON_USE_LIBCPP)
|
||||
|
||||
# prevent shared libraries from depending on Intel provided libraries
|
||||
if(${CMAKE_C_COMPILER_ID} MATCHES "Intel") # icc / icpc
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-intel")
|
||||
endif()
|
||||
|
||||
install(TARGETS simdjson-flags EXPORT simdjson-config)
|
||||
install(TARGETS simdjson-internal-flags EXPORT simdjson-config)
|
||||
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# ${SIMDJSON_USER_CMAKECACHE} contains the *user-specified* simdjson options so you can call cmake on
|
||||
# another branch or repository with the same options.
|
||||
#
|
||||
# Not supported on Windows at present, because the only thing that uses it is checkperf, which we
|
||||
# don't run on Windows.
|
||||
#
|
||||
set(SIMDJSON_USER_CMAKECACHE ${CMAKE_CURRENT_BINARY_DIR}/.simdjson-user-CMakeCache.txt)
|
||||
if (MSVC)
|
||||
add_custom_command(
|
||||
OUTPUT ${SIMDJSON_USER_CMAKECACHE}
|
||||
COMMAND findstr SIMDJSON_ ${PROJECT_BINARY_DIR}/CMakeCache.txt > ${SIMDJSON_USER_CMAKECACHE}.tmp
|
||||
COMMAND findstr /v SIMDJSON_LIB_ ${SIMDJSON_USER_CMAKECACHE}.tmp > ${SIMDJSON_USER_CMAKECACHE}
|
||||
VERBATIM # Makes it not do weird escaping with the command
|
||||
)
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT ${SIMDJSON_USER_CMAKECACHE}
|
||||
COMMAND grep SIMDJSON_ ${PROJECT_BINARY_DIR}/CMakeCache.txt > ${SIMDJSON_USER_CMAKECACHE}.tmp
|
||||
COMMAND grep -v SIMDJSON_LIB_ ${SIMDJSON_USER_CMAKECACHE}.tmp > ${SIMDJSON_USER_CMAKECACHE}
|
||||
VERBATIM # Makes it not do weird escaping with the command
|
||||
)
|
||||
endif()
|
||||
add_custom_target(simdjson-user-cmakecache DEPENDS ${SIMDJSON_USER_CMAKECACHE})
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
# Initializes a git submodule if it hasn't been initialized before
|
||||
|
||||
|
||||
find_package(Git QUIET) # We want the library to build even if git is missing
|
||||
if ((Git_FOUND) AND (SIMDJSON_IS_UNDER_GIT))
|
||||
message(STATUS "Git is available.")
|
||||
# Does NOT attempt to update or otherwise modify git submodules that are already initialized.
|
||||
function(initialize_submodule DIRECTORY)
|
||||
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${DIRECTORY}/.git)
|
||||
message(STATUS "${CMAKE_CURRENT_SOURCE_DIR}/${DIRECTORY}/.git does not exist. Initializing ${DIRECTORY} submodule ...")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init ${CMAKE_CURRENT_SOURCE_DIR}/${DIRECTORY}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
RESULT_VARIABLE GIT_EXIT_CODE)
|
||||
if(NOT GIT_EXIT_CODE EQUAL "0")
|
||||
message(FATAL_ERROR "${GIT_EXECUTABLE} submodule update --init dependencies/${DIRECTORY} failed with exit code ${GIT_EXIT_CODE}, please checkout submodules")
|
||||
endif()
|
||||
endif()
|
||||
endfunction(initialize_submodule)
|
||||
|
||||
if (SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
message (STATUS "'SIMDJSON_GOOGLE_BENCHMARKS' is requested, configuring..." )
|
||||
option(BENCHMARK_ENABLE_TESTING OFF)
|
||||
set(BENCHMARK_ENABLE_TESTING OFF)
|
||||
option(BENCHMARK_ENABLE_INSTALL OFF)
|
||||
set(BENCHMARK_ENABLE_INSTALL OFF)
|
||||
initialize_submodule(benchmark)
|
||||
add_subdirectory(benchmark)
|
||||
endif()
|
||||
|
||||
if (SIMDJSON_COMPETITION)
|
||||
initialize_submodule(cJSON)
|
||||
add_library(competition-cJSON INTERFACE)
|
||||
target_include_directories(competition-cJSON INTERFACE cJSON)
|
||||
|
||||
initialize_submodule(fastjson)
|
||||
add_library(competition-fastjson INTERFACE)
|
||||
target_include_directories(competition-fastjson INTERFACE fastjson/src fastjson/include)
|
||||
|
||||
initialize_submodule(gason)
|
||||
add_library(competition-gason INTERFACE)
|
||||
target_include_directories(competition-gason INTERFACE gason/src)
|
||||
|
||||
initialize_submodule(jsmn)
|
||||
add_library(competition-jsmn INTERFACE)
|
||||
target_include_directories(competition-jsmn INTERFACE jsmn)
|
||||
|
||||
initialize_submodule(json)
|
||||
add_library(competition-json INTERFACE)
|
||||
target_include_directories(competition-json INTERFACE json/single_include)
|
||||
|
||||
initialize_submodule(json11)
|
||||
add_library(competition-json11 INTERFACE)
|
||||
target_include_directories(competition-json11 INTERFACE json11)
|
||||
|
||||
add_library(competition-jsoncppdist INTERFACE)
|
||||
target_include_directories(competition-jsoncppdist INTERFACE jsoncppdist)
|
||||
|
||||
initialize_submodule(rapidjson)
|
||||
add_library(competition-rapidjson INTERFACE)
|
||||
target_include_directories(competition-rapidjson INTERFACE rapidjson/include)
|
||||
|
||||
initialize_submodule(sajson)
|
||||
add_library(competition-sajson INTERFACE)
|
||||
target_include_directories(competition-sajson INTERFACE sajson/include)
|
||||
|
||||
initialize_submodule(ujson4c)
|
||||
add_library(competition-ujson4c ujson4c/src/ujdecode.c)
|
||||
target_include_directories(competition-ujson4c PUBLIC ujson4c/3rdparty ujson4c/src)
|
||||
|
||||
add_library(competition-core INTERFACE)
|
||||
target_link_libraries(competition-core INTERFACE competition-json competition-rapidjson competition-sajson competition-cJSON competition-jsmn)
|
||||
|
||||
add_library(competition-all INTERFACE)
|
||||
target_link_libraries(competition-all INTERFACE competition-core competition-jsoncppdist competition-json11 competition-fastjson competition-gason competition-ujson4c)
|
||||
endif()
|
||||
|
||||
initialize_submodule(cxxopts)
|
||||
message(STATUS "We acquired cxxopts and we are adding it as a library and target.")
|
||||
add_library(cxxopts INTERFACE)
|
||||
target_include_directories(cxxopts INTERFACE cxxopts/include)
|
||||
else()
|
||||
message(STATUS "Git is unavailable.")
|
||||
if(SIMDJSON_COMPETITION)
|
||||
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()
|
||||
+1
Submodule dependencies/benchmark added at 8982e1ee6a
+1
Submodule dependencies/cxxopts added at 794c975287
Vendored
+8
@@ -2652,10 +2652,18 @@ char const* Exception::what() const JSONCPP_NOEXCEPT { return msg_.c_str(); }
|
||||
RuntimeError::RuntimeError(String const& msg) : Exception(msg) {}
|
||||
LogicError::LogicError(String const& msg) : Exception(msg) {}
|
||||
JSONCPP_NORETURN void throwRuntimeError(String const& msg) {
|
||||
#if __cpp_exceptions
|
||||
throw RuntimeError(msg);
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
JSONCPP_NORETURN void throwLogicError(String const& msg) {
|
||||
#if __cpp_exceptions
|
||||
throw LogicError(msg);
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
+574
@@ -0,0 +1,574 @@
|
||||
The Basics
|
||||
==========
|
||||
|
||||
An overview of what you need to know to use simdjson, with examples.
|
||||
|
||||
* [Requirements](#requirements)
|
||||
* [Including simdjson](#including-simdjson)
|
||||
* [Using simdjson as a CMake dependency](#using-simdjson-as-a-cmake-dependency)
|
||||
* [The Basics: Loading and Parsing JSON Documents](#the-basics-loading-and-parsing-json-documents)
|
||||
* [Using the Parsed JSON](#using-the-parsed-json)
|
||||
* [C++11 Support and string_view](#c++11-support-and-string_view)
|
||||
* [C++17 Support](#c++17-support)
|
||||
* [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
|
||||
* [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
* [JSON Pointer](#json-pointer)
|
||||
* [Error Handling](#error-handling)
|
||||
* [Error Handling Example](#error-handling-example)
|
||||
* [Exceptions](#exceptions)
|
||||
* [Tree Walking and JSON Element Types](#tree-walking-and-json-element-types)
|
||||
* [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
|
||||
* [Thread Safety](#thread-safety)
|
||||
|
||||
|
||||
Requirements
|
||||
------------------
|
||||
|
||||
- A recent compiler (LLVM clang6 or better, GNU GCC 7 or better) on a 64-bit (ARM or x64 Intel/AMD) POSIX systems such as macOS, freeBSD or Linux. We require that the compiler supports the C++11 standard or better.
|
||||
- Visual Studio 2017 or better under 64-bit Windows. Users should target a 64-bit build (x64) instead of a 32-bit build (x86). We support the LLVM clang compiler under Visual Studio (clangcl) as well as as the regular Visual Studio compiler. We also support MinGW 64-bit under Windows.
|
||||
|
||||
Including simdjson
|
||||
------------------
|
||||
|
||||
To include simdjson, copy [simdjson.h](/singleheader/simdjson.h) and [simdjson.cpp](/singleheader/simdjson.cpp)
|
||||
into your project. Then include it in your project with:
|
||||
|
||||
```c++
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson; // optional
|
||||
```
|
||||
|
||||
You can compile with:
|
||||
|
||||
```
|
||||
c++ myproject.cpp simdjson.cpp
|
||||
```
|
||||
|
||||
Note:
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ myproject.cpp simdjson.cpp`).
|
||||
- Visual Studio users should compile with the `_CRT_SECURE_NO_WARNINGS` flag to avoid warnings with respect to our use of standard C functions such as `fopen`.
|
||||
|
||||
Using simdjson as a CMake dependency
|
||||
------------------
|
||||
|
||||
You can include the simdjson repository as a folder in your CMake project. In the parent
|
||||
`CMakeLists.txt`, include the following lines:
|
||||
|
||||
```
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE STRING "Build just the library, nothing else." FORCE)
|
||||
add_subdirectory(simdjson EXCLUDE_FROM_ALL)
|
||||
```
|
||||
|
||||
Elsewhere in your project, you can declare dependencies on simdjson with lines such as these:
|
||||
|
||||
```
|
||||
add_executable(myprogram myprogram.cpp)
|
||||
target_link_libraries(myprogram simdjson)
|
||||
```
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmakedemo).
|
||||
|
||||
The Basics: Loading and Parsing JSON Documents
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library offers a simple DOM tree API, which you can access by creating a
|
||||
`dom::parser` and calling the `load()` method:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(filename); // load and parse a file
|
||||
```
|
||||
|
||||
Or by creating a padded string (for efficiency reasons, simdjson requires a string with
|
||||
SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
During the`load` or `parse` calls, neither the input file nor the input string are ever modified. After calling `load` or `parse`, the source (either a file or a string) can be safely discarded. All of the JSON data is stored in the `parser` instance. The parsed document is also immutable in simdjson: you do not modify it by accessing it.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
Once you have an element, you can navigate it with idiomatic C++ iterators, operators and casts.
|
||||
|
||||
* **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`,
|
||||
`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;
|
||||
simdjson::padded_string numberstring = "1.2"_padded; // our JSON input ("1.2")
|
||||
simdjson::dom::parser parser;
|
||||
double value; // variable where we store the value to be parsed
|
||||
error = parser.parse(numberstring).get(value);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
std::cout << "I parsed " << value << " from " << numberstring.data() << std::endl;
|
||||
```
|
||||
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
|
||||
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
|
||||
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
|
||||
* **Object Iteration:** You can iterate through an object's fields, too: `for (auto [key, value] : object)`
|
||||
* **Array Index:** To get at an array value by index, use the at() method: `array.at(0)` gets the
|
||||
first element.
|
||||
> Note that array[0] does not compile, because implementing [] gives the impression indexing is a
|
||||
> O(1) operation, which it is not presently in simdjson. Instead, you should iterate over the elements
|
||||
> using a for-loop, as in our examples.
|
||||
* **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`.
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::object car : parser.parse(cars_json)) {
|
||||
// Accessing a field by name
|
||||
cout << "Make/Model: " << car["make"] << "/" << car["model"] << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year = car["year"];
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
for (double tire_pressure : car["tire_pressure"]) {
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto field : car) {
|
||||
cout << "- " << field.key << ": " << field.value << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is a different example illustrating the same ideas:
|
||||
|
||||
```C++
|
||||
auto abstract_json = R"( [
|
||||
{ "12345" : {"a":12.34, "b":56.78, "c": 9998877} },
|
||||
{ "12545" : {"a":11.44, "b":12.78, "c": 11111111} }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
|
||||
// Parse and iterate through an array of objects
|
||||
for (dom::object obj : parser.parse(abstract_json)) {
|
||||
for(const auto& key_value : obj) {
|
||||
cout << "key: " << key_value.key << " : ";
|
||||
dom::object innerobj = key_value.value;
|
||||
cout << "a: " << double(innerobj["a"]) << ", ";
|
||||
cout << "b: " << double(innerobj["b"]) << ", ";
|
||||
cout << "c: " << int64_t(innerobj["c"]) << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And another one:
|
||||
|
||||
|
||||
```C++
|
||||
auto abstract_json = R"(
|
||||
{ "str" : { "123" : {"abc" : 3.14 } } } )"_padded;
|
||||
dom::parser parser;
|
||||
double v = parser.parse(abstract_json)["str"]["123"]["abc"];
|
||||
cout << "number: " << v << endl;
|
||||
```
|
||||
|
||||
|
||||
C++11 Support and string_view
|
||||
-------------
|
||||
|
||||
The simdjson library builds on compilers supporting the [C++11 standard](https://en.wikipedia.org/wiki/C%2B%2B11). It is also a strict requirement: we have no plan to support older C++ compilers.
|
||||
|
||||
We represent parsed strings in simdjson using the `std::string_view` class. It avoids
|
||||
the need to copy the data, as would be necessary with the `std::string` class. It also
|
||||
avoids the pitfalls of null-terminated C strings.
|
||||
|
||||
The `std::string_view` class has become standard as part of C++17 but it is not always available
|
||||
on compilers which only supports C++11. When we detect that `string_view` is natively
|
||||
available, we define the macro `SIMDJSON_HAS_STRING_VIEW`.
|
||||
|
||||
When we detect that it is unavailable,
|
||||
we use [string-view-lite](https://github.com/martinmoene/string-view-lite) as a
|
||||
substitute. In such cases, we use the type alias `using string_view = nonstd::string_view;` to
|
||||
offer the same API, irrespective of the compiler and standard library. The macro
|
||||
`SIMDJSON_HAS_STRING_VIEW` will be *undefined* to indicate that we emulate `string_view`.
|
||||
|
||||
|
||||
C++17 Support
|
||||
-------------
|
||||
|
||||
While the simdjson library can be used in any project using C++ 11 and above, field iteration has special support C++ 17's destructuring syntax. For example:
|
||||
|
||||
```c++
|
||||
padded_string json = R"( { "foo": 1, "bar": 2 } )"_padded;
|
||||
dom::parser parser;
|
||||
dom::object object;
|
||||
auto error = parser.parse(json).get(object);
|
||||
if (error) { cerr << error << endl; return; }
|
||||
for (auto [key, value] : object) {
|
||||
cout << key << " = " << value << endl;
|
||||
}
|
||||
```
|
||||
|
||||
For comparison, here is the C++ 11 version of the same code:
|
||||
|
||||
```c++
|
||||
// C++ 11 version for comparison
|
||||
padded_string json = R"( { "foo": 1, "bar": 2 } )"_padded;
|
||||
dom::parser parser;
|
||||
dom::object object;
|
||||
auto error = parser.parse(json).get(object);
|
||||
if (!error) { cerr << error << endl; return; }
|
||||
for (dom::key_value_pair field : object) {
|
||||
cout << field.key << " = " << field.value << endl;
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```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 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.
|
||||
```
|
||||
|
||||
Though it does not validate the JSON input, it will detect when the document ends with an unterminated string. E.g., it would refuse to minify the string `"this string is not terminated` because of the missing final quote.
|
||||
|
||||
|
||||
UTF-8 validation (alone)
|
||||
----------------------
|
||||
|
||||
The simdjson library has fast functions to validate UTF-8 strings. They are many times faster than most functions commonly found in libraries. You can use our fast functions, even if you do not care about JSON.
|
||||
|
||||
```C++
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
The UTF-8 validation function merely checks that the input is valid UTF-8: it works with strings in general, not just JSON strings.
|
||||
|
||||
Your input string does not need any padding. Any string will do. The `validate_utf8` function does not do any memory allocation on the heap, and it does not throw exceptions.
|
||||
|
||||
JSON Pointer
|
||||
------------
|
||||
|
||||
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the
|
||||
at() method, letting you reach further down into the document in a single call:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9
|
||||
```
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
All simdjson APIs that can fail return `simdjson_result<T>`, which is a <value, error_code>
|
||||
pair. You can retrieve the value with .get(), like so:
|
||||
|
||||
```c++
|
||||
dom::element doc;
|
||||
auto error = parser.parse(json).get(doc);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
```
|
||||
|
||||
When you use the code this way, it is your responsibility to check for error before using the
|
||||
result: if there is an error, the result value will not be valid and using it will caused undefined
|
||||
behavior.
|
||||
|
||||
We can write a "quick start" example where we attempt to parse a file and access some data, without triggering exceptions:
|
||||
|
||||
```C++
|
||||
#include "simdjson.h"
|
||||
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
|
||||
simdjson::dom::element tweets;
|
||||
auto error = parser.load("twitter.json").get(tweets);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
|
||||
simdjson::dom::element res;
|
||||
if ((error = tweets["search_metadata"]["count"].get(res))) {
|
||||
std::cerr << "could not access keys" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << res << " results." << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
|
||||
This is how the example in "Using the Parsed JSON" could be written using only error code checking:
|
||||
|
||||
```c++
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::array cars;
|
||||
auto error = parser.parse(cars_json).get(cars);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::element car_element : cars) {
|
||||
dom::object car;
|
||||
if ((error = car_element.get(car))) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Accessing a field by name
|
||||
std::string_view make, model;
|
||||
if ((error = car["make"].get(make))) { cerr << error << endl; exit(1); }
|
||||
if ((error = car["model"].get(model))) { cerr << error << endl; exit(1); }
|
||||
cout << "Make/Model: " << make << "/" << model << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year;
|
||||
if ((error = car["year"].get(year))) { cerr << error << endl; exit(1); }
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
dom::array tire_pressure_array;
|
||||
if ((error = car["tire_pressure"].get(tire_pressure_array))) { cerr << error << endl; exit(1); }
|
||||
for (dom::element tire_pressure_element : tire_pressure_array) {
|
||||
double tire_pressure;
|
||||
if ((error = tire_pressure_element.get(tire_pressure))) { cerr << error << endl; exit(1); }
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto field : car) {
|
||||
cout << "- " << field.key << ": " << field.value << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is another example:
|
||||
|
||||
```C++
|
||||
auto abstract_json = R"( [
|
||||
{ "12345" : {"a":12.34, "b":56.78, "c": 9998877} },
|
||||
{ "12545" : {"a":11.44, "b":12.78, "c": 11111111} }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::array array;
|
||||
auto error = parser.parse(abstract_json).get(array);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
// Iterate through an array of objects
|
||||
for (dom::element elem : array) {
|
||||
dom::object obj;
|
||||
if ((error = elem.get(obj))) { cerr << error << endl; exit(1); }
|
||||
for (auto & key_value : obj) {
|
||||
cout << "key: " << key_value.key << " : ";
|
||||
dom::object innerobj;
|
||||
if ((error = key_value.value.get(innerobj))) { cerr << error << endl; exit(1); }
|
||||
|
||||
double va, vb;
|
||||
if ((error = innerobj["a"].get(va))) { cerr << error << endl; exit(1); }
|
||||
cout << "a: " << va << ", ";
|
||||
if ((error = innerobj["b"].get(vc))) { cerr << error << endl; exit(1); }
|
||||
cout << "b: " << vb << ", ";
|
||||
|
||||
int64_t vc;
|
||||
if ((error = innerobj["c"].get(vc))) { cerr << error << endl; exit(1); }
|
||||
cout << "c: " << vc << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And another one:
|
||||
|
||||
```C++
|
||||
auto abstract_json = R"(
|
||||
{ "str" : { "123" : {"abc" : 3.14 } } } )"_padded;
|
||||
dom::parser parser;
|
||||
double v;
|
||||
auto error = parser.parse(abstract_json)["str"]["123"]["abc"].get(v);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
cout << "number: " << v << endl;
|
||||
```
|
||||
|
||||
Notice how we can string several operations (`parser.parse(abstract_json)["str"]["123"]["abc"].get(v)`) and only check for the error once, a strategy we call *error chaining*.
|
||||
|
||||
The next two functions will take as input a JSON document containing an array with a single element, either a string or a number. They return true upon success.
|
||||
|
||||
```C++
|
||||
simdjson::dom::parser parser{};
|
||||
|
||||
bool parse_double(const char *j, double &d) {
|
||||
auto error = parser.parse(j, std::strlen(j))
|
||||
.at(0)
|
||||
.get(d, error);
|
||||
if (error) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_string(const char *j, std::string &s) {
|
||||
std::string_view answer;
|
||||
auto error = parser.parse(j,strlen(j))
|
||||
.at(0)
|
||||
.get(answer, error);
|
||||
if (error) { return false; }
|
||||
s.assign(answer.data(), answer.size());
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
Users more comfortable with an exception flow may choose to directly cast the `simdjson_result<T>` to the desired type:
|
||||
|
||||
```c++
|
||||
dom::element doc = parser.parse(json); // Throws an exception if there was an error!
|
||||
```
|
||||
|
||||
When used this way, a `simdjson_error` exception will be thrown if an error occurs, preventing the
|
||||
program from continuing if there was an error.
|
||||
|
||||
Tree Walking and JSON Element Types
|
||||
-----------------------------------
|
||||
|
||||
Sometimes you don't necessarily have a document with a known type, and are trying to generically
|
||||
inspect or walk over JSON elements. To do that, you can use iterators and the type() method. For
|
||||
example, here's a quick and dirty recursive function that verbosely prints the JSON document as JSON
|
||||
(* ignoring nuances like trailing commas and escaping strings, for brevity's sake):
|
||||
|
||||
```c++
|
||||
void print_json(dom::element element) {
|
||||
switch (element.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
cout << "[";
|
||||
for (dom::element child : dom::array(element)) {
|
||||
print_json(child);
|
||||
cout << ",";
|
||||
}
|
||||
cout << "]";
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
cout << "{";
|
||||
for (dom::key_value_pair field : dom::object(element)) {
|
||||
cout << "\"" << field.key << "\": ";
|
||||
print_json(field.value);
|
||||
}
|
||||
cout << "}";
|
||||
break;
|
||||
case dom::element_type::INT64:
|
||||
cout << int64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::UINT64:
|
||||
cout << uint64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::DOUBLE:
|
||||
cout << double(element) << endl;
|
||||
break;
|
||||
case dom::element_type::STRING:
|
||||
cout << std::string_view(element) << endl;
|
||||
break;
|
||||
case dom::element_type::BOOL:
|
||||
cout << bool(element) << endl;
|
||||
break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
cout << "null" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void basics_treewalk_1() {
|
||||
dom::parser parser;
|
||||
print_json(parser.load("twitter.json"));
|
||||
}
|
||||
```
|
||||
|
||||
Newline-Delimited JSON (ndjson) and JSON lines
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library also support multithreaded JSON streaming through a large file containing many
|
||||
smaller JSON documents in either [ndjson](http://ndjson.org) or [JSON lines](http://jsonlines.org)
|
||||
format. If your JSON documents all contain arrays or objects, we even support direct file
|
||||
concatenation without whitespace. The concatenated file has no size restrictions (including larger
|
||||
than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```json
|
||||
{ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 }
|
||||
```
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
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)`.
|
||||
|
||||
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.
|
||||
|
||||
See [parse_many.md](parse_many.md) for detailed information and design.
|
||||
|
||||
Thread Safety
|
||||
-------------
|
||||
|
||||
We built simdjson with thread safety in mind.
|
||||
|
||||
The simdjson library is single-threaded except for [`parse_many`](parse_many.md) which may use secondary threads under its control when the library is compiled with thread support.
|
||||
|
||||
|
||||
We recommend using one `dom::parser` object per thread in which case the library is thread-safe.
|
||||
It is unsafe to reuse a `dom::parser` object between different threads.
|
||||
The parsed results (`dom::document`, `dom::element`, `array`, `object`) depend on the `dom::parser`, etc. therefore it is also potentially unsafe to use the result of the parsing between different threads.
|
||||
|
||||
The CPU detection, which runs the first time parsing is attempted and switches to the fastest
|
||||
parser for your CPU, is transparent and thread-safe.
|
||||
|
||||
Backwards Compatibility
|
||||
-----------------------
|
||||
|
||||
The only header file supported by simdjson is `simdjson.h`. Older versions of simdjson published a
|
||||
number of other include files such as `document.h` or `ParsedJson.h` alongside `simdjson.h`; these headers
|
||||
may be moved or removed in future versions.
|
||||
@@ -0,0 +1,556 @@
|
||||
The Basics
|
||||
==========
|
||||
|
||||
An overview of what you need to know to use simdjson, with examples.
|
||||
|
||||
|
||||
Requirements
|
||||
------------------
|
||||
|
||||
- A recent compiler (LLVM clang6 or better, GNU GCC 7 or better) on a 64-bit (ARM or x64 Intel/AMD) POSIX systems such as macOS, freeBSD or Linux. We require that the compiler supports the C++11 standard or better.
|
||||
- Visual Studio 2017 or better under 64-bit Windows. Users should target a 64-bit build (x64) instead of a 32-bit build (x86). We support the LLVM clang compiler under Visual Studio (clangcl) as well as as the regular Visual Studio compiler.
|
||||
|
||||
Including simdjson
|
||||
------------------
|
||||
|
||||
To include simdjson, copy the simdjson.h and simdjson.cpp files from the singleheader directory
|
||||
into your project. Then include the header file in your project with:
|
||||
|
||||
```
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson; // optional
|
||||
```
|
||||
|
||||
You can compile with:
|
||||
|
||||
```
|
||||
c++ myproject.cpp simdjson.cpp
|
||||
```
|
||||
|
||||
Note:
|
||||
- Users on macOS and other platforms were default compilers do not provide C++11 compliant by default should request it with the appropriate flag (e.g., `c++ myproject.cpp simdjson.cpp`).
|
||||
- Visual Studio users should compile with the `_CRT_SECURE_NO_WARNINGS` flag to avoid warnings with respect to our use of standard C functions such as `fopen`.
|
||||
|
||||
Using simdjson as a CMake dependency
|
||||
------------------
|
||||
|
||||
You can include the simdjson repository as a folder in your CMake project. In the parent
|
||||
`CMakeLists.txt`, include the following lines:
|
||||
|
||||
```
|
||||
set(SIMDJSON_JUST_LIBRARY ON CACHE STRING "Build just the library, nothing else." FORCE)
|
||||
add_subdirectory(simdjson EXCLUDE_FROM_ALL)
|
||||
```
|
||||
|
||||
Elsewhere in your project, you can declare dependencies on simdjson with lines such as these:
|
||||
|
||||
```
|
||||
add_executable(myprogram myprogram.cpp)
|
||||
target_link_libraries(myprogram simdjson)
|
||||
```
|
||||
|
||||
See [our CMake demonstration](https://github.com/simdjson/cmakedemo).
|
||||
|
||||
The Basics: Loading and Parsing JSON Documents
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library offers a simple DOM tree API, which you can access by creating a
|
||||
`dom::parser` and calling the `load()` method:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.load(filename); // load and parse a file
|
||||
```
|
||||
|
||||
Or by creating a padded string (for efficiency reasons, simdjson requires a string with
|
||||
SIMDJSON_PADDING bytes at the end) and calling `parse()`:
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::element doc = parser.parse("[1,2,3]"_padded); // parse a string
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
During the`load` or `parse` calls, neither the input file nor the input string are ever modified. After calling `load` or `parse`, the source (either a file or a string) can be safely discarded. All of the JSON data is stored in the `parser` instance. The parsed document is also immutable in simdjson: you do not modify it by accessing it.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
Once you have an element, you can navigate it with idiomatic C++ iterators, operators and casts.
|
||||
|
||||
* **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`,
|
||||
`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;
|
||||
simdjson::padded_string numberstring = "1.2"_padded; // our JSON input ("1.2")
|
||||
simdjson::dom::parser parser;
|
||||
double value; // variable where we store the value to be parsed
|
||||
error = parser.parse(numberstring).get(value);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
std::cout << "I parsed " << value << " from " << numberstring.data() << std::endl;
|
||||
```
|
||||
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
|
||||
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
|
||||
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
|
||||
* **Object Iteration:** You can iterate through an object's fields, too: `for (auto [key, value] : object)`
|
||||
* **Array Index:** To get at an array value by index, use the at() method: `array.at(0)` gets the
|
||||
first element.
|
||||
> Note that array[0] does not compile, because implementing [] gives the impression indexing is a
|
||||
> O(1) operation, which it is not presently in simdjson. Instead, you should iterate over the elements
|
||||
> using a for-loop, as in our examples.
|
||||
* **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`.
|
||||
|
||||
|
||||
Here are some examples of all of the above:
|
||||
|
||||
```
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::object car : parser.parse(cars_json)) {
|
||||
// Accessing a field by name
|
||||
cout << "Make/Model: " << car["make"] << "/" << car["model"] << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year = car["year"];
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
for (double tire_pressure : car["tire_pressure"]) {
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto field : car) {
|
||||
cout << "- " << field.key << ": " << field.value << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is a different example illustrating the same ideas:
|
||||
|
||||
```
|
||||
auto abstract_json = R"( [
|
||||
{ "12345" : {"a":12.34, "b":56.78, "c": 9998877} },
|
||||
{ "12545" : {"a":11.44, "b":12.78, "c": 11111111} }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
|
||||
// Parse and iterate through an array of objects
|
||||
for (dom::object obj : parser.parse(abstract_json)) {
|
||||
for(const auto& key_value : obj) {
|
||||
cout << "key: " << key_value.key << " : ";
|
||||
dom::object innerobj = key_value.value;
|
||||
cout << "a: " << double(innerobj["a"]) << ", ";
|
||||
cout << "b: " << double(innerobj["b"]) << ", ";
|
||||
cout << "c: " << int64_t(innerobj["c"]) << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And another one:
|
||||
|
||||
|
||||
```
|
||||
auto abstract_json = R"(
|
||||
{ "str" : { "123" : {"abc" : 3.14 } } } )"_padded;
|
||||
dom::parser parser;
|
||||
double v = parser.parse(abstract_json)["str"]["123"]["abc"];
|
||||
cout << "number: " << v << endl;
|
||||
```
|
||||
|
||||
|
||||
C++11 Support and string_view
|
||||
-------------
|
||||
|
||||
The simdjson library builds on compilers supporting the [C++11 standard](https://en.wikipedia.org/wiki/C%2B%2B11). It is also a strict requirement: we have no plan to support older C++ compilers.
|
||||
|
||||
We represent parsed strings in simdjson using the `std::string_view` class. It avoids
|
||||
the need to copy the data, as would be necessary with the `std::string` class. It also
|
||||
avoids the pitfalls of null-terminated C strings.
|
||||
|
||||
The `std::string_view` class has become standard as part of C++17 but it is not always available
|
||||
on compilers which only supports C++11. When we detect that `string_view` is natively
|
||||
available, we define the macro `SIMDJSON_HAS_STRING_VIEW`.
|
||||
|
||||
When we detect that it is unavailable,
|
||||
we use [string-view-lite](https://github.com/martinmoene/string-view-lite) as a
|
||||
substitute. In such cases, we use the type alias `using string_view = nonstd::string_view;` to
|
||||
offer the same API, irrespective of the compiler and standard library. The macro
|
||||
`SIMDJSON_HAS_STRING_VIEW` will be *undefined* to indicate that we emulate `string_view`.
|
||||
|
||||
|
||||
C++17 Support
|
||||
-------------
|
||||
|
||||
While the simdjson library can be used in any project using C++ 11 and above, field iteration has special support C++ 17's destructuring syntax. For example:
|
||||
|
||||
```
|
||||
padded_string json = R"( { "foo": 1, "bar": 2 } )"_padded;
|
||||
dom::parser parser;
|
||||
dom::object object;
|
||||
auto error = parser.parse(json).get(object);
|
||||
if (error) { cerr << error << endl; return; }
|
||||
for (auto [key, value] : object) {
|
||||
cout << key << " = " << value << endl;
|
||||
}
|
||||
```
|
||||
|
||||
For comparison, here is the C++ 11 version of the same code:
|
||||
|
||||
```
|
||||
// C++ 11 version for comparison
|
||||
padded_string json = R"( { "foo": 1, "bar": 2 } )"_padded;
|
||||
dom::parser parser;
|
||||
dom::object object;
|
||||
auto error = parser.parse(json).get(object);
|
||||
if (!error) { cerr << error << endl; return; }
|
||||
for (dom::key_value_pair field : object) {
|
||||
cout << field.key << " = " << field.value << endl;
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
// 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 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.
|
||||
```
|
||||
|
||||
Though it does not validate the JSON input, it will detect when the document ends with an unterminated string. E.g., it would refuse to minify the string `"this string is not terminated` because of the missing final quote.
|
||||
|
||||
|
||||
UTF-8 validation (alone)
|
||||
----------------------
|
||||
|
||||
The simdjson library has fast functions to validate UTF-8 strings. They are many times faster than most functions commonly found in libraries. You can use our fast functions, even if you do not care about JSON.
|
||||
|
||||
```
|
||||
const char * some_string = "[ 1, 2, 3, 4] ";
|
||||
size_t length = strlen(some_string);
|
||||
bool is_ok = simdjson::validate_utf8(some_string, length);
|
||||
```
|
||||
|
||||
The UTF-8 validation function merely checks that the input is valid UTF-8: it works with strings in general, not just JSON strings.
|
||||
|
||||
Your input string does not need any padding. Any string will do. The `validate_utf8` function does not do any memory allocation on the heap, and it does not throw exceptions.
|
||||
|
||||
JSON Pointer
|
||||
------------
|
||||
|
||||
The simdjson library also supports [JSON pointer](https://tools.ietf.org/html/rfc6901) through the
|
||||
at() method, letting you reach further down into the document in a single call:
|
||||
|
||||
```
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::element cars = parser.parse(cars_json);
|
||||
cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9
|
||||
```
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
All simdjson APIs that can fail return `simdjson_result<T>`, which is a <value, error_code>
|
||||
pair. You can retrieve the value with .get(), like so:
|
||||
|
||||
```
|
||||
dom::element doc;
|
||||
auto error = parser.parse(json).get(doc);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
```
|
||||
|
||||
When you use the code this way, it is your responsibility to check for error before using the
|
||||
result: if there is an error, the result value will not be valid and using it will caused undefined
|
||||
behavior.
|
||||
|
||||
We can write a "quick start" example where we attempt to parse a file and access some data, without triggering exceptions:
|
||||
|
||||
```
|
||||
#include "simdjson.h"
|
||||
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
|
||||
simdjson::dom::element tweets;
|
||||
auto error = parser.load("twitter.json").get(tweets);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
|
||||
simdjson::dom::element res;
|
||||
if ((error = tweets["search_metadata"]["count"].get(res))) {
|
||||
std::cerr << "could not access keys" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << res << " results." << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling Example
|
||||
|
||||
This is how the example in "Using the Parsed JSON" could be written using only error code checking:
|
||||
|
||||
```
|
||||
auto cars_json = R"( [
|
||||
{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::array cars;
|
||||
auto error = parser.parse(cars_json).get(cars);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Iterating through an array of objects
|
||||
for (dom::element car_element : cars) {
|
||||
dom::object car;
|
||||
if ((error = car_element.get(car))) { cerr << error << endl; exit(1); }
|
||||
|
||||
// Accessing a field by name
|
||||
std::string_view make, model;
|
||||
if ((error = car["make"].get(make))) { cerr << error << endl; exit(1); }
|
||||
if ((error = car["model"].get(model))) { cerr << error << endl; exit(1); }
|
||||
cout << "Make/Model: " << make << "/" << model << endl;
|
||||
|
||||
// Casting a JSON element to an integer
|
||||
uint64_t year;
|
||||
if ((error = car["year"].get(year))) { cerr << error << endl; exit(1); }
|
||||
cout << "- This car is " << 2020 - year << "years old." << endl;
|
||||
|
||||
// Iterating through an array of floats
|
||||
double total_tire_pressure = 0;
|
||||
dom::array tire_pressure_array;
|
||||
if ((error = car["tire_pressure"].get(tire_pressure_array))) { cerr << error << endl; exit(1); }
|
||||
for (dom::element tire_pressure_element : tire_pressure_array) {
|
||||
double tire_pressure;
|
||||
if ((error = tire_pressure_element.get(tire_pressure))) { cerr << error << endl; exit(1); }
|
||||
total_tire_pressure += tire_pressure;
|
||||
}
|
||||
cout << "- Average tire pressure: " << (total_tire_pressure / 4) << endl;
|
||||
|
||||
// Writing out all the information about the car
|
||||
for (auto field : car) {
|
||||
cout << "- " << field.key << ": " << field.value << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is another example:
|
||||
|
||||
```
|
||||
auto abstract_json = R"( [
|
||||
{ "12345" : {"a":12.34, "b":56.78, "c": 9998877} },
|
||||
{ "12545" : {"a":11.44, "b":12.78, "c": 11111111} }
|
||||
] )"_padded;
|
||||
dom::parser parser;
|
||||
dom::array array;
|
||||
auto error = parser.parse(abstract_json).get(array);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
// Iterate through an array of objects
|
||||
for (dom::element elem : array) {
|
||||
dom::object obj;
|
||||
if ((error = elem.get(obj))) { cerr << error << endl; exit(1); }
|
||||
for (auto & key_value : obj) {
|
||||
cout << "key: " << key_value.key << " : ";
|
||||
dom::object innerobj;
|
||||
if ((error = key_value.value.get(innerobj))) { cerr << error << endl; exit(1); }
|
||||
|
||||
double va, vb;
|
||||
if ((error = innerobj["a"].get(va))) { cerr << error << endl; exit(1); }
|
||||
cout << "a: " << va << ", ";
|
||||
if ((error = innerobj["b"].get(vc))) { cerr << error << endl; exit(1); }
|
||||
cout << "b: " << vb << ", ";
|
||||
|
||||
int64_t vc;
|
||||
if ((error = innerobj["c"].get(vc))) { cerr << error << endl; exit(1); }
|
||||
cout << "c: " << vc << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And another one:
|
||||
|
||||
```
|
||||
auto abstract_json = R"(
|
||||
{ "str" : { "123" : {"abc" : 3.14 } } } )"_padded;
|
||||
dom::parser parser;
|
||||
double v;
|
||||
auto error = parser.parse(abstract_json)["str"]["123"]["abc"].get(v);
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
cout << "number: " << v << endl;
|
||||
```
|
||||
|
||||
Notice how we can string several operations (`parser.parse(abstract_json)["str"]["123"]["abc"].get(v)`) and only check for the error once, a strategy we call *error chaining*.
|
||||
|
||||
The next two functions will take as input a JSON document containing an array with a single element, either a string or a number. They return true upon success.
|
||||
|
||||
```
|
||||
simdjson::dom::parser parser{};
|
||||
|
||||
bool parse_double(const char *j, double &d) {
|
||||
auto error = parser.parse(j, std::strlen(j))
|
||||
.at(0)
|
||||
.get(d, error);
|
||||
if (error) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_string(const char *j, std::string &s) {
|
||||
std::string_view answer;
|
||||
auto error = parser.parse(j,strlen(j))
|
||||
.at(0)
|
||||
.get(answer, error);
|
||||
if (error) { return false; }
|
||||
s.assign(answer.data(), answer.size());
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
Users more comfortable with an exception flow may choose to directly cast the `simdjson_result<T>` to the desired type:
|
||||
|
||||
```
|
||||
dom::element doc = parser.parse(json); // Throws an exception if there was an error!
|
||||
```
|
||||
|
||||
When used this way, a `simdjson_error` exception will be thrown if an error occurs, preventing the
|
||||
program from continuing if there was an error.
|
||||
|
||||
Tree Walking and JSON Element Types
|
||||
-----------------------------------
|
||||
|
||||
Sometimes you don't necessarily have a document with a known type, and are trying to generically
|
||||
inspect or walk over JSON elements. To do that, you can use iterators and the type() method. For
|
||||
example, here's a quick and dirty recursive function that verbosely prints the JSON document as JSON
|
||||
(* ignoring nuances like trailing commas and escaping strings, for brevity's sake):
|
||||
|
||||
```
|
||||
void print_json(dom::element element) {
|
||||
switch (element.type()) {
|
||||
case dom::element_type::ARRAY:
|
||||
cout << "[";
|
||||
for (dom::element child : dom::array(element)) {
|
||||
print_json(child);
|
||||
cout << ",";
|
||||
}
|
||||
cout << "]";
|
||||
break;
|
||||
case dom::element_type::OBJECT:
|
||||
cout << "{";
|
||||
for (dom::key_value_pair field : dom::object(element)) {
|
||||
cout << "\"" << field.key << "\": ";
|
||||
print_json(field.value);
|
||||
}
|
||||
cout << "}";
|
||||
break;
|
||||
case dom::element_type::INT64:
|
||||
cout << int64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::UINT64:
|
||||
cout << uint64_t(element) << endl;
|
||||
break;
|
||||
case dom::element_type::DOUBLE:
|
||||
cout << double(element) << endl;
|
||||
break;
|
||||
case dom::element_type::STRING:
|
||||
cout << std::string_view(element) << endl;
|
||||
break;
|
||||
case dom::element_type::BOOL:
|
||||
cout << bool(element) << endl;
|
||||
break;
|
||||
case dom::element_type::NULL_VALUE:
|
||||
cout << "null" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void basics_treewalk_1() {
|
||||
dom::parser parser;
|
||||
print_json(parser.load("twitter.json"));
|
||||
}
|
||||
```
|
||||
|
||||
Newline-Delimited JSON (ndjson) and JSON lines
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library also support multithreaded JSON streaming through a large file containing many
|
||||
smaller JSON documents in either [ndjson](http://ndjson.org) or [JSON lines](http://jsonlines.org)
|
||||
format. If your JSON documents all contain arrays or objects, we even support direct file
|
||||
concatenation without whitespace. The concatenated file has no size restrictions (including larger
|
||||
than 4GB), though each individual document must be no larger than 4 GB.
|
||||
|
||||
Here is a simple example, given "x.json" with this content:
|
||||
|
||||
```json
|
||||
{ "foo": 1 }
|
||||
{ "foo": 2 }
|
||||
{ "foo": 3 }
|
||||
```
|
||||
|
||||
```
|
||||
dom::parser parser;
|
||||
dom::document_stream docs = parser.load_many(filename);
|
||||
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)`.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Thread Safety
|
||||
-------------
|
||||
|
||||
We built simdjson with thread safety in mind.
|
||||
|
||||
The simdjson library is single-threaded except for `parse_many` which may use secondary threads under its control when the library is compiled with thread support.
|
||||
|
||||
|
||||
We recommend using one `dom::parser` object per thread in which case the library is thread-safe.
|
||||
It is unsafe to reuse a `dom::parser` object between different threads.
|
||||
The parsed results (`dom::document`, `dom::element`, `array`, `object`) depend on the `dom::parser`, etc. therefore it is also potentially unsafe to use the result of the parsing between different threads.
|
||||
|
||||
The CPU detection, which runs the first time parsing is attempted and switches to the fastest
|
||||
parser for your CPU, is transparent and thread-safe.
|
||||
|
||||
Backwards Compatibility
|
||||
-----------------------
|
||||
|
||||
The only header file supported by simdjson is `simdjson.h`. Older versions of simdjson published a
|
||||
number of other include files such as `document.h` or `ParsedJson.h` alongside `simdjson.h`; these headers
|
||||
may be moved or removed in future versions.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,83 @@
|
||||
CPU Architecture-Specific Implementations
|
||||
=========================================
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Runtime CPU Detection](#runtime-cpu-detection)
|
||||
* [Inspecting the Detected Implementation](#inspecting-the-detected-implementation)
|
||||
* [Querying Available Implementations](#querying-available-implementations)
|
||||
* [Manually Selecting the Implementation](#manually-selecting-the-implementation)
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The simdjson library takes advantage of SIMD instruction sets such as NEON, SSE and AVX to achieve
|
||||
much of its speed. Because these instruction sets work differently, simdjson has to compile a
|
||||
different version of the JSON parser for different CPU architectures, often with different
|
||||
algorithms to take better advantage of a given CPU!
|
||||
|
||||
The current implementations are:
|
||||
* haswell: AVX2 (2013 Intel Haswell or later)
|
||||
* westmere: SSE4.2 (2010 Westmere or later).
|
||||
* arm64: 64-bit ARMv8-A NEON
|
||||
* fallback: A generic implementation that runs on any 64-bit processor.
|
||||
|
||||
In many cases, you don't know where your compiled binary is going to run, so simdjson automatically
|
||||
compiles *all* the implementations into the executable. On Intel, it will include 3 implementations
|
||||
(haswell, westmere and fallback), and on ARM it will include 2 (arm64 and fallback).
|
||||
|
||||
If you know more about where you're going to run and want to save the space, you can disable any of
|
||||
these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is HASWELL,
|
||||
WESTMERE, ARM64 and FALLBACK).
|
||||
|
||||
The simdjson library automatically sets header flags for each implementation as it compiles; there
|
||||
is no need to set architecture-specific flags yourself (e.g., `-mavx2`, `/AVX2` or
|
||||
`-march=haswell`), and it may even break runtime dispatch and your binaries will fail to run on
|
||||
older processors.
|
||||
|
||||
Runtime CPU Detection
|
||||
---------------------
|
||||
|
||||
When you first use simdjson, it will detect the CPU you're running on, and swap over to the fastest
|
||||
implementation for it. This is a small, one-time cost and for many people will be paid the first
|
||||
time they call `parse()` or `load()`.
|
||||
|
||||
Inspecting the Detected Implementation
|
||||
--------------------------------------
|
||||
|
||||
You can check what implementation is running with `active_implementation`:
|
||||
|
||||
```c++
|
||||
cout << "simdjson v" << STRINGIFY(SIMDJSON_VERSION) << endl;
|
||||
cout << "Detected the best implementation for your machine: " << simdjson::active_implementation->name();
|
||||
cout << "(" << simdjson::active_implementation->description() << ")" << endl;
|
||||
```
|
||||
|
||||
Implementation detection will happen in this case when you first call `name()`.
|
||||
|
||||
Querying Available Implementations
|
||||
----------------------------------
|
||||
|
||||
You can list all available implementations, regardless of which one was selected:
|
||||
|
||||
```c++
|
||||
for (auto implementation : simdjson::available_implementations) {
|
||||
cout << implementation->name() << ": " << implementation->description() << endl;
|
||||
}
|
||||
```
|
||||
|
||||
And look them up by name:
|
||||
|
||||
```c++
|
||||
cout << simdjson::available_implementations["fallback"]->description() << endl;
|
||||
```
|
||||
|
||||
Manually Selecting the Implementation
|
||||
-------------------------------------
|
||||
|
||||
If you're trying to do performance tests or see how different implementations of simdjson run, you
|
||||
can select the CPU architecture yourself:
|
||||
|
||||
```c++
|
||||
// Use the fallback implementation, even though my machine is fast enough for anything
|
||||
simdjson::active_implementation = simdjson::available_implementations["fallback"];
|
||||
```
|
||||
@@ -0,0 +1,160 @@
|
||||
parse_many
|
||||
==========
|
||||
|
||||
An interface providing features to work with files or streams containing multiple JSON documents.
|
||||
As fast and convenient as possible.
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
- [Motivations](#motivations)
|
||||
- [Performance](#performance)
|
||||
- [How it works](#how-it-works)
|
||||
- [Support](#support)
|
||||
- [API](#api)
|
||||
- [Use cases](#use-cases)
|
||||
|
||||
Motivations
|
||||
-----------
|
||||
|
||||
The main motivation for this piece of software is to achieve maximum speed and offer a
|
||||
better quality of life in parsing files containing multiple JSON documents.
|
||||
|
||||
The JavaScript Object Notation (JSON) [RFC7159](https://tools.ietf.org/html/rfc7159) is a very handy
|
||||
serialization format. However, when serializing a large sequence of
|
||||
values as an array, or a possibly indeterminate-length or never-
|
||||
ending sequence of values, JSON becomes difficult to work with.
|
||||
|
||||
Consider a sequence of one million values, each possibly one kilobyte
|
||||
when encoded -- roughly one gigabyte. It is often desirable to process such a dataset incrementally
|
||||
without having to first read all of it before beginning to produce results.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Here is a chart comparing the speed of the different alternatives to parse a multiline JSON.
|
||||
The simdjson library provides a threaded and non-threaded parse_many() implementation. As the
|
||||
figure below shows, if you can, use threads, but if you can't, it's still pretty fast!
|
||||
[](/doc/Multiline_JSON_Parse_Competition.png)
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
### Context
|
||||
|
||||
The parsing in simdjson is divided into 2 stages. First, in stage 1, we parse the document and find
|
||||
all the structural indexes (`{`, `}`, `]`, `[`, `,`, `"`, ...) and validate UTF8. Then, in stage 2,
|
||||
we go through the document again and build the tape using structural indexes found during stage 1.
|
||||
Although stage 1 finds the structural indexes, it has no knowledge of the structure of the document
|
||||
nor does it know whether it parsed a valid document, multiple documents, or even if the document is
|
||||
complete.
|
||||
|
||||
Prior to parse_many, most people who had to parse a multiline JSON file would proceed by reading the
|
||||
file line by line, using a utility function like `std::getline` or equivalent, and would then use
|
||||
the `parse` on each of those lines. From a performance point of view, this process is highly
|
||||
inefficient, in that it requires a lot of unnecessary memory allocation and makes use of the
|
||||
`getline` function, which is fundamentally slow, slower than the act of parsing with simdjson
|
||||
[(more on this here)](https://lemire.me/blog/2019/06/18/how-fast-is-getline-in-c/).
|
||||
|
||||
Unlike the popular parser RapidJson, our DOM does not require the buffer once the parsing job is
|
||||
completed, the DOM and the buffer are completely independent. The drawback of this architecture is
|
||||
that we need to allocate some additional memory to store our ParsedJson data, for every document
|
||||
inside a given file. Memory allocation can be slow and become a bottleneck, therefore, we want to
|
||||
minimize it as much as possible.
|
||||
|
||||
### Design
|
||||
|
||||
To achieve a minimum amount of allocations, we opted for a design where we create only one
|
||||
parser object and therefore allocate its memory once, and then recycle it for every document in a
|
||||
given file. But, knowing that they often have largely varying size, we need to make sure that we
|
||||
allocate enough memory so that all the documents can fit. This value is what we call the batch size.
|
||||
As of right now, we need to manually specify a value for this batch size, it has to be at least as
|
||||
big as the biggest document in your file, but not too big so that it submerges the cached memory.
|
||||
The bigger the batch size, the fewer we need to make allocations. We found that 1MB is somewhat a
|
||||
sweet spot for now.
|
||||
|
||||
1. When the user calls `parse_many`, we return a `document_stream` which the user can iterate over
|
||||
to receive parsed documents.
|
||||
2. We call stage 1 on the first batch_size bytes of JSON in the buffer, detecting structural
|
||||
indexes for all documents in that batch.
|
||||
3. We call stage 2 on the indexes, reading tokens until we reach the end of a valid document (i.e.
|
||||
a single array, object, string, boolean, number or null).
|
||||
4. Each time the user calls `++` to read the next document, we call stage 2 to parse the next
|
||||
document where we left off.
|
||||
5. When we reach the end of the batch, we call stage 1 on the next batch, starting from the end of
|
||||
the last document, and go to step 3.
|
||||
|
||||
### Threads
|
||||
|
||||
But how can we make use of threads if they are available? We found a pretty cool algorithm that allows us to quickly
|
||||
identify the position of the last JSON document in a given batch. Knowing exactly where the end of
|
||||
the batch is, we no longer need for stage 2 to finish in order to load a new batch. We already know
|
||||
where to start the next batch. Therefore, we can run stage 1 on the next batch concurrently while
|
||||
the main thread is going through stage 2. Running stage 1 in a different thread can, in best
|
||||
cases, remove almost entirely its cost and replaces it by the overhead of a thread, which is orders
|
||||
of magnitude cheaper. Ain't that awesome!
|
||||
|
||||
Thread support is only active if thread supported is detected in which case the macro
|
||||
SIMDJSON_THREADS_ENABLED is set. Otherwise the library runs in single-thread mode.
|
||||
|
||||
A `document_stream` instance uses at most two threads: there is a main thread and a worker thread.
|
||||
You should expect the main thread to be fully occupied while the worker thread is partially busy
|
||||
(e.g., 80% of the time).
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
Since we want to offer flexibility and not restrict ourselves to a specific file
|
||||
format, we support any file that contains any amount of valid JSON document, **separated by one
|
||||
or more character that is considered whitespace** by the JSON spec. Anything that is
|
||||
not whitespace will be parsed as a JSON document and could lead to failure.
|
||||
|
||||
Whitespace Characters:
|
||||
- **Space**
|
||||
- **Linefeed**
|
||||
- **Carriage return**
|
||||
- **Horizontal tab**
|
||||
- **Nothing**
|
||||
|
||||
Some official formats **(non-exhaustive list)**:
|
||||
- [Newline-Delimited JSON (NDJSON)](http://ndjson.org/)
|
||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by JsonStream!
|
||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
|
||||
|
||||
## Use cases
|
||||
|
||||
From [jsonlines.org](http://jsonlines.org/examples/):
|
||||
|
||||
- **Better than CSV**
|
||||
```json
|
||||
["Name", "Session", "Score", "Completed"]
|
||||
["Gilbert", "2013", 24, true]
|
||||
["Alexa", "2013", 29, true]
|
||||
["May", "2012B", 14, false]
|
||||
["Deloise", "2012A", 19, true]
|
||||
```
|
||||
CSV seems so easy that many programmers have written code to generate it themselves, and almost every implementation is
|
||||
different. Handling broken CSV files is a common and frustrating task. CSV has no standard encoding, no standard column
|
||||
separator and multiple character escaping standards. String is the only type supported for cell values, so some programs
|
||||
attempt to guess the correct types.
|
||||
|
||||
JSON Lines handles tabular data cleanly and without ambiguity. Cells may use the standard JSON types.
|
||||
|
||||
The biggest missing piece is an import/export filter for popular spreadsheet programs so that non-programmers can use
|
||||
this format.
|
||||
|
||||
- **Easy Nested Data**
|
||||
```json
|
||||
{"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
|
||||
{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
|
||||
{"name": "May", "wins": []}
|
||||
{"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
|
||||
```
|
||||
JSON Lines' biggest strength is in handling lots of similar nested data structures. One .jsonl file is easier to
|
||||
work with than a directory full of XML files.
|
||||
@@ -0,0 +1,172 @@
|
||||
Performance Notes
|
||||
=================
|
||||
|
||||
simdjson strives to be at its fastest *without tuning*, and generally achieves this. However, there
|
||||
are still some scenarios where tuning can enhance performance.
|
||||
|
||||
* [Reusing the parser for maximum efficiency](#reusing-the-parser-for-maximum-efficiency)
|
||||
* [Keeping documents around for longer](#keeping-documents-around-for-longer)
|
||||
* [Server Loops: Long-Running Processes and Memory Capacity](#server-loops-long-running-processes-and-memory-capacity)
|
||||
* [Large files and huge page support](#large-files-and-huge-page-support)
|
||||
* [Computed GOTOs](#computed-gotos)
|
||||
* [Number parsing](#number-parsing)
|
||||
* [Visual Studio](#visual-studio)
|
||||
* [Downclocking](#downclocking)
|
||||
|
||||
|
||||
Reusing the parser for maximum efficiency
|
||||
-----------------------------------------
|
||||
|
||||
If you're using simdjson to parse multiple documents, or in a loop, you should make a parser once
|
||||
and reuse it. The simdjson library will allocate and retain internal buffers between parses, keeping
|
||||
buffers hot in cache and keeping memory allocation and initialization to a minimum. In this manner,
|
||||
you can parse terabytes of JSON data without doing any new allocation.
|
||||
|
||||
```c++
|
||||
dom::parser parser;
|
||||
|
||||
// This initializes buffers and a document big enough to handle this JSON.
|
||||
dom::element doc = parser.parse("[ true, false ]"_padded);
|
||||
cout << doc << endl;
|
||||
|
||||
// This reuses the existing buffers, and reuses and *overwrites* the old document
|
||||
doc = parser.parse("[1, 2, 3]"_padded);
|
||||
cout << doc << endl;
|
||||
|
||||
// This also reuses the existing buffers, and reuses and *overwrites* the old document
|
||||
dom::element doc2 = parser.parse("true"_padded);
|
||||
// Even if you keep the old reference around, doc and doc2 refer to the same document.
|
||||
cout << doc << endl;
|
||||
cout << doc2 << endl;
|
||||
```
|
||||
|
||||
It's not just internal buffers though. The simdjson library reuses the document itself. The dom::element, dom::object and dom::array instances are *references* to the internal document.
|
||||
You are only *borrowing* the document from simdjson, which purposely reuses and overwrites it each
|
||||
time you call parse. This prevent wasteful and unnecessary memory allocation in 99% of cases where
|
||||
JSON is just read, used, and converted to native values or thrown away.
|
||||
|
||||
> **You are only borrowing the document from the simdjson parser. Don't keep it long term!**
|
||||
|
||||
This is key: don't keep the `document&`, `dom::element`, `dom::array`, `dom::object`
|
||||
or `string_view` objects you get back from the API. Convert them to C++ native values, structs and
|
||||
arrays that you own.
|
||||
|
||||
Server Loops: Long-Running Processes and Memory Capacity
|
||||
--------------------------------------------------------
|
||||
|
||||
The simdjson library automatically expands its memory capacity when larger documents are parsed, so
|
||||
that you don't unexpectedly fail. In a short process that reads a bunch of files and then exits,
|
||||
this works pretty flawlessly.
|
||||
|
||||
Server loops, though, are long-running processes that will keep the parser around forever. This
|
||||
means that if you encounter a really, really large document, simdjson will not resize back down.
|
||||
The simdjson library lets you adjust your allocation strategy to prevent your server from growing
|
||||
without bound:
|
||||
|
||||
* You can set a *max capacity* when constructing a parser:
|
||||
|
||||
```c++
|
||||
dom::parser parser(1000*1000); // Never grow past documents > 1MB
|
||||
for (web_request request : listen()) {
|
||||
dom::element doc;
|
||||
auto error = parser.parse(request.body).get(doc);
|
||||
// If the document was above our limit, emit 413 = payload too large
|
||||
if (error == CAPACITY) { request.respond(413); continue; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This parser will grow normally as it encounters larger documents, but will never pass 1MB.
|
||||
|
||||
* You can set a *fixed capacity* that never grows, as well, which can be excellent for
|
||||
predictability and reliability, since simdjson will never call malloc after startup!
|
||||
|
||||
```c++
|
||||
dom::parser parser(0); // This parser will refuse to automatically grow capacity
|
||||
auto error = parser.allocate(1000*1000); // This allocates enough capacity to handle documents <= 1MB
|
||||
if (error) { cerr << error << endl; exit(1); }
|
||||
|
||||
for (web_request request : listen()) {
|
||||
dom::element doc;
|
||||
error = parser.parse(request.body).get(doc);
|
||||
// If the document was above our limit, emit 413 = payload too large
|
||||
if (error == CAPACITY) { request.respond(413); continue; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Large files and huge page support
|
||||
---------------------------------
|
||||
|
||||
There is a memory allocation performance cost the first time you process a large file (e.g. 100MB).
|
||||
Between the cost of allocation, the fact that the memory is not in cache, and the initial zeroing of
|
||||
memory, [on some systems, allocation runs far slower than parsing (e.g., 1.4GB/s)](https://lemire.me/blog/2020/01/14/how-fast-can-you-allocate-a-large-block-of-memory-in-c/). Reusing the parser mitigates this by
|
||||
paying the cost once, but does not eliminate it.
|
||||
|
||||
In large file use cases, enabling transparent huge page allocation on the OS can help a lot. We
|
||||
haven't found the right way to do this on Windows or OS/X, but on Linux, you can enable transparent
|
||||
huge page allocation with a command like:
|
||||
|
||||
```bash
|
||||
echo always > /sys/kernel/mm/transparent_hugepage/enabled
|
||||
```
|
||||
|
||||
In general, when running benchmarks over large files, we recommend that you report performance
|
||||
numbers with and without huge pages if possible. Furthermore, you should amortize the parsing (e.g.,
|
||||
by parsing several large files) to distinguish the time spent parsing from the time spent allocating
|
||||
memory. If you are using the `parse` benchmarking tool provided with the simdjson library, you can
|
||||
use the `-H` flag to omit the memory allocation cost from the benchmark results.
|
||||
|
||||
```
|
||||
./parse largefile # includes memory allocation cost
|
||||
./parse -H largefile # without memory allocation
|
||||
```
|
||||
|
||||
Computed GOTOs
|
||||
--------------
|
||||
|
||||
For best performance, we use a technique called "computed goto" when the compiler supports it, it is
|
||||
also sometimes described as "Labels as Values". Though it is not part of the C++ standard, it is
|
||||
supported by many major compilers and it brings measurable performance benefits that are difficult
|
||||
to achieve otherwise. The computed gotos are automatically disabled under Visual Studio.
|
||||
|
||||
If you wish to forcefully disable computed gotos, you can do so by compiling the code with
|
||||
`-DSIMDJSON_NO_COMPUTED_GOTO=1`. It is not recommended to disable computed gotos if your compiler
|
||||
supports it. In fact, you should almost never need to be concerned with computed gotos.
|
||||
|
||||
Number parsing
|
||||
--------------
|
||||
|
||||
Some JSON files contain many floating-point values. It is the case with many GeoJSON files. Accurately
|
||||
parsing decimal strings into binary floating-point values with proper rounding is challenging. To
|
||||
our knowledge, it is not possible, in general, to parse streams of numbers at gigabytes per second
|
||||
using a single core. While using the simdjson library, it is possible that you might be limited to a
|
||||
few hundred megabytes per second if your JSON documents are densely packed with floating-point values.
|
||||
|
||||
|
||||
- When possible, you should favor integer values written without a decimal point, as it simpler and faster to parse decimal integer values.
|
||||
- When serializing numbers, you should not use more digits than necessary: 17 digits is all that is needed to exactly represent double-precision floating-point numbers. Using many more digits than necessary will make your files larger and slower to parse.
|
||||
- When benchmarking parsing speeds, always report whether your JSON documents are made mostly of floating-point numbers when it is the case, since number parsing can then dominate the parsing time.
|
||||
|
||||
|
||||
Visual Studio
|
||||
--------------
|
||||
|
||||
On Intel and AMD Windows platforms, Microsoft Visual Studio enables programmers to build either 32-bit (x86) or 64-bit (x64) binaries. We urge you to always use 64-bit mode. Visual Studio 2019 should default on 64-bit builds when you have a 64-bit version of Windows, which we recommend.
|
||||
|
||||
We do not recommend that you compile simdjson with architecture-specific flags such as `arch:AVX2`. The simdjson library automatically selects the best execution kernel at runtime.
|
||||
|
||||
Recent versions of Microsoft Visual Studio on Windows provides support for the LLVM Clang compiler. You only need to install the "Clang compiler" optional component. You may also get a copy of the 64-bit LLVM CLang compiler for [Windows directly from LLVM](https://releases.llvm.org/download.html). The simdjson library fully supports the LLVM Clang compiler under Windows. In fact, you may get better performance out of simdjson with the LLVM Clang compiler than with the regular Visual Studio compiler.
|
||||
|
||||
|
||||
Downclocking
|
||||
--------------
|
||||
|
||||
You should not expect the simdjson library to cause downclocking of your recent Intel CPU cores.
|
||||
|
||||
On some Intel processors, using SIMD instructions in a sustained manner on the same CPU core may result in a phenomenon called downclocking whereas the processor initially runs these instructions at a slow speed before reducing the frequency of the core for a short time (milliseconds). Intel refers to these states as licenses. On some current Intel processors, it occurs under two scenarios:
|
||||
|
||||
- [Whenever 512-bit AVX-512 instructions are used](https://lemire.me/blog/2018/09/07/avx-512-when-and-how-to-use-these-new-instructions/).
|
||||
- Whenever heavy 256-bit or wider instructions are used. Heavy instructions are those involving floating point operations or integer multiplications (since these execute on the floating point unit).
|
||||
|
||||
The simdjson library does not currently support AVX-512 instructions and it does not make use of heavy 256-bit instructions. Thus there should be no downclocking due to simdjson on recent processors. You may still be worried about which SIMD instruction set is used by simdjson. Thankfully, [you can always determine and change which architecture-specific implementation is used](implementation-selection.md). Thus even if your CPU supports AVX2, you do not need to use AVX2. You are in control.
|
||||
+40
-38
@@ -28,45 +28,46 @@ It is sometimes useful to start with an example. Consider the following JSON doc
|
||||
|
||||
The following is a dump of the content of the tape, with the first number of each line representing the index of a tape element.
|
||||
|
||||
```bash
|
||||
$ ./json2json -d jsonexamples/small/demo.json
|
||||
0 : r // pointing to 38 (right after last node)
|
||||
1 : { // pointing to next tape location 38 (first node after the scope)
|
||||
2 : string "Image"
|
||||
3 : { // pointing to next tape location 37 (first node after the scope)
|
||||
4 : string "Width"
|
||||
5 : integer 800
|
||||
7 : string "Height"
|
||||
8 : integer 600
|
||||
10 : string "Title"
|
||||
11 : string "View from 15th Floor"
|
||||
12 : string "Thumbnail"
|
||||
13 : { // pointing to next tape location 23 (first node after the scope)
|
||||
14 : string "Url"
|
||||
15 : string "http://www.example.com/image/481989943"
|
||||
16 : string "Height"
|
||||
17 : integer 125
|
||||
19 : string "Width"
|
||||
20 : integer 100
|
||||
22 : } // pointing to previous tape location 13 (start of the scope)
|
||||
23 : string "Animated"
|
||||
24 : false
|
||||
25 : string "IDs"
|
||||
26 : [ // pointing to next tape location 36 (first node after the scope)
|
||||
27 : integer 116
|
||||
29 : integer 943
|
||||
31 : integer 234
|
||||
33 : integer 38793
|
||||
35 : ] // pointing to previous tape location 26 (start of the scope)
|
||||
36 : } // pointing to previous tape location 3 (start of the scope)
|
||||
37 : } // pointing to previous tape location 1 (start of the scope)
|
||||
38 : r // pointing to 0 (start root)
|
||||
### The Tape
|
||||
| index | element (64 bit word) |
|
||||
| ----- | ------------------------------------------------------------------- |
|
||||
| 0 | r // pointing to 38 (right after last node) |
|
||||
| 1 | { // pointing to next tape location 38 (first node after the scope) |
|
||||
| 2 | string "Image" |
|
||||
| 3 | { // pointing to next tape location 37 (first node after the scope) |
|
||||
| 4 | string "Width" |
|
||||
| 5 | integer 800 |
|
||||
| 7 | string "Height" |
|
||||
| 8 | integer 600 |
|
||||
| 10 | string "Title" |
|
||||
| 11 | string "View from 15th Floor" |
|
||||
| 12 | string "Thumbnail" |
|
||||
| 13 | { // pointing to next tape location 23 (first node after the scope) |
|
||||
| 14 | string "Url" |
|
||||
| 15 | string "http://www.example.com/image/481989943" |
|
||||
| 16 | string "Height" |
|
||||
| 17 | integer 125 |
|
||||
| 19 | string "Width" |
|
||||
| 20 | integer 100 |
|
||||
| 22 | } // pointing to previous tape location 13 (start of the scope) |
|
||||
| 23 | string "Animated" |
|
||||
| 24 | false |
|
||||
| 25 | string "IDs" |
|
||||
| 26 | [ // pointing to next tape location 36 (first node after the scope) |
|
||||
| 27 | integer 116 |
|
||||
| 29 | integer 943 |
|
||||
| 31 | integer 234 |
|
||||
| 33 | integer 38793 |
|
||||
| 35 | ] // pointing to previous tape location 26 (start of the scope) |
|
||||
| 36 | } // pointing to previous tape location 3 (start of the scope) |
|
||||
| 37 | } // pointing to previous tape location 1 (start of the scope) |
|
||||
| 38 | r // pointing to 0 (start root) |
|
||||
|
||||
|
||||
```
|
||||
|
||||
## General formal of the tape elements
|
||||
|
||||
Most tape elements are written as `('c' << 56) + x` where `'c'` is some ASCII character determining the type of the element (out of 't', 'f', 'n', 'l', 'd', '"', '{', '}', '[', ']' ,'r') and where `x` is a 56-bit value called the payload. The payload is normally interpreted as an unsigned 56-bit integer. Note that 56-bit integers can be quite large.
|
||||
Most tape elements are written as `('c' << 56) + x` where `'c'` is some ASCII character determining the type of the element (out of 't', 'f', 'n', 'l', 'u', 'd', '"', '{', '}', '[', ']' ,'r') and where `x` is a 56-bit value called the payload. The payload is normally interpreted as an unsigned 56-bit integer. Note that 56-bit integers can be quite large.
|
||||
|
||||
|
||||
Performance consideration: We believe that accessing the tape in regular units of 64 bits is more important for performance than saving memory.
|
||||
@@ -83,10 +84,12 @@ Simple JSON nodes are represented with one tape element:
|
||||
## Integer and Double values
|
||||
|
||||
Integer values are represented as two 64-bit tape elements:
|
||||
- The 64-bit value `('l' << 56)` followed by the 64-bit integer value litterally. Integer values are assumed to be signed 64-bit values, using two's complement notation.
|
||||
- The 64-bit value `('l' << 56)` followed by the 64-bit integer value literally. Integer values are assumed to be signed 64-bit values, using two's complement notation.
|
||||
- The 64-bit value `('u' << 56)` followed by the 64-bit integer value literally. Integer values are assumed to be unsigned 64-bit values.
|
||||
|
||||
|
||||
Float values are represented as two 64-bit tape elements:
|
||||
- The 64-bit value `('d' << 56)` followed by the 64-bit double value litterally in standard IEEE 754 notation.
|
||||
- The 64-bit value `('d' << 56)` followed by the 64-bit double value literally in standard IEEE 754 notation.
|
||||
|
||||
Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance.
|
||||
|
||||
@@ -131,4 +134,3 @@ In-between these two tape elements, we alternate between key (which must be stri
|
||||
All the content of the object is located between these two tape elements, including arrays and objects.
|
||||
|
||||
Performance consideration: We can skip the content of an object entirely by accessing the first 64-bit tape element, reading the payload and moving to the corresponding index on the tape.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
add_subdirectory(quickstart)
|
||||
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# Quickstart compile tests don't require any flags
|
||||
#
|
||||
|
||||
# TODO haven't quite decided the right way to run quickstart on Windows. Needs README update.
|
||||
#
|
||||
# Note: on macOS and other platforms, the 'command' described below may not work even if the cmake builds.
|
||||
# For example, it may be necessary to specify the sysroot, which CMake does, but the 'command' does not
|
||||
# handle such niceties. On a case-by-case basis it is fixable but it requires work that CMake knows how
|
||||
# to do but that is not trivial.
|
||||
#
|
||||
IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
# TODO run amalgamate first!
|
||||
function(add_quickstart_test TEST_NAME SOURCE_FILE)
|
||||
# Second argument is C++ standard name
|
||||
if (MSVC)
|
||||
if (ARGV2)
|
||||
set(QUICKSTART_FLAGS /std:${ARGV2})
|
||||
else()
|
||||
set(QUICKSTART_FLAGS /WX)
|
||||
endif()
|
||||
set(QUICKSTART_INCLUDE /I${PROJECT_SOURCE_DIR}/include /I${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/simdjson.cpp)
|
||||
else()
|
||||
if (ARGV2)
|
||||
set(QUICKSTART_FLAGS -Werror -std=${ARGV2})
|
||||
else()
|
||||
set(QUICKSTART_FLAGS -Werror)
|
||||
endif()
|
||||
set(QUICKSTART_INCLUDE -I${PROJECT_SOURCE_DIR}/include -I${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/simdjson.cpp)
|
||||
endif()
|
||||
|
||||
# Third argument tells whether to compile with -fno-exceptions
|
||||
if (ARGV3)
|
||||
if (NOT MSVC)
|
||||
set(QUICKSTART_FLAGS ${QUICKSTART_FLAGS} -fno-exceptions)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test(
|
||||
NAME ${TEST_NAME}
|
||||
COMMAND ${CMAKE_CXX_COMPILER} ${QUICKSTART_FLAGS} -I${PROJECT_SOURCE_DIR}/include -I${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/simdjson.cpp ${SOURCE_FILE}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/examples/quickstart
|
||||
)
|
||||
set_property(
|
||||
TEST ${TEST_NAME}
|
||||
APPEND PROPERTY DEPENDS simdjson-source ${PROJECT_SOURCE_DIR}/examples/quickstart/${SOURCE_FILE}
|
||||
)
|
||||
endfunction(add_quickstart_test)
|
||||
|
||||
if (SIMDJSON_EXCEPTIONS)
|
||||
add_quickstart_test(quickstart quickstart.cpp)
|
||||
add_quickstart_test(quickstart11 quickstart.cpp c++11)
|
||||
add_quickstart_test(quickstart14 quickstart.cpp c++14)
|
||||
set_property( TEST quickstart quickstart11 APPEND PROPERTY LABELS acceptance compiletests )
|
||||
endif()
|
||||
|
||||
add_quickstart_test(quickstart_noexceptions quickstart_noexceptions.cpp "" true)
|
||||
add_quickstart_test(quickstart_noexceptions11 quickstart_noexceptions.cpp c++11 true)
|
||||
set_property( TEST quickstart_noexceptions APPEND PROPERTY LABELS acceptance compile )
|
||||
endif()
|
||||
@@ -0,0 +1,32 @@
|
||||
ROOT=../..
|
||||
SINGLEHEADER=$(ROOT)/singleheader
|
||||
JSONEXAMPLES=$(ROOT)/jsonexamples
|
||||
|
||||
test: quickstart twitter.json
|
||||
./quickstart
|
||||
quickstart: quickstart.cpp simdjson.cpp simdjson.h
|
||||
c++ -o ./quickstart quickstart.cpp simdjson.cpp
|
||||
clean:
|
||||
rm -f simdjson.cpp simdjson.h twitter.json quickstart quickstart11 quickstart14
|
||||
simdjson.cpp: $(SINGLEHEADER)/simdjson.cpp
|
||||
cp $(SINGLEHEADER)/simdjson.cpp .
|
||||
simdjson.h: $(SINGLEHEADER)/simdjson.h
|
||||
cp $(SINGLEHEADER)/simdjson.h .
|
||||
twitter.json: $(JSONEXAMPLES)/twitter.json
|
||||
cp $(JSONEXAMPLES)/twitter.json .
|
||||
|
||||
quickstart11: $(ROOT)/src/**.h $(ROOT)/src/**.cpp $(ROOT)/include/**.h $(ROOT)/src/**.cpp
|
||||
rm -f simdjson.h simdjson.cpp
|
||||
c++ -o ./quickstart11 quickstart.cpp $(ROOT)/src/simdjson.cpp -I$(ROOT)/src -I$(ROOT)/include -std=c++11
|
||||
test11: quickstart11 twitter.json
|
||||
./quickstart11
|
||||
quickstart14: $(ROOT)/src/**.h $(ROOT)/src/**.cpp $(ROOT)/include/**.h $(ROOT)/src/**.cpp
|
||||
rm -f simdjson.h simdjson.cpp
|
||||
c++ -o ./quickstart14 quickstart.cpp $(ROOT)/src/simdjson.cpp -I$(ROOT)/src -I$(ROOT)/include -std=c++14
|
||||
test14: quickstart14 twitter.json
|
||||
./quickstart14
|
||||
quickstart17: $(ROOT)/src/**.h $(ROOT)/src/**.cpp $(ROOT)/include/**.h $(ROOT)/src/**.cpp
|
||||
rm -f simdjson.h simdjson.cpp
|
||||
c++ -o ./quickstart17 quickstart.cpp $(ROOT)/src/simdjson.cpp -I$(ROOT)/src -I$(ROOT)/include -std=c++17
|
||||
test17: quickstart17 twitter.json
|
||||
./quickstart17
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "simdjson.h"
|
||||
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets = parser.load("twitter.json");
|
||||
std::cout << tweets["search_metadata"]["count"] << " results." << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "simdjson.h"
|
||||
|
||||
int main(void) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element tweets;
|
||||
auto error = parser.load("twitter.json").get(tweets);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
simdjson::dom::element res;
|
||||
|
||||
if ((error = tweets["search_metadata"]["count"].get(res))) {
|
||||
std::cerr << "could not access keys" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << res << " results." << std::endl;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef DUMPBITS_H
|
||||
#define DUMPBITS_H
|
||||
#include <iostream>
|
||||
|
||||
// dump bits low to high
|
||||
inline void dumpbits_always(uint64_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
std::cout << (((v >> static_cast<uint64_t>(i)) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
|
||||
inline void dumpbits32_always(uint32_t v, const std::string &msg) {
|
||||
for (uint32_t i = 0; i < 32; i++) {
|
||||
std::cout << (((v >> i) & 0x1ULL) ? "1" : "_");
|
||||
}
|
||||
std::cout << " " << msg.c_str() << "\n";
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
option(ENABLE_FUZZING "enable building the fuzzers" ON)
|
||||
|
||||
if(ENABLE_FUZZING)
|
||||
|
||||
# First attempt at a fuzzer, using libFuzzer.
|
||||
#
|
||||
# compile like this:
|
||||
# mkdir build-fuzzer
|
||||
# cd build-fuzzer
|
||||
# export LDFLAGS="-fsanitize=address,undefined"
|
||||
# export CXXFLAGS="-fsanitize=fuzzer-no-link,address,undefined"
|
||||
# export CFLAGS="-fsanitize=fuzzer-no-link,address,undefined"
|
||||
# export CXX=clang++
|
||||
# export CC=clang++
|
||||
# cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_FUZZING=On -DSIMDJSON_FUZZ_LINKMAIN=Off -DSIMDJSON_FUZZ_LDFLAGS=-fsanitize=fuzzer
|
||||
# ninja
|
||||
|
||||
# settings this links in a main. useful for reproducing,
|
||||
# kcov, gdb, afl, valgrind.
|
||||
# (note that libFuzzer can also reproduce, just pass it the files)
|
||||
#
|
||||
# Using this by default, means the fuzzers will be built as a part of the normal
|
||||
# workflow, meaning they wont bitrot and will participate in refactoring etc.
|
||||
#
|
||||
option(SIMDJSON_FUZZ_LINKMAIN "links a main into fuzz targets for building reproducers" On)
|
||||
|
||||
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
|
||||
# the fuzz targets, otherwise the cmake configuration step fails.
|
||||
set(SIMDJSON_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
|
||||
|
||||
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)
|
||||
target_link_libraries(simdjson-fuzzer INTERFACE simdjson-source)
|
||||
target_sources(simdjson-fuzzer INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/main.cpp)
|
||||
else ()
|
||||
target_link_libraries(simdjson-fuzzer INTERFACE simdjson)
|
||||
endif ()
|
||||
target_link_libraries(simdjson-fuzzer INTERFACE simdjson-internal-flags)
|
||||
target_link_libraries(simdjson-fuzzer INTERFACE ${SIMDJSON_FUZZ_LDFLAGS})
|
||||
|
||||
# Define the fuzzers
|
||||
add_custom_target(all_fuzzers)
|
||||
|
||||
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)
|
||||
endfunction()
|
||||
|
||||
implement_fuzzer(fuzz_parser)
|
||||
implement_fuzzer(fuzz_minify)
|
||||
implement_fuzzer(fuzz_dump)
|
||||
implement_fuzzer(fuzz_print_json)
|
||||
implement_fuzzer(fuzz_dump_raw_tape)
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,86 @@
|
||||
# Fuzzing
|
||||
|
||||
[Fuzzing](https://en.wikipedia.org/wiki/Fuzzing) is efficient for finding bugs. Here are a few bugs in simdjson found by fuzzing:
|
||||
|
||||
- [#353](https://github.com/simdjson/simdjson/issues/353)
|
||||
- [#351](https://github.com/simdjson/simdjson/issues/351)
|
||||
- [#345](https://github.com/simdjson/simdjson/issues/345)
|
||||
- [oss-fuzz 18714](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=18714&sort=-opened&q=proj%3Asimdjson&can=1)
|
||||
|
||||
The simdjson library tries to follow [fuzzing best practises](https://google.github.io/oss-fuzz/advanced-topics/ideal-integration/#summary).
|
||||
|
||||
The simdjson library is continuously fuzzed on [oss-fuzz](https://github.com/google/oss-fuzz). 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).
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
## Integration with oss-fuzz
|
||||
|
||||
Changes to the integration with oss-fuzz are made by making pull requests against the oss-fuzz github repo. An example can be seen at [oss-fuzz pull request 3013](https://github.com/google/oss-fuzz/pull/3013).
|
||||
|
||||
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).
|
||||
|
||||
One can also grab the corpus as an artifact from the github actions job. Pick a run, then go to artifacts and download.
|
||||
|
||||
## Fuzzing coverage
|
||||
|
||||
The code coverage from fuzzing is most easily viewed on the [oss-fuzz status panel](https://oss-fuzz.com/fuzzer-stats). Viewing the coverage does not require login, but the direct link is 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.
|
||||
|
||||
## Reproducing
|
||||
To reproduce a test case, build the fuzzers, then invoke it with the testcase as a command line argument:
|
||||
```
|
||||
build/fuzz/fuzz_parser my_testcase.json
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// from https://stackoverflow.com/a/8244052
|
||||
class NulStreambuf : public std::streambuf {
|
||||
char dummyBuffer[64];
|
||||
|
||||
protected:
|
||||
virtual int overflow(int c) override final{
|
||||
setp(dummyBuffer, dummyBuffer + sizeof(dummyBuffer));
|
||||
return (c == traits_type::eof()) ? '\0' : c;
|
||||
}
|
||||
};
|
||||
|
||||
class NulOStream final : private NulStreambuf, public std::ostream {
|
||||
public:
|
||||
NulOStream() : std::ostream(this) {}
|
||||
NulStreambuf *rdbuf() { return this; }
|
||||
};
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Builds a corpus from all json files in the source directory.
|
||||
# The files are renamed to the sha1 of their content, and suffixed
|
||||
# .json. The files are zipped into a flat file named corpus.zip
|
||||
|
||||
set -eu
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
|
||||
root=$(readlink -f "$(dirname "$0")/..")
|
||||
|
||||
find $root -type f -name "*.json" | while read -r json; do
|
||||
cp "$json" "$tmp"/$(sha1sum < "$json" |cut -f1 -d' ').json
|
||||
done
|
||||
|
||||
zip --junk-paths -r corpus.zip "$tmp"
|
||||
rm -rf "$tmp"
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This file builds multiple variants of the fuzzers
|
||||
# - different sanitizers
|
||||
# - different build options
|
||||
# - reproduce build, for running through valgrind
|
||||
|
||||
# fail on error
|
||||
set -eu
|
||||
|
||||
unset CXX CC CFLAGS CXXFLAGS LDFLAGS
|
||||
|
||||
me=$(basename $0)
|
||||
|
||||
# A reproduce build, without avx but otherwise as plain
|
||||
# as it gets. No sanitizers or optimization.
|
||||
variant=plain-noavx
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=OFF \
|
||||
-DSIMDJSON_GOOGLE_BENCHMARKS=OFF \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On \
|
||||
-DSIMDJSON_IMPLEMENTATION_HASWELL=0
|
||||
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# A reproduce build as plain as it gets. Everythings tunable is
|
||||
# using the defaults.
|
||||
variant=plain-normal
|
||||
if [ ! -d build-$variant ] ; then
|
||||
mkdir build-$variant
|
||||
cd build-$variant
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_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, 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 \
|
||||
-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
|
||||
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"
|
||||
|
||||
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 \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=Off \
|
||||
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
|
||||
|
||||
ninja all_fuzzers
|
||||
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "$me: WARNING clang++-9 not found, please install it to build $variant"
|
||||
fi
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script emulates how oss fuzz invokes the build
|
||||
# process, handy for trouble shooting cmake issues and possibly
|
||||
# recreating testcases. For proper debugging of the oss fuzz
|
||||
# build, follow the procedure at https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally
|
||||
|
||||
set -eu
|
||||
|
||||
ossfuzz=$(readlink -f $(dirname $0))/ossfuzz.sh
|
||||
|
||||
mkdir -p ossfuzz-out
|
||||
export OUT=$(pwd)/ossfuzz-out
|
||||
export CC=clang
|
||||
export CXX="clang++"
|
||||
export CFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export CXXFLAGS="-fsanitize=fuzzer-no-link"
|
||||
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"
|
||||
|
||||
$ossfuzz
|
||||
|
||||
echo "look at the results in $OUT"
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
// example from doc/basics.md#tree-walking-and-json-element-types
|
||||
static void print_json(std::ostream& os, simdjson::dom::element element) {
|
||||
const char endl='\n';
|
||||
switch (element.type()) {
|
||||
case simdjson::dom::element_type::ARRAY:
|
||||
os << "[";
|
||||
for (simdjson::dom::element child : element.get<simdjson::dom::array>().first) {
|
||||
print_json(os, child);
|
||||
os << ",";
|
||||
}
|
||||
os << "]";
|
||||
break;
|
||||
case simdjson::dom::element_type::OBJECT:
|
||||
os << "{";
|
||||
for (simdjson::dom::key_value_pair field : element.get<simdjson::dom::object>().first) {
|
||||
os << "\"" << field.key << "\": ";
|
||||
print_json(os, field.value);
|
||||
}
|
||||
os << "}";
|
||||
break;
|
||||
case simdjson::dom::element_type::INT64:
|
||||
os << element.get<int64_t>().first << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::UINT64:
|
||||
os << element.get<uint64_t>().first << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::DOUBLE:
|
||||
os << element.get<double>().first << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::STRING:
|
||||
os << element.get<std::string_view>().first << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::BOOL:
|
||||
os << element.get<bool>().first << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::NULL_VALUE:
|
||||
os << "null" << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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; }
|
||||
NulOStream os;
|
||||
//std::ostream& os(std::cout);
|
||||
print_json(os,elem);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(Data, Size).get(elem);
|
||||
if (error) { return 1; }
|
||||
|
||||
NulOStream os;
|
||||
UNUSED auto dumpstatus = elem.dump_raw_tape(os);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
auto begin = (const char *)Data;
|
||||
auto end = begin + Size;
|
||||
|
||||
std::string str(begin, end);
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::element elem;
|
||||
auto error = parser.parse(str).get(elem);
|
||||
if (error) { return 1; }
|
||||
|
||||
std::string minified=simdjson::minify(elem);
|
||||
(void)minified;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
simdjson::dom::parser parser;
|
||||
UNUSED simdjson::dom::element elem;
|
||||
UNUSED auto error = parser.parse(Data, Size).get(elem);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "simdjson.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "NullBuffer.h"
|
||||
|
||||
|
||||
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) {
|
||||
NulOStream os;
|
||||
os<<elem;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
// view data as a byte pointer
|
||||
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
|
||||
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
|
||||
}
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size);
|
||||
int main(int argc, char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream in(argv[i]);
|
||||
assert(in);
|
||||
in.seekg(0, std::ios_base::end);
|
||||
const auto pos = in.tellg();
|
||||
assert(pos >= 0);
|
||||
in.seekg(0, std::ios_base::beg);
|
||||
std::vector<char> buf(static_cast<std::size_t>(pos));
|
||||
in.read(buf.data(), static_cast<long>(buf.size()));
|
||||
assert(in.gcount() == pos);
|
||||
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
|
||||
}
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# makes a coverage build.
|
||||
#
|
||||
# To measure and display the coverage:
|
||||
#
|
||||
#cd build-coverage
|
||||
#fuzz/fuzz_parser path/to/corpus/* # repeat with other fuzzers
|
||||
#gcovr -r . --html --html-details --sort-uncovered -o out.html
|
||||
# and view the results in out.html
|
||||
|
||||
bdir=build-coverage
|
||||
if [ ! -d $bdir ] ; then
|
||||
mkdir -p $bdir
|
||||
cd $bdir
|
||||
|
||||
export CC=gcc
|
||||
export CXX="g++"
|
||||
export CFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
export CXXFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
export LDFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=On
|
||||
ninja all_fuzzers
|
||||
cd ..
|
||||
fi
|
||||
|
||||
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# entry point for oss-fuzz, so that fuzzers
|
||||
# and build invocation can be changed without having
|
||||
# to modify the oss-fuzz repo.
|
||||
#
|
||||
# invoke it from the git root.
|
||||
|
||||
# make sure to exit on problems
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
|
||||
for prog in zip cmake ninja; do
|
||||
if ! which $prog >/dev/null; then
|
||||
echo please install $prog
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# build the corpus (all inputs are json, the same corpus can be used for everyone)
|
||||
fuzz/build_corpus.sh
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DSIMDJSON_BUILD_STATIC=On \
|
||||
-DENABLE_FUZZING=On \
|
||||
-DSIMDJSON_COMPETITION=Off \
|
||||
-DSIMDJSON_FUZZ_LINKMAIN=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
|
||||
for f in $(ls $OUT/fuzz* |grep -v '.zip$') ; do
|
||||
cp ../corpus.zip $OUT/$(basename $f).zip
|
||||
done
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Provides the simdjson headers.
|
||||
#
|
||||
# target_link_libraries(my-project simdjson-headers) grants the headers. It does not provide the
|
||||
# source, libraries or any compiler flags.
|
||||
#
|
||||
add_library(simdjson-headers INTERFACE)
|
||||
target_compile_features(simdjson-headers INTERFACE cxx_std_11) # headers require at least C++11
|
||||
target_include_directories(simdjson-headers INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCDIR}>)
|
||||
|
||||
install(TARGETS simdjson-headers EXPORT simdjson-config INCLUDES DESTINATION include)
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef SIMDJSON_H
|
||||
#define SIMDJSON_H
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
*
|
||||
* Check the [README.md](https://github.com/lemire/simdjson/blob/master/README.md#simdjson--parsing-gigabytes-of-json-per-second).
|
||||
*/
|
||||
|
||||
#include "simdjson/compiler_check.h"
|
||||
#include "simdjson/common_defs.h"
|
||||
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
|
||||
// Public API
|
||||
#include "simdjson/simdjson_version.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/dom/array.h"
|
||||
#include "simdjson/dom/document_stream.h"
|
||||
#include "simdjson/dom/document.h"
|
||||
#include "simdjson/dom/element.h"
|
||||
#include "simdjson/dom/object.h"
|
||||
#include "simdjson/dom/parser.h"
|
||||
|
||||
// Deprecated API
|
||||
#include "simdjson/dom/jsonparser.h"
|
||||
#include "simdjson/dom/parsedjson.h"
|
||||
#include "simdjson/dom/parsedjson_iterator.h"
|
||||
|
||||
// Inline functions
|
||||
#include "simdjson/inline/array.h"
|
||||
#include "simdjson/inline/document_stream.h"
|
||||
#include "simdjson/inline/document.h"
|
||||
#include "simdjson/inline/element.h"
|
||||
#include "simdjson/inline/error.h"
|
||||
#include "simdjson/inline/object.h"
|
||||
#include "simdjson/inline/padded_string.h"
|
||||
#include "simdjson/inline/parsedjson_iterator.h"
|
||||
#include "simdjson/inline/parser.h"
|
||||
#include "simdjson/inline/tape_ref.h"
|
||||
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
#endif // SIMDJSON_H
|
||||
+175
-69
@@ -1,26 +1,59 @@
|
||||
#ifndef SIMDJSON_COMMON_DEFS_H
|
||||
#define SIMDJSON_COMMON_DEFS_H
|
||||
|
||||
#include <cassert>
|
||||
#include "simdjson/portability.h"
|
||||
|
||||
#include <cassert>
|
||||
namespace simdjson {
|
||||
|
||||
// we support documents up to 4GB
|
||||
#define SIMDJSON_MAXSIZE_BYTES 0xFFFFFFFF
|
||||
|
||||
// the input buf should be readable up to buf + SIMDJSON_PADDING
|
||||
#ifdef __AVX2__
|
||||
#define SIMDJSON_PADDING sizeof(__m256i)
|
||||
#ifndef SIMDJSON_EXCEPTIONS
|
||||
#if __cpp_exceptions
|
||||
#define SIMDJSON_EXCEPTIONS 1
|
||||
#else
|
||||
// this is a stopgap; there should be a better description of the
|
||||
// main loop and its behavior that abstracts over this
|
||||
#define SIMDJSON_PADDING 32
|
||||
#define SIMDJSON_EXCEPTIONS 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// Implemented using Labels as Values which works in GCC and CLANG (and maybe
|
||||
// also in Intel's compiler), but won't work in MSVC.
|
||||
#define SIMDJSON_USE_COMPUTED_GOTO
|
||||
/** The maximum document size supported by simdjson. */
|
||||
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
|
||||
|
||||
/**
|
||||
* The amount of padding needed in a buffer to parse JSON.
|
||||
*
|
||||
* the input buf should be readable up to buf + SIMDJSON_PADDING
|
||||
* this is a stopgap; there should be a better description of the
|
||||
* main loop and its behavior that abstracts over this
|
||||
* See https://github.com/lemire/simdjson/issues/174
|
||||
*/
|
||||
constexpr size_t SIMDJSON_PADDING = 32;
|
||||
|
||||
/**
|
||||
* By default, simdjson supports this many nested objects and arrays.
|
||||
*
|
||||
* This is the default for parser::max_depth().
|
||||
*/
|
||||
constexpr size_t DEFAULT_MAX_DEPTH = 1024;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#if defined(__GNUC__)
|
||||
// Marks a block with a name so that MCA analysis can see it.
|
||||
#define BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name);
|
||||
#define END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name);
|
||||
#define DEBUG_BLOCK(name, block) BEGIN_DEBUG_BLOCK(name); block; END_DEBUG_BLOCK(name);
|
||||
#else
|
||||
#define BEGIN_DEBUG_BLOCK(name)
|
||||
#define END_DEBUG_BLOCK(name)
|
||||
#define DEBUG_BLOCK(name, block)
|
||||
#endif
|
||||
|
||||
#if !defined(SIMDJSON_REGULAR_VISUAL_STUDIO) && !defined(SIMDJSON_NO_COMPUTED_GOTO)
|
||||
// We assume here that *only* regular visual studio
|
||||
// does not support computed gotos.
|
||||
// Implemented using Labels as Values which works in GCC and CLANG (and maybe
|
||||
// also in Intel's compiler), but won't work in MSVC.
|
||||
// Compute gotos are good for performance, enable them if you can.
|
||||
#define SIMDJSON_USE_COMPUTED_GOTO
|
||||
#endif
|
||||
|
||||
// Align to N-byte boundary
|
||||
@@ -29,74 +62,147 @@
|
||||
|
||||
#define ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define really_inline __forceinline
|
||||
#define never_inline __declspec(noinline)
|
||||
#if defined(SIMDJSON_REGULAR_VISUAL_STUDIO)
|
||||
|
||||
#define UNUSED
|
||||
#define WARN_UNUSED
|
||||
#define really_inline __forceinline
|
||||
#define never_inline __declspec(noinline)
|
||||
|
||||
#ifndef likely
|
||||
#define likely(x) x
|
||||
#endif
|
||||
#ifndef unlikely
|
||||
#define unlikely(x) x
|
||||
#endif
|
||||
#define UNUSED
|
||||
#define WARN_UNUSED
|
||||
|
||||
// For Visual Studio compilers, same-page buffer overrun is not fine.
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
#ifndef likely
|
||||
#define likely(x) x
|
||||
#endif
|
||||
#ifndef unlikely
|
||||
#define unlikely(x) x
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define SIMDJSON_PUSH_DISABLE_WARNINGS __pragma(warning( push ))
|
||||
#define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS __pragma(warning( push, 0 ))
|
||||
#define SIMDJSON_DISABLE_VS_WARNING(WARNING_NUMBER) __pragma(warning( disable : WARNING_NUMBER ))
|
||||
// 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)
|
||||
#if __has_include(<CppCoreCheck\Warnings.h>)
|
||||
#include <CppCoreCheck\Warnings.h>
|
||||
#define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// For non-Visual Studio compilers, we may assume that same-page buffer overrun
|
||||
// is fine. However, it will make it difficult to be "valgrind clean".
|
||||
//#ifndef ALLOW_SAME_PAGE_BUFFER_OVERRUN
|
||||
//#define ALLOW_SAME_PAGE_BUFFER_OVERRUN true
|
||||
//#else
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
//#endif
|
||||
#ifndef SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#define SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#endif
|
||||
|
||||
// The following is likely unnecessarily complex.
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
// we have GCC, stuck with https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67368
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN false
|
||||
#elif defined(__has_feature)
|
||||
// we have CLANG?
|
||||
// todo: if we're setting ALLOW_SAME_PAGE_BUFFER_OVERRUN to false, why do we
|
||||
// have a non-empty qualifier?
|
||||
#if (__has_feature(address_sanitizer))
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER \
|
||||
__attribute__((no_sanitize("address")))
|
||||
#endif
|
||||
#endif
|
||||
#define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_VS_WARNING(4996)
|
||||
#define SIMDJSON_POP_DISABLE_WARNINGS __pragma(warning( pop ))
|
||||
|
||||
#if defined(__has_feature)
|
||||
#if (__has_feature(memory_sanitizer))
|
||||
#define LENIENT_MEM_SANITIZER __attribute__((no_sanitize("memory")))
|
||||
#endif
|
||||
#endif
|
||||
#else // SIMDJSON_REGULAR_VISUAL_STUDIO
|
||||
|
||||
#define really_inline inline __attribute__((always_inline, unused))
|
||||
#define never_inline inline __attribute__((noinline, unused))
|
||||
#define really_inline inline __attribute__((always_inline, unused))
|
||||
#define never_inline inline __attribute__((noinline, unused))
|
||||
|
||||
#define UNUSED __attribute__((unused))
|
||||
#define WARN_UNUSED __attribute__((warn_unused_result))
|
||||
|
||||
#ifndef likely
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#endif
|
||||
#ifndef unlikely
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
#define SIMDJSON_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push")
|
||||
// gcc doesn't seem to disable all warnings with all and extra, add warnings here as necessary
|
||||
#define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Weffc++) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wall) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wconversion) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wattributes) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wnon-virtual-dtor) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wreturn-type) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \
|
||||
SIMDJSON_DISABLE_GCC_WARNING(-Wunused-variable)
|
||||
#define SIMDJSON_PRAGMA(P) _Pragma(#P)
|
||||
#define SIMDJSON_DISABLE_GCC_WARNING(WARNING) SIMDJSON_PRAGMA(GCC diagnostic ignored #WARNING)
|
||||
#if defined(SIMDJSON_CLANG_VISUAL_STUDIO)
|
||||
#define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_GCC_WARNING(-Wmicrosoft-include)
|
||||
#else
|
||||
#define SIMDJSON_DISABLE_UNDESIRED_WARNINGS
|
||||
#endif
|
||||
#define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wdeprecated-declarations)
|
||||
#define SIMDJSON_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop")
|
||||
|
||||
#define UNUSED __attribute__((unused))
|
||||
#define WARN_UNUSED __attribute__((warn_unused_result))
|
||||
|
||||
#ifndef likely
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#endif
|
||||
#ifndef unlikely
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
#endif // MSC_VER
|
||||
|
||||
// if it does not apply, make it an empty macro
|
||||
#ifndef ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER
|
||||
#define ALLOW_SAME_PAGE_BUFFER_OVERRUN_QUALIFIER
|
||||
#endif
|
||||
#ifndef LENIENT_MEM_SANITIZER
|
||||
#define LENIENT_MEM_SANITIZER
|
||||
#if defined(SIMDJSON_VISUAL_STUDIO)
|
||||
/**
|
||||
* It does not matter here whether you are using
|
||||
* the regular visual studio or clang under visual
|
||||
* studio.
|
||||
*/
|
||||
#if SIMDJSON_USING_LIBRARY
|
||||
#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport)
|
||||
#else
|
||||
#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllexport)
|
||||
#endif
|
||||
#else
|
||||
#define SIMDJSON_DLLIMPORTEXPORT
|
||||
#endif
|
||||
|
||||
// C++17 requires string_view.
|
||||
#if SIMDJSON_CPLUSPLUS17
|
||||
#define SIMDJSON_HAS_STRING_VIEW
|
||||
#endif
|
||||
|
||||
// This macro (__cpp_lib_string_view) has to be defined
|
||||
// for C++17 and better, but if it is otherwise defined,
|
||||
// we are going to assume that string_view is available
|
||||
// even if we do not have C++17 support.
|
||||
#ifdef __cpp_lib_string_view
|
||||
#define SIMDJSON_HAS_STRING_VIEW
|
||||
#endif
|
||||
|
||||
// Some systems have string_view even if we do not have C++17 support,
|
||||
// and even if __cpp_lib_string_view is undefined, it is the case
|
||||
// with Apple clang version 11.
|
||||
// We must handle it. *This is important.*
|
||||
#ifndef SIMDJSON_HAS_STRING_VIEW
|
||||
#if defined __has_include
|
||||
// do not combine the next #if with the previous one (unsafe)
|
||||
#if __has_include (<string_view>)
|
||||
// now it is safe to trigger the include
|
||||
#include <string_view> // though the file is there, it does not follow that we got the implementation
|
||||
#if defined(_LIBCPP_STRING_VIEW)
|
||||
// Ah! So we under libc++ which under its Library Fundamentals Technical Specification, which preceeded C++17,
|
||||
// included string_view.
|
||||
// This means that we have string_view *even though* we may not have C++17.
|
||||
#define SIMDJSON_HAS_STRING_VIEW
|
||||
#endif // _LIBCPP_STRING_VIEW
|
||||
#endif // __has_include (<string_view>)
|
||||
#endif // defined __has_include
|
||||
#endif // def SIMDJSON_HAS_STRING_VIEW
|
||||
// end of complicated but important routine to try to detect string_view.
|
||||
|
||||
//
|
||||
// Backfill std::string_view using nonstd::string_view on systems where
|
||||
// we expect that string_view is missing. Important: if we get this wrong,
|
||||
// we will end up with two string_view definitions and potential trouble.
|
||||
// That is why we work so hard above to avoid it.
|
||||
//
|
||||
#ifndef SIMDJSON_HAS_STRING_VIEW
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
#include "simdjson/nonstd/string_view.hpp"
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
namespace std {
|
||||
using string_view = nonstd::string_view;
|
||||
}
|
||||
#endif // SIMDJSON_HAS_STRING_VIEW
|
||||
#undef SIMDJSON_HAS_STRING_VIEW // We are not going to need this macro anymore.
|
||||
|
||||
#endif // SIMDJSON_COMMON_DEFS_H
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef SIMDJSON_COMPILER_CHECK_H
|
||||
#define SIMDJSON_COMPILER_CHECK_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error simdjson requires a C++ compiler
|
||||
#endif
|
||||
|
||||
#ifndef SIMDJSON_CPLUSPLUS
|
||||
#if defined(_MSVC_LANG) && !defined(__clang__)
|
||||
#define SIMDJSON_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)
|
||||
#else
|
||||
#define SIMDJSON_CPLUSPLUS __cplusplus
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// C++ 17
|
||||
#if !defined(SIMDJSON_CPLUSPLUS17) && (SIMDJSON_CPLUSPLUS >= 201703L)
|
||||
#define SIMDJSON_CPLUSPLUS17 1
|
||||
#endif
|
||||
|
||||
// C++ 14
|
||||
#if !defined(SIMDJSON_CPLUSPLUS14) && (SIMDJSON_CPLUSPLUS >= 201402L)
|
||||
#define SIMDJSON_CPLUSPLUS14 1
|
||||
#endif
|
||||
|
||||
// C++ 11
|
||||
#if !defined(SIMDJSON_CPLUSPLUS11) && (SIMDJSON_CPLUSPLUS >= 201103L)
|
||||
#define SIMDJSON_CPLUSPLUS11 1
|
||||
#endif
|
||||
|
||||
#ifndef SIMDJSON_CPLUSPLUS11
|
||||
#error simdjson requires a compiler compliant with the C++11 standard
|
||||
#endif
|
||||
|
||||
#endif // SIMDJSON_COMPILER_CHECK_H
|
||||
@@ -0,0 +1,158 @@
|
||||
#ifndef SIMDJSON_DOM_ARRAY_H
|
||||
#define SIMDJSON_DOM_ARRAY_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/internal/tape_ref.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include <ostream>
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
class document;
|
||||
class element;
|
||||
|
||||
/**
|
||||
* JSON array.
|
||||
*/
|
||||
class array {
|
||||
public:
|
||||
/** Create a new, invalid array */
|
||||
really_inline array() noexcept;
|
||||
|
||||
class iterator {
|
||||
public:
|
||||
/**
|
||||
* Get the actual value
|
||||
*/
|
||||
inline element operator*() const noexcept;
|
||||
/**
|
||||
* Get the next value.
|
||||
*
|
||||
* Part of the std::iterator interface.
|
||||
*
|
||||
*/
|
||||
inline iterator& operator++() noexcept;
|
||||
/**
|
||||
* Check if these values come from the same place in the JSON.
|
||||
*
|
||||
* Part of the std::iterator interface.
|
||||
*/
|
||||
inline bool operator!=(const iterator& other) const noexcept;
|
||||
private:
|
||||
really_inline iterator(const internal::tape_ref &tape) noexcept;
|
||||
internal::tape_ref tape;
|
||||
friend class array;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the first array element.
|
||||
*
|
||||
* Part of the std::iterable interface.
|
||||
*/
|
||||
inline iterator begin() const noexcept;
|
||||
/**
|
||||
* One past the last array element.
|
||||
*
|
||||
* Part of the std::iterable interface.
|
||||
*/
|
||||
inline iterator end() const noexcept;
|
||||
/**
|
||||
* Get the size of the array (number of immediate children).
|
||||
* It is a saturated value with a maximum of 0xFFFFFF: if the value
|
||||
* is 0xFFFFFF then the size is 0xFFFFFF or greater.
|
||||
*/
|
||||
inline size_t size() const noexcept;
|
||||
/**
|
||||
* Get the value associated with the given JSON pointer.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* array a = parser.parse(R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded);
|
||||
* a.at("0/foo/a/1") == 20
|
||||
* a.at("0")["foo"]["a"].at(1) == 20
|
||||
*
|
||||
* @return The value associated with the given JSON pointer, or:
|
||||
* - NO_SUCH_FIELD if a field does not exist in an object
|
||||
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
|
||||
* - INCORRECT_TYPE if a non-integer is used to access an array
|
||||
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
|
||||
*/
|
||||
inline simdjson_result<element> at(const std::string_view &json_pointer) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value at the given index. This function has linear-time complexity and
|
||||
* is equivalent to the following:
|
||||
*
|
||||
* size_t i=0;
|
||||
* for (auto element : *this) {
|
||||
* if (i == index) { return element; }
|
||||
* i++;
|
||||
* }
|
||||
* return INDEX_OUT_OF_BOUNDS;
|
||||
*
|
||||
* Avoid calling the at() function repeatedly.
|
||||
*
|
||||
* @return The value at the given index, or:
|
||||
* - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length
|
||||
*/
|
||||
inline simdjson_result<element> at(size_t index) const noexcept;
|
||||
|
||||
private:
|
||||
really_inline array(const internal::tape_ref &tape) noexcept;
|
||||
internal::tape_ref tape;
|
||||
friend class element;
|
||||
friend struct simdjson_result<element>;
|
||||
template<typename T>
|
||||
friend class simdjson::minifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const array &value);
|
||||
|
||||
} // namespace dom
|
||||
|
||||
/** The result of a JSON conversion that may fail. */
|
||||
template<>
|
||||
struct simdjson_result<dom::array> : public internal::simdjson_result_base<dom::array> {
|
||||
public:
|
||||
really_inline simdjson_result() noexcept; ///< @private
|
||||
really_inline simdjson_result(dom::array value) noexcept; ///< @private
|
||||
really_inline simdjson_result(error_code error) noexcept; ///< @private
|
||||
|
||||
inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
|
||||
inline simdjson_result<dom::element> at(size_t index) const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
inline dom::array::iterator begin() const noexcept(false);
|
||||
inline dom::array::iterator end() const noexcept(false);
|
||||
inline size_t size() const noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
};
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw simdjson_error if the result being printed has an error. If there is an error with the
|
||||
* underlying output stream, that error will be propagated (simdjson_error will not be
|
||||
* thrown).
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const simdjson_result<dom::array> &value) noexcept(false);
|
||||
#endif
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_ARRAY_H
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifndef SIMDJSON_DOM_DOCUMENT_H
|
||||
#define SIMDJSON_DOM_DOCUMENT_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
class element;
|
||||
|
||||
/**
|
||||
* A parsed JSON document.
|
||||
*
|
||||
* This class cannot be copied, only moved, to avoid unintended allocations.
|
||||
*/
|
||||
class document {
|
||||
public:
|
||||
/**
|
||||
* Create a document container with zero capacity.
|
||||
*
|
||||
* The parser will allocate capacity as needed.
|
||||
*/
|
||||
document() noexcept = default;
|
||||
~document() noexcept = default;
|
||||
|
||||
/**
|
||||
* Take another document's buffers.
|
||||
*
|
||||
* @param other The document to take. Its capacity is zeroed and it is invalidated.
|
||||
*/
|
||||
document(document &&other) noexcept = default;
|
||||
/** @private */
|
||||
document(const document &) = delete; // Disallow copying
|
||||
/**
|
||||
* Take another document's buffers.
|
||||
*
|
||||
* @param other The document to take. Its capacity is zeroed.
|
||||
*/
|
||||
document &operator=(document &&other) noexcept = default;
|
||||
/** @private */
|
||||
document &operator=(const document &) = delete; // Disallow copying
|
||||
|
||||
/**
|
||||
* Get the root element of this document as a JSON array.
|
||||
*/
|
||||
element root() const noexcept;
|
||||
|
||||
/**
|
||||
* @private Dump the raw tape for debugging.
|
||||
*
|
||||
* @param os the stream to output to.
|
||||
* @return false if the tape is likely wrong (e.g., you did not parse a valid JSON).
|
||||
*/
|
||||
bool dump_raw_tape(std::ostream &os) const noexcept;
|
||||
|
||||
/** @private Structural values. */
|
||||
std::unique_ptr<uint64_t[]> tape{};
|
||||
|
||||
/** @private String values.
|
||||
*
|
||||
* Should be at least byte_capacity.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
|
||||
private:
|
||||
inline error_code allocate(size_t len) noexcept;
|
||||
template<typename T>
|
||||
friend class simdjson::minifier;
|
||||
friend class parser;
|
||||
}; // class document
|
||||
|
||||
} // namespace dom
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_DOCUMENT_H
|
||||
@@ -0,0 +1,261 @@
|
||||
#ifndef SIMDJSON_DOCUMENT_STREAM_H
|
||||
#define SIMDJSON_DOCUMENT_STREAM_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/dom/parser.h"
|
||||
#include "simdjson/error.h"
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#endif
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
/** @private Custom worker class **/
|
||||
struct stage1_worker {
|
||||
stage1_worker() noexcept = default;
|
||||
stage1_worker(const stage1_worker&) = delete;
|
||||
stage1_worker(stage1_worker&&) = delete;
|
||||
stage1_worker operator=(const stage1_worker&) = delete;
|
||||
~stage1_worker();
|
||||
/**
|
||||
* We only start the thread when it is needed, not at object construction, this may throw.
|
||||
* You should only call this once.
|
||||
**/
|
||||
void start_thread();
|
||||
/**
|
||||
* Start a stage 1 job. You should first call 'run', then 'finish'.
|
||||
* You must call start_thread once before.
|
||||
*/
|
||||
void run(document_stream * ds, dom::parser * stage1, size_t next_batch_start);
|
||||
/** Wait for the run to finish (blocking). You should first call 'run', then 'finish'. **/
|
||||
void finish();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Normally, we would never stop the thread. But we do in the destructor.
|
||||
* This function is only safe assuming that you are not waiting for results. You
|
||||
* should have called run, then finish, and be done.
|
||||
**/
|
||||
void stop_thread();
|
||||
|
||||
std::thread thread{};
|
||||
/** These three variables define the work done by the thread. **/
|
||||
dom::parser * stage1_thread_parser{};
|
||||
size_t _next_batch_start{};
|
||||
document_stream * owner{};
|
||||
/**
|
||||
* We have two state variables. This could be streamlined to one variable in the future but
|
||||
* we use two for clarity.
|
||||
*/
|
||||
bool has_work{false};
|
||||
bool can_work{true};
|
||||
|
||||
/**
|
||||
* We lock using a mutex.
|
||||
*/
|
||||
std::mutex locking_mutex{};
|
||||
std::condition_variable cond_var{};
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A forward-only stream of documents.
|
||||
*
|
||||
* Produced by parser::parse_many.
|
||||
*
|
||||
*/
|
||||
class document_stream {
|
||||
public:
|
||||
/**
|
||||
* Construct an uninitialized document_stream.
|
||||
*
|
||||
* ```c++
|
||||
* document_stream docs;
|
||||
* error = parser.parse_many(json).get(docs);
|
||||
* ```
|
||||
*/
|
||||
really_inline document_stream() noexcept;
|
||||
/** Move one document_stream to another. */
|
||||
really_inline document_stream(document_stream &&other) noexcept = default;
|
||||
/** Move one document_stream to another. */
|
||||
really_inline document_stream &operator=(document_stream &&other) noexcept = default;
|
||||
|
||||
really_inline ~document_stream() noexcept;
|
||||
|
||||
/**
|
||||
* An iterator through a forward-only stream of documents.
|
||||
*/
|
||||
class iterator {
|
||||
public:
|
||||
/**
|
||||
* Get the current document (or error).
|
||||
*/
|
||||
really_inline simdjson_result<element> operator*() noexcept;
|
||||
/**
|
||||
* Advance to the next document.
|
||||
*/
|
||||
inline iterator& operator++() noexcept;
|
||||
/**
|
||||
* Check if we're at the end yet.
|
||||
* @param other the end iterator to compare to.
|
||||
*/
|
||||
really_inline bool operator!=(const iterator &other) const noexcept;
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Gives the current index in the input document in bytes.
|
||||
*
|
||||
* document_stream stream = parser.parse_many(json,window);
|
||||
* for(auto i = stream.begin(); i != stream.end(); ++i) {
|
||||
* auto doc = *i;
|
||||
* size_t index = i.current_index();
|
||||
* }
|
||||
*
|
||||
* This function (current_index()) is experimental and the usage
|
||||
* may change in future versions of simdjson: we find the API somewhat
|
||||
* awkward and we would like to offer something friendlier.
|
||||
*/
|
||||
really_inline size_t current_index() noexcept;
|
||||
private:
|
||||
really_inline iterator(document_stream &s, bool finished) noexcept;
|
||||
/** The document_stream we're iterating through. */
|
||||
document_stream& stream;
|
||||
/** Whether we're finished or not. */
|
||||
bool finished;
|
||||
friend class document_stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start iterating the documents in the stream.
|
||||
*/
|
||||
really_inline iterator begin() noexcept;
|
||||
/**
|
||||
* The end of the stream, for iterator comparison purposes.
|
||||
*/
|
||||
really_inline iterator end() noexcept;
|
||||
|
||||
private:
|
||||
|
||||
document_stream &operator=(const document_stream &) = delete; // Disallow copying
|
||||
document_stream(const document_stream &other) = delete; // Disallow copying
|
||||
|
||||
/**
|
||||
* Construct a document_stream. Does not allocate or parse anything until the iterator is
|
||||
* used.
|
||||
*/
|
||||
really_inline document_stream(
|
||||
dom::parser &parser,
|
||||
const uint8_t *buf,
|
||||
size_t len,
|
||||
size_t batch_size
|
||||
) noexcept;
|
||||
|
||||
/**
|
||||
* Parse the first document in the buffer. Used by begin(), to handle allocation and
|
||||
* initialization.
|
||||
*/
|
||||
inline void start() noexcept;
|
||||
|
||||
/**
|
||||
* Parse the next document found in the buffer previously given to document_stream.
|
||||
*
|
||||
* The content should be a valid JSON document encoded as UTF-8. If there is a
|
||||
* UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are
|
||||
* discouraged.
|
||||
*
|
||||
* You do NOT need to pre-allocate a parser. This function takes care of
|
||||
* pre-allocating a capacity defined by the batch_size defined when creating the
|
||||
* document_stream object.
|
||||
*
|
||||
* The function returns simdjson::EMPTY if there is no more data to be parsed.
|
||||
*
|
||||
* The function returns simdjson::SUCCESS (as integer = 0) in case of success
|
||||
* and indicates that the buffer has successfully been parsed to the end.
|
||||
* Every document it contained has been parsed without error.
|
||||
*
|
||||
* The function returns an error code from simdjson/simdjson.h in case of failure
|
||||
* such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth;
|
||||
* the simdjson::error_message function converts these error codes into a string).
|
||||
*
|
||||
* You can also check validity by calling parser.is_valid(). The same parser can
|
||||
* and should be reused for the other documents in the buffer.
|
||||
*/
|
||||
inline void next() noexcept;
|
||||
|
||||
/**
|
||||
* Pass the next batch through stage 1 and return when finished.
|
||||
* When threads are enabled, this may wait for the stage 1 thread to finish.
|
||||
*/
|
||||
inline void load_batch() noexcept;
|
||||
|
||||
/** Get the next document index. */
|
||||
inline size_t next_batch_start() const noexcept;
|
||||
|
||||
/** Pass the next batch through stage 1 with the given parser. */
|
||||
inline error_code run_stage1(dom::parser &p, size_t batch_start) noexcept;
|
||||
|
||||
dom::parser *parser;
|
||||
const uint8_t *buf;
|
||||
size_t len;
|
||||
size_t batch_size;
|
||||
/** The error (or lack thereof) from the current document. */
|
||||
error_code error;
|
||||
size_t batch_start{0};
|
||||
size_t doc_index{};
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
inline void load_from_stage1_thread() noexcept;
|
||||
|
||||
/** Start a thread to run stage 1 on the next batch. */
|
||||
inline void start_stage1_thread() noexcept;
|
||||
|
||||
/** Wait for the stage 1 thread to finish and capture the results. */
|
||||
inline void finish_stage1_thread() noexcept;
|
||||
|
||||
/** The error returned from the stage 1 thread. */
|
||||
error_code stage1_thread_error{UNINITIALIZED};
|
||||
/** The thread used to run stage 1 against the next batch in the background. */
|
||||
friend struct stage1_worker;
|
||||
std::unique_ptr<stage1_worker> worker{new(std::nothrow) stage1_worker()};
|
||||
/**
|
||||
* The parser used to run stage 1 in the background. Will be swapped
|
||||
* with the regular parser when finished.
|
||||
*/
|
||||
dom::parser stage1_thread_parser{};
|
||||
#endif // SIMDJSON_THREADS_ENABLED
|
||||
|
||||
friend class dom::parser;
|
||||
friend struct simdjson_result<dom::document_stream>;
|
||||
friend struct internal::simdjson_result_base<dom::document_stream>;
|
||||
|
||||
}; // class document_stream
|
||||
|
||||
} // namespace dom
|
||||
|
||||
template<>
|
||||
struct simdjson_result<dom::document_stream> : public internal::simdjson_result_base<dom::document_stream> {
|
||||
public:
|
||||
really_inline simdjson_result() noexcept; ///< @private
|
||||
really_inline simdjson_result(error_code error) noexcept; ///< @private
|
||||
really_inline simdjson_result(dom::document_stream &&value) noexcept; ///< @private
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
really_inline dom::document_stream::iterator begin() noexcept(false);
|
||||
really_inline dom::document_stream::iterator end() noexcept(false);
|
||||
#else // SIMDJSON_EXCEPTIONS
|
||||
[[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]]
|
||||
really_inline dom::document_stream::iterator begin() noexcept;
|
||||
[[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]]
|
||||
really_inline dom::document_stream::iterator end() noexcept;
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
}; // struct simdjson_result<dom::document_stream>
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOCUMENT_STREAM_H
|
||||
@@ -0,0 +1,545 @@
|
||||
#ifndef SIMDJSON_DOM_ELEMENT_H
|
||||
#define SIMDJSON_DOM_ELEMENT_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/internal/tape_ref.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include <ostream>
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
class array;
|
||||
class document;
|
||||
class object;
|
||||
|
||||
/**
|
||||
* The actual concrete type of a JSON element
|
||||
* This is the type it is most easily cast to with get<>.
|
||||
*/
|
||||
enum class element_type {
|
||||
ARRAY = '[', ///< dom::array
|
||||
OBJECT = '{', ///< dom::object
|
||||
INT64 = 'l', ///< int64_t
|
||||
UINT64 = 'u', ///< uint64_t: any integer that fits in uint64_t but *not* int64_t
|
||||
DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double.
|
||||
STRING = '"', ///< std::string_view
|
||||
BOOL = 't', ///< bool
|
||||
NULL_VALUE = 'n' ///< null
|
||||
};
|
||||
|
||||
/**
|
||||
* A JSON element.
|
||||
*
|
||||
* References an element in a JSON document, representing a JSON null, boolean, string, number,
|
||||
* array or object.
|
||||
*/
|
||||
class element {
|
||||
public:
|
||||
/** Create a new, invalid element. */
|
||||
really_inline element() noexcept;
|
||||
|
||||
/** The type of this element. */
|
||||
really_inline element_type type() const noexcept;
|
||||
|
||||
/**
|
||||
* Cast this element to an array.
|
||||
*
|
||||
* Equivalent to get<array>().
|
||||
*
|
||||
* @returns An object that can be used to iterate the array, or:
|
||||
* INCORRECT_TYPE if the JSON element is not an array.
|
||||
*/
|
||||
inline simdjson_result<array> get_array() const noexcept;
|
||||
/**
|
||||
* Cast this element to an object.
|
||||
*
|
||||
* Equivalent to get<object>().
|
||||
*
|
||||
* @returns An object that can be used to look up or iterate the object's fields, or:
|
||||
* INCORRECT_TYPE if the JSON element is not an object.
|
||||
*/
|
||||
inline simdjson_result<object> get_object() const noexcept;
|
||||
/**
|
||||
* Cast this element to a null-terminated C string.
|
||||
*
|
||||
* The string is guaranteed to be valid UTF-8.
|
||||
*
|
||||
* The get_c_str() function is equivalent to get<const char *>().
|
||||
*
|
||||
* The length of the string is given by get_string_length(). Because JSON strings
|
||||
* may contain null characters, it may be incorrect to use strlen to determine the
|
||||
* string length.
|
||||
*
|
||||
* It is possible to get a single string_view instance which represents both the string
|
||||
* content and its length: see get_string().
|
||||
*
|
||||
* @returns A pointer to a null-terminated UTF-8 string. This string is stored in the parser and will
|
||||
* be invalidated the next time it parses a document or when it is destroyed.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not a string.
|
||||
*/
|
||||
inline simdjson_result<const char *> get_c_str() const noexcept;
|
||||
/**
|
||||
* Gives the length in bytes of the string.
|
||||
*
|
||||
* It is possible to get a single string_view instance which represents both the string
|
||||
* content and its length: see get_string().
|
||||
*
|
||||
* @returns A string length in bytes.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not a string.
|
||||
*/
|
||||
inline simdjson_result<size_t> get_string_length() const noexcept;
|
||||
/**
|
||||
* Cast this element to a string.
|
||||
*
|
||||
* The string is guaranteed to be valid UTF-8.
|
||||
*
|
||||
* Equivalent to get<std::string_view>().
|
||||
*
|
||||
* @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next time it
|
||||
* parses a document or when it is destroyed.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not a string.
|
||||
*/
|
||||
inline simdjson_result<std::string_view> get_string() const noexcept;
|
||||
/**
|
||||
* Cast this element to a signed integer.
|
||||
*
|
||||
* Equivalent to get<int64_t>().
|
||||
*
|
||||
* @returns A signed 64-bit integer.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE
|
||||
* if it is negative.
|
||||
*/
|
||||
inline simdjson_result<int64_t> get_int64() const noexcept;
|
||||
/**
|
||||
* Cast this element to an unsigned integer.
|
||||
*
|
||||
* Equivalent to get<uint64_t>().
|
||||
*
|
||||
* @returns An unsigned 64-bit integer.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE
|
||||
* if it is too large.
|
||||
*/
|
||||
inline simdjson_result<uint64_t> get_uint64() const noexcept;
|
||||
/**
|
||||
* Cast this element to an double floating-point.
|
||||
*
|
||||
* Equivalent to get<double>().
|
||||
*
|
||||
* @returns A double value.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not a number.
|
||||
*/
|
||||
inline simdjson_result<double> get_double() const noexcept;
|
||||
/**
|
||||
* Cast this element to a bool.
|
||||
*
|
||||
* Equivalent to get<bool>().
|
||||
*
|
||||
* @returns A bool value.
|
||||
* Returns INCORRECT_TYPE if the JSON element is not a boolean.
|
||||
*/
|
||||
inline simdjson_result<bool> get_bool() const noexcept;
|
||||
|
||||
/**
|
||||
* Whether this element is a json array.
|
||||
*
|
||||
* Equivalent to is<array>().
|
||||
*/
|
||||
inline bool is_array() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json object.
|
||||
*
|
||||
* Equivalent to is<object>().
|
||||
*/
|
||||
inline bool is_object() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json string.
|
||||
*
|
||||
* Equivalent to is<std::string_view>() or is<const char *>().
|
||||
*/
|
||||
inline bool is_string() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json number that fits in a signed 64-bit integer.
|
||||
*
|
||||
* Equivalent to is<int64_t>().
|
||||
*/
|
||||
inline bool is_int64() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json number that fits in an unsigned 64-bit integer.
|
||||
*
|
||||
* Equivalent to is<uint64_t>().
|
||||
*/
|
||||
inline bool is_uint64() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json number that fits in a double.
|
||||
*
|
||||
* Equivalent to is<double>().
|
||||
*/
|
||||
inline bool is_double() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json number.
|
||||
*
|
||||
* Both integers and floating points will return true.
|
||||
*/
|
||||
inline bool is_number() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json `true` or `false`.
|
||||
*
|
||||
* Equivalent to is<bool>().
|
||||
*/
|
||||
inline bool is_bool() const noexcept;
|
||||
/**
|
||||
* Whether this element is a json `null`.
|
||||
*/
|
||||
inline bool is_null() const noexcept;
|
||||
|
||||
/**
|
||||
* Tell whether the value can be cast to provided type (T).
|
||||
*
|
||||
* Supported types:
|
||||
* - Boolean: bool
|
||||
* - Number: double, uint64_t, int64_t
|
||||
* - String: std::string_view, const char *
|
||||
* - Array: dom::array
|
||||
* - Object: dom::object
|
||||
*
|
||||
* @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object
|
||||
*/
|
||||
template<typename T>
|
||||
really_inline bool is() const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value as the provided type (T).
|
||||
*
|
||||
* Supported types:
|
||||
* - Boolean: bool
|
||||
* - Number: double, uint64_t, int64_t
|
||||
* - String: std::string_view, const char *
|
||||
* - Array: dom::array
|
||||
* - Object: dom::object
|
||||
*
|
||||
* @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object
|
||||
*
|
||||
* @returns The value cast to the given type, or:
|
||||
* INCORRECT_TYPE if the value cannot be cast to the given type.
|
||||
*/
|
||||
template<typename T>
|
||||
inline simdjson_result<T> get() const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value as the provided type (T).
|
||||
*
|
||||
* Supported types:
|
||||
* - Boolean: bool
|
||||
* - Number: double, uint64_t, int64_t
|
||||
* - String: std::string_view, const char *
|
||||
* - Array: dom::array
|
||||
* - Object: dom::object
|
||||
*
|
||||
* @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object
|
||||
*
|
||||
* @param value The variable to set to the value. May not be set if there is an error.
|
||||
*
|
||||
* @returns The error that occurred, or SUCCESS if there was no error.
|
||||
*/
|
||||
template<typename T>
|
||||
WARN_UNUSED really_inline error_code get(T &value) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value as the provided type (T), setting error if it's not the given type.
|
||||
*
|
||||
* Supported types:
|
||||
* - Boolean: bool
|
||||
* - Number: double, uint64_t, int64_t
|
||||
* - String: std::string_view, const char *
|
||||
* - Array: dom::array
|
||||
* - Object: dom::object
|
||||
*
|
||||
* @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object
|
||||
*
|
||||
* @param value The variable to set to the given type. value is undefined if there is an error.
|
||||
* @param error The variable to store the error. error is set to error_code::SUCCEED if there is an error.
|
||||
*/
|
||||
template<typename T>
|
||||
inline void tie(T &value, error_code &error) && noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/**
|
||||
* Read this element as a boolean.
|
||||
*
|
||||
* @return The boolean value
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a boolean.
|
||||
*/
|
||||
inline operator bool() const noexcept(false);
|
||||
|
||||
/**
|
||||
* Read this element as a null-terminated UTF-8 string.
|
||||
*
|
||||
* Be mindful that JSON allows strings to contain null characters.
|
||||
*
|
||||
* Does *not* convert other types to a string; requires that the JSON type of the element was
|
||||
* an actual string.
|
||||
*
|
||||
* @return The string value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string.
|
||||
*/
|
||||
inline explicit operator const char*() const noexcept(false);
|
||||
|
||||
/**
|
||||
* Read this element as a null-terminated UTF-8 string.
|
||||
*
|
||||
* Does *not* convert other types to a string; requires that the JSON type of the element was
|
||||
* an actual string.
|
||||
*
|
||||
* @return The string value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string.
|
||||
*/
|
||||
inline operator std::string_view() const noexcept(false);
|
||||
|
||||
/**
|
||||
* Read this element as an unsigned integer.
|
||||
*
|
||||
* @return The integer value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer
|
||||
* @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits or is negative
|
||||
*/
|
||||
inline operator uint64_t() const noexcept(false);
|
||||
/**
|
||||
* Read this element as an signed integer.
|
||||
*
|
||||
* @return The integer value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer
|
||||
* @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits
|
||||
*/
|
||||
inline operator int64_t() const noexcept(false);
|
||||
/**
|
||||
* Read this element as an double.
|
||||
*
|
||||
* @return The double value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a number
|
||||
* @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits or is negative
|
||||
*/
|
||||
inline operator double() const noexcept(false);
|
||||
/**
|
||||
* Read this element as a JSON array.
|
||||
*
|
||||
* @return The JSON array.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array
|
||||
*/
|
||||
inline operator array() const noexcept(false);
|
||||
/**
|
||||
* Read this element as a JSON object (key/value pairs).
|
||||
*
|
||||
* @return The JSON object.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an object
|
||||
*/
|
||||
inline operator object() const noexcept(false);
|
||||
|
||||
/**
|
||||
* Iterate over each element in this array.
|
||||
*
|
||||
* @return The beginning of the iteration.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array
|
||||
*/
|
||||
inline dom::array::iterator begin() const noexcept(false);
|
||||
|
||||
/**
|
||||
* Iterate over each element in this array.
|
||||
*
|
||||
* @return The end of the iteration.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array
|
||||
*/
|
||||
inline dom::array::iterator end() const noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
* - INCORRECT_TYPE if this is not an object
|
||||
*/
|
||||
inline simdjson_result<element> operator[](const std::string_view &key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
* - INCORRECT_TYPE if this is not an object
|
||||
*/
|
||||
inline simdjson_result<element> operator[](const char *key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given JSON pointer.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded);
|
||||
* doc.at("/foo/a/1") == 20
|
||||
* doc.at("/")["foo"]["a"].at(1) == 20
|
||||
* doc.at("")["foo"]["a"].at(1) == 20
|
||||
*
|
||||
* @return The value associated with the given JSON pointer, or:
|
||||
* - NO_SUCH_FIELD if a field does not exist in an object
|
||||
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
|
||||
* - INCORRECT_TYPE if a non-integer is used to access an array
|
||||
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
|
||||
*/
|
||||
inline simdjson_result<element> at(const std::string_view &json_pointer) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value at the given index.
|
||||
*
|
||||
* @return The value at the given index, or:
|
||||
* - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length
|
||||
*/
|
||||
inline simdjson_result<element> at(size_t index) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
*/
|
||||
inline simdjson_result<element> at_key(const std::string_view &key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key in a case-insensitive manner.
|
||||
*
|
||||
* Note: The key will be matched against **unescaped** JSON.
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
*/
|
||||
inline simdjson_result<element> at_key_case_insensitive(const std::string_view &key) const noexcept;
|
||||
|
||||
/** @private for debugging. Prints out the root element. */
|
||||
inline bool dump_raw_tape(std::ostream &out) const noexcept;
|
||||
|
||||
private:
|
||||
really_inline element(const internal::tape_ref &tape) noexcept;
|
||||
internal::tape_ref tape;
|
||||
friend class document;
|
||||
friend class object;
|
||||
friend class array;
|
||||
friend struct simdjson_result<element>;
|
||||
template<typename T>
|
||||
friend class simdjson::minifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const element &value);
|
||||
|
||||
/**
|
||||
* Print element type to an output stream.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, element_type type);
|
||||
|
||||
} // namespace dom
|
||||
|
||||
/** The result of a JSON navigation that may fail. */
|
||||
template<>
|
||||
struct simdjson_result<dom::element> : public internal::simdjson_result_base<dom::element> {
|
||||
public:
|
||||
really_inline simdjson_result() noexcept; ///< @private
|
||||
really_inline simdjson_result(dom::element &&value) noexcept; ///< @private
|
||||
really_inline simdjson_result(error_code error) noexcept; ///< @private
|
||||
|
||||
really_inline simdjson_result<dom::element_type> type() const noexcept;
|
||||
template<typename T>
|
||||
really_inline bool is() const noexcept;
|
||||
template<typename T>
|
||||
really_inline simdjson_result<T> get() const noexcept;
|
||||
template<typename T>
|
||||
WARN_UNUSED really_inline error_code get(T &value) const noexcept;
|
||||
|
||||
really_inline simdjson_result<dom::array> get_array() const noexcept;
|
||||
really_inline simdjson_result<dom::object> get_object() const noexcept;
|
||||
really_inline simdjson_result<const char *> get_c_str() const noexcept;
|
||||
really_inline simdjson_result<size_t> get_string_length() const noexcept;
|
||||
really_inline simdjson_result<std::string_view> get_string() const noexcept;
|
||||
really_inline simdjson_result<int64_t> get_int64() const noexcept;
|
||||
really_inline simdjson_result<uint64_t> get_uint64() const noexcept;
|
||||
really_inline simdjson_result<double> get_double() const noexcept;
|
||||
really_inline simdjson_result<bool> get_bool() const noexcept;
|
||||
|
||||
really_inline bool is_array() const noexcept;
|
||||
really_inline bool is_object() const noexcept;
|
||||
really_inline bool is_string() const noexcept;
|
||||
really_inline bool is_int64() const noexcept;
|
||||
really_inline bool is_uint64() const noexcept;
|
||||
really_inline bool is_double() const noexcept;
|
||||
really_inline bool is_bool() const noexcept;
|
||||
really_inline bool is_null() const noexcept;
|
||||
|
||||
really_inline simdjson_result<dom::element> operator[](const std::string_view &key) const noexcept;
|
||||
really_inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
|
||||
really_inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
|
||||
really_inline simdjson_result<dom::element> at(size_t index) const noexcept;
|
||||
really_inline simdjson_result<dom::element> at_key(const std::string_view &key) const noexcept;
|
||||
really_inline simdjson_result<dom::element> at_key_case_insensitive(const std::string_view &key) const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
really_inline operator bool() const noexcept(false);
|
||||
really_inline explicit operator const char*() const noexcept(false);
|
||||
really_inline operator std::string_view() const noexcept(false);
|
||||
really_inline operator uint64_t() const noexcept(false);
|
||||
really_inline operator int64_t() const noexcept(false);
|
||||
really_inline operator double() const noexcept(false);
|
||||
really_inline operator dom::array() const noexcept(false);
|
||||
really_inline operator dom::object() const noexcept(false);
|
||||
|
||||
really_inline dom::array::iterator begin() const noexcept(false);
|
||||
really_inline dom::array::iterator end() const noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
};
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw simdjson_error if the result being printed has an error. If there is an error with the
|
||||
* underlying output stream, that error will be propagated (simdjson_error will not be
|
||||
* thrown).
|
||||
*/
|
||||
really_inline std::ostream& operator<<(std::ostream& out, const simdjson_result<dom::element> &value) noexcept(false);
|
||||
#endif
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_DOCUMENT_H
|
||||
@@ -0,0 +1,117 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_DOM_JSONPARSER_H
|
||||
#define SIMDJSON_DOM_JSONPARSER_H
|
||||
|
||||
#include "simdjson/dom/document.h"
|
||||
#include "simdjson/dom/parsedjson.h"
|
||||
#include "simdjson/jsonioutil.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
//
|
||||
// C API (json_parse and build_parsed_json) declarations
|
||||
//
|
||||
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const uint8_t *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const char *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const std::string &s, dom::parser &parser, bool realloc_if_needed = true) noexcept {
|
||||
error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
inline int json_parse(const padded_string &s, dom::parser &parser) noexcept {
|
||||
error_code code = parser.parse(s).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return code;
|
||||
}
|
||||
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const char *buf, size_t len, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(buf, len, realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const std::string &s, bool realloc_if_needed = true) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
[[deprecated("Use parser.parse() instead")]]
|
||||
WARN_UNUSED inline dom::parser build_parsed_json(const padded_string &s) noexcept {
|
||||
dom::parser parser;
|
||||
error_code code = parser.parse(s).error();
|
||||
// The deprecated json_parse API is a signal that the user plans to *use* the error code / valid
|
||||
// bits in the parser instead of heeding the result code. The normal parser unsets those in
|
||||
// anticipation of making the error code ephemeral.
|
||||
// Here we put the code back into the parser, until we've removed this method.
|
||||
parser.valid = code == SUCCESS;
|
||||
parser.error = code;
|
||||
return parser;
|
||||
}
|
||||
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
int json_parse(const char *buf, dom::parser &parser) noexcept = delete;
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
dom::parser build_parsed_json(const char *buf) noexcept = delete;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_JSONPARSER_H
|
||||
@@ -0,0 +1,264 @@
|
||||
#ifndef SIMDJSON_DOM_OBJECT_H
|
||||
#define SIMDJSON_DOM_OBJECT_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/internal/tape_ref.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include <ostream>
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
class document;
|
||||
class element;
|
||||
class key_value_pair;
|
||||
|
||||
/**
|
||||
* JSON object.
|
||||
*/
|
||||
class object {
|
||||
public:
|
||||
/** Create a new, invalid object */
|
||||
really_inline object() noexcept;
|
||||
|
||||
class iterator {
|
||||
public:
|
||||
/**
|
||||
* Get the actual key/value pair
|
||||
*/
|
||||
inline const key_value_pair operator*() const noexcept;
|
||||
/**
|
||||
* Get the next key/value pair.
|
||||
*
|
||||
* Part of the std::iterator interface.
|
||||
*
|
||||
*/
|
||||
inline iterator& operator++() noexcept;
|
||||
/**
|
||||
* Check if these key value pairs come from the same place in the JSON.
|
||||
*
|
||||
* Part of the std::iterator interface.
|
||||
*/
|
||||
inline bool operator!=(const iterator& other) const noexcept;
|
||||
/**
|
||||
* Get the key of this key/value pair.
|
||||
*/
|
||||
inline std::string_view key() const noexcept;
|
||||
/**
|
||||
* Get the length (in bytes) of the key in this key/value pair.
|
||||
* You should expect this function to be faster than key().size().
|
||||
*/
|
||||
inline uint32_t key_length() const noexcept;
|
||||
/**
|
||||
* Returns true if the key in this key/value pair is equal
|
||||
* to the provided string_view.
|
||||
*/
|
||||
inline bool key_equals(const std::string_view & o) const noexcept;
|
||||
/**
|
||||
* Returns true if the key in this key/value pair is equal
|
||||
* to the provided string_view in a case-insensitive manner.
|
||||
* Case comparisons may only be handled correctly for ASCII strings.
|
||||
*/
|
||||
inline bool key_equals_case_insensitive(const std::string_view & o) const noexcept;
|
||||
/**
|
||||
* Get the key of this key/value pair.
|
||||
*/
|
||||
inline const char *key_c_str() const noexcept;
|
||||
/**
|
||||
* Get the value of this key/value pair.
|
||||
*/
|
||||
inline element value() const noexcept;
|
||||
private:
|
||||
really_inline iterator(const internal::tape_ref &tape) noexcept;
|
||||
|
||||
internal::tape_ref tape;
|
||||
|
||||
friend class object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the first key/value pair.
|
||||
*
|
||||
* Part of the std::iterable interface.
|
||||
*/
|
||||
inline iterator begin() const noexcept;
|
||||
/**
|
||||
* One past the last key/value pair.
|
||||
*
|
||||
* Part of the std::iterable interface.
|
||||
*/
|
||||
inline iterator end() const noexcept;
|
||||
/**
|
||||
* Get the size of the object (number of keys).
|
||||
* It is a saturated value with a maximum of 0xFFFFFF: if the value
|
||||
* is 0xFFFFFF then the size is 0xFFFFFF or greater.
|
||||
*/
|
||||
inline size_t size() const noexcept;
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* This function has linear-time complexity: the keys are checked one by one.
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
* - INCORRECT_TYPE if this is not an object
|
||||
*/
|
||||
inline simdjson_result<element> operator[](const std::string_view &key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* This function has linear-time complexity: the keys are checked one by one.
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
* - INCORRECT_TYPE if this is not an object
|
||||
*/
|
||||
inline simdjson_result<element> operator[](const char *key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given JSON pointer.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* object obj = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded);
|
||||
* obj.at("foo/a/1") == 20
|
||||
* obj.at("foo")["a"].at(1) == 20
|
||||
*
|
||||
* @return The value associated with the given JSON pointer, or:
|
||||
* - NO_SUCH_FIELD if a field does not exist in an object
|
||||
* - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length
|
||||
* - INCORRECT_TYPE if a non-integer is used to access an array
|
||||
* - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed
|
||||
*/
|
||||
inline simdjson_result<element> at(const std::string_view &json_pointer) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key.
|
||||
*
|
||||
* The key will be matched against **unescaped** JSON:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\n"].get<uint64_t>().first == 1
|
||||
* parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get<uint64_t>().error() == NO_SUCH_FIELD
|
||||
*
|
||||
* This function has linear-time complexity: the keys are checked one by one.
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
*/
|
||||
inline simdjson_result<element> at_key(const std::string_view &key) const noexcept;
|
||||
|
||||
/**
|
||||
* Get the value associated with the given key in a case-insensitive manner.
|
||||
* It is only guaranteed to work over ASCII inputs.
|
||||
*
|
||||
* Note: The key will be matched against **unescaped** JSON.
|
||||
*
|
||||
* This function has linear-time complexity: the keys are checked one by one.
|
||||
*
|
||||
* @return The value associated with this field, or:
|
||||
* - NO_SUCH_FIELD if the field does not exist in the object
|
||||
*/
|
||||
inline simdjson_result<element> at_key_case_insensitive(const std::string_view &key) const noexcept;
|
||||
|
||||
private:
|
||||
really_inline object(const internal::tape_ref &tape) noexcept;
|
||||
|
||||
internal::tape_ref tape;
|
||||
|
||||
friend class element;
|
||||
friend struct simdjson_result<element>;
|
||||
template<typename T>
|
||||
friend class simdjson::minifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Key/value pair in an object.
|
||||
*/
|
||||
class key_value_pair {
|
||||
public:
|
||||
/** key in the key-value pair **/
|
||||
std::string_view key;
|
||||
/** value in the key-value pair **/
|
||||
element value;
|
||||
|
||||
private:
|
||||
really_inline key_value_pair(const std::string_view &_key, element _value) noexcept;
|
||||
friend class object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const object &value);
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw if there is an error with the underlying output stream. simdjson itself will not throw.
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const key_value_pair &value);
|
||||
|
||||
} // namespace dom
|
||||
|
||||
/** The result of a JSON conversion that may fail. */
|
||||
template<>
|
||||
struct simdjson_result<dom::object> : public internal::simdjson_result_base<dom::object> {
|
||||
public:
|
||||
really_inline simdjson_result() noexcept; ///< @private
|
||||
really_inline simdjson_result(dom::object value) noexcept; ///< @private
|
||||
really_inline simdjson_result(error_code error) noexcept; ///< @private
|
||||
|
||||
inline simdjson_result<dom::element> operator[](const std::string_view &key) const noexcept;
|
||||
inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
|
||||
inline simdjson_result<dom::element> at(const std::string_view &json_pointer) const noexcept;
|
||||
inline simdjson_result<dom::element> at_key(const std::string_view &key) const noexcept;
|
||||
inline simdjson_result<dom::element> at_key_case_insensitive(const std::string_view &key) const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
inline dom::object::iterator begin() const noexcept(false);
|
||||
inline dom::object::iterator end() const noexcept(false);
|
||||
inline size_t size() const noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
};
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/**
|
||||
* Print JSON to an output stream.
|
||||
*
|
||||
* By default, the value will be printed minified.
|
||||
*
|
||||
* @param out The output stream.
|
||||
* @param value The value to print.
|
||||
* @throw simdjson_error if the result being printed has an error. If there is an error with the
|
||||
* underlying output stream, that error will be propagated (simdjson_error will not be
|
||||
* thrown).
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, const simdjson_result<dom::object> &value) noexcept(false);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_OBJECT_H
|
||||
@@ -0,0 +1,17 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_DOM_PARSEDJSON_H
|
||||
#define SIMDJSON_DOM_PARSEDJSON_H
|
||||
|
||||
#include "simdjson/dom/document.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/**
|
||||
* @deprecated Use `dom::parser` instead.
|
||||
*/
|
||||
using ParsedJson [[deprecated("Use dom::parser instead")]] = dom::parser;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_PARSEDJSON_H
|
||||
@@ -0,0 +1,270 @@
|
||||
// TODO Remove this -- deprecated API and files
|
||||
|
||||
#ifndef SIMDJSON_DOM_PARSEDJSON_ITERATOR_H
|
||||
#define SIMDJSON_DOM_PARSEDJSON_ITERATOR_H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "simdjson/dom/document.h"
|
||||
#include "simdjson/dom/parsedjson.h"
|
||||
#include "simdjson/internal/jsonformatutils.h"
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/** @private **/
|
||||
class [[deprecated("Use the new DOM navigation API instead (see doc/basics.md)")]] dom::parser::Iterator {
|
||||
public:
|
||||
inline Iterator(const dom::parser &parser) noexcept(false);
|
||||
inline Iterator(const Iterator &o) noexcept;
|
||||
inline ~Iterator() noexcept;
|
||||
|
||||
inline Iterator& operator=(const Iterator&) = delete;
|
||||
|
||||
inline bool is_ok() const;
|
||||
|
||||
// useful for debugging purposes
|
||||
inline size_t get_tape_location() const;
|
||||
|
||||
// useful for debugging purposes
|
||||
inline size_t get_tape_length() const;
|
||||
|
||||
// returns the current depth (start at 1 with 0 reserved for the fictitious
|
||||
// root node)
|
||||
inline size_t get_depth() const;
|
||||
|
||||
// A scope is a series of nodes at the same depth, typically it is either an
|
||||
// object ({) or an array ([). The root node has type 'r'.
|
||||
inline uint8_t get_scope_type() const;
|
||||
|
||||
// move forward in document order
|
||||
inline bool move_forward();
|
||||
|
||||
// retrieve the character code of what we're looking at:
|
||||
// [{"slutfn are the possibilities
|
||||
inline uint8_t get_type() const {
|
||||
return current_type; // short functions should be inlined!
|
||||
}
|
||||
|
||||
// get the int64_t value at this node; valid only if get_type is "l"
|
||||
inline int64_t get_integer() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return 0; // default value in case of error
|
||||
}
|
||||
return static_cast<int64_t>(doc.tape[location + 1]);
|
||||
}
|
||||
|
||||
// get the value as uint64; valid only if if get_type is "u"
|
||||
inline uint64_t get_unsigned_integer() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return 0; // default value in case of error
|
||||
}
|
||||
return doc.tape[location + 1];
|
||||
}
|
||||
|
||||
// get the string value at this node (NULL ended); valid only if get_type is "
|
||||
// note that tabs, and line endings are escaped in the returned value (see
|
||||
// print_with_escapes) return value is valid UTF-8, it may contain NULL chars
|
||||
// within the string: get_string_length determines the true string length.
|
||||
inline const char *get_string() const {
|
||||
return reinterpret_cast<const char *>(
|
||||
doc.string_buf.get() + (current_val & internal::JSON_VALUE_MASK) + sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// return the length of the string in bytes
|
||||
inline uint32_t get_string_length() const {
|
||||
uint32_t answer;
|
||||
memcpy(&answer,
|
||||
reinterpret_cast<const char *>(doc.string_buf.get() +
|
||||
(current_val & internal::JSON_VALUE_MASK)),
|
||||
sizeof(uint32_t));
|
||||
return answer;
|
||||
}
|
||||
|
||||
// get the double value at this node; valid only if
|
||||
// get_type() is "d"
|
||||
inline double get_double() const {
|
||||
if (location + 1 >= tape_length) {
|
||||
return std::numeric_limits<double>::quiet_NaN(); // default value in
|
||||
// case of error
|
||||
}
|
||||
double answer;
|
||||
memcpy(&answer, &doc.tape[location + 1], sizeof(answer));
|
||||
return answer;
|
||||
}
|
||||
|
||||
inline bool is_object_or_array() const { return is_object() || is_array(); }
|
||||
|
||||
inline bool is_object() const { return get_type() == '{'; }
|
||||
|
||||
inline bool is_array() const { return get_type() == '['; }
|
||||
|
||||
inline bool is_string() const { return get_type() == '"'; }
|
||||
|
||||
// Returns true if the current type of the node is an signed integer.
|
||||
// You can get its value with `get_integer()`.
|
||||
inline bool is_integer() const { return get_type() == 'l'; }
|
||||
|
||||
// Returns true if the current type of the node is an unsigned integer.
|
||||
// You can get its value with `get_unsigned_integer()`.
|
||||
//
|
||||
// NOTE:
|
||||
// Only a large value, which is out of range of a 64-bit signed integer, is
|
||||
// represented internally as an unsigned node. On the other hand, a typical
|
||||
// positive integer, such as 1, 42, or 1000000, is as a signed node.
|
||||
// Be aware this function returns false for a signed node.
|
||||
inline bool is_unsigned_integer() const { return get_type() == 'u'; }
|
||||
// Returns true if the current type of the node is a double floating-point number.
|
||||
inline bool is_double() const { return get_type() == 'd'; }
|
||||
// Returns true if the current type of the node is a number (integer or floating-point).
|
||||
inline bool is_number() const {
|
||||
return is_integer() || is_unsigned_integer() || is_double();
|
||||
}
|
||||
// Returns true if the current type of the node is a bool with true value.
|
||||
inline bool is_true() const { return get_type() == 't'; }
|
||||
// Returns true if the current type of the node is a bool with false value.
|
||||
inline bool is_false() const { return get_type() == 'f'; }
|
||||
// Returns true if the current type of the node is null.
|
||||
inline bool is_null() const { return get_type() == 'n'; }
|
||||
// Returns true if the type byte represents an object of an array
|
||||
static bool is_object_or_array(uint8_t type) {
|
||||
return ((type == '[') || (type == '{'));
|
||||
}
|
||||
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// We seek the key using C's strcmp so if your JSON strings contain
|
||||
// NULL chars, this would trigger a false positive: if you expect that
|
||||
// to be the case, take extra precautions.
|
||||
// Furthermore, we do the comparison character-by-character
|
||||
// without taking into account Unicode equivalence.
|
||||
inline bool move_to_key(const char *key);
|
||||
|
||||
// as above, but case insensitive lookup (strcmpi instead of strcmp)
|
||||
inline bool move_to_key_insensitive(const char *key);
|
||||
|
||||
// when at {, go one level deep, looking for a given key
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the object ({)
|
||||
// (in case of repeated keys, this only finds the first one).
|
||||
// The string we search for can contain NULL values.
|
||||
// Furthermore, we do the comparison character-by-character
|
||||
// without taking into account Unicode equivalence.
|
||||
inline bool move_to_key(const char *key, uint32_t length);
|
||||
|
||||
// when at a key location within an object, this moves to the accompanying
|
||||
// value (located next to it). This is equivalent but much faster than
|
||||
// calling "next()".
|
||||
inline void move_to_value();
|
||||
|
||||
// when at [, go one level deep, and advance to the given index.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing at the array ([)
|
||||
inline bool move_to_index(uint32_t index);
|
||||
|
||||
// Moves the iterator to the value corresponding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer follows the rfc6901 standard's syntax:
|
||||
// https://tools.ietf.org/html/rfc6901 However, the standard says "If a
|
||||
// referenced member name is not unique in an object, the member that is
|
||||
// referenced is undefined, and evaluation fails". Here we just return the
|
||||
// first corresponding value. The length parameter is the length of the
|
||||
// jsonpointer string ('pointer').
|
||||
inline bool move_to(const char *pointer, uint32_t length);
|
||||
|
||||
// Moves the iterator to the value corresponding to the json pointer.
|
||||
// Always search from the root of the document.
|
||||
// if successful, we are left pointing at the value,
|
||||
// if not, we are still pointing the same value we were pointing before the
|
||||
// call. The json pointer implementation follows the rfc6901 standard's
|
||||
// syntax: https://tools.ietf.org/html/rfc6901 However, the standard says
|
||||
// "If a referenced member name is not unique in an object, the member that
|
||||
// is referenced is undefined, and evaluation fails". Here we just return
|
||||
// the first corresponding value.
|
||||
inline bool move_to(const std::string &pointer) {
|
||||
return move_to(pointer.c_str(), uint32_t(pointer.length()));
|
||||
}
|
||||
|
||||
private:
|
||||
// Almost the same as move_to(), except it searches from the current
|
||||
// position. The pointer's syntax is identical, though that case is not
|
||||
// handled by the rfc6901 standard. The '/' is still required at the
|
||||
// beginning. However, contrary to move_to(), the URI Fragment Identifier
|
||||
// Representation is not supported here. Also, in case of failure, we are
|
||||
// left pointing at the closest value it could reach. For these reasons it
|
||||
// is private. It exists because it is used by move_to().
|
||||
inline bool relative_move_to(const char *pointer, uint32_t length);
|
||||
|
||||
public:
|
||||
// throughout return true if we can do the navigation, false
|
||||
// otherwise
|
||||
|
||||
// Withing a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move forward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit true, null, {
|
||||
// and [. At the object ({) or at the array ([), you can issue a "down" to
|
||||
// visit their content. valid if we're not at the end of a scope (returns
|
||||
// true).
|
||||
inline bool next();
|
||||
|
||||
// Within a given scope (series of nodes at the same depth within either an
|
||||
// array or an object), we move backward.
|
||||
// Thus, given [true, null, {"a":1}, [1,2]], we would visit ], }, null, true
|
||||
// when starting at the end of the scope. At the object ({) or at the array
|
||||
// ([), you can issue a "down" to visit their content.
|
||||
// Performance warning: This function is implemented by starting again
|
||||
// from the beginning of the scope and scanning forward. You should expect
|
||||
// it to be relatively slow.
|
||||
inline bool prev();
|
||||
|
||||
// Moves back to either the containing array or object (type { or [) from
|
||||
// within a contained scope.
|
||||
// Valid unless we are at the first level of the document
|
||||
inline bool up();
|
||||
|
||||
// Valid if we're at a [ or { and it starts a non-empty scope; moves us to
|
||||
// start of that deeper scope if it not empty. Thus, given [true, null,
|
||||
// {"a":1}, [1,2]], if we are at the { node, we would move to the "a" node.
|
||||
inline bool down();
|
||||
|
||||
// move us to the start of our current scope,
|
||||
// a scope is a series of nodes at the same level
|
||||
inline void to_start_scope();
|
||||
|
||||
inline void rewind() {
|
||||
while (up())
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// print the node we are currently pointing at
|
||||
inline bool print(std::ostream &os, bool escape_strings = true) const;
|
||||
|
||||
private:
|
||||
const document &doc;
|
||||
size_t max_depth{};
|
||||
size_t depth{};
|
||||
size_t location{}; // our current location on a tape
|
||||
size_t tape_length{};
|
||||
uint8_t current_type{};
|
||||
uint64_t current_val{};
|
||||
typedef struct {
|
||||
size_t start_of_scope;
|
||||
uint8_t scope_type;
|
||||
} scopeindex_t;
|
||||
|
||||
scopeindex_t *depth_index{};
|
||||
};
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_PARSEDJSON_ITERATOR_H
|
||||
@@ -0,0 +1,434 @@
|
||||
#ifndef SIMDJSON_DOM_PARSER_H
|
||||
#define SIMDJSON_DOM_PARSER_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/dom/document.h"
|
||||
#include "simdjson/error.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include "simdjson/internal/tape_ref.h"
|
||||
#include "simdjson/minify.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/portability.h"
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
namespace dom {
|
||||
|
||||
class document_stream;
|
||||
class element;
|
||||
|
||||
/** The default batch size for parser.parse_many() and parser.load_many() */
|
||||
static constexpr size_t DEFAULT_BATCH_SIZE = 1000000;
|
||||
|
||||
/**
|
||||
* A persistent document parser.
|
||||
*
|
||||
* The parser is designed to be reused, holding the internal buffers necessary to do parsing,
|
||||
* as well as memory for a single document. The parsed document is overwritten on each parse.
|
||||
*
|
||||
* This class cannot be copied, only moved, to avoid unintended allocations.
|
||||
*
|
||||
* @note This is not thread safe: one parser cannot produce two documents at the same time!
|
||||
*/
|
||||
class parser {
|
||||
public:
|
||||
/**
|
||||
* Create a JSON parser.
|
||||
*
|
||||
* The new parser will have zero capacity.
|
||||
*
|
||||
* @param max_capacity The maximum document length the parser can automatically handle. The parser
|
||||
* will allocate more capacity on an as needed basis (when it sees documents too big to handle)
|
||||
* up to this amount. The parser still starts with zero capacity no matter what this number is:
|
||||
* to allocate an initial capacity, call allocate() after constructing the parser.
|
||||
* Defaults to SIMDJSON_MAXSIZE_BYTES (the largest single document simdjson can process).
|
||||
*/
|
||||
really_inline explicit parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept;
|
||||
/**
|
||||
* Take another parser's buffers and state.
|
||||
*
|
||||
* @param other The parser to take. Its capacity is zeroed.
|
||||
*/
|
||||
really_inline parser(parser &&other) noexcept;
|
||||
parser(const parser &) = delete; ///< @private Disallow copying
|
||||
/**
|
||||
* Take another parser's buffers and state.
|
||||
*
|
||||
* @param other The parser to take. Its capacity is zeroed.
|
||||
*/
|
||||
really_inline parser &operator=(parser &&other) noexcept;
|
||||
parser &operator=(const parser &) = delete; ///< @private Disallow copying
|
||||
|
||||
/** Deallocate the JSON parser. */
|
||||
~parser()=default;
|
||||
|
||||
/**
|
||||
* Load a JSON document from a file and return a reference to it.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* const element doc = parser.load("jsonexamples/twitter.json");
|
||||
*
|
||||
* ### IMPORTANT: Document Lifetime
|
||||
*
|
||||
* The JSON document still lives in the parser: this is the most efficient way to parse JSON
|
||||
* documents because it reuses the same buffers, but you *must* use the document before you
|
||||
* destroy the parser or call parse() again.
|
||||
*
|
||||
* ### Parser Capacity
|
||||
*
|
||||
* If the parser's current capacity is less than the file length, it will allocate enough capacity
|
||||
* to handle it (up to max_capacity).
|
||||
*
|
||||
* @param path The path to load.
|
||||
* @return The document, or an error:
|
||||
* - IO_ERROR if there was an error opening or reading the file.
|
||||
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails.
|
||||
* - CAPACITY if the parser does not have enough capacity and len > max_capacity.
|
||||
* - other json errors if parsing fails.
|
||||
*/
|
||||
inline simdjson_result<element> load(const std::string &path) & noexcept;
|
||||
inline simdjson_result<element> load(const std::string &path) && = delete ;
|
||||
/**
|
||||
* Parse a JSON document and return a temporary reference to it.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* element doc = parser.parse(buf, len);
|
||||
*
|
||||
* ### IMPORTANT: Document Lifetime
|
||||
*
|
||||
* The JSON document still lives in the parser: this is the most efficient way to parse JSON
|
||||
* documents because it reuses the same buffers, but you *must* use the document before you
|
||||
* destroy the parser or call parse() again.
|
||||
*
|
||||
* ### REQUIRED: Buffer Padding
|
||||
*
|
||||
* The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what
|
||||
* those bytes are initialized to, as long as they are allocated.
|
||||
*
|
||||
* If realloc_if_needed is true, it is assumed that the buffer does *not* have enough padding,
|
||||
* and it is copied into an enlarged temporary buffer before parsing.
|
||||
*
|
||||
* ### Parser Capacity
|
||||
*
|
||||
* If the parser's current capacity is less than len, it will allocate enough capacity
|
||||
* to handle it (up to max_capacity).
|
||||
*
|
||||
* @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless
|
||||
* realloc_if_needed is true.
|
||||
* @param len The length of the JSON.
|
||||
* @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding.
|
||||
* @return The document, or an error:
|
||||
* - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity,
|
||||
* and memory allocation fails.
|
||||
* - CAPACITY if the parser does not have enough capacity and len > max_capacity.
|
||||
* - other json errors if parsing fails.
|
||||
*/
|
||||
inline simdjson_result<element> parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) & noexcept;
|
||||
inline simdjson_result<element> parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) && =delete;
|
||||
/** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */
|
||||
really_inline simdjson_result<element> parse(const char *buf, size_t len, bool realloc_if_needed = true) & noexcept;
|
||||
really_inline simdjson_result<element> parse(const char *buf, size_t len, bool realloc_if_needed = true) && =delete;
|
||||
/** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */
|
||||
really_inline simdjson_result<element> parse(const std::string &s) & noexcept;
|
||||
really_inline simdjson_result<element> parse(const std::string &s) && =delete;
|
||||
/** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */
|
||||
really_inline simdjson_result<element> parse(const padded_string &s) & noexcept;
|
||||
really_inline simdjson_result<element> parse(const padded_string &s) && =delete;
|
||||
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
really_inline simdjson_result<element> parse(const char *buf) noexcept = delete;
|
||||
|
||||
/**
|
||||
* Load a file containing many JSON documents.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* for (const element doc : parser.load_many(path)) {
|
||||
* cout << std::string(doc["title"]) << endl;
|
||||
* }
|
||||
*
|
||||
* ### Format
|
||||
*
|
||||
* The file must contain a series of one or more JSON documents, concatenated into a single
|
||||
* buffer, separated by whitespace. It effectively parses until it has a fully valid document,
|
||||
* then starts parsing the next document at that point. (It does this with more parallelism and
|
||||
* lookahead than you might think, though.)
|
||||
*
|
||||
* documents that consist of an object or array may omit the whitespace between them, concatenating
|
||||
* with no separator. documents that consist of a single primitive (i.e. documents that are not
|
||||
* arrays or objects) MUST be separated with whitespace.
|
||||
*
|
||||
* The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse.
|
||||
* Setting batch_size to excessively large or excesively small values may impact negatively the
|
||||
* performance.
|
||||
*
|
||||
* ### Error Handling
|
||||
*
|
||||
* All errors are returned during iteration: if there is a global error such as memory allocation,
|
||||
* it will be yielded as the first result. Iteration always stops after the first error.
|
||||
*
|
||||
* As with all other simdjson methods, non-exception error handling is readily available through
|
||||
* the same interface, requiring you to check the error before using the document:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* dom::document_stream docs;
|
||||
* auto error = parser.load_many(path).get(docs);
|
||||
* if (error) { cerr << error << endl; exit(1); }
|
||||
* for (auto doc : docs) {
|
||||
* std::string_view title;
|
||||
* if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); }
|
||||
* cout << title << endl;
|
||||
* }
|
||||
*
|
||||
* ### Threads
|
||||
*
|
||||
* When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the
|
||||
* hood to do some lookahead.
|
||||
*
|
||||
* ### Parser Capacity
|
||||
*
|
||||
* If the parser's current capacity is less than batch_size, it will allocate enough capacity
|
||||
* to handle it (up to max_capacity).
|
||||
*
|
||||
* @param path File name pointing at the concatenated JSON to parse.
|
||||
* @param batch_size The batch size to use. MUST be larger than the largest document. The sweet
|
||||
* spot is cache-related: small enough to fit in cache, yet big enough to
|
||||
* parse as many documents as possible in one tight loop.
|
||||
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
|
||||
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
|
||||
* - IO_ERROR if there was an error opening or reading the file.
|
||||
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails.
|
||||
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
|
||||
* - other json errors if parsing fails.
|
||||
*/
|
||||
inline simdjson_result<document_stream> load_many(const std::string &path, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
|
||||
/**
|
||||
* Parse a buffer containing many JSON documents.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* for (element doc : parser.parse_many(buf, len)) {
|
||||
* cout << std::string(doc["title"]) << endl;
|
||||
* }
|
||||
*
|
||||
* ### Format
|
||||
*
|
||||
* The buffer must contain a series of one or more JSON documents, concatenated into a single
|
||||
* buffer, separated by whitespace. It effectively parses until it has a fully valid document,
|
||||
* then starts parsing the next document at that point. (It does this with more parallelism and
|
||||
* lookahead than you might think, though.)
|
||||
*
|
||||
* documents that consist of an object or array may omit the whitespace between them, concatenating
|
||||
* with no separator. documents that consist of a single primitive (i.e. documents that are not
|
||||
* arrays or objects) MUST be separated with whitespace.
|
||||
*
|
||||
* The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse.
|
||||
* Setting batch_size to excessively large or excesively small values may impact negatively the
|
||||
* performance.
|
||||
*
|
||||
* ### Error Handling
|
||||
*
|
||||
* All errors are returned during iteration: if there is a global error such as memory allocation,
|
||||
* it will be yielded as the first result. Iteration always stops after the first error.
|
||||
*
|
||||
* As with all other simdjson methods, non-exception error handling is readily available through
|
||||
* the same interface, requiring you to check the error before using the document:
|
||||
*
|
||||
* dom::parser parser;
|
||||
* dom::document_stream docs;
|
||||
* auto error = parser.load_many(path).get(docs);
|
||||
* if (error) { cerr << error << endl; exit(1); }
|
||||
* for (auto doc : docs) {
|
||||
* std::string_view title;
|
||||
* if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); }
|
||||
* cout << title << endl;
|
||||
* }
|
||||
*
|
||||
* ### REQUIRED: Buffer Padding
|
||||
*
|
||||
* The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what
|
||||
* those bytes are initialized to, as long as they are allocated.
|
||||
*
|
||||
* ### Threads
|
||||
*
|
||||
* When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the
|
||||
* hood to do some lookahead.
|
||||
*
|
||||
* ### Parser Capacity
|
||||
*
|
||||
* If the parser's current capacity is less than batch_size, it will allocate enough capacity
|
||||
* to handle it (up to max_capacity).
|
||||
*
|
||||
* @param buf The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes.
|
||||
* @param len The length of the concatenated JSON.
|
||||
* @param batch_size The batch size to use. MUST be larger than the largest document. The sweet
|
||||
* spot is cache-related: small enough to fit in cache, yet big enough to
|
||||
* parse as many documents as possible in one tight loop.
|
||||
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
|
||||
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
|
||||
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails
|
||||
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
|
||||
* - other json errors if parsing fails.
|
||||
*/
|
||||
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
|
||||
|
||||
/**
|
||||
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
|
||||
* and `max_depth` depth.
|
||||
*
|
||||
* @param capacity The new capacity.
|
||||
* @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH.
|
||||
* @return The error, if there is one.
|
||||
*/
|
||||
WARN_UNUSED inline error_code allocate(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept;
|
||||
|
||||
/**
|
||||
* @private deprecated because it returns bool instead of error_code, which is our standard for
|
||||
* failures. Use allocate() instead.
|
||||
*
|
||||
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
|
||||
* and `max_depth` depth.
|
||||
*
|
||||
* @param capacity The new capacity.
|
||||
* @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH.
|
||||
* @return true if successful, false if allocation failed.
|
||||
*/
|
||||
[[deprecated("Use allocate() instead.")]]
|
||||
WARN_UNUSED inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept;
|
||||
|
||||
/**
|
||||
* The largest document this parser can support without reallocating.
|
||||
*
|
||||
* @return Current capacity, in bytes.
|
||||
*/
|
||||
really_inline size_t capacity() const noexcept;
|
||||
|
||||
/**
|
||||
* The largest document this parser can automatically support.
|
||||
*
|
||||
* The parser may reallocate internal buffers as needed up to this amount.
|
||||
*
|
||||
* @return Maximum capacity, in bytes.
|
||||
*/
|
||||
really_inline size_t max_capacity() const noexcept;
|
||||
|
||||
/**
|
||||
* The maximum level of nested object and arrays supported by this parser.
|
||||
*
|
||||
* @return Maximum depth, in bytes.
|
||||
*/
|
||||
really_inline size_t max_depth() const noexcept;
|
||||
|
||||
/**
|
||||
* Set max_capacity. This is the largest document this parser can automatically support.
|
||||
*
|
||||
* The parser may reallocate internal buffers as needed up to this amount as documents are passed
|
||||
* to it.
|
||||
*
|
||||
* This call will not allocate or deallocate, even if capacity is currently above max_capacity.
|
||||
*
|
||||
* @param max_capacity The new maximum capacity, in bytes.
|
||||
*/
|
||||
really_inline void set_max_capacity(size_t max_capacity) noexcept;
|
||||
|
||||
/** @private Use the new DOM API instead */
|
||||
class Iterator;
|
||||
/** @private Use simdjson_error instead */
|
||||
using InvalidJSON [[deprecated("Use simdjson_error instead")]] = simdjson_error;
|
||||
|
||||
/** @private [for benchmarking access] The implementation to use */
|
||||
std::unique_ptr<internal::dom_parser_implementation> implementation{};
|
||||
|
||||
/** @private Use `if (parser.parse(...).error())` instead */
|
||||
bool valid{false};
|
||||
/** @private Use `parser.parse(...).error()` instead */
|
||||
error_code error{UNINITIALIZED};
|
||||
|
||||
/** @private Use `parser.parse(...).value()` instead */
|
||||
document doc{};
|
||||
|
||||
/** @private returns true if the document parsed was valid */
|
||||
[[deprecated("Use the result of parser.parse() instead")]]
|
||||
inline bool is_valid() const noexcept;
|
||||
|
||||
/**
|
||||
* @private return an error code corresponding to the last parsing attempt, see
|
||||
* simdjson.h will return UNITIALIZED if no parsing was attempted
|
||||
*/
|
||||
[[deprecated("Use the result of parser.parse() instead")]]
|
||||
inline int get_error_code() const noexcept;
|
||||
|
||||
/** @private return the string equivalent of "get_error_code" */
|
||||
[[deprecated("Use error_message() on the result of parser.parse() instead, or cout << error")]]
|
||||
inline std::string get_error_message() const noexcept;
|
||||
|
||||
/** @private */
|
||||
[[deprecated("Use cout << on the result of parser.parse() instead")]]
|
||||
inline bool print_json(std::ostream &os) const noexcept;
|
||||
|
||||
/** @private Private and deprecated: use `parser.parse(...).doc.dump_raw_tape()` instead */
|
||||
inline bool dump_raw_tape(std::ostream &os) const noexcept;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The maximum document length this parser will automatically support.
|
||||
*
|
||||
* The parser will not be automatically allocated above this amount.
|
||||
*/
|
||||
size_t _max_capacity;
|
||||
|
||||
/**
|
||||
* The loaded buffer (reused each time load() is called)
|
||||
*/
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1910
|
||||
// older versions of Visual Studio lack proper support for unique_ptr.
|
||||
std::unique_ptr<char[]> loaded_bytes;
|
||||
#else
|
||||
std::unique_ptr<char[], decltype(&aligned_free_char)> loaded_bytes;
|
||||
#endif
|
||||
|
||||
/** Capacity of loaded_bytes buffer. */
|
||||
size_t _loaded_bytes_capacity{0};
|
||||
|
||||
// all nodes are stored on the doc.tape using a 64-bit word.
|
||||
//
|
||||
// strings, double and ints are stored as
|
||||
// a 64-bit word with a pointer to the actual value
|
||||
//
|
||||
//
|
||||
//
|
||||
// for objects or arrays, store [ or { at the beginning and } and ] at the
|
||||
// end. For the openings ([ or {), we annotate them with a reference to the
|
||||
// location on the doc.tape of the end, and for then closings (} and ]), we
|
||||
// annotate them with a reference to the location of the opening
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* Ensure we have enough capacity to handle at least desired_capacity bytes,
|
||||
* and auto-allocate if not.
|
||||
*/
|
||||
inline error_code ensure_capacity(size_t desired_capacity) noexcept;
|
||||
|
||||
/** Read the file into loaded_bytes */
|
||||
inline simdjson_result<size_t> read_file(const std::string &path) noexcept;
|
||||
|
||||
friend class parser::Iterator;
|
||||
friend class document_stream;
|
||||
}; // class parser
|
||||
|
||||
} // namespace dom
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_DOM_PARSER_H
|
||||
@@ -0,0 +1,252 @@
|
||||
#ifndef SIMDJSON_ERROR_H
|
||||
#define SIMDJSON_ERROR_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include <string>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/**
|
||||
* All possible errors returned by simdjson.
|
||||
*/
|
||||
enum error_code {
|
||||
SUCCESS = 0, ///< No error
|
||||
CAPACITY, ///< This parser can't support a document that big
|
||||
MEMALLOC, ///< Error allocating memory, most likely out of memory
|
||||
TAPE_ERROR, ///< Something went wrong while writing to the tape (stage 2), this is a generic error
|
||||
DEPTH_ERROR, ///< Your document exceeds the user-specified depth limitation
|
||||
STRING_ERROR, ///< Problem while parsing a string
|
||||
T_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 't'
|
||||
F_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'f'
|
||||
N_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'n'
|
||||
NUMBER_ERROR, ///< Problem while parsing a number
|
||||
UTF8_ERROR, ///< the input is not valid UTF-8
|
||||
UNINITIALIZED, ///< unknown error, or uninitialized document
|
||||
EMPTY, ///< no structural element found
|
||||
UNESCAPED_CHARS, ///< found unescaped characters in a string.
|
||||
UNCLOSED_STRING, ///< missing quote at the end
|
||||
UNSUPPORTED_ARCHITECTURE, ///< unsupported architecture
|
||||
INCORRECT_TYPE, ///< JSON element has a different type than user expected
|
||||
NUMBER_OUT_OF_RANGE, ///< JSON number does not fit in 64 bits
|
||||
INDEX_OUT_OF_BOUNDS, ///< JSON array index too large
|
||||
NO_SUCH_FIELD, ///< JSON field not found in object
|
||||
IO_ERROR, ///< Error reading a file
|
||||
INVALID_JSON_POINTER, ///< Invalid JSON pointer reference
|
||||
INVALID_URI_FRAGMENT, ///< Invalid URI fragment
|
||||
UNEXPECTED_ERROR, ///< indicative of a bug in simdjson
|
||||
/** @private Number of error codes */
|
||||
NUM_ERROR_CODES
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the error message for the given error code.
|
||||
*
|
||||
* dom::parser parser;
|
||||
* dom::element doc;
|
||||
* auto error = parser.parse("foo").get(doc);
|
||||
* if (error) { printf("Error: %s\n", error_message(error)); }
|
||||
*
|
||||
* @return The error message.
|
||||
*/
|
||||
inline const char *error_message(error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Write the error message to the output stream
|
||||
*/
|
||||
inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Exception thrown when an exception-supporting simdjson method is called
|
||||
*/
|
||||
struct simdjson_error : public std::exception {
|
||||
/**
|
||||
* Create an exception from a simdjson error code.
|
||||
* @param error The error code
|
||||
*/
|
||||
simdjson_error(error_code error) noexcept : _error{error} { }
|
||||
/** The error message */
|
||||
const char *what() const noexcept { return error_message(error()); }
|
||||
/** The error code */
|
||||
error_code error() const noexcept { return _error; }
|
||||
private:
|
||||
/** The error code that was used */
|
||||
error_code _error;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* The result of a simdjson operation that could fail.
|
||||
*
|
||||
* Gives the option of reading error codes, or throwing an exception by casting to the desired result.
|
||||
*
|
||||
* This is a base class for implementations that want to add functions to the result type for
|
||||
* chaining.
|
||||
*
|
||||
* Override like:
|
||||
*
|
||||
* struct simdjson_result<T> : public internal::simdjson_result_base<T> {
|
||||
* simdjson_result() noexcept : internal::simdjson_result_base<T>() {}
|
||||
* simdjson_result(error_code error) noexcept : internal::simdjson_result_base<T>(error) {}
|
||||
* simdjson_result(T &&value) noexcept : internal::simdjson_result_base<T>(std::forward(value)) {}
|
||||
* simdjson_result(T &&value, error_code error) noexcept : internal::simdjson_result_base<T>(value, error) {}
|
||||
* // Your extra methods here
|
||||
* }
|
||||
*
|
||||
* Then any method returning simdjson_result<T> will be chainable with your methods.
|
||||
*/
|
||||
template<typename T>
|
||||
struct simdjson_result_base : public std::pair<T, error_code> {
|
||||
|
||||
/**
|
||||
* Create a new empty result with error = UNINITIALIZED.
|
||||
*/
|
||||
really_inline simdjson_result_base() noexcept;
|
||||
|
||||
/**
|
||||
* Create a new error result.
|
||||
*/
|
||||
really_inline simdjson_result_base(error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Create a new successful result.
|
||||
*/
|
||||
really_inline simdjson_result_base(T &&value) noexcept;
|
||||
|
||||
/**
|
||||
* Create a new result with both things (use if you don't want to branch when creating the result).
|
||||
*/
|
||||
really_inline simdjson_result_base(T &&value, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Move the value and the error to the provided variables.
|
||||
*
|
||||
* @param value The variable to assign the value to. May not be set if there is an error.
|
||||
* @param error The variable to assign the error to. Set to SUCCESS if there is no error.
|
||||
*/
|
||||
really_inline void tie(T &value, error_code &error) && noexcept;
|
||||
|
||||
/**
|
||||
* Move the value to the provided variable.
|
||||
*
|
||||
* @param value The variable to assign the value to. May not be set if there is an error.
|
||||
*/
|
||||
really_inline error_code get(T &value) && noexcept;
|
||||
|
||||
/**
|
||||
* The error.
|
||||
*/
|
||||
really_inline error_code error() const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
/**
|
||||
* Get the result value.
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T& value() noexcept(false);
|
||||
|
||||
/**
|
||||
* Take the result value (move it).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T&& take_value() && noexcept(false);
|
||||
|
||||
/**
|
||||
* Cast to the value (will throw on error).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline operator T&&() && noexcept(false);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
}; // struct simdjson_result_base
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* The result of a simdjson operation that could fail.
|
||||
*
|
||||
* Gives the option of reading error codes, or throwing an exception by casting to the desired result.
|
||||
*/
|
||||
template<typename T>
|
||||
struct simdjson_result : public internal::simdjson_result_base<T> {
|
||||
/**
|
||||
* @private Create a new empty result with error = UNINITIALIZED.
|
||||
*/
|
||||
really_inline simdjson_result() noexcept;
|
||||
/**
|
||||
* @private Create a new error result.
|
||||
*/
|
||||
really_inline simdjson_result(T &&value) noexcept;
|
||||
/**
|
||||
* @private Create a new successful result.
|
||||
*/
|
||||
really_inline simdjson_result(error_code error_code) noexcept;
|
||||
/**
|
||||
* @private Create a new result with both things (use if you don't want to branch when creating the result).
|
||||
*/
|
||||
really_inline simdjson_result(T &&value, error_code error) noexcept;
|
||||
|
||||
/**
|
||||
* Move the value and the error to the provided variables.
|
||||
*
|
||||
* @param value The variable to assign the value to. May not be set if there is an error.
|
||||
* @param error The variable to assign the error to. Set to SUCCESS if there is no error.
|
||||
*/
|
||||
really_inline void tie(T &value, error_code &error) && noexcept;
|
||||
|
||||
/**
|
||||
* Move the value to the provided variable.
|
||||
*
|
||||
* @param value The variable to assign the value to. May not be set if there is an error.
|
||||
*/
|
||||
WARN_UNUSED really_inline error_code get(T &value) && noexcept;
|
||||
|
||||
/**
|
||||
* The error.
|
||||
*/
|
||||
really_inline error_code error() const noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
/**
|
||||
* Get the result value.
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T& value() noexcept(false);
|
||||
|
||||
/**
|
||||
* Take the result value (move it).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline T&& take_value() && noexcept(false);
|
||||
|
||||
/**
|
||||
* Cast to the value (will throw on error).
|
||||
*
|
||||
* @throw simdjson_error if there was an error.
|
||||
*/
|
||||
really_inline operator T&&() && noexcept(false);
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
}; // struct simdjson_result
|
||||
|
||||
/**
|
||||
* @deprecated This is an alias and will be removed, use error_code instead
|
||||
*/
|
||||
using ErrorValues [[deprecated("This is an alias and will be removed, use error_code instead")]] = error_code;
|
||||
|
||||
/**
|
||||
* @deprecated Error codes should be stored and returned as `error_code`, use `error_message()` instead.
|
||||
*/
|
||||
[[deprecated("Error codes should be stored and returned as `error_code`, use `error_message()` instead.")]]
|
||||
inline const std::string &error_message(int error) noexcept;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_ERROR_H
|
||||
@@ -0,0 +1,244 @@
|
||||
#ifndef SIMDJSON_IMPLEMENTATION_H
|
||||
#define SIMDJSON_IMPLEMENTATION_H
|
||||
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
/**
|
||||
* Validate the UTF-8 string.
|
||||
*
|
||||
* @param buf the string to validate.
|
||||
* @param len the length of the string in bytes.
|
||||
* @return true if the string is valid UTF-8.
|
||||
*/
|
||||
WARN_UNUSED bool validate_utf8(const char * buf, size_t len) noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* Validate the UTF-8 string.
|
||||
*
|
||||
* @param sv the string_view to validate.
|
||||
* @return true if the string is valid UTF-8.
|
||||
*/
|
||||
really_inline WARN_UNUSED bool validate_utf8(const std::string_view sv) noexcept {
|
||||
return validate_utf8(sv.data(), sv.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the UTF-8 string.
|
||||
*
|
||||
* @param p the string to validate.
|
||||
* @return true if the string is valid UTF-8.
|
||||
*/
|
||||
really_inline WARN_UNUSED bool validate_utf8(const std::string& s) noexcept {
|
||||
return validate_utf8(s.data(), s.size());
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
class document;
|
||||
} // namespace dom
|
||||
|
||||
/**
|
||||
* An implementation of simdjson for a particular CPU architecture.
|
||||
*
|
||||
* Also used to maintain the currently active implementation. The active implementation is
|
||||
* automatically initialized on first use to the most advanced implementation supported by the host.
|
||||
*/
|
||||
class implementation {
|
||||
public:
|
||||
|
||||
/**
|
||||
* The name of this implementation.
|
||||
*
|
||||
* const implementation *impl = simdjson::active_implementation;
|
||||
* cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl;
|
||||
*
|
||||
* @return the name of the implementation, e.g. "haswell", "westmere", "arm64"
|
||||
*/
|
||||
virtual const std::string &name() const { return _name; }
|
||||
|
||||
/**
|
||||
* The description of this implementation.
|
||||
*
|
||||
* const implementation *impl = simdjson::active_implementation;
|
||||
* cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl;
|
||||
*
|
||||
* @return the name of the implementation, e.g. "haswell", "westmere", "arm64"
|
||||
*/
|
||||
virtual const std::string &description() const { return _description; }
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* The instruction sets this implementation is compiled against.
|
||||
*
|
||||
* @return a mask of all required `instruction_set` values
|
||||
*/
|
||||
virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; };
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* const implementation *impl = simdjson::active_implementation;
|
||||
* cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl;
|
||||
*
|
||||
* @param capacity The largest document that will be passed to the parser.
|
||||
* @param max_depth The maximum JSON object/array nesting this parser is expected to handle.
|
||||
* @param dst The place to put the resulting parser implementation.
|
||||
* @return the name of the implementation, e.g. "haswell", "westmere", "arm64"
|
||||
*/
|
||||
virtual error_code create_dom_parser_implementation(
|
||||
size_t capacity,
|
||||
size_t max_depth,
|
||||
std::unique_ptr<internal::dom_parser_implementation> &dst
|
||||
) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* @private For internal implementation use
|
||||
*
|
||||
* Minify the input string assuming that it represents a JSON string, does not parse or validate.
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the json document to minify.
|
||||
* @param len the length of the json document.
|
||||
* @param dst the buffer to write the minified document to. *MUST* be allocated up to len + SIMDJSON_PADDING bytes.
|
||||
* @param dst_len the number of bytes written. Output only.
|
||||
* @return the error code, or SUCCESS if there was no error.
|
||||
*/
|
||||
WARN_UNUSED virtual error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Validate the UTF-8 string.
|
||||
*
|
||||
* Overridden by each implementation.
|
||||
*
|
||||
* @param buf the string to validate.
|
||||
* @param len the length of the string in bytes.
|
||||
* @return true if and only if the string is valid UTF-8.
|
||||
*/
|
||||
WARN_UNUSED virtual bool validate_utf8(const char *buf, size_t len) const noexcept = 0;
|
||||
|
||||
protected:
|
||||
/** @private Construct an implementation with the given name and description. For subclasses. */
|
||||
really_inline implementation(
|
||||
std::string_view name,
|
||||
std::string_view description,
|
||||
uint32_t required_instruction_sets
|
||||
) :
|
||||
_name(name),
|
||||
_description(description),
|
||||
_required_instruction_sets(required_instruction_sets)
|
||||
{
|
||||
}
|
||||
virtual ~implementation()=default;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The name of this implementation.
|
||||
*/
|
||||
const std::string _name;
|
||||
|
||||
/**
|
||||
* The description of this implementation.
|
||||
*/
|
||||
const std::string _description;
|
||||
|
||||
/**
|
||||
* Instruction sets required for this implementation.
|
||||
*/
|
||||
const uint32_t _required_instruction_sets;
|
||||
};
|
||||
|
||||
/** @private */
|
||||
namespace internal {
|
||||
|
||||
/**
|
||||
* The list of available implementations compiled into simdjson.
|
||||
*/
|
||||
class available_implementation_list {
|
||||
public:
|
||||
/** Get the list of available implementations compiled into simdjson */
|
||||
really_inline available_implementation_list() {}
|
||||
/** Number of implementations */
|
||||
size_t size() const noexcept;
|
||||
/** STL const begin() iterator */
|
||||
const implementation * const *begin() const noexcept;
|
||||
/** STL const end() iterator */
|
||||
const implementation * const *end() const noexcept;
|
||||
|
||||
/**
|
||||
* Get the implementation with the given name.
|
||||
*
|
||||
* Case sensitive.
|
||||
*
|
||||
* const implementation *impl = simdjson::available_implementations["westmere"];
|
||||
* if (!impl) { exit(1); }
|
||||
* simdjson::active_implementation = impl;
|
||||
*
|
||||
* @param name the implementation to find, e.g. "westmere", "haswell", "arm64"
|
||||
* @return the implementation, or nullptr if the parse failed.
|
||||
*/
|
||||
const implementation * operator[](const std::string_view &name) const noexcept {
|
||||
for (const implementation * impl : *this) {
|
||||
if (impl->name() == name) { return impl; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the most advanced implementation supported by the current host.
|
||||
*
|
||||
* This is used to initialize the implementation on startup.
|
||||
*
|
||||
* const implementation *impl = simdjson::available_implementation::detect_best_supported();
|
||||
* simdjson::active_implementation = impl;
|
||||
*
|
||||
* @return the most advanced supported implementation for the current host, or an
|
||||
* implementation that returns UNSUPPORTED_ARCHITECTURE if there is no supported
|
||||
* implementation. Will never return nullptr.
|
||||
*/
|
||||
const implementation *detect_best_supported() const noexcept;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class atomic_ptr {
|
||||
public:
|
||||
atomic_ptr(T *_ptr) : ptr{_ptr} {}
|
||||
|
||||
operator const T*() const { return ptr.load(); }
|
||||
const T& operator*() const { return *ptr; }
|
||||
const T* operator->() const { return ptr.load(); }
|
||||
|
||||
operator T*() { return ptr.load(); }
|
||||
T& operator*() { return *ptr; }
|
||||
T* operator->() { return ptr.load(); }
|
||||
atomic_ptr& operator=(T *_ptr) { ptr = _ptr; return *this; }
|
||||
|
||||
private:
|
||||
std::atomic<T*> ptr;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* The list of available implementations compiled into simdjson.
|
||||
*/
|
||||
extern SIMDJSON_DLLIMPORTEXPORT const internal::available_implementation_list available_implementations;
|
||||
|
||||
/**
|
||||
* The active implementation.
|
||||
*
|
||||
* Automatically initialized on first use to the most advanced implementation supported by this hardware.
|
||||
*/
|
||||
extern SIMDJSON_DLLIMPORTEXPORT internal::atomic_ptr<const implementation> active_implementation;
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_IMPLEMENTATION_H
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef SIMDJSON_INLINE_ARRAY_H
|
||||
#define SIMDJSON_INLINE_ARRAY_H
|
||||
|
||||
// Inline implementations go in here.
|
||||
|
||||
#include "simdjson/dom/array.h"
|
||||
#include "simdjson/dom/element.h"
|
||||
#include <utility>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
//
|
||||
// simdjson_result<dom::array> inline implementation
|
||||
//
|
||||
really_inline simdjson_result<dom::array>::simdjson_result() noexcept
|
||||
: internal::simdjson_result_base<dom::array>() {}
|
||||
really_inline simdjson_result<dom::array>::simdjson_result(dom::array value) noexcept
|
||||
: internal::simdjson_result_base<dom::array>(std::forward<dom::array>(value)) {}
|
||||
really_inline simdjson_result<dom::array>::simdjson_result(error_code error) noexcept
|
||||
: internal::simdjson_result_base<dom::array>(error) {}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
inline dom::array::iterator simdjson_result<dom::array>::begin() const noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.begin();
|
||||
}
|
||||
inline dom::array::iterator simdjson_result<dom::array>::end() const noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.end();
|
||||
}
|
||||
inline size_t simdjson_result<dom::array>::size() const noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.size();
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
inline simdjson_result<dom::element> simdjson_result<dom::array>::at(const std::string_view &json_pointer) const noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.at(json_pointer);
|
||||
}
|
||||
inline simdjson_result<dom::element> simdjson_result<dom::array>::at(size_t index) const noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.at(index);
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
//
|
||||
// array inline implementation
|
||||
//
|
||||
really_inline array::array() noexcept : tape{} {}
|
||||
really_inline array::array(const internal::tape_ref &_tape) noexcept : tape{_tape} {}
|
||||
inline array::iterator array::begin() const noexcept {
|
||||
return internal::tape_ref(tape.doc, tape.json_index + 1);
|
||||
}
|
||||
inline array::iterator array::end() const noexcept {
|
||||
return internal::tape_ref(tape.doc, tape.after_element() - 1);
|
||||
}
|
||||
inline size_t array::size() const noexcept {
|
||||
return tape.scope_count();
|
||||
}
|
||||
inline simdjson_result<element> array::at(const std::string_view &json_pointer) const noexcept {
|
||||
// - means "the append position" or "the element after the end of the array"
|
||||
// We don't support this, because we're returning a real element, not a position.
|
||||
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
|
||||
|
||||
// Read the array index
|
||||
size_t array_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) {
|
||||
uint8_t digit = uint8_t(json_pointer[i] - '0');
|
||||
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
|
||||
if (digit > 9) { return INCORRECT_TYPE; }
|
||||
array_index = array_index*10 + digit;
|
||||
}
|
||||
|
||||
// 0 followed by other digits is invalid
|
||||
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
|
||||
|
||||
// Empty string is invalid; so is a "/" with no digits before it
|
||||
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
|
||||
|
||||
// Get the child
|
||||
auto child = array(tape).at(array_index);
|
||||
// If there is a /, we're not done yet, call recursively.
|
||||
if (i < json_pointer.length()) {
|
||||
child = child.at(json_pointer.substr(i+1));
|
||||
}
|
||||
return child;
|
||||
}
|
||||
inline simdjson_result<element> array::at(size_t index) const noexcept {
|
||||
size_t i=0;
|
||||
for (auto element : *this) {
|
||||
if (i == index) { return element; }
|
||||
i++;
|
||||
}
|
||||
return INDEX_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
//
|
||||
// array::iterator inline implementation
|
||||
//
|
||||
really_inline array::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { }
|
||||
inline element array::iterator::operator*() const noexcept {
|
||||
return element(tape);
|
||||
}
|
||||
inline bool array::iterator::operator!=(const array::iterator& other) const noexcept {
|
||||
return tape.json_index != other.tape.json_index;
|
||||
}
|
||||
inline array::iterator& array::iterator::operator++() noexcept {
|
||||
tape.json_index = tape.after_element();
|
||||
return *this;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#endif // SIMDJSON_INLINE_ARRAY_H
|
||||
@@ -0,0 +1,141 @@
|
||||
#ifndef SIMDJSON_INLINE_DOCUMENT_H
|
||||
#define SIMDJSON_INLINE_DOCUMENT_H
|
||||
|
||||
// Inline implementations go in here.
|
||||
|
||||
#include "simdjson/dom/document.h"
|
||||
#include "simdjson/dom/element.h"
|
||||
#include "simdjson/internal/tape_ref.h"
|
||||
#include "simdjson/internal/jsonformatutils.h"
|
||||
#include <ostream>
|
||||
#include <cstring>
|
||||
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
//
|
||||
// document inline implementation
|
||||
//
|
||||
inline element document::root() const noexcept {
|
||||
return element(internal::tape_ref(this, 1));
|
||||
}
|
||||
|
||||
WARN_UNUSED
|
||||
inline error_code document::allocate(size_t capacity) noexcept {
|
||||
if (capacity == 0) {
|
||||
string_buf.reset();
|
||||
tape.reset();
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// a pathological input like "[[[[..." would generate len tape elements, so
|
||||
// need a capacity of at least len + 1, but it is also possible to do
|
||||
// worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6"
|
||||
//where len + 1 tape elements are
|
||||
// generated, see issue https://github.com/lemire/simdjson/issues/345
|
||||
size_t tape_capacity = ROUNDUP_N(capacity + 3, 64);
|
||||
// a document with only zero-length strings... could have len/3 string
|
||||
// and we would need len/3 * 5 bytes on the string buffer
|
||||
size_t string_capacity = ROUNDUP_N(5 * capacity / 3 + 32, 64);
|
||||
string_buf.reset( new (std::nothrow) uint8_t[string_capacity]);
|
||||
tape.reset(new (std::nothrow) uint64_t[tape_capacity]);
|
||||
return string_buf && tape ? SUCCESS : MEMALLOC;
|
||||
}
|
||||
|
||||
inline bool document::dump_raw_tape(std::ostream &os) const noexcept {
|
||||
uint32_t string_length;
|
||||
size_t tape_idx = 0;
|
||||
uint64_t tape_val = tape[tape_idx];
|
||||
uint8_t type = uint8_t(tape_val >> 56);
|
||||
os << tape_idx << " : " << type;
|
||||
tape_idx++;
|
||||
size_t how_many = 0;
|
||||
if (type == 'r') {
|
||||
how_many = size_t(tape_val & internal::JSON_VALUE_MASK);
|
||||
} else {
|
||||
// Error: no starting root node?
|
||||
return false;
|
||||
}
|
||||
os << "\t// pointing to " << how_many << " (right after last node)\n";
|
||||
uint64_t payload;
|
||||
for (; tape_idx < how_many; tape_idx++) {
|
||||
os << tape_idx << " : ";
|
||||
tape_val = tape[tape_idx];
|
||||
payload = tape_val & internal::JSON_VALUE_MASK;
|
||||
type = uint8_t(tape_val >> 56);
|
||||
switch (type) {
|
||||
case '"': // we have a string
|
||||
os << "string \"";
|
||||
memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t));
|
||||
os << internal::escape_json_string(std::string_view(
|
||||
(const char *)(string_buf.get() + payload + sizeof(uint32_t)),
|
||||
string_length
|
||||
));
|
||||
os << '"';
|
||||
os << '\n';
|
||||
break;
|
||||
case 'l': // we have a long int
|
||||
if (tape_idx + 1 >= how_many) {
|
||||
return false;
|
||||
}
|
||||
os << "integer " << static_cast<int64_t>(tape[++tape_idx]) << "\n";
|
||||
break;
|
||||
case 'u': // we have a long uint
|
||||
if (tape_idx + 1 >= how_many) {
|
||||
return false;
|
||||
}
|
||||
os << "unsigned integer " << tape[++tape_idx] << "\n";
|
||||
break;
|
||||
case 'd': // we have a double
|
||||
os << "float ";
|
||||
if (tape_idx + 1 >= how_many) {
|
||||
return false;
|
||||
}
|
||||
double answer;
|
||||
memcpy(&answer, &tape[++tape_idx], sizeof(answer));
|
||||
os << answer << '\n';
|
||||
break;
|
||||
case 'n': // we have a null
|
||||
os << "null\n";
|
||||
break;
|
||||
case 't': // we have a true
|
||||
os << "true\n";
|
||||
break;
|
||||
case 'f': // we have a false
|
||||
os << "false\n";
|
||||
break;
|
||||
case '{': // we have an object
|
||||
os << "{\t// pointing to next tape location " << payload
|
||||
<< " (first node after the scope) \n";
|
||||
break;
|
||||
case '}': // we end an object
|
||||
os << "}\t// pointing to previous tape location " << payload
|
||||
<< " (start of the scope) \n";
|
||||
break;
|
||||
case '[': // we start an array
|
||||
os << "[\t// pointing to next tape location " << payload
|
||||
<< " (first node after the scope) \n";
|
||||
break;
|
||||
case ']': // we end an array
|
||||
os << "]\t// pointing to previous tape location " << payload
|
||||
<< " (start of the scope) \n";
|
||||
break;
|
||||
case 'r': // we start and end with the root node
|
||||
// should we be hitting the root node?
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tape_val = tape[tape_idx];
|
||||
payload = tape_val & internal::JSON_VALUE_MASK;
|
||||
type = uint8_t(tape_val >> 56);
|
||||
os << tape_idx << " : " << type << "\t// pointing to " << payload
|
||||
<< " (start root)\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_INLINE_DOCUMENT_H
|
||||
@@ -0,0 +1,255 @@
|
||||
#ifndef SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
#define SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
|
||||
#include "simdjson/dom/document_stream.h"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
namespace simdjson {
|
||||
namespace dom {
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
inline void stage1_worker::finish() {
|
||||
std::unique_lock<std::mutex> lock(locking_mutex);
|
||||
cond_var.wait(lock, [this]{return has_work == false;});
|
||||
}
|
||||
|
||||
inline stage1_worker::~stage1_worker() {
|
||||
stop_thread();
|
||||
}
|
||||
|
||||
inline void stage1_worker::start_thread() {
|
||||
std::unique_lock<std::mutex> lock(locking_mutex);
|
||||
if(thread.joinable()) {
|
||||
return; // This should never happen but we never want to create more than one thread.
|
||||
}
|
||||
thread = std::thread([this]{
|
||||
while(can_work) {
|
||||
std::unique_lock<std::mutex> thread_lock(locking_mutex);
|
||||
cond_var.wait(thread_lock, [this]{return has_work || !can_work;});
|
||||
if(!can_work) {
|
||||
break;
|
||||
}
|
||||
this->owner->stage1_thread_error = this->owner->run_stage1(*this->stage1_thread_parser,
|
||||
this->_next_batch_start);
|
||||
this->has_work = false;
|
||||
thread_lock.unlock();
|
||||
cond_var.notify_one(); // will notify "finish"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
inline void stage1_worker::stop_thread() {
|
||||
std::unique_lock<std::mutex> lock(locking_mutex);
|
||||
// We have to make sure that all locks can be released.
|
||||
can_work = false;
|
||||
has_work = false;
|
||||
lock.unlock();
|
||||
cond_var.notify_all();
|
||||
if(thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
inline void stage1_worker::run(document_stream * ds, dom::parser * stage1, size_t next_batch_start) {
|
||||
std::unique_lock<std::mutex> lock(locking_mutex);
|
||||
owner = ds;
|
||||
_next_batch_start = next_batch_start;
|
||||
stage1_thread_parser = stage1;
|
||||
has_work = true;
|
||||
lock.unlock();
|
||||
cond_var.notify_one();// will notify the thread lock
|
||||
}
|
||||
#endif
|
||||
|
||||
really_inline document_stream::document_stream(
|
||||
dom::parser &_parser,
|
||||
const uint8_t *_buf,
|
||||
size_t _len,
|
||||
size_t _batch_size
|
||||
) noexcept
|
||||
: parser{&_parser},
|
||||
buf{_buf},
|
||||
len{_len},
|
||||
batch_size{_batch_size},
|
||||
error{SUCCESS}
|
||||
{
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
if(worker.get() == nullptr) {
|
||||
error = MEMALLOC;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
really_inline document_stream::document_stream() noexcept
|
||||
: parser{nullptr},
|
||||
buf{nullptr},
|
||||
len{0},
|
||||
batch_size{0},
|
||||
error{UNINITIALIZED} {
|
||||
}
|
||||
|
||||
really_inline document_stream::~document_stream() noexcept {
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator document_stream::begin() noexcept {
|
||||
start();
|
||||
// If there are no documents, we're finished.
|
||||
return iterator(*this, error == EMPTY);
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator document_stream::end() noexcept {
|
||||
return iterator(*this, true);
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator::iterator(document_stream& _stream, bool is_end) noexcept
|
||||
: stream{_stream}, finished{is_end} {
|
||||
}
|
||||
|
||||
really_inline simdjson_result<element> document_stream::iterator::operator*() noexcept {
|
||||
// Once we have yielded any errors, we're finished.
|
||||
if (stream.error) { finished = true; return stream.error; }
|
||||
return stream.parser->doc.root();
|
||||
}
|
||||
|
||||
really_inline document_stream::iterator& document_stream::iterator::operator++() noexcept {
|
||||
stream.next();
|
||||
// If that was the last document, we're finished.
|
||||
if (stream.error == EMPTY) { finished = true; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
really_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept {
|
||||
return finished != other.finished;
|
||||
}
|
||||
|
||||
inline void document_stream::start() noexcept {
|
||||
if (error) { return; }
|
||||
|
||||
error = parser->ensure_capacity(batch_size);
|
||||
if (error) { return; }
|
||||
|
||||
// Always run the first stage 1 parse immediately
|
||||
batch_start = 0;
|
||||
error = run_stage1(*parser, batch_start);
|
||||
if (error) { return; }
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
if (next_batch_start() < len) {
|
||||
// Kick off the first thread if needed
|
||||
error = stage1_thread_parser.ensure_capacity(batch_size);
|
||||
if (error) { return; }
|
||||
worker->start_thread();
|
||||
start_stage1_thread();
|
||||
if (error) { return; }
|
||||
}
|
||||
#endif // SIMDJSON_THREADS_ENABLED
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
really_inline size_t document_stream::iterator::current_index() noexcept {
|
||||
return stream.doc_index;
|
||||
}
|
||||
inline void document_stream::next() noexcept {
|
||||
if (error) { return; }
|
||||
|
||||
// Load the next document from the batch
|
||||
doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index];
|
||||
error = parser->implementation->stage2_next(parser->doc);
|
||||
// If that was the last document in the batch, load another batch (if available)
|
||||
while (error == EMPTY) {
|
||||
batch_start = next_batch_start();
|
||||
if (batch_start >= len) { break; }
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
load_from_stage1_thread();
|
||||
#else
|
||||
error = run_stage1(*parser, batch_start);
|
||||
#endif
|
||||
if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
|
||||
// Run stage 2 on the first document in the batch
|
||||
doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index];
|
||||
error = parser->implementation->stage2_next(parser->doc);
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t document_stream::next_batch_start() const noexcept {
|
||||
return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes];
|
||||
}
|
||||
|
||||
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
|
||||
// If this is the final batch, pass partial = false
|
||||
size_t remaining = len - _batch_start;
|
||||
if (remaining <= batch_size) {
|
||||
return p.implementation->stage1(&buf[_batch_start], remaining, false);
|
||||
} else {
|
||||
return p.implementation->stage1(&buf[_batch_start], batch_size, true);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
|
||||
inline void document_stream::load_from_stage1_thread() noexcept {
|
||||
worker->finish();
|
||||
// Swap to the parser that was loaded up in the thread. Make sure the parser has
|
||||
// enough memory to swap to, as well.
|
||||
std::swap(*parser, stage1_thread_parser);
|
||||
error = stage1_thread_error;
|
||||
if (error) { return; }
|
||||
|
||||
// If there's anything left, start the stage 1 thread!
|
||||
if (next_batch_start() < len) {
|
||||
start_stage1_thread();
|
||||
}
|
||||
}
|
||||
|
||||
inline void document_stream::start_stage1_thread() noexcept {
|
||||
// we call the thread on a lambda that will update
|
||||
// this->stage1_thread_error
|
||||
// there is only one thread that may write to this value
|
||||
// TODO this is NOT exception-safe.
|
||||
this->stage1_thread_error = UNINITIALIZED; // In case something goes wrong, make sure it's an error
|
||||
size_t _next_batch_start = this->next_batch_start();
|
||||
|
||||
worker->run(this, & this->stage1_thread_parser, _next_batch_start);
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_THREADS_ENABLED
|
||||
|
||||
} // namespace dom
|
||||
|
||||
really_inline simdjson_result<dom::document_stream>::simdjson_result() noexcept
|
||||
: simdjson_result_base() {
|
||||
}
|
||||
really_inline simdjson_result<dom::document_stream>::simdjson_result(error_code error) noexcept
|
||||
: simdjson_result_base(error) {
|
||||
}
|
||||
really_inline simdjson_result<dom::document_stream>::simdjson_result(dom::document_stream &&value) noexcept
|
||||
: simdjson_result_base(std::forward<dom::document_stream>(value)) {
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
really_inline dom::document_stream::iterator simdjson_result<dom::document_stream>::begin() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.begin();
|
||||
}
|
||||
really_inline dom::document_stream::iterator simdjson_result<dom::document_stream>::end() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.end();
|
||||
}
|
||||
#else // SIMDJSON_EXCEPTIONS
|
||||
really_inline dom::document_stream::iterator simdjson_result<dom::document_stream>::begin() noexcept {
|
||||
first.error = error();
|
||||
return first.begin();
|
||||
}
|
||||
really_inline dom::document_stream::iterator simdjson_result<dom::document_stream>::end() noexcept {
|
||||
first.error = error();
|
||||
return first.end();
|
||||
}
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace simdjson
|
||||
#endif // SIMDJSON_INLINE_DOCUMENT_STREAM_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user