Skip to content

Adding benchmarks for JsonDocument.Parse, GetProperty, and Enumerates.#852

Merged
adamsitnik merged 14 commits intodotnet:masterfrom
jozkee:792
Oct 15, 2020
Merged

Adding benchmarks for JsonDocument.Parse, GetProperty, and Enumerates.#852
adamsitnik merged 14 commits intodotnet:masterfrom
jozkee:792

Conversation

@jozkee
Copy link
Member

@jozkee jozkee commented Sep 3, 2019

Fixes #792

@jozkee
Copy link
Member Author

jozkee commented Sep 3, 2019

Benchmark results.

Perf_DocumentParse

Method IsDataCompact TestRandomAccess TestCase Mean Error StdDev Median Min Max Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
NewtonsoftParse False False HelloWorld 940.5 ns 42.88 ns 49.38 ns 935.4 ns 877.6 ns 1,051.7 ns 1.00 0.00 0.7326 - - 3064 B
Parse False False HelloWorld 467.2 ns 7.37 ns 6.89 ns 465.7 ns 455.6 ns 483.5 ns 0.50 0.03 0.0173 - - 80 B
NewtonsoftParse False False BasicJson 5,390.6 ns 114.83 ns 122.87 ns 5,362.1 ns 5,218.7 ns 5,622.4 ns 1.00 0.00 1.5215 - - 6408 B
Parse False False BasicJson 1,740.9 ns 34.22 ns 32.01 ns 1,731.9 ns 1,694.8 ns 1,798.4 ns 0.32 0.01 0.0138 - - 80 B
NewtonsoftParse False False Json400B 8,146.1 ns 191.60 ns 220.65 ns 8,075.9 ns 7,857.7 ns 8,624.3 ns 1.00 0.00 1.9227 - - 8168 B
Parse False False Json400B 2,069.7 ns 40.48 ns 41.57 ns 2,052.9 ns 2,026.0 ns 2,170.6 ns 0.25 0.01 0.0164 - - 80 B
NewtonsoftParse False False Json400KB 9,898,101.4 ns 217,554.19 ns 241,810.91 ns 9,893,903.8 ns 9,535,538.5 ns 10,366,550.0 ns 1.00 0.00 730.7692 346.1538 - 4611608 B
Parse False False Json400KB 1,600,696.0 ns 26,211.26 ns 23,235.60 ns 1,595,332.5 ns 1,577,540.6 ns 1,653,366.2 ns 0.16 0.00 - - - 80 B
NewtonsoftParse False True HelloWorld 1,042.8 ns 31.21 ns 34.69 ns 1,032.2 ns 991.8 ns 1,123.1 ns 1.00 0.00 0.7325 - - 3064 B
Parse False True HelloWorld 618.1 ns 11.74 ns 10.98 ns 617.8 ns 604.7 ns 644.1 ns 0.60 0.02 0.0284 - - 128 B
NewtonsoftParse False True BasicJson 6,465.5 ns 131.60 ns 146.28 ns 6,415.2 ns 6,290.9 ns 6,754.1 ns 1.00 0.00 1.6537 - - 6944 B
Parse False True BasicJson 2,995.9 ns 42.00 ns 37.23 ns 2,994.7 ns 2,946.0 ns 3,073.9 ns 0.46 0.01 0.1905 - - 832 B
NewtonsoftParse False True Json400B 10,364.3 ns 211.45 ns 226.25 ns 10,280.8 ns 10,119.0 ns 11,034.0 ns 1.00 0.00 2.2511 - - 9544 B
Parse False True Json400B 4,954.6 ns 97.74 ns 104.58 ns 4,911.4 ns 4,835.6 ns 5,189.1 ns 0.48 0.02 0.4556 - - 1936 B
NewtonsoftParse False True Json400KB 12,002,558.7 ns 414,910.48 ns 477,811.52 ns 11,826,566.7 ns 11,474,593.3 ns 13,130,620.0 ns 1.00 0.00 933.3333 466.6667 66.6667 5532305 B
Parse False True Json400KB 9,142,168.0 ns 700,736.14 ns 806,968.78 ns 8,993,550.0 ns 8,224,450.0 ns 10,682,013.3 ns 0.76 0.09 266.6667 166.6667 66.6667 1508730 B
NewtonsoftParse True False HelloWorld 941.4 ns 28.53 ns 30.52 ns 936.0 ns 903.6 ns 1,004.8 ns 1.00 0.00 0.7298 - - 3064 B
Parse True False HelloWorld 453.0 ns 4.06 ns 3.80 ns 452.1 ns 446.1 ns 459.3 ns 0.48 0.02 0.0185 - - 80 B
NewtonsoftParse True False BasicJson 5,066.6 ns 104.44 ns 116.08 ns 5,037.0 ns 4,911.2 ns 5,299.1 ns 1.00 0.00 1.5211 - - 6408 B
Parse True False BasicJson 1,568.3 ns 22.59 ns 20.02 ns 1,560.3 ns 1,543.7 ns 1,610.2 ns 0.31 0.01 0.0184 - - 80 B
NewtonsoftParse True False Json400B 7,884.9 ns 178.77 ns 205.87 ns 7,819.1 ns 7,653.0 ns 8,286.9 ns 1.00 0.00 1.9385 - - 8168 B
Parse True False Json400B 1,780.8 ns 21.54 ns 20.15 ns 1,777.4 ns 1,752.6 ns 1,831.5 ns 0.23 0.01 0.0143 - - 80 B
NewtonsoftParse True False Json400KB 9,690,237.7 ns 162,711.02 ns 152,199.99 ns 9,720,703.8 ns 9,394,523.1 ns 9,896,050.0 ns 1.00 0.00 730.7692 346.1538 - 4611608 B
Parse True False Json400KB 1,326,002.3 ns 17,147.65 ns 15,200.95 ns 1,321,416.9 ns 1,310,957.6 ns 1,360,682.0 ns 0.14 0.00 - - - 80 B
NewtonsoftParse True True HelloWorld 980.6 ns 18.83 ns 19.34 ns 975.6 ns 953.0 ns 1,015.0 ns 1.00 0.00 0.7303 - - 3064 B
Parse True True HelloWorld 601.6 ns 9.19 ns 8.60 ns 601.0 ns 590.8 ns 623.5 ns 0.61 0.02 0.0304 - - 128 B
NewtonsoftParse True True BasicJson 6,112.9 ns 112.44 ns 93.90 ns 6,113.3 ns 5,989.8 ns 6,340.2 ns 1.00 0.00 1.6507 - - 6944 B
Parse True True BasicJson 2,842.3 ns 43.13 ns 38.23 ns 2,834.7 ns 2,789.1 ns 2,906.6 ns 0.47 0.01 0.1917 - - 832 B
NewtonsoftParse True True Json400B 10,457.7 ns 275.75 ns 270.82 ns 10,484.8 ns 9,920.7 ns 10,822.7 ns 1.00 0.00 2.2541 - - 9544 B
Parse True True Json400B 4,890.9 ns 92.36 ns 90.71 ns 4,895.4 ns 4,739.6 ns 5,024.5 ns 0.47 0.01 0.4439 - - 1936 B
NewtonsoftParse True True Json400KB 13,046,351.0 ns 958,263.70 ns 1,065,107.56 ns 12,747,861.5 ns 11,784,307.7 ns 15,653,330.8 ns 1.00 0.00 923.0769 461.5385 76.9231 5532302 B
Parse True True Json400KB 8,304,700.9 ns 261,693.41 ns 301,366.52 ns 8,321,200.0 ns 7,845,463.6 ns 8,958,336.4 ns 0.64 0.06 227.2727 136.3636 45.4545 1508495 B

Perf_EnumerateArray

Method TestCase Mean Error StdDev Median Min Max Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
NewtonsoftParseAndEnumerateArray Json400KB 12.636 ms 0.5002 ms 0.5560 ms 12.543 ms 11.895 ms 14.074 ms 1.00 0.00 750.0000 350.0000 - 4651608 B
NewtonsoftParseAndIterateUsingIndex Json400KB 20.324 ms 0.5050 ms 0.5614 ms 20.147 ms 19.703 ms 21.555 ms 1.61 0.09 2100.0000 600.0000 200.0000 11811609 B
NewtonsoftParseAndIterateUsingIndexReverse Json400KB 20.740 ms 0.3998 ms 0.3740 ms 20.615 ms 20.271 ms 21.676 ms 1.64 0.08 2111.1111 666.6667 222.2222 11811630 B
NewtonsoftParseAndGetFirst Json400KB 20.401 ms 0.3932 ms 0.4528 ms 20.317 ms 19.819 ms 21.421 ms 1.62 0.07 2125.0000 625.0000 250.0000 11811609 B
NewtonsoftParseAndGetMiddle Json400KB 20.596 ms 0.4859 ms 0.4990 ms 20.557 ms 19.708 ms 21.480 ms 1.63 0.07 2125.0000 625.0000 250.0000 11811609 B
ParseAndEnumerateArray Json400KB 4.454 ms 0.0349 ms 0.0291 ms 4.451 ms 4.414 ms 4.521 ms 0.36 0.01 - - - 80 B
ParseAndIterateUsingIndex Json400KB 184.845 ms 2.7020 ms 2.3952 ms 185.018 ms 181.658 ms 188.843 ms 14.75 0.61 - - - 8824 B
ParseAndIterateUsingIndexReverse Json400KB 182.094 ms 1.8335 ms 1.5310 ms 181.904 ms 178.212 ms 184.023 ms 14.61 0.42 - - - 6792 B
ParseAndGetFirst Json400KB 3.426 ms 0.0451 ms 0.0377 ms 3.411 ms 3.386 ms 3.490 ms 0.27 0.01 - - - 86 B
ParseAndGetMiddle Json400KB 178.373 ms 1.4489 ms 1.2844 ms 178.280 ms 176.234 ms 180.712 ms 14.23 0.51 - - - 80 B
NewtonsoftParseAndEnumerateArray ArrayOfNumbers 2.831 ms 0.0564 ms 0.0580 ms 2.811 ms 2.768 ms 2.987 ms 1.00 0.00 10.4167 - - 84376 B
NewtonsoftParseAndIterateUsingIndex ArrayOfNumbers 2.966 ms 0.0513 ms 0.0455 ms 2.945 ms 2.913 ms 3.044 ms 1.05 0.03 1729.1667 20.8333 - 7244376 B
NewtonsoftParseAndIterateUsingIndexReverse ArrayOfNumbers 3.081 ms 0.0579 ms 0.0484 ms 3.071 ms 3.027 ms 3.188 ms 1.09 0.03 1729.1667 20.8333 - 7244380 B
NewtonsoftParseAndGetFirst ArrayOfNumbers 3.033 ms 0.0396 ms 0.0351 ms 3.037 ms 2.983 ms 3.103 ms 1.07 0.03 1729.1667 20.8333 - 7244376 B
NewtonsoftParseAndGetMiddle ArrayOfNumbers 3.057 ms 0.0568 ms 0.0531 ms 3.034 ms 2.998 ms 3.170 ms 1.08 0.03 1725.0000 25.0000 - 7244376 B
ParseAndEnumerateArray ArrayOfNumbers 2.623 ms 0.0371 ms 0.0347 ms 2.614 ms 2.580 ms 2.697 ms 0.93 0.02 - - - 80 B
ParseAndIterateUsingIndex ArrayOfNumbers 1.952 ms 0.0225 ms 0.0199 ms 1.953 ms 1.925 ms 1.994 ms 0.69 0.02 - - - 80 B
ParseAndIterateUsingIndexReverse ArrayOfNumbers 1.957 ms 0.0230 ms 0.0215 ms 1.949 ms 1.931 ms 1.998 ms 0.69 0.02 - - - 80 B
ParseAndGetFirst ArrayOfNumbers 1.962 ms 0.0261 ms 0.0232 ms 1.950 ms 1.935 ms 1.997 ms 0.69 0.01 - - - 80 B
ParseAndGetMiddle ArrayOfNumbers 1.958 ms 0.0299 ms 0.0265 ms 1.953 ms 1.917 ms 2.009 ms 0.69 0.02 - - - 80 B
NewtonsoftParseAndEnumerateArray ArrayOfStrings 2.795 ms 0.0822 ms 0.0913 ms 2.759 ms 2.689 ms 3.029 ms 1.00 0.00 20.8333 - - 87808 B
NewtonsoftParseAndIterateUsingIndex ArrayOfStrings 3.011 ms 0.0595 ms 0.0637 ms 2.993 ms 2.934 ms 3.151 ms 1.08 0.04 1725.0000 - - 7247808 B
NewtonsoftParseAndIterateUsingIndexReverse ArrayOfStrings 2.977 ms 0.0589 ms 0.0578 ms 2.952 ms 2.907 ms 3.106 ms 1.06 0.04 1729.1667 - - 7247808 B
NewtonsoftParseAndGetFirst ArrayOfStrings 2.981 ms 0.0498 ms 0.0466 ms 2.961 ms 2.920 ms 3.069 ms 1.06 0.04 1729.1667 - - 7247808 B
NewtonsoftParseAndGetMiddle ArrayOfStrings 3.025 ms 0.0565 ms 0.0555 ms 3.013 ms 2.955 ms 3.150 ms 1.08 0.04 1725.0000 - - 7247808 B
ParseAndEnumerateArray ArrayOfStrings 2.736 ms 0.0476 ms 0.0398 ms 2.736 ms 2.665 ms 2.823 ms 0.97 0.04 - - - 80 B
ParseAndIterateUsingIndex ArrayOfStrings 1.958 ms 0.0365 ms 0.0323 ms 1.944 ms 1.930 ms 2.038 ms 0.70 0.03 - - - 80 B
ParseAndIterateUsingIndexReverse ArrayOfStrings 1.952 ms 0.0252 ms 0.0224 ms 1.954 ms 1.910 ms 1.983 ms 0.70 0.02 - - - 80 B
ParseAndGetFirst ArrayOfStrings 2.011 ms 0.0770 ms 0.0824 ms 1.968 ms 1.940 ms 2.265 ms 0.72 0.03 - - - 90 B
ParseAndGetMiddle ArrayOfStrings 1.949 ms 0.0310 ms 0.0275 ms 1.944 ms 1.913 ms 2.007 ms 0.69 0.02 - - - 80 B

Perf_EnumerateObject

Method TestCase Mean Error StdDev Median Min Max Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
NewtonsoftParseAndEnumerateObject ObjectProperties 11.410 ms 0.2857 ms 0.3175 ms 11.257 ms 11.007 ms 12.163 ms 1.00 0.00 692.3077 307.6923 - 4416912 B
NewtonsoftParseAndGetFirst ObjectProperties 23.300 ms 0.6789 ms 0.7818 ms 22.898 ms 22.454 ms 25.230 ms 2.04 0.09 714.2857 285.7143 - 4416912 B
NewtonsoftParseAndGetMiddle ObjectProperties 22.922 ms 0.4773 ms 0.4902 ms 22.710 ms 22.576 ms 24.123 ms 2.01 0.08 714.2857 285.7143 - 4417002 B
ParseAndEnumerateObject ObjectProperties 5.598 ms 0.0898 ms 0.0840 ms 5.595 ms 5.467 ms 5.781 ms 0.49 0.01 - - - 80 B
ParseAndGetFirst ObjectProperties 745.643 ms 18.9004 ms 21.7658 ms 740.823 ms 710.994 ms 799.452 ms 65.15 2.30 - - - 80 B
ParseAndGetMiddle ObjectProperties 374.385 ms 5.8281 ms 5.4516 ms 374.998 ms 362.509 ms 382.276 ms 32.87 1.09 - - - 80 B
NewtonsoftParseAndEnumerateObject StringProperties 2.754 ms 0.0670 ms 0.0658 ms 2.737 ms 2.684 ms 2.919 ms 1.00 0.00 31.2500 - - 168336 B
NewtonsoftParseAndGetFirst StringProperties 14.865 ms 0.3340 ms 0.3430 ms 14.901 ms 14.408 ms 15.554 ms 5.40 0.20 - - - 168336 B
NewtonsoftParseAndGetMiddle StringProperties 14.807 ms 0.2735 ms 0.2284 ms 14.814 ms 14.380 ms 15.284 ms 5.36 0.16 - - - 168336 B
ParseAndEnumerateObject StringProperties 4.108 ms 0.0501 ms 0.0469 ms 4.100 ms 4.061 ms 4.229 ms 1.49 0.05 - - - 80 B
ParseAndGetFirst StringProperties 450.262 ms 7.7468 ms 7.2463 ms 448.404 ms 441.818 ms 466.470 ms 163.50 5.40 - - - 80 B
ParseAndGetMiddle StringProperties 244.297 ms 7.2866 ms 8.3913 ms 240.873 ms 234.726 ms 262.474 ms 89.07 3.48 - - - 80 B
NewtonsoftParseAndEnumerateObject NumericProperties 2.844 ms 0.0530 ms 0.0443 ms 2.843 ms 2.787 ms 2.957 ms 1.00 0.00 37.5000 - - 164360 B
NewtonsoftParseAndGetFirst NumericProperties 14.119 ms 0.5707 ms 0.6343 ms 13.875 ms 13.407 ms 15.562 ms 5.03 0.23 - - - 164443 B
NewtonsoftParseAndGetMiddle NumericProperties 13.489 ms 0.1837 ms 0.1629 ms 13.468 ms 13.318 ms 13.869 ms 4.74 0.06 - - - 164360 B
ParseAndEnumerateObject NumericProperties 4.102 ms 0.0462 ms 0.0432 ms 4.111 ms 4.039 ms 4.168 ms 1.44 0.02 - - - 87 B
ParseAndGetFirst NumericProperties 450.666 ms 4.0514 ms 3.5915 ms 450.662 ms 445.268 ms 457.704 ms 158.48 3.13 - - - 80 B
ParseAndGetMiddle NumericProperties 232.856 ms 4.4821 ms 4.1926 ms 231.277 ms 228.490 ms 242.908 ms 81.68 1.16 - - - 80 B

Perf_ParseThenWrite

Method IsDataCompact TestCase Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
NewtonsoftParseThenWrite False HelloWorld 1,417.5 ns 34.00 ns 36.38 ns 1,408.6 ns 1,381.5 ns 1,508.5 ns 0.9254 - - 3880 B
ParseThenWrite False HelloWorld 746.1 ns 26.47 ns 24.76 ns 739.2 ns 720.2 ns 803.3 ns 0.0460 - - 200 B
NewtonsoftParseThenWrite False DeepTree 65,956.2 ns 2,941.96 ns 3,387.96 ns 65,598.7 ns 61,955.0 ns 72,298.2 ns 13.1956 - - 55816 B
ParseThenWrite False DeepTree 30,521.1 ns 939.05 ns 1,004.77 ns 30,367.3 ns 29,346.1 ns 32,886.3 ns - - - 200 B
NewtonsoftParseThenWrite False BroadTree 115,827.3 ns 4,463.98 ns 4,961.70 ns 114,415.9 ns 109,935.5 ns 127,599.8 ns 20.8057 0.4427 - 87720 B
ParseThenWrite False BroadTree 44,330.6 ns 1,531.87 ns 1,573.11 ns 43,867.2 ns 42,134.3 ns 48,462.2 ns - - - 201 B
NewtonsoftParseThenWrite False LotsOfNumbers 31,509.6 ns 538.42 ns 449.60 ns 31,665.3 ns 30,862.3 ns 32,304.9 ns 5.6302 0.1280 - 23576 B
ParseThenWrite False LotsOfNumbers 8,316.6 ns 155.21 ns 145.18 ns 8,280.2 ns 8,092.1 ns 8,681.8 ns 0.0329 - - 200 B
NewtonsoftParseThenWrite False LotsOfStrings 17,373.3 ns 444.14 ns 493.66 ns 17,371.8 ns 16,730.9 ns 18,766.9 ns 3.2340 - - 13776 B
ParseThenWrite False LotsOfStrings 7,918.3 ns 221.86 ns 237.39 ns 7,898.9 ns 7,620.1 ns 8,545.8 ns 0.0310 - - 200 B
NewtonsoftParseThenWrite False Json400B 12,010.5 ns 333.82 ns 371.04 ns 12,087.8 ns 11,468.1 ns 12,784.8 ns 2.4323 - - 10336 B
ParseThenWrite False Json400B 3,854.2 ns 108.14 ns 111.05 ns 3,818.2 ns 3,721.3 ns 4,131.8 ns 0.0455 - - 200 B
NewtonsoftParseThenWrite False Json4KB 85,686.1 ns 4,197.25 ns 4,665.23 ns 83,636.9 ns 81,339.7 ns 96,151.5 ns 15.8783 0.6616 - 66648 B
ParseThenWrite False Json4KB 30,072.6 ns 522.69 ns 436.47 ns 30,182.3 ns 29,523.9 ns 30,882.1 ns - - - 200 B
NewtonsoftParseThenWrite False Json400KB 12,683,411.0 ns 214,786.08 ns 200,911.03 ns 12,704,210.0 ns 12,408,030.0 ns 13,020,625.0 ns 900.0000 450.0000 - 5507728 B
ParseThenWrite False Json400KB 2,946,047.1 ns 57,657.29 ns 53,932.66 ns 2,928,106.0 ns 2,896,595.2 ns 3,073,910.7 ns - - - 215 B
NewtonsoftParseThenWrite True HelloWorld 1,333.8 ns 35.55 ns 40.94 ns 1,320.5 ns 1,288.0 ns 1,424.8 ns 0.8533 - - 3584 B
ParseThenWrite True HelloWorld 714.5 ns 10.47 ns 9.28 ns 710.5 ns 703.6 ns 733.5 ns 0.0466 - - 200 B
NewtonsoftParseThenWrite True DeepTree 49,857.0 ns 1,042.05 ns 1,114.98 ns 49,873.5 ns 48,202.1 ns 52,442.1 ns 10.2238 0.1929 - 43370 B
ParseThenWrite True DeepTree 20,189.8 ns 354.57 ns 314.32 ns 20,138.5 ns 19,781.1 ns 20,698.9 ns - - - 200 B
NewtonsoftParseThenWrite True BroadTree 98,727.7 ns 1,728.08 ns 1,616.44 ns 98,245.3 ns 96,517.2 ns 101,613.7 ns 19.0217 0.7764 - 79568 B
ParseThenWrite True BroadTree 35,179.5 ns 843.79 ns 902.84 ns 34,649.6 ns 34,328.6 ns 37,221.0 ns - - - 200 B
NewtonsoftParseThenWrite True LotsOfNumbers 28,178.6 ns 557.41 ns 596.43 ns 28,111.8 ns 27,353.7 ns 29,302.8 ns 5.5704 0.1114 - 23416 B
ParseThenWrite True LotsOfNumbers 6,999.1 ns 130.34 ns 115.54 ns 6,972.1 ns 6,841.5 ns 7,245.2 ns 0.0271 - - 200 B
NewtonsoftParseThenWrite True LotsOfStrings 14,606.7 ns 286.34 ns 281.22 ns 14,486.0 ns 14,258.4 ns 15,270.6 ns 2.9790 - - 12520 B
ParseThenWrite True LotsOfStrings 6,776.1 ns 130.58 ns 109.04 ns 6,720.5 ns 6,661.2 ns 6,985.8 ns 0.0292 - - 200 B
NewtonsoftParseThenWrite True Json400B 10,682.3 ns 168.88 ns 157.97 ns 10,666.3 ns 10,467.5 ns 11,020.8 ns 2.4264 - - 10224 B
ParseThenWrite True Json400B 3,361.3 ns 65.19 ns 66.94 ns 3,357.2 ns 3,286.0 ns 3,507.6 ns 0.0402 - - 200 B
NewtonsoftParseThenWrite True Json4KB 78,766.0 ns 653.08 ns 509.88 ns 78,727.3 ns 77,711.8 ns 79,887.2 ns 16.6667 0.3205 - 70832 B
ParseThenWrite True Json4KB 25,164.6 ns 599.67 ns 615.82 ns 24,987.9 ns 24,530.8 ns 26,746.4 ns - - - 200 B
NewtonsoftParseThenWrite True Json400KB 13,346,969.2 ns 940,359.95 ns 1,082,919.91 ns 13,555,725.0 ns 11,770,933.3 ns 15,900,783.3 ns 916.6667 416.6667 - 5299160 B
ParseThenWrite True Json400KB 2,607,937.4 ns 58,040.73 ns 59,603.56 ns 2,591,511.5 ns 2,555,713.5 ns 2,777,778.1 ns - - - 214 B

@jozkee
Copy link
Member Author

jozkee commented Sep 3, 2019

@ahsonkhan @adamsitnik

@jozkee
Copy link
Member Author

jozkee commented Sep 4, 2019

@adamsitnik is there a way to not need to create/update xlf files for resx files?
Changing the Access Modifier works on my local but not on the CI.

@chris-brown
Copy link

@jozkee I found these benchmarks really useful thanks. Is this PR abandoned? I would love to see Object Serialization added to this list. For example. System.Text.Json.JsonSerializer.Deserialize<MyClass>(). If this work has moved somewhere else could you redirect me please.

@ahsonkhan
Copy link
Contributor

I would love to see Object Serialization added to this list. For example. System.Text.Json.JsonSerializer.Deserialize<MyClass>(). If this work has moved somewhere else could you redirect me please.

Serialization/Deserialization benchmarks already exist which measure the JsonSerializer APIs (see the System.Text.Json/Serializer folder in this repo):

public T DeserializeFromString() => JsonSerializer.Deserialize<T>(_serialized);

This PR is specifically for measuring the performance of the Document Object Model (DOM) APIs which include JsonDocument and JsonElement.

public enum TestCaseType
{
HelloWorld,
BasicJson,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spacing

Suggested change
BasicJson,
BasicJson,

@steveharter
Copy link
Contributor

@jozkee -- is this abandoned?

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks go to me, but I would prefer to fix few things befor we merge it.

Thank you @jozkee and @steveharter !

Comment on lines +89 to +108
private static string ReadJson400B(JsonElement elem)
{
var sb = new StringBuilder();
for (int i = 0; i < elem.GetArrayLength(); i++)
{
sb.Append(elem[i].GetProperty("_id").GetString());
sb.Append(elem[i].GetProperty("index").GetInt32());
sb.Append(elem[i].GetProperty("isActive").GetBoolean());
sb.Append(elem[i].GetProperty("balance").GetString());
sb.Append(elem[i].GetProperty("picture").GetString());
sb.Append(elem[i].GetProperty("age").GetInt32());
sb.Append(elem[i].GetProperty("email").GetString());
sb.Append(elem[i].GetProperty("phone").GetString());
sb.Append(elem[i].GetProperty("address").GetString());
sb.Append(elem[i].GetProperty("registered").GetString());
sb.Append(elem[i].GetProperty("latitude").GetDouble());
sb.Append(elem[i].GetProperty("longitude").GetDouble());
}
return sb.ToString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid that creating a big string builder and appending a lot of content to it might dominate the reported execution time. Instead of this, we can just create an int variable and add the values/lengths to it.

Suggested change
private static string ReadJson400B(JsonElement elem)
{
var sb = new StringBuilder();
for (int i = 0; i < elem.GetArrayLength(); i++)
{
sb.Append(elem[i].GetProperty("_id").GetString());
sb.Append(elem[i].GetProperty("index").GetInt32());
sb.Append(elem[i].GetProperty("isActive").GetBoolean());
sb.Append(elem[i].GetProperty("balance").GetString());
sb.Append(elem[i].GetProperty("picture").GetString());
sb.Append(elem[i].GetProperty("age").GetInt32());
sb.Append(elem[i].GetProperty("email").GetString());
sb.Append(elem[i].GetProperty("phone").GetString());
sb.Append(elem[i].GetProperty("address").GetString());
sb.Append(elem[i].GetProperty("registered").GetString());
sb.Append(elem[i].GetProperty("latitude").GetDouble());
sb.Append(elem[i].GetProperty("longitude").GetDouble());
}
return sb.ToString();
}
private static int ReadJson400B(JsonElement elem)
{
int result = 0;
for (int i = 0; i < elem.GetArrayLength(); i++)
{
result += elem[i].GetProperty("_id").GetString().Length;
result += elem[i].GetProperty("index").GetInt32());
result += (int)elem[i].GetProperty("isActive").GetBoolean();
result += elem[i].GetProperty("balance").GetString().Length;
result += elem[i].GetProperty("picture").GetString().Length;
result += elem[i].GetProperty("age").GetInt32();
result += elem[i].GetProperty("email").GetString().Length;
result += elem[i].GetProperty("phone").GetString().Length;
result += elem[i].GetProperty("address").GetString().Length;
result += elem[i].GetProperty("registered").GetString().Length;
result += (int)elem[i].GetProperty("latitude").GetDouble();
result += (int)elem[i].GetProperty("longitude").GetDouble();
}
return result;
}

public bool IsDataCompact;

[Params(false, true)]
public bool TestRandomAccess;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether this name describes what it does. To me, it should rather be called AccessProperties

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "random access" terminology comes from trying to access the JSON properties from a DOM in potentially out-of-order fashion.

The reader gives access forward only, linearly. A tree structure allows for random acess.

https://en.wikipedia.org/wiki/Random_access

Random access (more precisely and more generally called direct access) is the ability to access an arbitrary element of a sequence in equal time or any datum from a population of addressable elements roughly as easily and efficiently as any other, no matter how many elements may be in the set. In computer science it is typically contrasted to sequential access which requires data to be retrieved in the order it was stored.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reader gives access forward only, linearly. A tree structure allows for random acess.

@ahsonkhan thanks for the explanation. I was not aware of this implementation detail and it was not obvious for me when looking at the benchmarks

sb.Append(address.GetProperty("city").GetString());
sb.Append(address.GetProperty("zip").GetInt32());
return sb.ToString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, we should use a cheaper way of consuming the result

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

sb.Append(elem[i].GetProperty("favoriteFruit").GetString());
}
return sb.ToString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +50 to +137
[Benchmark]
public void ParseAndEnumerateArray()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;

for (int j = 0; j < IterationCount; j++)
{
foreach (JsonElement withinArray in elem.EnumerateArray())
{
}
}

obj.Dispose();
}

[Benchmark]
public void ParseAndIterateUsingIndex()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();

for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = elem[i];
}
}

obj.Dispose();
}

[Benchmark]
public void ParseAndIterateUsingIndexReverse()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();

for (int j = 0; j < IterationCount; j++)
{
for (int i = arrayLength - 1; i >= 0; i--)
{
JsonElement withinArray = obj.RootElement[i];
}
}

obj.Dispose();
}

[Benchmark]
public void ParseAndGetFirst()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();

for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = obj.RootElement[0];
}
}

obj.Dispose();
}

[Benchmark]
public void ParseAndGetMiddle()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();

for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = elem[arrayLength / 2];
}
}

obj.Dispose();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to wrap my head around these benchmarks and I think that the best approach here would be to have:

  • 1 benchmark just for parsing
  • 1 benchmark for just EnumerateArray over a pre-parsed document (int the setup)
  • 1 benchmark for enumerating a pre-parsed document (int the setup) using an indexer

This would reduce the number of benchmarks: fewer benchmarks to track for devs and less time to run the benchmarks. And also allow for easier identification of the regression: if a benchmark that parses and iterates over an array of ints regressed, what has regressed: parsing or iterating?

Suggested change
[Benchmark]
public void ParseAndEnumerateArray()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
for (int j = 0; j < IterationCount; j++)
{
foreach (JsonElement withinArray in elem.EnumerateArray())
{
}
}
obj.Dispose();
}
[Benchmark]
public void ParseAndIterateUsingIndex()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();
for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = elem[i];
}
}
obj.Dispose();
}
[Benchmark]
public void ParseAndIterateUsingIndexReverse()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();
for (int j = 0; j < IterationCount; j++)
{
for (int i = arrayLength - 1; i >= 0; i--)
{
JsonElement withinArray = obj.RootElement[i];
}
}
obj.Dispose();
}
[Benchmark]
public void ParseAndGetFirst()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();
for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = obj.RootElement[0];
}
}
obj.Dispose();
}
[Benchmark]
public void ParseAndGetMiddle()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
int arrayLength = elem.GetArrayLength();
for (int j = 0; j < IterationCount; j++)
{
for (int i = 0; i < arrayLength; i++)
{
JsonElement withinArray = elem[arrayLength / 2];
}
}
obj.Dispose();
}
}
[Benchmark]
public void Parse()
{
JsonDocument obj = JsonDocument.Parse(_dataUtf8);
JsonElement elem = obj.RootElement;
obj.Dispose();
}
[Benchmark]
public int EnumerateArray()
{
int count = 0;
foreach (JsonElement withinArray in _elem.EnumerateArray())
{
count++;
}
return count;
}
[Benchmark]
public void IterateUsingIndexer()
{
int arrayLength = _elem.GetArrayLength();
for (int j = 0; j < arrayLength; j++)
{
_ = elem[j];
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the separation. However, we should keep the reverse indexer and possibly even the repeatedly accessing length/2 element separate test cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we should keep the reverse indexer and possibly even the repeatedly accessing length/2 element separate test cases.

Is it possible that we could introduce a regression to the product that would be detected by only one of these benchmarks? If not, we should not have that many benchmarks

Copy link
Contributor

@ahsonkhan ahsonkhan Oct 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that we could introduce a regression to the product that would be detected by only one of these benchmarks?

From what I recall, yes. The iteration logic of the JsonElement has some complexity due to the way it is optimized which can regress some cases if modified (it is not a simple dictionary look up which is almost always O(1)).

That said, someone should confirm and validate that, because I could be mistaken :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. I removed the reverse test since it is by index, not an enumerator, and I don't see how that would regress.


obj.Dispose();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the previous type with benchmarks, I would refactor it into two benchmarks:

  • 1 for parsing only
  • 1 for EnumerateObject over a pre-parsed element

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +45 to +53
// Remove all formatting/indentation
using (var jsonReader = new JsonTextReader(new StringReader(jsonString)))
using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
JToken obj = JToken.ReadFrom(jsonReader);
obj.WriteTo(jsonWriter);
jsonString = stringWriter.ToString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic exists in all 4 classes, we should consider refactoring it into a helper type to avoid code duplication and possible bugs

public TestCaseType TestCase;

[Params(true, false)]
public bool IsDataCompact;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would Indented be a better and more consistent name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. Indented is the opposite of IsDataCompact, so we would have to flip the boolean if we choose to change it. I think IsDataCompact is reasonably self-describing.

How is Indented more consistent?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is Indented more consistent?

In the setup we have the following usage of this property: new JsonWriterOptions { Indented = !IsDataCompact }

So we have two names that describe the same thing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean.

That setup is for a different test (Perf.ParseThenWrite.cs) and using Indented is a little strange for a parsing-only test like Perf.DocumentParse.cs .

The way I thought about it was:

  • IsDataCompact is about the characteristic of the data provided by the benchmark setup which being read/parsed to build a DOM.
  • The Indented option is to control how to write the JSON back in the benchmark itself.

But you do bring up a good point about terminology. Maybe, rename it to IsDataIndented to avoid having multiple terms for the same concept?

Suggested change
public bool IsDataCompact;
public bool IsDataIndented;

public void ParseThenWrite()
{

var arrayBufferWriter = new ArrayBufferWriter<byte>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we allocate a new instance of the ArrayBufferWriter for every benchmark invocation or move it to a setup method and reset after every benchmark invocation? what do our users typically do when they use this API?

https://github.com/dotnet/performance/blob/master/docs/microbenchmark-design-guidelines.md#setup

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think if someone wanted to optimize their code, they would re-use the instance. So I'll change it to clear.

The issue previously was that it was not cleared and continued to grow, plus I found a case where that could cause a hang which I am still investigating but unable to repro yet outside of the benchmarks.

Comment on lines +64 to +70
_arrayBufferWriter.Clear();

using (JsonDocument document = JsonDocument.Parse(_dataUtf8))
using (Utf8JsonWriter writer = new Utf8JsonWriter(_arrayBufferWriter, new JsonWriterOptions { Indented = !IsDataCompact }))
{
document.WriteTo(writer);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could go one step further and cache the writer as a field and re-use that, calling writer.Reset() on it, instead of explicitly clearing the ABW.

Reset clears the writer state including the underling ABW. And then dispose the writer on the global cleanup of the benchmark.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Done

[Benchmark]
public void ParseThenWrite()
{
_arrayBufferWriter.Clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to dispose this ABW on global cleanup of the benchmark set to avoid exhausting the arraypool between runs, that might impact perf of other benchmarks that might be running in parallel.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ABW does not use the pool

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, good point.

<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableXlfLocalization>false</EnableXlfLocalization>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsitnik can this be set? It appears to solve my local issues with having to manually generate the XLF files and then ignore during commit.

Do we have localized resources?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveharter as long as it makes your dev experience better and the CI is green, it's perfectly fine

Do we have localized resources?

afaik not. All resources were added to avoid having big strings defined as const string?

return Encoding.UTF8.GetBytes(jsonString);
}
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add new line at eof

result += elem.GetProperty("first").GetString().Length;
result += elem.GetProperty("last").GetString().Length;

JsonElement phoneNumbers = elem.GetProperty("phoneNumbers");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonElement phoneNumbers = elem.GetProperty("phoneNumbers");
JsonElement phoneNumbers = elem.GetProperty("phoneNumbers");

string jsonString = JsonStrings.ResourceManager.GetString(TestCase.ToString());
_dataUtf8 = DocumentHelpers.RemoveFormatting(jsonString);

JsonDocument document = JsonDocument.Parse(_dataUtf8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store this as a field and dispose it at global cleanup to avoid exhausting the underlying pool. JsonDocument is IDisposable.

int arrayLength = _element.GetArrayLength();
for (int j = 0; j < arrayLength; j++)
{
_ = _element[j];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will BDN optimize this indexer access out? Do we need to aggregate the result somehow and return it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, since calling element[j] could have side-effects.

However, we did all of the work prior to this to change from string\StringBuffer to int\Count() apparently out of the same concern.

@adamsitnik do benchmarks have to worry about code being optimized out? If not, we should also remove the int\Count() work that was done earlier (originally StringBuffer).

string jsonString = JsonStrings.ResourceManager.GetString(TestCase.ToString());
_dataUtf8 = DocumentHelpers.RemoveFormatting(jsonString);

JsonDocument document = JsonDocument.Parse(_dataUtf8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here/elsewhere. Remember to dispose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Thanks. Probably wouldn't matter for the benchmark numbers (perf not affected; memory not exhausted), but we should definitively do it.

[Benchmark]
public void EnumerateProperties()
{
foreach (JsonProperty withinArray in _element.EnumerateObject()) { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, will this actually go through the loop while measuring?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I verified this and a couple other areas that the jitter won't remove. So I also removed the int-based "Count" logic.

Comment on lines +56 to +71
if (TestCase == TestCaseType.HelloWorld)
{
ReadHelloWorld(document.RootElement);
}
else if (TestCase == TestCaseType.Json400B)
{
ReadJson400B(document.RootElement);
}
else if (TestCase == TestCaseType.BasicJson)
{
ReadJsonBasic(document.RootElement);
}
else if (TestCase == TestCaseType.Json400KB)
{
ReadJson400KB(document.RootElement);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a switch statement?

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you @steveharter !

<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableXlfLocalization>false</EnableXlfLocalization>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveharter as long as it makes your dev experience better and the CI is green, it's perfectly fine

Do we have localized resources?

afaik not. All resources were added to avoid having big strings defined as const string?

@adamsitnik
Copy link
Member

the CI failures are not related (this PR added microbenchmarks, did not touch scenarios) so I am merging

@adamsitnik adamsitnik merged commit aa79d8c into dotnet:master Oct 15, 2020
@steveharter steveharter deleted the 792 branch October 16, 2020 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add System.Text.Json.JsonDocument and JsonElement performance tests

8 participants