diff --git a/.changeset/doc-import-allocation.md b/.changeset/doc-import-allocation.md new file mode 100644 index 0000000..e73c209 --- /dev/null +++ b/.changeset/doc-import-allocation.md @@ -0,0 +1,7 @@ +--- +"@extend-ai/react-docx": patch +--- + +Reduce legacy Word import overhead by moving compound-file streams into the parser, reusing decoded text and formatting data, and appending XML directly into output buffers. Preserve the generated package, parsed model, and structured-block representation. + +Use immutable native cache snapshots with thread-local regex readers and a bounded update actor. Skip optional cache admission when its mailbox is full, while preserving complete parsing results. diff --git a/Cargo.lock b/Cargo.lock index 4816109..a39c47d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -145,6 +154,7 @@ dependencies = [ name = "docx-core" version = "0.1.0" dependencies = [ + "arc-swap", "base64", "base64-simd", "itoa", diff --git a/crates/docx-core/Cargo.toml b/crates/docx-core/Cargo.toml index 906581a..7ceab03 100644 --- a/crates/docx-core/Cargo.toml +++ b/crates/docx-core/Cargo.toml @@ -31,6 +31,7 @@ unicode-segmentation = "=1.12.0" sha2 = { version = "0.10", features = ["asm"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +arc-swap = "=1.9.3" base64-simd = "=0.8.0" libdeflater = { version = "1.26.1", optional = true } rayon = "=1.12.0" diff --git a/crates/docx-core/README.md b/crates/docx-core/README.md index 8d34452..033e0af 100644 --- a/crates/docx-core/README.md +++ b/crates/docx-core/README.md @@ -68,68 +68,68 @@ Deserializing `BlockDetails` stores `Raw(Value)` to retain unknown fields, inclu ## Parser latency comparisons -Measured November 2, 2026 for release **0.13.0**, against **docx-rs 0.4.22**, **office_oxide 0.1.15**, and **undoc 0.27.1**. The measured Rust source is commit `9ae335abdc79f7defe5a2843065a3ff13c6e5cea` and is unchanged in the release. +Measured November 2, 2026 for the **unreleased parser optimizations**, against **docx-rs 0.4.22**, **office_oxide 0.1.15**, and **undoc 0.27.1**. The measured source is commit `dcce836072cb01a24f6490c97643f825d2d6902e`. The [published 0.13.0 results](../../docs/benchmarks-0.13.0.md) remain available separately. -![Median and p95 native parser latency for DOCX, DOC, DOT, DOTX, DOCM, and DOTM](../../docs/readme-assets/docx-core-0.13.0-latency-overview.png) +![Median and p95 native parser latency for DOCX, DOC, DOT, DOTX, DOCM, and DOTM](../../docs/readme-assets/docx-core-optimized-20261102-latency-overview.png) These are native optimized parse and model/IR timings; docx-core also includes structured-block projection. Each document has five warm samples, each averaging 1–20 parses. Charts use the common successful inputs for the parsers supporting each format. File reads, process startup, JSON serialization, destruction, and reference conversion are excluded. The p95 is across per-document medians, not request-level or browser latency. Measurements were taken on a shared development machine. DOCX uses the separate seeded 1,000-input corpus. DOC and the template/macro formats pool the two multi-format cohorts. The additional 75 DOCX inputs are shown separately below because they became regressions after inspection and repair. Template and macro-enabled cohorts are small (2–12 inputs); these results do not establish universal performance. -Core has the lowest measured DOCX median (0.800 ms), close to office_oxide (0.816 ms), while office_oxide has the lower DOCX p95. Core is slower than the other binary parsers on DOC. See the [full accuracy comparison and limitations](../../docs/benchmarks-0.13.0.md) when interpreting speed: the parsers retain different amounts of structure, and text F1 does not measure visual layout. +Core has the lowest measured DOCX median (0.673 ms) and p95 (5.080 ms) in this run. Core remains slower than the other binary parsers on DOC; the two paths retain different amounts of structure. See the [full accuracy comparison and limitations](../../docs/benchmarks-optimized-20261102.md) when interpreting speed: the parsers retain different amounts of structure, and text F1 does not measure visual layout. Each format chart includes the cumulative latency distribution and latency against input bytes, parsed paragraph count, and feature occurrences. Complexity counts use the core result for the same document across every parser. The axes use logarithmic scales so small and large documents remain visible.
DOCX — seeded corpus: latency distribution and complexity -![DOCX — seeded corpus native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-docx.png) +![DOCX — seeded corpus native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-docx.png)
DOC: latency distribution and complexity -![DOC native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-doc.png) +![DOC native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-doc.png)
DOT: latency distribution and complexity -![DOT native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-dot.png) +![DOT native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-dot.png)
DOTX: latency distribution and complexity -![DOTX native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-dotx.png) +![DOTX native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-dotx.png)
DOCM: latency distribution and complexity -![DOCM native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-docm.png) +![DOCM native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-docm.png)
DOTM: latency distribution and complexity -![DOTM native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-dotm.png) +![DOTM native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-dotm.png)
DOCX — additional regression cohort: latency distribution and complexity -![DOCX — additional regression cohort native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-0.13.0-latency-docx-regressions.png) +![DOCX — additional regression cohort native latency distribution and complexity comparisons](../../docs/readme-assets/docx-core-optimized-20261102-latency-docx-regressions.png)
-The [summary](benchmarks/0.13.0/latency.json) and [chart data](benchmarks/0.13.0/latency.csv) contain timings and numeric complexity measurements, with no document names or parsed content. Regenerate the figures with [readme_charts.py](../../scripts/parser-benchmark/readme_charts.py) and the retained release benchmark runs. +The [summary](benchmarks/optimized-20261102/latency.json) and [chart data](benchmarks/optimized-20261102/latency.csv) contain timings and numeric complexity measurements, with no document names or parsed content. Regenerate the figures with [readme_charts.py](../../scripts/parser-benchmark/readme_charts.py) and the retained optimized benchmark runs and the label, asset-prefix, and measured-commit options. ## Regression inputs diff --git a/crates/docx-core/benchmarks/optimized-20261102/latency.csv b/crates/docx-core/benchmarks/optimized-20261102/latency.csv new file mode 100644 index 0000000..6df9b28 --- /dev/null +++ b/crates/docx-core/benchmarks/optimized-20261102/latency.csv @@ -0,0 +1,1309 @@ +format,bytes,paragraphs,feature_occurrences,docx-core_ms,docx-rs_ms,office_oxide_ms,undoc_ms +docx,21931,35,48,0.41923462499999986,1.1959026666666668,0.5318250000000001,0.7491896499999999 +docx,17941,51,49,0.3447047272727272,0.50551665,0.3830022,0.6251084500000001 +docx,980306,52,107,2.639917,3.0565059999999997,2.0157965555555557,5.13796875 +docx,21537,15,65,0.3384998888888889,0.9767526249999999,1.0815939166666666,0.9681278 +docx,52555,183,247,1.32137525,2.1295835,1.5036893636363642,4.1023249999999996 +docx,47491,37,47,1.8880976666666667,0.91533075,0.6359497368421053,1.1006956153846152 +docx,19832,8,9,0.29165958333333336,0.9021297777777778,0.5931712222222222,0.8821886470588236 +docx,71259,539,247,1.6847914,3.1585761666666667,2.7506371428571432,5.1754685 +docx,33996,25,34,0.30621862499999997,0.7895687058823531,0.44139180000000006,0.5162396 +docx,41366,93,141,1.3661663333333334,1.8114771111111112,0.9573779333333337,3.2884636250000003 +docx,70159,19,8,1.7372915,2.7863035714285713,0.8363058666666666,0.867188705882353 +docx,13656,12,3,0.20087987499999999,0.21863755,0.2146125,0.12579995 +docx,154427,18,17,0.432033,1.1499849999999998,0.63061455,0.6830263684210526 +docx,79630,4,5,0.21817566666666668,0.2854,0.18664560000000002,0.16611035 +docx,102264,759,619,4.210604,6.130569666666666,3.9563503999999994,18.671916 +docx,15780,39,71,0.27246954545454555,0.9396721333333333,0.40621885,0.7355521 +docx,60017,193,270,1.271925,4.295275,2.169859125,18.399167 +docx,25224,46,31,0.4183334166666666,1.052075,0.6817126315789473,1.312982833333333 +docx,44085,88,64,1.1841145,1.4526425000000003,0.9482944999999999,1.7446527777777778 +docx,118478,308,726,7.172416500000001,6.709792,4.4140504,13.1531875 +docx,168728,2562,2566,12.143001,147.654583,14.448103999999999,49.880624999999995 +docx,108865,72,47,0.5330652857142857,1.0971893076923076,0.9212012941176472,1.3871633333333335 +docx,27169,26,10,0.6587547777777778,0.7615330526315791,0.42850004999999997,0.768070157894737 +docx,53341,77,30,0.722677,0.9636554,1.5103103333333332,1.1441192142857146 +docx,17240,27,8,0.42257287499999996,0.6820938,0.5384835000000001,1.2276786428571427 +docx,354192,1588,1861,12.764792000000002,21.065082999999998,12.0260005,42.750542 +docx,12201,3,0,0.17711250000000003,0.3244146,0.18092910000000004,0.11634769999999998 +docx,116982,176,204,2.5574062499999997,10.004375,4.910677,13.866104499999999 +docx,44107,347,345,1.3752752000000001,2.6542561428571427,1.6957579000000003,5.04489575 +docx,19103,27,33,0.2998140909090909,0.54831875,0.45644575000000015,0.4928002 +docx,8916,28,3,0.21547921428571432,0.62619365,0.4100064,0.5771040500000001 +docx,15982,218,225,1.005906125,2.49161975,1.214608846153846,3.8308750000000003 +docx,533973,0,1,0.32929177777777774,0.34855415,0.20097510000000005,0.14319799999999996 +docx,31145,42,39,0.38783314285714293,0.8201667222222222,0.45695630000000004,0.7936959499999999 +docx,28329,41,52,0.5067986666666666,1.0289834,0.7148259411764707,1.2321615000000001 +docx,22617,42,12,0.39505088888888884,0.55956465,0.534182375,0.7083772222222221 +docx,141405,477,492,2.865971333333333,5.0317395000000005,2.6681728571428573,8.387417 +docx,155463,81,73,1.4511042500000002,5.304406500000001,3.1552986666666665,6.812152666666667 +docx,19551,34,52,0.3319765555555556,0.8608981111111113,0.61106255,1.238803642857143 +docx,123833,25,44,0.7926813333333333,0.7695937500000001,0.48564165,0.8641250526315789 +docx,76457,305,179,2.341138333333333,3.7353502,3.0136548571428574,3.7789414 +docx,19140,36,29,0.3427726363636365,0.9345972000000002,0.5280311,1.0962865 +docx,13662,15,3,0.2077324285714286,0.22407515,0.29951255,0.14894995000000003 +docx,60077,20,6,1.297236,2.1108022500000003,0.8048073125000001,0.8585808823529412 +docx,51858,39,52,1.1909497999999998,0.8969218124999999,0.61524605,0.9791041249999999 +docx,26178,27,6,0.5008097142857142,0.7943837894736843,0.5243792,0.6950166999999999 +docx,19856,69,62,0.43446190909090904,0.76924375,0.61181885,1.1748273571428574 +docx,344613,10,16,0.5784511666666666,0.36704375000000006,0.35520414999999994,0.3448021 +docx,589919,1574,2819,40.949251000000004,13.306896,9.7033335,32.694624999999995 +docx,1021559,335,415,6.6679585,33.313750000000006,17.89,46.426375 +docx,19569,41,43,0.47788319999999995,0.70514375,0.48312494999999994,0.7704749 +docx,45375,254,304,1.1393084000000002,4.721302,2.175239375,5.6351145 +docx,158108,1995,2061,7.4741035,12.0174165,8.432584,18.4957705 +docx,18630,51,12,0.39582061538461527,0.8513455882352942,0.56602515,0.9641223125000001 +docx,201626,345,283,1.5631390000000003,5.7022397499999995,3.2982711666666664,6.941069333333334 +docx,63439,276,341,1.56415625,3.66805,2.500942625,5.272063 +docx,16677,25,25,0.23699989999999999,0.7190812,0.42781909999999995,0.64996035 +docx,32556,20,39,0.6397678571428571,1.2011056923076922,0.7498332941176472,1.2117200714285714 +docx,79741,80,42,0.8839795,1.2765659999999996,0.754696,1.7411898888888888 +docx,61044,66,85,0.6342636666666667,1.1460207857142855,0.9149376250000001,1.5094735454545456 +docx,10445,24,9,0.23060907692307694,0.2300563,0.19005014999999997,0.18412085000000003 +docx,25228,71,93,0.5881661666666667,1.3486631666666664,0.8187521764705883,1.5233485454545452 +docx,68545,34,87,0.6729685,1.215496923076923,0.7353354000000001,1.3228505833333333 +docx,36352,115,171,0.8651,1.4896551818181818,2.247760625,2.2872292499999998 +docx,21800,31,38,8.046555999999999,1.2911216923076922,1.2765193846153846,1.0425499333333332 +docx,20498,18,10,0.2887916666666667,0.6954758421052633,0.3909979,0.5267708499999999 +docx,40057,56,54,1.51879175,3.852286888888889,1.0528138,1.9811435555555557 +docx,29022,111,99,0.8109303333333333,3.216715333333333,1.2087022142857144,4.2633645 +docx,14361,20,34,0.23954169999999997,0.48782905000000004,0.276673,0.40463765 +docx,24994,42,63,0.88190625,0.7344041499999999,0.5718251,1.2587692307692306 +docx,10726,26,9,0.25626408333333334,0.25613969999999997,0.2776188,0.24467709999999995 +docx,60104,361,330,1.620958,3.6720999999999995,1.8535186666666665,3.9627834 +docx,288950,11,14,0.8242707499999999,0.9929555333333332,0.53589595,0.64142715 +docx,84307,169,215,1.3787363333333333,2.628613142857143,1.4836136363636363,4.2550916 +docx,13543,18,13,0.19248026666666668,0.34151034999999996,0.22118745,0.19889575 +docx,20288,28,5,0.6337084000000001,1.1890357857142857,0.5759168499999999,1.6395529090909091 +docx,24103,36,11,0.5688437499999999,0.6736834999999999,0.5583623499999999,0.5916353500000001 +docx,12681,11,43,0.21942206250000001,0.59059805,0.26809365000000007,0.2803731 +docx,22934,14,5,0.45643340000000004,0.35524809999999996,1.297509111111111,0.25772494999999995 +docx,74961,27,28,0.4159669999999999,3.4322778333333335,1.5066135454545455,0.9671888 +docx,105520,93,140,1.082458,4.797594,1.7625500000000003,2.995500166666666 +docx,45615,121,35,0.7469788333333334,2.5772084285714287,1.423021,2.4571012857142853 +docx,24560,24,5,0.33818033333333336,0.8530601666666667,0.51990015,0.9738567499999999 +docx,81359,101,134,1.2571977500000002,1.8519167777777779,1.1639049285714285,2.293135375 +docx,899346,43,48,3.5502915,3.53975,2.5305771428571426,4.60037475 +docx,42391,117,102,0.8119748000000001,2.447821571428572,1.3564478333333334,1.8239631111111112 +docx,10398,23,9,0.23809033333333338,0.23968965,0.20601664999999997,0.17884779999999997 +docx,13506,364,409,1.4189345714285715,1.237596076923077,0.909781823529412,1.9378193333333331 +docx,20328,40,19,0.353020875,0.8346804999999999,0.4539166,0.8071710526315791 +docx,47157,109,124,0.7690939999999999,3.4139001999999996,1.0439694666666668,2.16774975 +docx,32784,69,70,0.42506775,1.2981038333333335,0.9385221052631579,1.0769047857142857 +docx,82617,1477,1125,4.220959000000001,9.843645500000001,5.891653000000001,17.3969165 +docx,52002,49,99,1.1326875,1.2266285833333332,0.8166750666666667,1.3840288461538464 +docx,590716,46,48,0.64278075,1.0021250000000002,0.5060958999999999,0.8049101052631579 +docx,466516,12,13,2.0007085,0.5251937499999999,0.32750619999999997,0.4538624500000001 +docx,8492,43,61,0.5018265000000001,0.44926865000000005,0.6076560499999999,0.5550874 +docx,7916,5,5,0.16081275000000003,0.16541034999999998,0.13844995,0.08594144999999999 +docx,12209,1,1,0.18840608333333334,0.3086313500000001,0.19933735,0.09445415000000001 +docx,93411,10,33,0.2537499999999999,0.3498479500000001,0.23825225,0.3249855 +docx,55420,11,34,0.25639075,0.4782624000000001,0.25117295,0.3193313 +docx,19389,19,15,0.3502345,1.1023748571428569,0.6226852999999999,0.7184291 +docx,20659,74,33,0.3907314444444444,0.7666139473684209,0.49638954999999996,0.79275 +docx,16280,30,26,0.2757122727272728,0.67876055,0.4128750000000001,0.66958965 +docx,139017,1314,364,3.7578340000000003,8.193347000000001,5.28122925,10.1044795 +docx,85552,79,92,1.2872226666666664,2.1194168749999998,1.3558370833333333,2.1179581250000004 +docx,78546,54,33,1.28874975,5.526323,1.7350695555555555,2.6566488571428573 +docx,50519,74,118,15.342083,2.4860107499999997,1.4702802727272726,2.7882618571428575 +docx,24050,176,29,1.3341390000000002,1.2264230769230768,0.9884350625,1.6613582 +docx,381721,22,17,0.6593092857142857,0.6638896,0.4503479,0.6755584 +docx,56974,33,50,0.4936145,0.957757875,0.55172705,0.8133101111111112 +docx,110229,283,287,4.451188,5.14522925,2.8199822857142856,4.5505336 +docx,752121,36,52,0.9038235,0.9041277500000001,0.7113138235294117,1.0637757692307692 +docx,120081,156,182,2.6033756666666665,2.9011131428571426,1.5745542000000001,4.99238525 +docx,48502,198,197,1.1555166,3.006486,1.8645973333333334,4.53508325 +docx,101577,16,21,0.80000025,0.7851161578947368,0.45272104999999996,1.1061429285714288 +docx,86143,21,16,0.18556768750000002,0.4362500500000001,0.24932499999999996,0.29793115000000003 +docx,8461,19,16,0.577427,0.45347489999999996,0.30766685000000005,0.5648999499999999 +docx,64350,329,184,1.9457216666666668,5.20631275,2.8694525714285715,6.776902666666667 +docx,501555,5608,2249,16.045916,29.929125,20.2335,47.839417000000005 +docx,491468,1400,2264,19.600333,23.962417,13.748104,39.451125 +docx,18248,33,47,0.3228981111111111,0.6193374500000001,0.41803135,0.6761376 +docx,1976324,123,127,7.9849165,10.384,10.294749999999999,11.095375 +docx,34260,41,64,1.6677075000000001,1.0101875,0.52597275,0.8895489411764707 +docx,335151,47,35,0.48511816666666663,0.750214894736842,0.58197915,0.861072875 +docx,12439,27,37,0.29171981818181814,0.6137458500000001,0.41353115,0.72449145 +docx,88983,94,115,0.8103748333333334,2.8419096666666666,1.7769624999999998,3.728575 +docx,134827,178,239,1.4936249999999998,3.0942635000000003,1.9544582222222227,4.8155315 +docx,16018,30,41,0.360104,0.7190624000000001,0.43636459999999994,0.7437688 +docx,46688,78,55,0.9382780000000001,2.644053714285714,1.4616145000000003,2.4096875 +docx,16827,36,6,0.3005892857142857,0.8933074375000001,0.5887604,0.8769195333333334 +docx,10567,26,9,0.24368923076923077,0.24394575000000002,0.20847485,0.20449995000000004 +docx,8529,42,60,0.5387460000000002,0.45224575,0.32291865000000003,1.547150615384615 +docx,33571,76,32,0.7643125,1.207442230769231,0.689713294117647,1.5257081818181817 +docx,459914,124,75,1.0721975000000001,1.720325,1.1872739999999997,2.192265625 +docx,26573,81,48,0.6250623750000001,1.6429458000000001,0.9731943333333333,2.1895367777777777 +docx,123219,488,452,4.668958,7.527569333333333,3.8492502,11.393896000000002 +docx,51614,46,31,0.8504500000000002,0.8261812941176468,0.5669374,0.8451003529411764 +docx,100910,25,77,0.7010753999999999,0.9745053999999999,0.6528612222222222,1.22817375 +docx,13522,2,0,0.22567213333333333,0.4573187999999999,0.2747624,0.31596055000000006 +docx,11575,11,10,0.20565163636363637,0.47392504999999996,0.2313002,0.34746035 +docx,13233,7,4,0.20308338461538464,0.3911375,0.42699385,0.2034687 +docx,13623,17,14,0.6974236666666668,0.5689542000000001,0.3118917,0.47815414999999994 +docx,9333,8,0,0.15387085,2.067125,0.1601999,0.10005005 +docx,938888,15,18,0.9374793333333332,1.3776597499999996,1.3315938333333333,0.9364093529411763 +docx,12749,14,51,0.3341840833333333,0.6471083,0.38900219999999996,0.7047897 +docx,21413,19,19,0.44440112500000006,0.8029675555555555,0.39990229999999993,0.60573345 +docx,44348,111,116,0.6070961538461539,1.0313696,1.1819495714285715,1.5773875 +docx,48874,40,109,0.6124940000000001,1.5075491818181819,0.9371600769230769,1.1625267857142858 +docx,15083,7,7,0.22404757142857143,0.4781416500000001,0.26132710000000003,0.26935214999999996 +docx,24529,18,24,0.4465417777777778,0.51497705,0.2985457,0.33712705000000004 +docx,759051,82,106,2.0960273333333332,2.322375142857143,1.1162881666666664,1.5654459 +docx,82119,19,20,0.6433332,1.1814513333333332,0.7599385263157895,0.9822996249999999 +docx,50875,24,26,0.45684179999999996,1.284269230769231,0.6511563,1.0727028000000003 +docx,38244,108,175,1.04988525,2.7371845714285716,1.7290324444444447,3.7886666000000004 +docx,146022,69,112,2.945833,2.3531873749999996,1.5602050833333336,3.1446251666666662 +docx,38922,152,253,1.2124443333333332,2.2233392857142857,1.5246457,2.7310179999999997 +docx,16739,27,58,0.3565085,0.7819122631578946,0.50744575,0.901909411764706 +docx,17029,18,13,0.19005533333333335,0.36163335,0.26991675,0.32487495 +docx,16116,18,3,0.31995833333333334,0.4831812999999999,0.28443530000000006,0.35171245000000007 +docx,83267,35,37,2.4878964999999997,2.279307125,1.155212,1.5822542 +docx,183432,54,96,8.2035205,4.0574416,1.7417668999999996,4.798927 +docx,113626,358,421,3.5566255,7.521375,3.6955754,8.792277666666665 +docx,25714,56,136,0.5732151666666666,1.313795,0.8927795882352941,1.2940588333333334 +docx,30777,115,189,0.9715748,3.9548246,1.4516979999999997,2.5439347142857143 +docx,52700,42,98,1.823083,2.551333142857143,1.204496923076923,2.1478657777777777 +docx,53271,177,188,1.1073833999999998,4.09581275,5.21933325,8.729222333333333 +docx,40164,52,59,0.5572748,5.246427,2.3956409999999995,2.30550525 +docx,51503,20,5,1.112611,2.183380125,0.9049609375,0.7987176111111112 +docx,20444,59,69,0.37299544444444444,0.8638750000000001,0.54744185,1.189646 +docx,313177,135,139,1.3956042499999999,2.6599045714285716,1.4502431666666666,3.8186166 +docx,41889,925,884,3.0541526666666665,7.821499666666667,3.9789586,14.858187000000001 +docx,21757,57,81,0.3284754,1.1480832857142858,0.7703134210526316,1.2945349166666669 +docx,25671,106,82,0.7227141428571429,2.035092555555556,1.112785857142857,3.0853335000000004 +docx,1318015,117,143,1.7429306666666669,3.5709835999999995,2.4186664999999996,5.18358325 +docx,15765,19,10,0.29059854545454544,0.7741396500000001,0.42340209999999995,0.57374175 +docx,170941,237,472,3.0006245000000007,11.9746045,4.59886425,9.914562499999999 +docx,14191,8,13,0.23388488235294114,0.49316259999999995,0.26543324999999995,0.3458164999999999 +docx,8744,7,7,0.17464,0.1653124,0.14872915000000003,0.11851665000000003 +docx,39109,28,31,0.4958834,0.5694395500000001,0.42693955,0.5783146 +docx,22601,21,0,0.32255423076923073,0.7686273684210526,7.176166,1.0854637333333335 +docx,13601,24,25,1.0149761428571429,2.696514333333333,0.48981045000000006,0.7875232777777779 +docx,14329,13,5,0.21235860000000004,0.35515425,0.23648335,0.30212915 +docx,27698,56,116,1.1040626666666666,1.4076944166666667,0.853200705882353,2.341468875 +docx,75030,45,65,1.422375,1.6781667000000002,0.9429265882352941,1.9426157777777782 +docx,22634,18,10,0.35392187499999994,0.6915463333333334,0.43296684999999996,0.6698286111111111 +docx,24239,46,31,0.3809044,1.224,4.843666499999999,1.0224140625 +docx,12287,3,1,0.15360435714285714,0.3299230000000001,0.17752485,0.12425425000000004 +docx,216161,156,114,1.4321600000000003,2.5363988571428573,1.8130142222222223,3.6123499999999993 +docx,20322,42,21,0.30298118181818184,0.8023194444444445,0.517445052631579,0.7091973684210525 +docx,8929,15,4,0.24322672222222222,0.1588708,0.15879600000000005,0.12201870000000001 +docx,87967,104,53,1.6943336666666668,3.437541833333333,1.5526752,1.8885418888888887 +docx,83101,31,54,0.7490417500000001,1.6573581999999998,0.7246270555555556,1.1335610666666667 +docx,33030,31,11,0.4112336,0.9366605,0.30686660000000004,0.5470334500000001 +docx,16091,58,87,0.40707399999999994,0.8195692777777778,0.5635250500000001,1.1897589285714287 +docx,15761,66,92,0.5032407777777776,0.8647198333333335,0.5066794499999999,0.9202548823529413 +docx,55602,25,14,1.4280002500000002,1.7285793999999999,0.7354068235294118,0.7779675555555556 +docx,124902,262,94,1.177958,3.038847,1.7967334000000001,2.6946488571428566 +docx,25105,184,76,0.7083818333333335,1.2442814166666667,1.1056785714285713,1.9448839999999998 +docx,2506524,74,101,4.521235999999999,1.4284272500000001,1.0540472,1.6696502000000002 +docx,22889,76,48,0.5095828000000001,1.0319375,9.492084,1.1154774615384615 +docx,346182,25,33,2.318847333333333,7.949597333333334,4.724458799999999,2.0255738888888892 +docx,4304217,490,424,3.888306333333333,5.729020500000001,4.1803998,7.7902916666666675 +docx,17932,45,41,0.4833666,0.6033312,0.46849995,0.61308965 +docx,14100,10,1,0.19975964705882354,0.3268083,0.2014438,0.26621449999999997 +docx,77352,209,148,2.7963329999999993,3.3742332000000004,2.1614271250000003,2.8616607142857142 +docx,76832,22,25,0.6327605000000001,1.0729638,0.6115769500000001,0.8402200555555557 +docx,26654,75,76,0.47496887499999996,1.2370223846153847,1.0713906249999998,1.5577689999999997 +docx,18804,13,24,0.315979125,0.68178745,0.33383915,0.52801465 +docx,12448,10,0,0.18462735,0.3449313,0.1692189,0.14144795000000002 +docx,47982,371,207,1.2597082,4.581021,2.631291285714286,6.19841675 +docx,137012,160,104,1.4523747500000002,6.446864499999999,1.7719333,2.5842500000000004 +docx,12508,276,316,0.945161625,0.9401470588235294,0.6285415263157894,1.2925736923076923 +docx,323523,9,6,0.514213625,0.34873965,0.24740209999999996,0.27713325 +docx,89389,465,500,2.1948469999999998,7.219444333333333,3.3040346666666665,7.949069333333334 +docx,18638,23,27,0.29967069999999996,0.6255665,0.4184875,0.42123545 +docx,444771,60,99,0.7225104999999999,1.2937436153846156,0.9342893529411765,1.4880938333333333 +docx,81434,61,28,1.5729583333333332,3.3584373333333333,1.2572879090909093,1.5780292 +docx,22167,50,120,0.48287872727272724,1.1164722666666667,0.8373333333333334,1.4541136363636364 +docx,25103,15,36,1.5423022499999999,0.9924305555555555,0.61970005,1.3368943076923079 +docx,12401,10,0,0.17701465000000002,0.3300126,0.19517290000000004,0.20655615 +docx,27554,48,85,0.36236816666666666,1.0026275624999998,0.6512249500000001,0.8300319411764706 +docx,97798,90,20,1.1367809999999998,3.020285714285714,0.9572276,2.081847111111111 +docx,8536,54,60,0.3638612222222222,1.0661838333333333,0.7182167500000001,1.6539468181818184 +docx,33392,27,31,0.6947778333333333,1.4617195333333333,0.749875052631579,1.0559249333333334 +docx,53423,2,3,1.1295840000000001,0.49890850000000003,0.3394062,0.32157075000000007 +docx,15831,54,44,0.318693,0.7080916500000001,0.5290708,1.6275707000000001 +docx,20415,22,31,0.29751025,1.0046561875,0.55780635,0.7936805555555554 +docx,73080,13,6,1.856819,2.9292916666666664,0.7828601764705883,0.9951951874999999 +docx,42263,69,173,15.957791,1.2879004615384615,0.9214972666666666,1.3835450909090907 +docx,974432,8,13,0.534656375,0.5067416,0.38159385,0.5234876 +docx,15546,14,11,0.43496700000000005,0.3008104,0.2962313,0.2621165 +docx,17350,39,3,0.3682234545454545,0.6443769999999998,0.3960145499999999,0.59528115 +docx,67101,65,89,0.7713125000000001,1.964384,1.0207805999999997,1.4956666666666667 +docx,33935,56,72,0.69010425,2.1566515454545456,0.8168652352941176,2.2648125 +docx,42433,70,85,1.985667,0.9549168125,0.6421374500000001,1.0764166666666664 +docx,14764,9,0,0.2289644,0.3051834,0.22547894999999998,0.33373325 +docx,28250,28,31,0.75596875,0.6451188999999999,0.37024375,0.5544186499999999 +docx,1910366,34,26,1.0247004,1.034996692307692,0.6463609999999999,0.9634332 +docx,13290,22,17,0.3237731111111111,0.5883355000000001,0.4116605,0.5465021 +docx,194810,40,43,0.9775000000000001,2.3751303750000003,1.0637855714285716,1.6388126 +docx,15013,27,11,0.24410238461538464,0.34282290000000004,0.25440635,0.29841865 +docx,18819,11,15,0.329199,0.6630351499999999,0.42407079999999997,0.63847515 +docx,10574,25,9,0.23391308333333335,0.23698745000000004,0.22029154999999995,0.20177919999999996 +docx,18541,40,46,0.320776125,0.7610695000000001,0.4823876,0.7257958000000001 +docx,11347,12,11,0.22706,0.25389165,0.1931333,0.19155214999999998 +docx,13207,17,3,0.2368795,0.7803187500000002,0.29702095,0.43947485 +docx,49192,5,3,0.294776,0.4072291,0.30912055000000005,0.23748334999999993 +docx,38113,391,568,1.8607831999999997,3.0556528333333333,1.7260208000000001,4.45491675 +docx,16470,10,5,0.22024407142857144,0.48990195000000003,0.3968875499999999,0.3556165 +docx,59818,216,146,1.0700418333333335,5.297885,2.7548871428571426,6.080125 +docx,39183,199,246,1.1004720000000001,3.4062334,1.8019041999999998,6.256625250000001 +docx,332194,39,48,1.4089723333333335,1.8034307777777778,0.9859858666666668,1.5883377 +docx,28019,63,36,0.4534122222222222,1.0430893571428572,0.6828640526315789,0.9414218750000002 +docx,122197,1232,1445,8.642958499999999,12.425438,6.777833333333334,23.559208 +docx,591919,10,13,1.2275696666666664,0.45586249999999995,0.33778559999999996,0.28104575 +docx,31777,22,35,0.36222916666666666,0.5713104499999999,0.31921885000000005,0.45800634999999995 +docx,15805,44,27,0.28086,0.69645625,0.48936270000000004,0.6447916499999999 +docx,37800,176,142,0.9005420000000001,3.1455626666666667,1.609018727272727,3.6775416 +docx,154289,34,45,0.4327358333333333,1.0251362,0.8257498571428572,0.819307888888889 +docx,38437,276,95,1.2407,2.431643142857143,1.8528379999999995,3.7176168000000005 +docx,26472,270,405,1.2834941428571427,3.55466,1.7133795000000003,5.935062500000001 +docx,59333,32,44,0.49343319999999996,1.1031696428571427,0.962955076923077,1.184913642857143 +docx,16677,121,84,0.5720167,1.457632,0.9698360625000001,1.7700543 +docx,29718,24,28,0.5173664,0.8024144736842106,0.5018436,0.59946465 +docx,18566,20,33,0.32235171428571424,0.6335331499999999,0.35029375,0.5055438000000001 +docx,14100,3,10,0.1743621538461539,0.26130430000000004,0.14233344999999997,0.11527090000000004 +docx,527599,306,333,3.329499333333333,4.071616799999999,2.22720825,6.13976075 +docx,25931,14,25,0.38327777777777783,1.493729166666667,1.148375,0.7922675789473683 +docx,379673,42,27,0.5069166,1.2613846153846153,0.8879927777777775,0.8904777999999999 +docx,60144,46,76,1.1951113333333334,3.2811179999999998,1.8269170000000001,3.9202916 +docx,17558,40,26,0.4423832,1.8057501111111114,0.5496687,0.9906419333333332 +docx,812847,30,46,1.7206253333333335,1.5223256363636362,1.0521992857142857,1.3115796666666666 +docx,12902,25,0,0.21655031578947367,0.36409585000000005,0.22661059999999997,0.17642084999999996 +docx,2138824,69,45,4.1111147500000005,2.7834583333333334,0.8615883125000001,1.9097084 +docx,85656,19,39,1.0607815,0.7171353,0.5223023,0.7880046111111112 +docx,20593,31,0,0.39602612499999995,0.48606454999999993,0.4696668,0.5219437 +docx,12889,60,1,0.41881262500000005,1.7262540999999998,1.2933334166666666,2.901430833333334 +docx,42899,18,33,0.3750555,0.5335646,0.37879815000000006,0.37140209999999996 +docx,35258,25,17,0.7001178333333332,3.0273121666666665,0.51299775,0.9079337058823528 +docx,16765,36,28,0.36269999999999997,0.6171667,0.39551265,0.64187915 +docx,349374,61,94,1.0306870000000001,1.621924272727273,2.102536375,2.206068 +docx,613028,137,130,1.0076663333333333,2.672285857142857,1.3716134545454544,2.366468625 +docx,13835,19,12,0.25983718181818183,0.5089833,0.30900620000000006,0.41197290000000003 +docx,159350,444,214,4.504313000000001,14.8447915,7.428292333333332,20.078208 +docx,31618,634,418,2.82869775,5.397722333333333,2.7517085,10.3002705 +docx,200748,72,233,1.48818775,1.3425624999999999,0.9011735000000001,2.81715625 +docx,63109,47,88,0.8252400000000001,1.4497569166666668,0.8105916,1.8113667 +docx,291536,7326,7351,47.415541000000005,104.47595899999999,38.653124,145.64966700000002 +docx,158439,212,454,6.6640835,12.34825,5.87474975,18.976646000000002 +docx,17131,22,30,0.255787,0.6040853500000001,0.46134365,0.7018374999999999 +docx,120075,213,519,2.7864996666666664,5.95312525,3.2112433333333334,8.950916666666666 +docx,13284,39,90,0.27765527272727275,0.5147042,0.32395645000000006,0.52117905 +docx,39252,28,298,0.6153405,2.7152499999999997,1.2094195714285711,2.1855927777777775 +docx,150294,17,39,0.34929785714285716,0.6383792631578947,0.34729399999999994,0.47411855 +docx,21052,38,35,0.35220344444444446,1.3383784166666668,1.151310111111111,0.9125465882352942 +docx,188489,28,60,0.9991979999999999,1.3121355,0.6941315789473684,1.1314518461538463 +docx,19242,27,9,0.3450925555555555,0.864740294117647,0.43894584999999997,0.6283646 +docx,12769,16,1,0.3351054666666667,0.43709165000000005,0.26567295,0.303127 +docx,71653,155,136,2.1356663333333334,2.783541833333333,1.6629771111111114,2.2055937500000002 +docx,17465,14,0,0.40577957142857146,0.37916875000000005,0.2473563,0.22318955000000001 +docx,14812,8,10,0.2982873333333333,0.57642305,0.26960415000000004,0.50852915 +docx,130735,38,38,0.7868437499999998,1.0581369285714286,0.5996500499999999,0.9483357647058822 +docx,139725,1528,427,5.256479,7.496124999999999,5.15414525,10.6399165 +docx,17038,73,81,0.48974400000000007,0.74449795,0.4452539999999999,0.8511754210526317 +docx,177304,1521,2534,4.7269995,16.007625,9.696083,21.234125 +docx,30925,24,25,0.4151090769230769,1.288355846153846,1.0600575625,1.7404581999999997 +docx,15445,12,1,0.25339715384615386,0.4850937499999999,0.27783345000000004,0.7682477222222224 +docx,79030,1116,1185,4.200916666666667,8.884125,4.2412668,14.3060005 +docx,24959,44,95,0.6713336666666667,1.5036008333333335,0.7911595555555554,1.6284751 +docx,1335917,36,58,4.756583666666667,1.3558332499999999,0.891033875,2.235078375 +docx,156443,1462,707,5.5254585,11.026604,6.104944666666667,15.175458500000001 +docx,77670,29,42,0.6203225000000001,0.8346876250000002,0.62719385,0.8630588823529413 +docx,19997,9,6,0.2756101818181818,0.63405825,0.3330793000000001,0.44531655 +docx,28239,77,55,0.4528512857142858,1.311471230769231,0.84055725,1.428227272727273 +docx,119252,1716,954,6.061396,10.511895500000001,6.643875666666666,14.088916999999999 +docx,26835,49,67,0.45941671428571423,19.41108325,0.8500468235294117,1.371529 +docx,12895,13,16,0.23784615384615385,0.4090813000000001,0.22716675,0.30617920000000004 +docx,30759,45,56,0.5742153333333333,2.6147974285714284,0.8940556666666666,1.8086169 +docx,20884,385,387,1.7030138333333333,3.5286736666666667,1.9773875,6.2552915 +docx,264187,0,2,1.5648471666666666,1.4835230000000001,2.0835,1.3315876000000002 +docx,56807,39,55,0.5096086000000001,1.200887846153846,0.6976137894736842,1.1646637142857141 +docx,480968,64,55,0.7215625,1.3020763333333332,0.7780416249999998,1.232493538461538 +docx,18349,5,4,0.26423599999999997,0.68720005,0.4380624999999999,0.6123354000000001 +docx,13121,13,0,0.2105602777777778,0.40116864999999996,0.21614165000000002,0.2458625 +docx,147152,454,202,1.7290166,5.97800025,2.9073748571428575,5.014677000000001 +docx,26727,43,32,0.6657291666666667,1.32567725,1.4251056153846153,1.4321629090909092 +docx,160328,535,896,3.885639,7.828930333333333,6.160948,12.635979 +docx,37869,163,300,1.2203125000000001,2.0526512500000003,1.3467234545454545,3.025145666666667 +docx,26979,9,2,0.34814580000000006,2.071203777777778,0.8849884444444445,0.9355668000000001 +docx,64145,200,267,1.50433325,3.6794752,1.762907111111111,5.3378232500000005 +docx,1341162,20,42,3.9628550000000002,1.3487500000000001,1.0925969999999998,0.8696718750000001 +docx,31965,46,60,0.6102748,0.960536705882353,0.6112207500000001,1.0871537692307691 +docx,450754,118,106,2.5313893333333333,2.842428428571428,1.7404334000000001,3.0550763333333335 +docx,14836,15,36,0.2860625,0.4526229,0.2965334,0.46109989999999995 +docx,70052,161,177,1.2164689999999998,3.0125209999999996,1.8567749,3.1437569999999995 +docx,96056,1338,1997,7.2759165,18.101208,9.828667000000001,31.277167000000002 +docx,20570,249,250,1.5230532857142858,3.0870278333333334,1.569966272727273,5.2672395 +docx,27954,110,64,0.7652583999999999,1.1022167333333333,0.798142,1.8634445555555557 +docx,34269,38,12,0.5797836000000001,0.8979779411764708,0.4843854,0.8260093888888888 +docx,122167,496,1219,2.6664482499999997,10.097145999999999,5.28940625,14.9349165 +docx,127968,1648,1624,5.070291666666667,9.4603125,5.5312815,15.405750000000001 +docx,54863,44,69,0.46008316666666654,0.8912446875,3.2642498000000004,0.986401125 +docx,781709,141,217,1.938222,3.2167776666666668,1.6052206999999998,4.187349599999999 +docx,1954940,1,10,0.985567,0.8030786666666668,0.4588102,3.0579305555555556 +docx,67985,132,131,1.941125,4.6688168,2.4877438571428567,6.391472 +docx,20469,27,20,0.50622925,0.7470291500000001,0.4977141999999999,0.53671045 +docx,21233,45,28,0.47662525,1.0059895625,0.5601229000000001,2.6219499333333336 +docx,12331,5,0,0.177035,0.3446000000000001,0.18407495000000001,0.12210230000000004 +docx,18715,89,160,0.47554936363636363,0.826860117647059,0.7053493333333333,1.0004533749999998 +docx,16784,26,14,0.4385543,0.5845915500000001,0.4136387777777778,0.6238208 +docx,146767,149,131,7.367958,2.850649,1.5918750999999998,4.295816799999999 +docx,23821,70,78,0.45398214285714283,1.3581944166666666,0.7884138125000001,1.5230660833333334 +docx,157721,21,25,0.5965292,0.45985195,1.0559975,0.5516041499999998 +docx,54737,91,102,1.722806,2.6955416666666667,1.3156290000000002,1.9044817777777778 +docx,20794,41,31,0.5011805,0.9328774117647061,0.6305771500000001,1.1532242307692306 +docx,42771,250,190,3.27097875,5.47788525,3.855975,4.231400000000001 +docx,20033,43,53,0.4920832,0.5821290499999999,0.34055834999999995,0.6992084000000001 +docx,64884,48,36,0.5823681666666667,0.6561835500000002,0.4630999,0.62288755 +docx,19095,19,13,0.2498334444444444,0.6961118421052633,0.4057043,0.50398945 +docx,17486,4,0,0.24730335714285712,0.49559385,0.27895615000000007,0.23183115000000004 +docx,38960,46,87,0.954021,2.0016249999999998,0.9803860666666666,1.403932 +docx,16614,21,36,0.4377407777777778,0.56972505,0.3662353000000001,0.41516030000000004 +docx,73083,1116,1499,3.7423606666666664,8.678652666666666,4.68382275,13.821582999999999 +docx,34997,33,55,0.76148925,0.9367915294117647,0.5881917,1.1639195714285715 +docx,98117,22,27,0.5961979999999999,1.046425,0.571806894736842,0.7611031052631579 +docx,38043,39,70,0.6913084000000002,1.4628296363636364,1.000473142857143,1.6625875 +docx,24713,30,32,0.39624999999999994,0.7908171111111113,0.44350639999999997,1.4724209 +docx,39164,10,7,0.5869062500000001,0.8149297368421052,0.457623,0.7807771176470588 +docx,355653,71,104,1.8484033333333334,2.2743855,1.8559375,3.265534666666667 +docx,33842,21,15,0.49018442857142863,2.05697925,0.8623576470588235,0.9439094705882354 +docx,43203,175,166,0.868646,3.9686584,1.7620665,1.5667418 +docx,80364,174,107,2.0610836666666663,2.989271,1.6685291,2.689499714285714 +docx,269192,21,29,0.8832636666666668,0.7799142777777777,0.5583147499999999,0.8351641764705883 +docx,21383,109,167,0.7679048571428572,1.6432271818181818,0.99778125,2.267842666666667 +docx,11468,56,78,0.3447362222222222,0.7138968947368421,0.44042475000000003,0.763548157894737 +docx,245432,61,89,7.1523959999999995,1.8059537777777779,1.2681321666666667,3.3774583333333332 +docx,11882,12,6,0.18624544444444446,0.32818745,0.16375650000000003,0.13487505000000002 +docx,22305,10,7,1.410958,0.9172325833333334,0.41030834999999993,0.47145205 +docx,12755,7,13,0.20024758823529415,0.39809985000000003,0.21191240000000003,0.24102715000000002 +docx,36040,127,109,0.7764644285714286,2.226479375,1.5501324545454545,2.9617426666666673 +docx,130699,28,46,2.4249723333333333,5.498760749999999,2.31075025,2.4166724285714283 +docx,37185,7,13,0.17304163636363637,0.31563345,0.21698745000000003,0.1911875 +docx,78403,33,22,1.606652666666667,3.2989025,1.060735,1.363030090909091 +docx,289514,8,23,0.846281,0.41168734999999995,0.27396655000000003,0.4489312 +docx,22503,36,18,0.6312442857142858,0.6807374500000001,2.035557625,0.6488977499999999 +docx,2978563,1453,457,27.925437,8.4698335,3.6065916000000002,8.983305333333334 +docx,86407,52,39,1.2802395,3.5705332,1.3602954545454544,2.6485417142857144 +docx,18899,19,21,0.1993214285714286,0.6695958,0.3566084500000001,0.47825830000000014 +docx,141922,19,35,0.5050118571428571,0.7902829473684212,0.6005436000000001,0.9796444 +docx,106045,533,243,2.586402666666667,5.37605225,3.276396,6.309444333333334 +docx,29062,205,2,1.0637384285714286,1.6046402727272726,2.1748885555555555,2.437911625 +docx,636240,14,4,5.502583666666666,4.51779175,5.073396,4.9450105 +docx,15696,22,1,0.2336318333333333,0.6894626500000001,0.4011439,0.4950833 +docx,17291,15,1,0.26330891666666667,0.39225625,0.27502719999999997,0.27167705 +docx,37040,434,786,1.957597166666667,7.314902333333333,3.4241734999999998,10.3402915 +docx,912160,24,32,1.3967399999999999,1.3266910833333332,0.9044740624999998,1.485731 +docx,33381,156,26,0.5376601666666666,1.7283915999999997,0.9227811875000003,1.3090797499999998 +docx,18502,18,5,0.23824015384615385,1.1005110666666666,0.5940519,1.0469819374999998 +docx,63263,155,493,1.43468,4.1894167499999995,2.2181247499999994,5.119229000000001 +docx,108353,449,552,4.634062,7.481375,4.047083000000001,11.788813 +docx,23281,32,25,0.3280026875,0.51646035,0.5629436,0.8877557142857142 +docx,43180,50,62,0.436619,0.7997061999999998,0.6003999,0.7659956315789473 +docx,14298,16,24,0.28981436363636365,0.49062285,0.2720771,0.35724805000000004 +docx,773857,32,83,1.37520825,1.8087822222222225,1.1003717692307693,2.9596459999999998 +docx,314233,2495,1648,10.285854,26.501916,15.2711045,43.087 +docx,632802,163,198,1.0194668000000002,1.8875045555555556,1.0548268461538464,2.7606668571428563 +docx,13871,13,6,0.36589591666666665,0.5824041999999999,0.38095425,0.5083249999999999 +docx,22935,9,4,0.6781775,0.45945195000000005,0.31035625,0.42602710000000005 +docx,23241,32,10,0.500588,0.6099501,0.4518623,0.5304227499999998 +docx,19900,41,44,1.2566110000000001,0.7998819999999999,0.45627074999999995,0.5101852999999998 +docx,62076,87,90,0.851309857142857,3.2336752000000004,1.4524769999999998,3.0321390000000004 +docx,65053,29,26,0.247984,0.5889208,0.3145312,0.46109365000000013 +docx,84704,84,64,1.0505315,1.889782444444444,1.1823174615384617,2.417890625 +docx,72266,283,518,2.005042,3.4263502000000003,4.299566799999999,5.203927 +docx,2630476,27,20,13.1818545,18.9356875,19.399334,19.86825 +docx,16551,31,9,0.23092307692307698,0.6984811500000001,0.4601624,0.5644812999999999 +docx,60976,26,12,1.3659439999999998,2.510553428571428,0.9152473999999998,0.898622375 +docx,37177,33,54,0.62923975,4.7639832,1.6620333999999999,1.6656325454545453 +docx,560635,109,203,0.8658002,2.8620594285714285,1.5142665000000002,3.5086386666666667 +docx,20460,43,15,0.25270175,0.56546875,0.39655235,0.42501250000000007 +docx,154150,48,59,1.0428647500000001,1.0797999333333332,0.5806437500000001,0.9956842857142856 +docx,15550,9,11,0.24928417647058823,0.38278540000000005,0.22767510000000005,0.21514375000000002 +docx,48163,709,744,3.0828746666666667,6.7601526666666665,3.438888666666666,11.357104 +docx,30894,53,58,0.96240625,1.6443207999999998,1.0057021428571429,1.1226964285714285 +docx,262157,4113,876,14.032583,18.294917,12.489187000000001,28.201583 +docx,21402,33,30,0.5323749999999999,0.8989901176470587,0.6067687000000002,0.9302499411764708 +docx,293834,3707,1411,19.7975,33.753625,19.179958,48.685542 +docx,104353,79,125,0.661271,1.0761972,1.03901045,2.1987185 +docx,33602,71,131,0.6217914,2.453781125,1.5486059090909092,2.146083333333334 +docx,46662,25,50,0.42285100000000003,0.8349933684210525,0.6112533333333334,0.8174823684210525 +docx,29672,62,111,0.51515,1.0747470000000001,0.9163159999999999,1.6058166999999997 +docx,90797,75,101,2.5375,1.4268144545454542,0.796004882352941,1.451185727272727 +docx,112942,40,41,0.9521663333333333,4.0868418,2.12304175,1.7155667 +docx,77468,60,31,0.6661412,0.9701328124999999,0.59982285,0.8483933749999999 +docx,16528,13,12,0.2307003,0.5952522,0.29528135000000005,0.34363950000000004 +docx,17395,40,45,0.3318455,0.6666339473684211,0.9412874999999999,0.7922389473684209 +docx,1278177,33,40,1.0261949999999997,1.40567025,0.7655878888888887,1.3618366666666668 +docx,23239,100,46,0.7096803333333334,1.4475983636363634,0.831229125,2.007009222222222 +docx,22348,39,48,0.3502831999999999,1.4395485833333332,0.719397,1.0785110666666666 +docx,11399,6,9,0.3853204,0.24229794999999993,0.27951479999999995,0.337198 +docx,74584,20,10,1.7486803333333334,2.918458428571429,1.0012751333333334,0.9058542999999999 +docx,52759,194,196,1.2485252,4.573583,2.268531375,5.4188855 +docx,169872,183,214,1.69503125,4.1649334,2.7897682857142856,5.895 +docx,18001,28,38,0.34488562500000003,0.65579585,0.38814585,0.57583335 +docx,15784,13,12,0.3154711,1.30870825,0.29341435,0.43859795 +docx,10890,26,9,0.26082649999999996,0.23946245,0.24937500000000004,0.2070625 +docx,15577,38,39,0.29021199999999997,0.6880146499999998,0.41060215,0.5642083 +docx,57204,78,73,1.2493859999999999,1.298038,0.8366416666666667,1.122982142857143 +docx,601004,117,231,3.1817083333333334,10.365874999999999,4.0969752,9.36875 +docx,451820,36,66,0.9950546666666668,1.7598251,0.8955833125000001,0.8147130555555555 +docx,70641,15,38,1.54645825,0.842181411764706,0.7293666999999998,1.139467923076923 +docx,65954,14,17,1.2319170000000002,2.0618125,0.5126583499999999,0.7431771500000001 +docx,38321,31,11,0.446509111111111,3.5673165999999994,1.4338559999999998,0.9403888666666667 +docx,73021,20,6,1.3148753333333334,2.906034833333333,0.9370027333333334,0.8860833125 +docx,49352,10,15,0.5569685,0.57022705,0.31600225000000004,0.42224380000000006 +docx,28235,30,37,0.5035497999999999,1.6885375999999996,0.7637985555555558,1.0092388 +docx,132108,931,847,7.2131045,8.121083333333333,6.176302000000001,17.415437499999996 +docx,600771,260,757,5.20575,4.8434165,2.8652416,8.320666666666666 +docx,1875533,18,45,2.5325143333333333,1.5797582,2.824986666666667,1.4455833333333334 +docx,46446,255,323,2.27519775,3.7983918,1.9882035555555555,4.8638125 +docx,24542,42,47,0.5507974285714285,1.1924647692307688,0.9852960526315788,0.9769530624999998 +docx,86376,42,16,1.06554175,2.9238468333333336,1.3412604166666664,1.4680114545454546 +docx,23163,67,42,0.347354375,1.0553416,0.7036936,1.118949357142857 +docx,17152,10,5,0.2613237777777778,0.4959313500000001,0.3065708,0.27129784999999995 +docx,715588,10,19,22.709042,1.8514292,19.307040999999998,1.0113667333333334 +docx,106339,127,139,1.0134026666666667,1.9353084000000003,1.1351904285714285,1.856685111111111 +docx,21927,46,43,0.8214445,1.00322675,0.7113746999999999,1.199971230769231 +docx,14698,23,9,0.2692916666666667,0.5519249500000001,0.28924985000000003,0.44559994999999997 +docx,40463,116,161,0.8212170000000001,3.0233403333333335,1.1253155714285714,2.4662977142857145 +docx,28545,43,11,0.8007501999999999,0.7128355263157895,0.608975,0.6956734444444443 +docx,22151,30,33,0.316587,0.5158917000000001,0.3316542,0.5281833000000001 +docx,71460,24,27,0.7560693333333334,3.1581736666666664,1.4541594166666665,1.3902689090909093 +docx,41954,44,65,1.1395556666666666,1.152966666666667,0.6766688421052633,1.3559280000000002 +docx,17127,9,4,0.26795831249999996,0.49428540000000004,0.6462147500000001,0.18886665 +docx,161992,1359,3504,7.534562,23.78875,12.0728965,34.8165 +docx,66158,25,65,2.4729795000000006,2.021379444444445,1.1914641428571429,2.988928571428571 +docx,28505,16,16,0.5136668333333333,1.2644515000000003,0.52881875,0.6714937 +docx,28195,95,214,0.8593417999999999,1.5250498000000001,0.8885245882352941,2.0460045555555557 +docx,14340,41,45,0.28768172727272723,0.8075393888888889,0.663673,0.6850688 +docx,35816,395,307,1.2126666,2.5551844285714287,1.6016327272727275,4.142208800000001 +docx,72090,22,34,1.02975025,0.6543582000000001,0.45307305,0.6917259473684212 +docx,78392,45,51,0.5497248,1.0823804666666665,0.6338435499999999,0.953472882352941 +docx,17296,43,34,0.530833125,0.7840463888888891,0.48801880000000003,0.7195571052631579 +docx,16372,87,117,1.9734180000000001,1.12348425,0.5271001,1.1235636666666664 +docx,14009,99,140,0.7324208000000001,0.7177874999999998,0.42490415000000004,0.9184400625 +docx,15745,49,60,0.555361,0.8288026842105264,0.4993188,0.9135073529411765 +docx,269180,168,149,0.88458275,2.075972222222222,1.245810923076923,2.7268214285714287 +docx,19700,22,0,0.29807633333333333,0.9428360625,0.48170830000000003,0.7324955789473684 +docx,26807,63,63,0.5821086,1.0915081333333336,0.73936455,1.081166625 +docx,323252,40,54,0.75188525,0.9122544444444445,0.5648603500000001,0.9817162499999998 +docx,48246,82,320,1.7533613333333333,4.838531,2.854784666666666,7.8249863333333325 +docx,24132,27,23,0.38799475,1.0100026874999999,0.71031665,1.1308483571428571 +docx,59233,57,106,0.5894082,1.2136249230769232,0.7813773333333334,1.4353645833333335 +docx,29044,18,25,0.3680235714285714,1.0162739285714286,0.48537705,0.5547978500000001 +docx,187847,378,336,3.1927916666666665,7.297083666666666,4.685625,10.0904585 +docx,13997,16,5,0.18446079999999998,0.3076605499999999,0.19122924999999996,0.2211667 +docx,16608,78,11,0.8843612222222221,0.9576276470588235,0.5699123000000001,1.0725158125 +docx,17217,10,16,0.2292433157894737,0.6256188,0.3873062,0.5884854499999999 +docx,71837,68,65,3.580347,7.734944666666666,3.080958166666667,2.527339428571429 +docx,18159,28,0,0.2578066363636364,0.7604167000000001,0.4829563499999999,0.7471603500000001 +docx,69311,163,325,9.1451045,8.877527666666667,5.3791975,14.255271 +docx,1036231,25,43,0.694158,0.6057895999999999,0.4238562,0.952226052631579 +docx,63229,78,70,1.4320205000000001,2.4415834999999997,1.3414743076923077,4.3095332 +docx,35173,86,22,0.8623752857142858,1.3023683846153846,1.0734109285714286,1.8816204444444442 +docx,22293,176,204,0.532526125,2.1522449999999997,1.2038973076923074,2.20299475 +docx,109937,57,65,1.351236,1.4326805833333334,0.9221119375,1.7585832 +docx,15617,65,84,0.4111099090909091,0.8032061052631578,0.4875560500000001,1.002639705882353 +docx,62495,27,25,0.641052,1.1608302857142856,0.6165613684210526,1.0879076428571426 +docx,42704,136,204,0.8878167999999999,3.9896586,1.20233,3.4998332 +docx,30665,28,29,0.5700415555555556,1.2598108461538462,0.6399665,0.850757875 +docx,17168,26,34,0.34693077777777775,0.5069397,0.29591044999999994,0.52011885 +docx,135353,56,261,0.9292814999999999,2.072592666666667,1.3021877,2.448029857142857 +docx,24712,67,106,0.4117082857142857,1.0400445999999999,0.6703582,1.3913711818181818 +docx,20395,151,40,0.607395875,2.166052,2.07697925,2.392890875 +docx,40906,251,249,1.3930334,4.1312252,2.004273111111111,5.2979165 +docx,62121,138,201,1.2146244999999998,3.6444082,2.0724467142857144,3.515673666666667 +docx,33380,26,32,0.6349238333333334,1.1548838571428568,0.6953647000000001,0.8054209473684211 +docx,57299,21,7,1.1368196666666668,2.3468280000000004,0.7901665,0.824819388888889 +docx,74770,26,23,1.5086667499999997,7.269444333333333,1.3594269166666668,1.6286043 +docx,48070,168,394,1.2934375,1.6916792999999999,1.0616748000000003,2.8989345714285717 +docx,29109,162,137,0.5591945555555555,2.4164896249999996,1.6632116153846153,2.272442625 +docx,781760,3,2,0.5261787142857142,0.51188145,0.3155,0.26065409999999994 +docx,1288095,140,157,1.6842812500000002,2.062609375,1.1488195833333334,3.363771 +docx,64550,1025,1152,4.312069666666667,11.1357915,5.22552075,14.162833 +docx,15543,16,26,0.27503,0.43673550000000005,0.30116875000000004,0.35552095 +docx,30042,30,81,0.60154175,0.9906026923076924,0.5737582999999999,0.9878880625 +docx,10977,33,12,0.24310584615384614,0.27000615,0.21995204999999998,0.3880812 +docx,215248,134,289,1.6495624999999998,3.7404666,2.609619285714286,5.741896 +docx,21450,20,1,0.38080942857142863,0.6969647499999999,0.5664458,0.6035248500000001 +docx,442692,316,416,1.75878125,5.3144374999999995,3.0587498333333336,6.347041666666667 +docx,79145,30,23,0.8161806666666666,0.8364556875,0.5313897,0.980101625 +docx,16540,56,1,1.4969303333333335,1.01560425,0.6817458499999999,1.0154387999999999 +docx,9332,87,49,0.33795828571428566,0.34836254999999994,0.27131665,0.36260624999999996 +docx,23066,60,63,0.3502536363636364,0.8031731578947369,0.5669541,1.0684194 +docx,14757,54,69,0.42659845454545453,0.8033009444444444,0.48781854999999996,0.9983945333333335 +docx,18557,16,6,0.3035288461538462,0.6377125499999999,0.33448939999999994,0.43167919999999993 +docx,109759,561,128,2.4164475000000003,5.264103999999999,3.929633199999999,9.29375 +docx,63065,28,27,0.498281375,1.0534834,0.65584815,1.2658680833333333 +docx,29127,174,157,0.6401,1.9795926666666663,1.191849230769231,2.628166428571429 +docx,19356,56,107,0.5801876666666667,1.0289276666666667,5.866319666666667,1.4051285 +docx,141585,41,46,0.6864162,0.6672895499999999,0.49189365000000007,0.7289519500000001 +docx,52240,153,446,1.3544685,2.951284833333333,1.7577002,3.4442336000000005 +docx,57870,43,50,0.7760727500000001,1.5490459,0.8071397647058823,1.0742693076923078 +docx,23233,60,90,0.4886561249999999,1.3148690714285716,1.1997968749999999,1.3395970000000001 +docx,23316,95,117,0.67792725,1.192551153846154,0.62488525,1.312312333333333 +docx,17722,22,8,0.30433333333333334,0.8894837777777779,0.44777290000000003,0.6071853500000001 +docx,18793,34,59,0.3237318571428572,1.0013488750000001,0.5579500499999999,0.824539294117647 +docx,14857,16,14,0.24999645454545452,0.4801187499999999,0.2671583,0.38553335 +docx,58305,44,29,0.6969166,1.6406959,0.9420625,1.2921923846153844 +docx,26941,40,80,0.5372250000000001,0.99807025,0.6377346315789474,1.0694137142857143 +docx,14085,38,45,1.4159345714285716,1.4766551111111115,0.5760769,0.9999528666666665 +docx,22116,31,13,0.39594275,1.0643527333333336,0.6515505263157894,0.9640938750000001 +docx,105074,455,502,3.111153,8.32354175,7.663250000000001,8.606125 +docx,677849,1797,1811,12.930459,19.967334,11.562521,33.749292 +docx,689762,248,221,2.617854,4.7683747499999996,3.7196416,5.258260249999999 +docx,19567,30,33,0.765031,0.7674912631578946,0.55202515,0.9492988823529409 +docx,1424714,42,69,1.6083246,1.044541625,0.7529753529411765,1.5355373999999997 +docx,1272879,21,5,1.52845875,0.6147040499999997,0.50933125,0.6956596666666667 +docx,36127,75,41,0.8065066666666668,1.2039762142857142,1.1597367368421054,1.5483407272727272 +docx,9367,20,11,0.15922050000000001,0.23394379999999998,0.2297478,0.2456334 +docx,44779,20,58,0.31849971428571433,0.4242355,0.2862876000000001,0.40775825 +docx,26925,166,223,1.5926420000000001,2.973994142857143,1.413341,4.4860918 +docx,29598,147,144,1.0436662,2.267823,1.1295226923076922,3.716325 +docx,29653,20,5,0.581073,0.5677645999999998,0.32252705000000004,0.4764062000000001 +docx,109612,96,79,1.66677075,3.83238525,1.93013,3.2193331666666665 +docx,31749,448,372,1.8924236666666665,4.3404752,2.0787862500000003,6.738555666666667 +docx,33085,12,25,0.31890983333333334,0.6046225294117648,0.39665834999999994,0.5825102999999999 +docx,128438,24,30,0.4235626666666667,0.5700333500000001,0.37212914999999996,0.52476875 +docx,14570,11,4,0.3543409090909091,0.56630205,0.28837070000000004,0.43607914999999997 +docx,38780,46,103,1.1065654285714286,3.4567501999999997,2.046041777777778,5.070322750000001 +docx,36669,32,86,0.5479168000000001,0.8488496111111111,0.4993855,0.7711062000000001 +docx,463553,6,12,0.5695205,0.7674341052631579,0.44763555000000005,0.5176792000000001 +docx,24101,25,28,0.6328952500000001,2.50179175,0.38515625,0.707715 +docx,109909,32,81,0.7177996,1.3136457499999998,0.8374929444444443,1.283544769230769 +docx,74042,87,139,1.0036664,1.8828424444444443,0.9638776666666667,2.14123975 +docx,253327,68,138,4.5203545,4.54843775,2.696660857142858,6.443639 +docx,68674,226,181,2.7037359999999997,3.2378121666666666,1.8560184444444445,5.38312525 +docx,318993,39,65,0.5446316666666665,2.9729026666666667,0.6202254,0.9023957500000002 +docx,31588,25,30,0.4782664,0.827633894736842,0.5360602999999998,1.0834732142857144 +docx,42610,130,111,1.7332783333333335,2.8229224285714283,1.7639415,6.295777666666666 +docx,25700,59,71,0.43570828571428566,1.2441635384615386,0.760572105263158,1.0710915333333335 +docx,103154,35,24,0.6401547142857142,0.63778335,0.4065792499999999,0.5471895499999999 +docx,468904,7,38,0.3469047142857143,0.57628335,0.38485225000000006,0.46540419999999993 +docx,23791,17,26,0.48342725000000003,0.7949213888888889,0.41595429999999994,0.7778508947368422 +docx,33394,22,30,1.2113171999999999,2.27632325,1.1802409285714284,3.5645140000000004 +docx,65907,204,145,1.9695276666666668,2.4460475714285717,1.6171336,3.7462418 +docx,43961,42,208,0.854770875,3.3932155,1.8182082222222224,4.52713525 +docx,83413,35,16,1.0182499999999999,3.056339285714286,1.23370475,1.37657975 +docx,26201,77,47,0.7061919999999999,1.1775834285714286,0.7885857222222222,1.512610090909091 +docx,54551,39,38,0.5892999999999999,1.1606763076923075,0.901802,0.9164135384615384 +docx,61156,27,12,1.8645416666666668,2.4649045714285713,0.9166442,0.9167915333333333 +docx,46353,103,91,0.7588746000000001,3.7209918,2.0266112222222223,3.256527833333333 +docx,21939,81,158,0.9404834,1.140949357142857,0.8390587647058823,1.8668472222222223 +docx,509956,1125,728,5.034125333333333,9.2247915,5.56125,19.992812999999998 +docx,132939,26,0,0.24877876923076922,0.37665209999999993,0.30959575,0.3580603000000001 +docx,94307,274,119,1.3838332,2.31032275,5.267875,3.4762915999999997 +docx,21668,54,46,0.4200472499999999,1.116197066666667,0.6780478499999999,1.1419762142857142 +docx,48733,28,48,0.5915416666666666,1.0552474,0.6748032777777777,1.3042881666666668 +docx,306467,12,34,0.3827145714285714,0.6663520999999999,0.3635081499999999,0.49927089999999985 +docx,17104,46,52,0.3973503,0.9123700588235293,0.6065737647058824,1.0916668 +docx,74189,19,33,2.00225,1.5684,0.6832185,1.0625625 +docx,16308,21,4,0.27366324999999997,0.6141369285714285,0.4230332,0.5558687999999999 +docx,15780,5,1,0.22684643750000003,0.3318104,0.22339160000000002,0.21011044999999995 +docx,17038,19,22,1.2555892857142859,0.6474540999999999,0.46832910000000005,0.63578745 +docx,97296,90,143,0.9246665000000001,3.690507,1.4815414166666667,5.094520999999999 +docx,18568,20,33,0.30934514285714293,0.6509563499999999,0.3875875,0.49636249999999993 +docx,17247,13,0,0.15018205263157894,0.36034360000000004,0.22255850000000002,0.18618135 +docx,62865,357,425,2.3765205,4.75337475,2.8440119999999998,6.143611333333332 +docx,17157,21,15,0.3755917,0.6264999,0.3804146,0.57915425 +docx,26803,25,37,0.81965625,0.9108161764705883,0.5980521499999999,1.1304833333333333 +docx,22469,70,7,0.45321825000000004,0.9923359375,0.6558875000000002,1.1681249999999999 +docx,22770,195,187,1.1313676666666665,2.6563691428571423,1.495534181818182,4.3319334 +docx,28104,113,108,0.5535695,1.2084464285714283,0.7649092352941177,1.4338636363636363 +docx,15949,26,12,0.3298749,0.5619833000000002,0.2994084,0.45932695 +docx,87886,85,79,0.7614477500000001,1.2387084615384614,0.8398100000000001,1.5361212727272724 +docx,22234,19,18,0.4422826666666666,0.6567729499999999,0.7592961578947368,0.5951542499999999 +docx,19000,32,29,0.4742015,0.49187499999999995,0.37330839999999993,0.5055457500000001 +docx,50217,116,166,1.5943053333333335,2.859398857142857,1.4835721818181817,4.5162081999999995 +docx,39008,29,29,0.4007334,0.43091665,0.3714980499999999,0.51176035 +docx,68032,80,82,0.8314443333333333,2.034648111111111,1.1358813846153848,2.3243125 +docx,18003,45,33,0.3231041,0.8549931111111112,0.50326435,0.8763403333333333 +docx,16925,51,66,0.337121,0.57207925,0.36086014999999994,15.502021 +docx,150375,36,49,1.6007086666666666,8.851639,0.9827168666666668,1.0368554666666667 +docx,14792,21,28,0.29772509999999996,0.6988292499999998,0.3585185999999999,0.5394520500000001 +docx,14193,29,29,0.3385474285714286,0.45742084999999993,0.24119375,0.3361186499999999 +docx,83899,666,803,2.39999,5.8099895,3.4580625,9.483625 +docx,24162,34,42,0.43495811111111116,1.8521249000000002,0.6912869444444445,1.0596804 +docx,39527,44,59,0.5428336666666665,0.64050215,1.5150685,0.8345208124999999 +docx,13636,30,30,0.3255313333333334,0.6957291000000001,0.4068875,0.8229277368421053 +docx,99543,18,23,0.8981663333333333,0.7231495294117648,0.51193115,0.5577437999999999 +docx,22869,130,55,3.3336869999999994,1.8176,1.0511247692307693,2.3488331428571425 +docx,103565,288,468,1.25884375,3.6920916,1.8199767777777778,4.95629175 +docx,24322,12,13,0.7997738571428572,0.7609896,0.22382914999999998,0.28383125 +docx,44002,44,44,0.35111975,1.1914423076923077,0.6180333,1.0249406428571428 +docx,89356,387,1315,4.508722333333334,10.2734585,5.039562500000001,16.691395999999997 +docx,32200,20,6,0.4289240000000001,1.32128475,0.59337715,0.6208395999999999 +docx,36366,36,43,0.8346,0.6036104999999999,0.4472061,0.7999035263157894 +docx,225485,14,2,0.238396,0.3368375,0.22986879999999998,0.24232294999999993 +docx,93116,25,27,1.0212705,1.7972363333333332,0.8203135294117647,0.8991740588235295 +docx,15721,18,12,0.34270133333333336,0.32127909999999993,0.26027485,0.28271674999999996 +docx,266375,471,566,4.617139666666667,4.336216800000001,4.0461668,5.6912502499999995 +docx,38119,42,41,0.85017675,1.5694507272727272,1.4253000999999998,1.29805925 +docx,109379,97,74,1.2023616666666668,1.9615832222222223,1.047131384615385,1.7509583333333334 +docx,41615,344,362,1.8555833999999998,3.2901179999999997,2.0921041249999996,5.155948 +docx,797537,78,83,8.093583,16.495625,12.543271,12.2979585 +docx,38251,60,81,0.5804235000000001,1.2155514615384617,0.6724647000000001,1.4443509166666668 +docx,3197318,3503,4017,34.201291,24.353458,15.1615005,40.358167 +docx,15911,74,68,0.519010625,0.8606936470588235,0.56068115,1.2900345833333335 +docx,193926,87,150,1.0778893333333335,3.4926084000000004,2.109130375,2.8297437142857143 +docx,104949,15,20,0.6374416,0.7163708499999999,0.35630210000000007,0.5380938500000001 +docx,117556,734,433,2.9613955,6.227638666666667,4.001583,8.106472333333334 +docx,27430,113,65,0.574687625,2.083020875,1.1747605833333334,2.4103958750000003 +docx,21195,52,27,0.6942430000000002,0.661349611111111,0.4332060999999999,0.57233975 +docx,23151,147,16,1.0124705714285713,1.1387025714285712,0.7857720588235293,1.348611166666667 +docx,21579,28,62,0.32248437500000005,0.8228377368421051,0.6123334999999999,0.8529890000000001 +docx,12553,11,25,0.2208245,1.2745624285714288,0.4864419,0.44518745 +docx,21839,32,36,0.318389,1.1017362,0.49032300000000006,0.7649235555555554 +docx,19620,184,205,1.2087738571428572,2.665702714285714,1.3349846363636362,4.1038082 +docx,60190,172,215,1.7197776666666666,2.5173632857142856,1.6306251818181818,3.8037917500000002 +docx,42936,273,269,0.9509513333333333,3.5757832,1.7004001,4.031258200000001 +docx,91718,271,552,23.716791999999998,32.717875,15.7310835,64.745666 +docx,44081,45,51,0.4430916,1.0245304000000002,0.7297581500000001,1.1071442666666667 +docx,14786,7,6,0.6030150909090909,0.5466374,0.3724375499999999,0.34939794999999996 +docx,22830,22,44,0.2866840833333333,0.8031184736842106,0.8963377000000001,0.6781207368421052 +docx,19510,13,42,0.5063746666666668,0.8118682222222222,0.4872354499999999,0.7328662631578947 +docx,26405,112,307,0.734208375,1.5353371818181818,0.8631030588235292,2.4622029999999997 +docx,37958,75,72,1.018,0.9270387999999999,0.6962958999999999,1.758146 +docx,16016,23,6,0.8901916,0.63872085,0.37177905000000006,0.56630835 +docx,692195,2,3,0.39865266666666666,0.27962709999999996,0.20729794999999998,0.15967919999999997 +docx,10949,6,15,0.18400953846153845,0.28962930000000003,0.2144625,0.20570210000000003 +docx,2707406,0,8,1.3888396666666667,0.5178875500000001,0.4093499499999999,0.4733750499999999 +docx,20145,30,36,0.2965835714285715,0.7306916,0.42755845,0.7983102222222223 +docx,35462,125,313,1.9439375,3.320625,1.7520626,5.17445825 +docx,13719,16,6,0.23963263636363638,0.46912699999999996,0.2562625,0.2612604 +docx,10014,10,0,0.16847584210526315,0.1565728,0.16562074999999998,0.11199374999999998 +docx,24094,91,105,0.44792300000000007,0.9798247999999999,0.5871270500000001,0.921604125 +docx,8637,8,0,0.14593740000000002,0.14634579999999997,0.14017905,0.0857334 +docx,228508,668,1305,20.801084000000003,7.248083333333334,3.766825,15.581604 +docx,71684,18,8,1.3629586666666667,2.701416714285714,0.7535394705882352,0.8126527777777778 +docx,59473,53,41,0.74379125,1.0587144285714285,0.722134388888889,1.1493837857142857 +docx,20528,37,12,0.573229625,1.2813524615384613,0.8055390555555555,1.7377500000000001 +docx,83115,32,16,0.95241625,2.8226844285714283,1.2344585000000001,1.3177292500000002 +docx,27042,206,198,1.9091334,3.0632291666666664,1.5253404166666664,5.02344775 +docx,57657,61,124,0.80097925,2.153895875,1.1613263333333332,1.9675885 +docx,18756,53,72,1.061501,0.922561411764706,0.5564955,1.4640598125 +docx,43136,224,90,1.007525,2.895694666666666,1.535412909090909,2.8505835714285714 +docx,30905,25,22,0.4697,2.5595058571428573,1.0162531428571429,1.2744807692307691 +docx,32275,20,139,0.5957705,1.127967285714286,0.6185311,1.0488916000000001 +docx,21803,51,52,1.036472,1.0886340714285714,0.7183817777777778,1.2910588333333335 +docx,31454,14,2,0.46323842857142855,2.1213841111111114,0.70063335,0.751229 +docx,45368,398,359,1.6606147500000001,4.3322252,2.4693125,8.65325 +docx,17484,32,59,0.367953625,0.57196255,0.46085282352941187,0.6241459 +docx,26578,40,66,0.8943329999999999,1.1196845714285717,0.6341606000000001,1.1975031538461542 +docx,204772,69,107,2.4104303333333337,4.7590835,2.299683857142857,6.578027666666666 +docx,47879,305,284,1.5400485000000002,4.5770748,2.584178714285714,7.042069333333333 +docx,16860,18,34,1.330494,0.6384728499999999,0.41498545,0.7323625500000001 +docx,79846,268,464,4.722027333333333,8.899888666666667,4.206724799999999,12.762 +docx,56164,25,21,0.517242,0.8525925555555556,0.5099228499999999,0.7663113157894738 +docx,56274,116,204,1.1184794999999998,1.9010186666666664,1.5004621818181818,2.9241847142857145 +docx,16504,30,26,0.24697070588235293,0.4842584,0.26319800000000004,0.3326188499999999 +docx,127202,104,70,0.7179584999999999,1.5665983636363636,1.0657886153846152,2.122698125 +docx,16186,11,6,0.2315001818181818,0.66847495,0.3813417,0.42149780000000003 +docx,57454,33,60,0.7077083333333335,0.8858627058823529,0.5413666499999998,0.9540344705882352 +docx,21713,63,44,0.38583355555555554,1.1530537142857145,1.0583980555555554,1.0855554666666665 +docx,169235,23,19,0.36311974999999996,1.1212380714285712,0.4491478500000001,0.7427084210526316 +docx,18353,8,10,0.4567083333333334,0.45456474999999996,0.2784147,0.40377925000000003 +docx,103986,381,417,3.215958,5.6938645,3.3857225,7.578486000000001 +docx,21682,17,2,0.3975045555555555,0.6037667000000001,0.39604990000000007,0.3877148947368421 +docx,103931,15,12,2.437305333333333,1.4968674545454546,0.7471095263157895,1.4693827272727276 +docx,17182,14,18,0.32090711111111114,0.63786255,0.3724855,0.6696374999999999 +docx,105899,2106,125,5.998666999999999,21.682334,10.124958000000001,33.581792 +docx,130027,40,68,0.7200314999999999,1.1900237142857144,0.6435878947368421,0.9403671249999999 +docx,164564,1081,1178,5.511082999999999,14.9832505,7.848860999999999,24.212125 +docx,13413,19,2,0.23762153846153838,0.49421445,0.60213325,0.4095894500000001 +docx,45518,355,346,1.8739336000000002,4.97819775,2.7357708333333335,9.2548335 +docx,8004,12,4,0.14858110000000002,0.15967085,0.139725,0.09241255 +docx,30228,87,70,0.6785178571428572,1.8375206999999996,1.3430512307692308,2.3262603750000004 +docx,46787,38,95,0.6783664,1.697426,1.0827796428571428,1.9427963333333333 +docx,41204,277,439,1.1954467142857144,3.4378124999999997,2.167984625,5.0658745000000005 +docx,116970,937,813,5.6532505,9.300562500000002,5.23104225,10.388562499999999 +docx,57367,181,191,4.098125,1.7757586000000003,0.8526457222222222,3.1741596666666667 +docx,277878,426,739,3.3014385,7.535263666666668,3.7025164000000004,11.566479000000001 +docx,57234,123,137,0.9466106666666668,3.1544585,2.975756285714286,3.2849513333333333 +docx,246752,837,860,7.414875,19.579791,6.083235666666667,16.185896 +docx,170110,50,59,1.8859579999999998,1.44198275,1.0271390000000002,1.1765685714285714 +docx,725044,89,237,2.255333333333333,3.3240975,2.874347,4.2284664 +docx,80994,21,8,1.278125,2.680077285714286,0.806625125,0.8052205294117647 +docx,16680,55,70,0.39872933333333327,0.9055241052631582,0.41588672222222217,0.6099498999999999 +docx,88080,73,78,0.41700537499999996,0.48878125,0.3587458,0.7071582000000001 +docx,35388,32,31,0.6755298571428572,1.5226134545454544,0.846818588235294,1.4740530909090912 +docx,82221,176,265,4.007708333333333,4.4763834,2.2897293750000003,5.351427 +docx,48826,85,95,0.595367,2.9837286666666665,0.8626429374999999,2.423255 +docx,906132,9005,11615,58.408041999999995,124.060208,62.115583,209.525417 +docx,337757,31,35,1.8367499999999997,1.9871341111111112,1.7532588888888887,2.014102 +docx,243948,11,13,1.14818,2.9225971666666664,1.1190535,1.627573692307692 +docx,72713,106,32,0.8271875000000001,3.0995554999999997,1.4709621818181817,3.3145624999999996 +docx,903243,30,32,0.6871806666666668,0.66149595,0.38608970000000004,0.7127293000000001 +docx,79935,463,1231,2.5091565,11.520208499999999,6.3982643333333336,11.7178125 +docx,15570,8,9,0.2530955,0.9455138333333333,0.5664792000000001,0.7212062499999999 +docx,20143,19,17,0.2537307692307692,0.6305917,0.32491875000000003,0.36086445000000006 +docx,882938,41,32,1.21552075,1.2627917499999997,0.7296665882352942,1.0307115384615384 +docx,21588,46,46,0.610656,0.8722685000000001,0.54539585,1.030776125 +docx,20021,18,1,0.3316043,0.8607104000000001,0.5173519999999999,0.7781692352941176 +docx,482782,15,10,12.7622705,110.55775,1.140352866666667,62.880542 +docx,85887,181,143,1.301528,5.5104375,1.8109070000000003,3.6061666000000003 +docx,2531368,58,9,2.15020825,1.258621846153846,1.1663915999999999,1.7079165714285711 +docx,76717,424,94,1.805000333333333,3.5377708333333335,2.735279714285714,5.8280525 +docx,12861,2,1,0.17565900000000004,0.3702853,0.1656355,0.1306811 +docx,29317,47,47,0.49785437499999996,1.1944821428571426,0.993919642857143,1.8459234999999998 +docx,164966,101,111,0.7847252,1.8451156666666668,1.7014863333333334,2.6856905714285717 +docx,63106,100,44,1.3435133333333333,1.800490666666667,1.26535425,2.6582202857142856 +docx,11305,30,9,0.27122754545454547,0.24831674999999995,0.25568555000000004,0.24153765000000002 +docx,16810,48,15,0.27243554545454546,0.5381791,0.55873755,0.8663803125 +docx,28374,29,7,0.5595205,2.1987759999999996,0.92823175,1.5255186666666667 +docx,187282,2782,1738,12.791167000000002,29.436375,14.853687999999998,75.488083 +docx,50429,11,9,0.4719670000000001,1.073031923076923,0.4194207,0.4796563 +docx,14128,14,9,0.30113025,0.29978119999999997,0.36849775000000007,0.28375625 +docx,85374,27,37,0.5245141666666666,1.0726306666666665,0.7185898947368422,1.371787909090909 +docx,23448,91,132,0.5891665714285714,1.488068090909091,0.7635685882352939,1.4689616666666667 +docx,170455,23,6,0.5956,0.9577473333333333,0.51974355,0.7107000499999999 +docx,127220,147,181,1.8288473333333333,3.0946113333333334,1.6103417999999998,4.99937525 +docx,12892,52,101,0.45178542857142856,0.7725854736842104,0.4710478,0.5774041999999999 +docx,538708,47,70,0.6715164,1.3062692307692307,0.8082718823529413,1.626338375 +docx,18403,43,2,0.45651366666666665,0.524025,0.3233539499999999,0.47994364999999994 +docx,12386,7,0,0.1766761111111111,0.5176666000000002,0.18831469999999997,0.15403535000000002 +docx,13484,10,7,0.2073723529411765,0.30133960000000004,0.20096665000000002,0.25815620000000006 +docx,116514,56,55,0.7380625,1.3326732307692308,0.7905989374999999,1.2118889166666669 +docx,64245,15,17,0.9660464999999999,0.57741675,0.3630771999999999,0.64604995 +docx,110534,44,48,0.451125,0.7588333157894738,0.50308535,0.8449188888888889 +docx,35410,19,11,0.4468275714285714,0.47815434999999995,0.46283140000000006,0.4672393500000001 +docx,334744,53,49,0.646775,0.8898652352941175,0.6708771,1.0454731428571429 +docx,65855,54,91,1.6830280000000002,2.636893,2.2924063749999997,3.1674860000000002 +docx,24336,31,64,0.35428427272727264,0.8797206470588236,0.6764290000000001,1.2506089230769233 +docx,858463,206,161,2.7447913333333336,4.8414065,2.292791285714286,4.5593332 +docx,102749,36,51,0.541562,1.226875076923077,0.822395611111111,1.318173 +docx,200125,66,82,0.5096198749999999,0.9658933124999999,1.3285693333333333,1.269157 +docx,63858,296,298,1.811500333333333,5.2816875,2.9979583333333335,7.304417000000001 +docx,52871,33,43,0.4056004000000001,1.0481665999999998,0.6517748999999999,0.9583830666666667 +docx,914124,25,60,1.9351946666666668,2.655220285714286,1.2255736153846155,3.225597333333333 +docx,94783,22,35,0.6555835,0.8429901176470589,0.8391945333333334,1.1224791666666667 +docx,32041,59,28,0.36200725,1.1473813076923076,0.7378223157894737,1.5745304545454544 +docx,52501,81,58,0.5944592,1.4856704545454547,0.9183175625000001,1.1234005384615384 +docx,65662,100,59,1.7051530000000001,3.0704096666666665,1.6343958,1.9977964444444443 +docx,46413,20,5,2.724067,1.2749903846153845,0.6365167,0.5732853 +docx,51137,55,78,0.9333332499999999,2.833488142857143,0.8090002222222225,1.205247 +docx,40582,12,11,0.5115500000000001,0.750556947368421,0.4964917999999999,1.9892207999999996 +docx,41086,546,241,1.6220970000000001,4.0609418,2.8664404285714284,5.6016565 +docx,122435,38,56,0.8536145,2.2408645000000003,1.3395205833333332,2.6278514285714287 +docx,26803,75,102,3.785875,4.0718418,2.094667125,8.218999666666667 +docx,123521,90,170,1.1348752500000001,1.4414581818181817,0.9427556,1.991227 +docx,29916,204,246,0.8543474999999999,2.8407085,2.039697,2.931416857142857 +docx,53082,54,97,2.287889333333333,1.7101207999999999,0.9835972000000001,3.076097166666667 +docx,232315,2857,1605,13.010583,20.350959,11.206104,29.136625 +docx,107485,674,112,2.6651455,5.3923335,3.8116082,8.504153 +docx,21776,19,16,0.28580712500000005,0.9010636315789475,0.3861647,0.41591465000000005 +docx,10622,26,9,0.23339730769230768,0.26154790000000006,0.24735440000000003,0.2065208 +docx,66505,292,286,1.5049685,2.8568405,1.6111622999999997,4.1714084 +docx,14204,11,13,0.23995491666666668,0.4906896499999999,0.29602929999999994,0.25690205 +docx,42276,11,30,0.4437957,0.41431465,0.2718106,0.29961679999999996 +docx,40985,20,23,0.35575,0.9840260000000001,0.57534805,0.8724437647058824 +docx,9770,19,17,0.15658353846153844,0.22801875000000002,0.24295609999999995,0.20261235000000002 +docx,13228,5,2,0.19433340000000004,0.36060630000000005,0.20879585,0.12476240000000001 +docx,66824,38,39,1.1098193333333333,2.4698272857142856,1.3464896666666666,1.8805648888888886 +docx,3225858,119,149,1.7018996000000002,1.9504674444444443,1.3484038,3.1208196666666663 +docx,61222,105,153,0.6296746000000001,2.1185156249999997,1.1922145384615384,2.490797285714286 +docx,40263,28,10,0.27872375,0.5066167,0.33364375000000007,0.56498335 +docx,17834,25,2,0.45635424999999996,0.430575,0.3090163,0.34071035000000005 +docx,130890,358,724,2.8366106666666666,6.263472,4.4518752500000005,9.467041666666667 +docx,94298,640,1247,4.566652666666666,20.089834,10.8108955,30.116209 +docx,51916,301,182,1.6010835,2.9382361666666665,2.0473470000000002,4.630051999999999 +docx,16050,6,14,0.21545813333333336,0.46127075,0.26481040000000006,0.20750215 +docx,17412,8,13,0.9347707500000001,1.5424445,0.8327008823529412,2.397297 +docx,93327,945,417,3.3874579999999996,22.246771000000003,4.1614162,7.795902666666667 +docx,23839,43,59,0.381732,1.1965386153846154,0.7496617777777778,1.2476793076923076 +docx,228359,8,13,0.49882480000000007,0.785475894736842,0.39650625,0.5268896 +docx,12191,11,3,0.17205835,0.33232920000000005,0.1591082,0.12368139999999998 +docx,13496,14,3,0.20304764285714283,0.3738228,0.25033770000000005,0.3159499499999999 +docx,169546,55,175,2.6977216666666664,2.349932125,1.3460607272727274,3.621381833333334 +docx,46819,27,33,0.6016245,1.0211305333333331,0.48911245000000003,0.6393706315789474 +docx,166571,1127,1258,4.097958333333334,11.1879585,5.910320000000001,16.7400835 +docx,104778,36,46,0.9977997999999999,1.470787909090909,0.8195462941176471,1.8675792000000002 +docx,85065,864,413,4.076021,6.58175,4.3544920000000005,8.566708 +docx,17725,39,55,0.3258376,0.6401896,0.3450479,0.5178624499999999 +docx,25075,86,65,0.7111432857142856,2.7186902857142856,1.4377773333333332,3.236284833333333 +docx,20432,10,24,0.3123175,0.8632792941176471,0.46556675000000014,0.6065750000000001 +docx,20983,30,23,0.3232543333333333,0.9054667333333334,0.44548960000000004,0.5648626 +docx,16084,68,14,0.3093916,0.5412729,0.4241396,0.5410939 +docx,54051,32,14,0.6268541666666668,0.61023955,0.4319894000000001,0.5925187 +docx,31974,239,239,2.014666333333333,2.521702428571428,1.9039248,4.4231582000000005 +docx,74075,1131,1552,5.4386255,14.914521,8.231251,23.705375 +docx,29264,8,0,0.5350248000000001,0.523725,0.30946665,0.38112695 +docx,16507,31,19,0.43192699999999995,0.45013140000000007,2.4919555,0.44967284999999996 +docx,66040,586,397,2.1820419999999996,3.9824501999999997,2.810083,5.945177 +docx,85499,23,48,1.154778,1.8366711111111111,1.0897167333333335,2.303541666666667 +docx,27735,10,10,0.2080625,0.44300625,0.2835813,0.40269164999999996 +docx,30186,13,36,0.935365,0.9050495625000001,0.6040083,1.2858589999999999 +docx,47601,221,401,2.11822875,5.549072499999999,1.8568844444444446,4.93349975 +docx,65978,159,166,1.512098,2.4406072857142855,1.5983041,3.0210209999999997 +docx,170550,7,15,0.3372674285714286,0.49338745,0.31247294999999997,0.3652623 +docx,27431,64,86,0.6131945555555556,1.1890833076923077,1.8093672727272727,1.5275264545454548 +docx,14496,21,31,0.23256600000000002,0.5245958,0.28570425000000005,0.34377709999999995 +docx,57065,15,35,0.4982501666666666,1.2850729166666668,0.5834187368421052,2.695474 +docx,28758,23,55,0.5605084,1.0469643571428568,0.5561455999999999,0.7452346842105262 +docx,118615,46,49,1.0700525,2.634238142857143,0.9658195333333333,2.3417447499999997 +docx,33259,91,138,0.8490237142857142,2.1245835714285715,3.0587349999999995,3.0826526666666663 +docx,267226,584,781,5.1716045,10.633313000000001,5.38241625,16.8991255 +docx,5206244,74,357,14.382207999999999,15.324271,10.131917,20.903458999999998 +docx,22191,37,33,2.0964666,1.8982364444444444,0.9616557333333333,2.0741146249999995 +docx,28723,41,80,0.6651418,1.1100103125,0.6552125500000001,1.2618184285714287 +docx,97780,618,703,3.5271255,11.483854000000001,6.554347,18.289042000000002 +docx,15281,10,16,0.2623973076923077,0.5639583499999998,0.2633917,0.26590634999999996 +docx,113409,3641,49,5.397361,24.374916,9.7169585,27.66575 +docx,11956,31,42,0.28432050000000003,0.6728457999999999,0.34667285,0.6261687 +docx,163526,270,377,1.4063336666666666,4.47368775,2.6257740000000003,6.14898975 +docx,38737,182,339,1.3390174285714285,3.1465761666666663,2.2381456666666666,118.53441600000001 +docx,26728,17,18,0.422611,0.7834091764705884,0.46400220000000003,0.7580987368421053 +docx,16678,17,13,0.3441311428571429,0.5758122999999999,0.31138730000000003,0.45822915000000003 +docx,7186,7,4,0.1318214705882353,0.3047395,0.21134795000000003,0.29160635 +docx,57354,22,33,0.35132142857142856,0.833585705882353,0.5293519499999999,0.7831458500000001 +docx,453381,59,115,1.8943193333333335,1.4535644545454547,0.7612180588235294,1.9765046666666666 +docx,23400,39,5,0.4682293333333334,1.1756429999999998,0.70865435,1.142291642857143 +docx,44626,112,171,1.339778,2.869672571428572,1.6487123999999997,3.418618 +docx,12627,121,225,0.85365625,1.4527329166666665,0.8757706249999999,2.484196571428572 +docx,20197,93,129,0.7732824444444445,1.4151841666666665,0.807566294117647,2.2508275714285713 +docx,15428,26,31,0.23745639999999998,0.5078666999999999,0.37750220000000007,0.32450619999999997 +docx,346515,155,207,2.2016806666666664,4.9552707499999995,3.6042221666666667,7.2596110000000005 +docx,20433,29,53,0.30734579999999995,0.7030812499999998,0.47058334999999996,0.6561499500000002 +docx,12100,1,1,0.17411953333333333,0.3095791500000001,0.17828335,0.10341884999999999 +docx,64539,61,30,1.4394586666666667,3.2631455000000003,1.6607574545454542,3.0361111666666663 +docx,28136,18,4,0.6068332857142857,0.7133792000000001,0.44471245000000004,0.52672285 +docx,620674,44,48,1.0335833333333333,1.3220660833333333,0.6689102105263158,1.0993493846153846 +docx,20101,31,15,4.56,1.0135130625,0.4842291,1.0722402941176468 +docx,101498,12,1,0.394916625,0.5926981,0.33401055,0.38275420000000004 +docx,8427,41,80,0.4803138461538462,0.44273745,0.3287228,0.5225750499999999 +docx,474112,23,33,0.49352083333333335,0.69838535,0.4182353,0.65512295 +docx,37457,135,133,2.0904248,1.0010222,0.6660791500000001,1.2906755 +docx,13739,7,7,0.22468779999999997,0.45945395,0.27834790000000004,0.2399189 +docx,59811,210,477,1.5298919999999998,4.186999999999999,2.720107285714285,5.903927 +docx,13890,8,9,0.21450227777777783,0.41988745000000005,0.23695824999999995,0.25303529999999996 +docx,14550,27,25,0.30448518181818185,0.56834165,0.31393125000000005,0.679498 +docx,14956,11,14,0.309424,0.4988728000000001,0.2535582,0.3728166 +docx,929945,1,8,0.5409655000000001,0.45947725000000006,0.30752075,0.37967285000000006 +docx,159782,422,622,3.347986333333333,11.176020999999999,5.972697999999999,14.7957295 +docx,69996,17,13,0.7715666,0.892946,0.6605948333333335,0.44481675000000004 +docx,37806,45,10,0.537057,0.6574020499999998,0.41546475000000005,0.5882540999999999 +docx,14575,9,0,0.21154138888888893,0.25886869999999995,0.17062265000000004,0.14683945 +docx,37381,121,105,0.6067969999999999,1.7191997000000003,1.2760481538461539,2.606148857142857 +docx,68058,428,361,3.292000333333334,5.037229,2.764798,9.030562499999998 +docx,85998,267,12,3.188458333333333,1.9812173333333334,2.459562375,3.4151998 +docx,17862,6,19,0.2782667,0.973961,0.61224395,0.9399425625000001 +docx,16363,21,25,0.359704,0.7155562499999999,0.63070005,0.6589272 +docx,14193,6,1,0.23398492857142858,0.43713745,0.24330619999999997,0.28987700000000005 +docx,34037,115,25,0.6349166666666667,1.6784165999999998,1.1319112,2.1735885 +docx,26562,25,9,0.5971310000000001,0.6700772500000001,0.35105000000000003,0.5358709500000001 +docx,41246,49,66,0.6133608333333332,1.288670166666667,0.8357206470588235,1.472250090909091 +docx,21855,38,25,0.33087490909090905,0.791693105263158,0.3522168,0.5661230500000001 +docx,26754,23,7,0.46690612500000006,0.6404188,0.3187460000000001,0.45084169999999996 +docx,168614,44,73,1.231135,1.6384832,1.4643335454545456,1.0178973333333332 +docx,14981,20,36,0.25281275000000003,0.53893545,0.30829375,0.46253954999999997 +docx,210967,106,124,2.149236,3.7150168,1.379026727272727,2.499323 +docx,10678,26,9,0.2651957,0.252175,0.21310425000000005,0.20381035 +docx,21641,56,67,0.9840834999999998,0.974906125,0.7041894999999998,0.8821324117647058 +docx,20159,83,95,0.39371387500000005,0.9272550588235295,0.6085187000000001,0.9586888666666666 +docx,18907,15,3,0.3463584,0.47387075,0.30793735,0.3644603 +docx,292508,109,99,1.8682363333333332,3.220055333333333,1.582977090909091,4.3855415 +docx,241445,17,17,0.6333498,0.907708294117647,0.59181875,0.8861493529411762 +docx,47252,85,212,2.064458,3.090222166666667,1.8099458,4.386408399999999 +docx,13539,27,19,0.41854142857142856,0.4878584,0.601208,0.3843436 +docx,40608,95,11,1.010756142857143,0.9369451875000001,1.1193212142857143,1.1546312307692308 +docx,318139,24,32,1.02885425,1.2937674166666666,0.63647495,0.7671873888888889 +docx,16236,13,10,0.2757705833333333,0.6144686500000002,0.6693288333333335,0.5711542499999999 +docx,48810,29,83,0.67110425,3.2148681666666667,1.145839846153846,1.3627756153846156 +docx,22824,36,32,1.422374666666667,2.349104375,1.008769533333333,1.4015960769230769 +docx,58633,9,18,0.651937,0.9546055333333334,0.5083021000000001,0.5587791500000001 +docx,29711,208,258,0.957453375,2.4386727142857145,1.5703854166666666,5.453374666666666 +docx,289096,183,216,1.964694333333333,3.669493166666667,1.8621158888888891,5.22381225 +docx,41910,66,86,0.6034332,4.0656916,2.737451333333334,2.6169225714285713 +docx,15757,30,19,0.4074282727272727,0.56453525,0.39823755000000005,0.3820207 +docx,29394,20,11,0.3525838333333333,1.9734270833333332,0.45066249999999997,0.5367479500000001 +docx,463521,41,37,1.4486459999999999,1.387837090909091,0.7714878235294116,1.9415553333333335 +docx,147148,39,81,0.853683,1.423941,0.773101888888889,1.8134629999999998 +docx,1402591,73,161,4.8508125,4.55223925,2.8656187142857137,8.039403 +docx,75195,30,31,4.0468125,2.0735887500000003,1.4391591818181817,1.609765090909091 +docx,28614,206,119,0.801404857142857,1.7641943333333332,1.1343957142857142,2.9765831666666664 +docx,14321,25,2,0.3130832,0.7908848235294117,0.24080850000000004,0.3398813 +docx,218366,0,1,0.22230555555555556,0.3298873,0.2080648,0.1321291 +docx,15419,10,0,0.25264571428571425,0.40867085000000003,0.24286679999999997,0.1957125 +docx,49582,98,22,0.8042239999999998,7.783916666666666,1.0678525384615385,1.764958222222222 +docx,16506,13,23,0.24585892307692309,0.65861455,0.30206229999999995,0.4390563529411764 +docx,58560,108,282,1.1021355,3.0480905000000003,2.060722333333333,3.0032848333333333 +docx,10786,25,9,0.23047099999999998,0.24908549999999993,0.28828125000000004,0.20828754999999996 +docx,145464,60,28,2.04662475,6.185986,5.309083,9.264292000000001 +docx,17331,149,151,0.7037445,1.6704696363636364,0.9310285624999998,2.7474582857142855 +docx,26929,7,11,0.46703433333333333,2.087199111111111,0.6818062,0.8849606666666666 +docx,15488,35,0,0.3256109333333333,0.4317812000000001,0.47894433333333336,0.377423 +docx,32468,22,42,1.1607500000000002,1.4454736363636365,0.7662013333333335,1.5505681818181818 +docx,18395,62,96,0.51549975,1.0271943333333335,0.6687269499999999,1.5030341818181818 +docx,27583,18,21,0.42600409999999994,0.7153999499999999,0.36902904999999997,0.4357458500000001 +docx,18377,33,15,0.2585089285714286,0.49068535000000013,0.36477085000000004,0.5503125500000001 +docx,51749,284,292,1.619325,4.7439895,2.148937625,4.59855225 +docx,15945,22,3,0.3167985,0.8235635263157892,0.5430709,0.8645695000000001 +docx,13448,12,12,0.202552125,0.4101668000000001,0.4586105999999999,0.23109794999999997 +docx,271508,65,51,2.3128056666666663,1.5934291999999999,0.939985,2.180213375 +docx,37764,23,22,0.4242271818181818,3.5720418333333335,1.3562670833333332,0.7844738421052632 +docx,308218,19,22,0.6863582,0.8501166,0.4706498,0.4640729500000001 +docx,75320,55,36,1.9981803333333332,2.8375950000000003,1.074064153846154,1.1058722142857145 +docx,22423,37,41,0.4011251666666667,0.6500396,0.43945015000000004,0.6141208499999999 +docx,50135,110,66,1.117583,0.8546334000000001,1.097617,1.175282153846154 +docx,17947,20,15,0.3542015,0.7516417000000001,0.53218745,0.7319771 +docx,18671,24,30,0.346073,0.9249362941176473,0.5611268500000001,0.9888943999999998 +docx,31907,63,11,0.747671375,0.7613726111111111,0.7495657894736842,0.8219218749999999 +docx,23772,173,155,1.0671785714285715,2.038708375,1.1187948461538462,3.5004165 +docx,34758,31,34,0.59955,0.9831561875,0.6720601111111111,0.7576691176470588 +docx,33488,221,249,1.6140136666666667,2.9829465714285712,1.9128209999999999,5.902177249999999 +docx,34398,61,51,0.9448746666666666,1.21942025,0.9706953125000001,1.6943751000000002 +docx,50820,52,58,1.0580730000000003,2.3699462857142857,1.30484625,1.4372883846153846 +docx,35094,164,172,1.21116675,3.2776875000000003,1.7974625555555557,11.463312499999999 +docx,23910,128,28,0.4676335,1.4314164999999999,0.9085833999999999,1.6661212727272727 +docx,15432,21,6,0.22914206666666667,0.3346855000000001,0.2538081,0.29454165000000004 +docx,10489,41,51,0.27717122222222224,0.5690041,0.3498311,0.8200603000000001 +docx,6976,17,26,0.21734228571428568,0.23801880000000003,0.21443754999999998,0.27702075000000004 +docx,132204,20,27,0.605475,0.8401484374999998,0.53452505,0.7144628888888889 +docx,36038,142,203,1.0457832,3.1962568333333334,1.6377375,3.7415083999999994 +docx,15457,19,0,0.34050699999999995,0.51870625,0.31765205,0.42284164999999996 +docx,26014,13,19,0.2705556666666667,1.0953474666666667,0.49243575,0.5863645500000001 +docx,23152,41,137,0.46312988888888884,0.9780611764705883,0.6182853500000001,1.113593 +docx,17360,24,32,0.35888525,1.2314453846153848,0.7937322105263158,1.6888865454545454 +docx,32306,40,71,0.9205416666666667,2.6112359999999994,0.9066915999999998,1.8059115000000001 +docx,96830,133,200,2.2546980000000003,2.5057430000000003,1.3656323333333333,3.1146875 +docx,34260,172,27,1.58420825,1.4053541666666665,1.0218472,1.949648111111111 +docx,46542,59,108,1.592521,3.0790764999999998,1.3862084166666666,3.6283668 +docx,15681,18,45,0.2901437272727273,0.7591167000000001,0.4305165000000001,0.65071235 +docx,16304,25,28,0.271031,0.7029606111111113,0.4372897,0.4807793 +docx,18611,52,40,0.40204155555555565,1.197406923076923,0.5619834499999999,1.1270777333333333 +docx,15776,20,10,0.22192366666666663,0.5422083499999999,0.26901240000000004,0.36765835 +docx,218180,3,4,0.5922084000000001,1.080122375,0.9443479411764706,0.9056691250000002 +docx,28520,52,25,0.54775975,1.1060054666666668,0.64887515,1.3682535833333336 +docx,62149,26,16,0.5787022857142857,0.5794854,0.4815853,0.699260894736842 +docx,15496,20,17,0.29020491666666665,0.6308313000000001,0.38661235000000005,1.0228719285714285 +docx,211642,5,15,0.6762412,0.6037968125000001,0.32122694999999996,0.4268292000000001 +docx,14965,13,12,0.23605761538461542,0.5590978000000001,0.29891015,0.41163755 +docx,57469,29,30,1.8689306666666667,1.0351965714285716,0.6065894000000001,0.8524606111111112 +docx,35223,94,163,1.335917,1.8917084444444445,1.0834167692307692,2.8301873333333334 +docx,21631,32,11,0.445236,0.932514705882353,0.6322667500000001,0.9647672941176472 +docx,18480,44,28,0.4761528333333333,0.60512505,0.44427705,0.6398021 +docx,13130,26,25,0.20844905555555554,0.44374164999999993,0.25167695,0.29834369999999993 +docx,45437,42,175,0.8908545,1.5004757500000003,1.0868229166666667,1.3780068333333333 +docx,27870,66,75,0.6957586000000001,1.3489271666666667,0.853945125,1.6155791999999998 +docx,30362,43,60,0.698698,2.027532333333333,1.0635362000000004,1.4835417272727272 +docx,13381,21,35,0.2613457,0.54364785,0.35273555,0.5226771000000001 +docx,32249,28,33,0.51120875,1.2956528333333335,0.5326852499999999,1.6559415000000002 +docx,113679,350,398,1.48877075,3.508613,2.3684167142857144,3.0295066666666663 +docx,28852,108,83,0.6645738888888889,2.0393518888888886,0.8548583333333334,1.9846991111111112 +docx,754574,25,117,1.6827185,4.33934375,1.2883925000000003,1.5117497777777775 +docx,278296,21,13,0.3737013333333334,0.70480225,0.4713915,0.7189959500000002 +docx,38338,13,17,0.6910625,0.7816598333333333,0.42457079999999997,0.6503042499999999 +docx,50023,13,5,0.7861248,1.6606289090909092,0.588198,0.4803938500000001 +docx,96030,26,17,0.43990819999999997,0.59483535,0.34829155,0.3793581 +docx,294772,14,25,0.96037475,2.3176977500000002,2.5501595,0.9661279285714286 +doc,10240,7,12,0.10449206249999998,,0.03427925,0.018458249999999995 +doc,25088,18,28,0.15325819999999996,,0.09054175,0.0236042 +doc,31744,1,1,0.138479125,,0.048449950000000006,0.031104099999999996 +doc,319488,4,6,0.68004175,,0.33681669999999986,0.30415615 +doc,46592,62,22,0.652671625,,0.25491245,0.1177 +doc,265728,64,52,1.74187525,,0.27657315000000005,0.13900415000000002 +doc,28672,7,4,0.10123175,,0.0513811,0.0190021 +doc,31232,12,18,0.2299662,,0.07003540000000001,0.024570799999999997 +doc,9728,1,1,0.06895176923076923,,0.03379164999999999,0.0185708 +doc,22528,1,0,0.045858600000000006,,0.03438750000000001,0.014548000000000002 +doc,27136,1,2,0.0679481,,0.03809775000000001,0.015735399999999993 +doc,624640,976,926,8.95675,,2.4287438571428575,1.4565241666666664 +doc,812032,0,1,0.7794866666666668,,0.4018810500000001,0.40567080000000005 +doc,36864,68,16,0.5372655,,0.2355375,0.10177495 +doc,46592,72,16,1.5265973333333331,,0.68048115,0.13156045000000002 +doc,83968,61,41,1.0896248333333332,,0.311479,0.11394800000000001 +doc,26624,6,1,0.0833167,,0.046410400000000004,0.01592715 +doc,898048,0,4,1.5065664,,1.0174902941176471,1.0032403529411764 +doc,50688,85,76,0.7266422000000001,,0.15914595,0.07251665000000002 +doc,48128,64,23,0.7345,,0.24649155,0.10394385000000002 +doc,1140736,7,10,1.9490830000000001,,0.1132395,0.1421771 +doc,62976,130,64,1.6742415999999998,,0.42772305,0.21954785000000004 +doc,52736,63,23,0.8423277142857143,,0.2563418,0.09642695 +doc,22528,2,0,0.05864155,,0.049966500000000004,0.014970800000000006 +doc,16384,3,10,0.3798055833333333,,0.07372290000000001,0.023668650000000003 +doc,85504,65,43,1.1697223333333333,,0.3154939500000001,0.1140104 +doc,23040,9,0,0.16166025,,0.043641599999999996,0.01819365 +doc,49152,79,28,0.8906528333333333,,0.2981523,0.11385835 +doc,145408,22,26,6.2731875,,0.7330284736842104,0.056635499999999984 +doc,22016,0,0,0.03155600000000001,,0.08231865,0.01409165 +doc,22016,1,2,0.03728524999999999,,0.0327688,0.014495950000000002 +doc,398848,691,717,11.808333,,4.498025,2.234224 +doc,57856,246,284,1.2353678333333333,,0.3243479,0.14085195 +doc,81408,53,31,1.0905748,,0.29650195,0.1207125 +doc,9216,2,0,0.04734989999999999,,0.03457495,0.016431300000000003 +doc,89088,75,45,1.4053473333333333,,0.4173334999999999,0.1710312 +doc,86016,66,44,1.1445252,,0.3242521500000001,0.11414984999999997 +doc,49664,66,26,1.9289169999999995,,0.28071039999999997,0.11652489999999997 +doc,22528,2,2,0.09498518181818182,,0.03911665,0.015302099999999999 +doc,46592,61,22,0.6644895,,0.245952,0.08423735000000002 +doc,93184,288,232,2.5589001999999996,,0.9933541249999999,0.43144995 +doc,45056,72,20,0.6578332857142858,,0.28424594999999997,0.11269784999999999 +doc,97280,3,22,1.6804272500000002,,0.12384809999999997,0.06824374999999999 +doc,136192,30,46,0.7734664,,0.15531029999999996,0.06272275000000001 +doc,84992,64,37,1.1322291666666668,,0.33249350000000005,0.12116455 +doc,43520,65,15,0.59818225,,0.23583355,0.09846475 +doc,44032,72,16,0.696654857142857,,0.28206050000000005,0.11989365 +doc,112640,197,279,1.9863086000000003,,0.6820106,0.26347305000000004 +doc,46592,61,24,1.4022615714285716,,0.25786450000000005,0.08808954999999999 +doc,30720,11,2,0.3666498,,0.07221045000000001,0.02487295 +doc,24576,3,3,0.07546035,,0.03958955,0.01608135 +doc,46592,26,13,0.6020511111111111,,0.18489775000000003,0.07418745 +doc,23552,0,0,0.09774370000000002,,0.09171059999999998,0.015008300000000002 +doc,61440,0,1,0.0981145,,0.030196150000000005,0.017452099999999998 +doc,49152,61,44,0.7331071428571428,,0.33980829999999995,0.1162105 +doc,65024,233,196,5.2232705,,0.6648562499999999,0.38809789999999994 +doc,44544,23,90,0.7145205,,0.2685041,0.0982127 +doc,32256,67,67,0.28755962499999993,,0.07659374999999999,0.02572295 +doc,26112,0,1,0.0580993846153846,,0.18838535,0.015337499999999999 +doc,26624,1,1,0.05413893333333333,,0.04643750000000001,0.014954300000000007 +doc,36352,195,391,3.940222,,0.06848125000000002,0.022904149999999998 +doc,38400,3,8,0.27566199999999996,,0.15284365,0.08694165 +doc,27136,6,2,0.09332270000000001,,0.09800004999999998,0.019256199999999998 +doc,22016,1,0,0.03289995,,0.029208400000000002,0.01469385 +doc,31232,18,16,0.13353118749999998,,0.062331199999999996,0.02087705 +doc,9728,1,0,0.04581269999999999,,0.031625,0.01673335 +doc,36864,1,0,0.03824355,,0.0384041,0.02639785 +doc,137216,170,214,0.8721165,,0.5156644210526317,0.09818535000000002 +doc,23040,1,0,0.10289871428571427,,0.08091669999999998,0.015497849999999999 +doc,23040,1,0,0.0636792,,0.04001475,0.01598755 +doc,218526,387,388,10.282479499999999,,2.046527888888889,0.4823082499999999 +doc,76800,177,96,1.1481525,,0.41372495000000004,0.24878755 +doc,61440,51,54,1.0684416,,0.4520601999999999,0.13894580000000004 +doc,695296,711,736,3.4262292499999996,,1.3113646666666667,0.4176278571428571 +doc,134656,28,48,0.7894414000000001,,0.15955405000000003,0.05911019999999999 +doc,9728,2,0,0.06944091666666666,,0.0369835,0.016683399999999998 +doc,26112,5,2,0.053108199999999994,,0.039460499999999996,0.0163396 +doc,105984,115,95,1.5154248000000001,,0.48264580000000007,0.17734370000000002 +doc,19456,1,0,0.032652099999999996,,0.030387549999999996,0.015168900000000003 +doc,39936,9,12,0.4229308333333333,,0.107123,0.0348625 +doc,10240,1,0,0.0717854,,0.031212500000000004,0.01911665 +doc,26624,3,0,0.04256449999999999,,0.03590185,0.014056150000000003 +doc,34304,35,40,0.26731669999999996,,0.31020205,0.05072909999999999 +doc,53760,96,233,1.314637,,0.2553773,0.13621445 +doc,31232,1,3,0.14971566666666666,,0.0596583,0.01670415 +doc,88064,1,2,0.08944275,,0.029037550000000002,0.021423049999999992 +doc,62464,7,5,0.293151,,0.08707709999999999,0.03297699999999999 +doc,39936,11,14,0.41214300000000004,,0.15560010000000002,0.0246979 +doc,167936,287,214,8.6002295,,1.5329443333333332,0.6799875000000002 +doc,1829220,337,497,7.986749499999999,,2.5968394285714287,0.7506713333333335 +doc,34304,3,3,0.24199462499999996,,0.04990615000000001,0.0237813 +doc,148992,250,321,2.2143747499999997,,0.7456141052631579,0.1669875 +doc,11776,14,16,0.158729125,,0.045887449999999996,0.017129150000000003 +doc,38400,1,2,0.03929355,,0.036245700000000006,0.027858300000000003 +doc,32768,20,22,0.3555294285714285,,0.07423330000000002,0.03038965 +doc,36352,4,3,0.14802587499999997,,0.03307935000000001,0.01959585 +doc,30720,3,0,0.2546184166666667,,0.06378945,0.024118849999999997 +doc,79872,49,11,0.8108681666666665,,0.35000829999999994,0.11627505 +doc,26624,1,0,0.03834785,,0.04942305000000001,0.015093600000000004 +doc,28160,6,5,0.126904,,0.06110224999999999,0.016756349999999996 +doc,28672,15,0,0.10069365,,0.0717625,0.02801260000000001 +doc,64512,15,2,0.08388765,,0.04512290000000001,0.025533350000000003 +doc,19968,1,0,0.06564733333333334,,0.03275825,0.017264599999999998 +doc,24576,2,0,0.055885399999999995,,0.048737550000000004,0.01883335 +doc,27648,9,2,0.12426865000000001,,0.05894775,0.0202479 +doc,19456,8,7,0.06210005,,0.036656299999999996,0.0164772 +doc,25600,2,2,0.1281334,,0.04836045,0.0171625 +doc,84992,25,20,2.0811256666666664,,0.41636875000000007,0.11383329999999998 +doc,16384,2,1,0.09429699999999999,,0.04229594999999999,0.021970799999999995 +doc,14848,1,1,0.07955699999999999,,0.02960625,0.01701645 +doc,47104,98,101,1.004779857142857,,0.32493539999999993,0.13709585 +doc,31232,25,7,0.596593875,,0.18967085,0.0766502 +doc,10240,5,0,0.10699777777777777,,0.04703739999999999,0.02172305 +doc,27136,6,2,0.102489,,0.05565835000000001,0.019552049999999998 +doc,22528,3,2,0.046139599999999996,,0.035052000000000014,0.014666650000000003 +doc,24064,0,1,0.06462499999999999,,0.039725050000000005,0.02163955 +doc,36864,57,14,0.5863277142857143,,0.14263969999999998,0.060139599999999994 +doc,27648,22,2,0.264887,,0.09906245,0.04030005 +doc,25088,6,0,0.10772505000000002,,0.04120225000000001,0.0176271 +doc,95232,74,29,1.9227747999999998,,0.9094913157894735,0.3889667 +doc,22528,3,2,0.084113,,0.046608250000000004,0.016683399999999994 +doc,11264,2,1,0.12315749999999998,,0.03532935000000001,0.025399949999999994 +doc,36352,71,21,0.6597546666666667,,0.2340938,0.0776374 +doc,679936,72,98,2.990292,,0.30595,0.22114795000000004 +doc,56320,49,48,0.574216,,0.13217275,0.033295899999999996 +doc,135680,5,8,0.265642,,0.06461655000000002,0.0348436 +doc,23552,12,20,0.09157509999999999,,0.0417937,0.018039600000000003 +doc,27648,0,1,0.07221385714285714,,0.030441649999999997,0.014464600000000003 +doc,9728,4,0,0.13447773333333332,,0.04198524999999999,0.018552199999999998 +doc,27648,39,30,0.17380921052631576,,0.0686832,0.02438535 +doc,33280,44,47,0.5389051818181818,,0.19200215,0.025670800000000004 +doc,120320,7,8,0.3248084,,0.07750000000000001,0.03748545 +doc,25088,7,5,0.07536885000000001,,0.05740624999999999,0.018866649999999995 +doc,28160,9,4,0.099998,,0.05406855000000001,0.018947899999999997 +doc,27136,9,10,0.0807749,,0.05167705,0.016131249999999996 +doc,376320,646,581,6.028562,,2.0018645,0.8208925263157895 +doc,29696,6,4,0.11988769999999997,,0.05544795,0.02698535 +doc,25600,11,0,0.1552437692307692,,0.05300010000000001,0.020489699999999996 +doc,19968,1,0,0.031137599999999994,,0.09055010000000001,0.014416650000000003 +doc,306688,12,25,0.48228483333333333,,0.08082910000000001,0.04775840000000001 +doc,36864,29,30,0.40346428571428566,,0.06447264999999999,0.023916649999999998 +doc,167936,11,7,0.19336885714285715,,0.056237499999999996,0.03033325 +doc,22528,3,0,0.087771,,0.038922849999999995,0.015493700000000004 +doc,218112,12,17,1.6438585999999997,,0.11151470000000001,0.057620700000000004 +doc,36352,46,55,0.3903921666666667,,0.14256465000000001,0.03657295 +doc,38400,5,11,0.470238,,0.09904355,0.030277000000000005 +doc,90624,70,170,2.5192226666666664,,0.22704375,0.07094385 +doc,101888,183,203,1.8376996,,0.6104499499999999,0.2211186 +doc,24064,2,2,0.0683265,,0.03551665,0.014566650000000004 +doc,35840,31,0,0.2249630588235294,,0.14089165,0.04867909999999999 +doc,24064,2,1,0.09651214285714285,,0.033193900000000005,0.015706150000000002 +doc,51200,11,9,0.6580838,,0.11697710000000001,0.0526938 +doc,63488,5,16,0.582979375,,0.11148115,0.034902050000000004 +doc,588800,119,137,4.087999666666667,,1.2678660714285714,0.2721251 +doc,26112,2,0,0.037754199999999995,,0.035535250000000004,0.014133300000000001 +doc,67072,3,3,0.8011666,,0.05311245,0.028837450000000004 +doc,30720,3,1,0.25872547058823536,,0.04292285,0.01914375 +doc,133120,27,25,0.7738748,,0.1700063,0.0510104 +doc,19968,1,0,0.05785835,,0.03312910000000001,0.014772950000000003 +doc,39424,46,40,1.0231249999999998,,0.1083083,0.05916879999999999 +doc,49152,60,53,1.3237431666666666,,0.28266669999999994,0.07449994999999998 +doc,10240,5,0,0.07593364999999999,,0.0416603,0.019785399999999998 +doc,39936,2,3,0.34215540000000005,,0.04746665,0.02679170000000001 +doc,448000,16,16,1.6276806666666668,,0.14039375,0.06187920000000001 +doc,346290,497,846,6.419139,,1.6642006363636364,0.3086416499999999 +doc,97280,131,91,2.1815935,,0.9519206999999998,0.3082749 +doc,86528,102,129,1.5816249999999998,,0.5471541500000001,0.14135835000000002 +doc,27136,2,3,0.05680630000000001,,0.041689750000000005,0.015543799999999997 +doc,9216,2,0,0.04665615,,0.03361879999999999,0.015770849999999996 +doc,28160,0,0,0.0532505,,0.0345938,0.014516750000000004 +doc,11264,11,18,0.1146857,,0.042379099999999996,0.017170749999999995 +doc,9216,1,0,0.04398955,,0.03736435,0.0162354 +doc,26624,1,5,0.07545475,,0.25674165,0.015522900000000003 +doc,77312,319,387,2.17895825,,0.70293955,0.30782705 +doc,22016,21,8,0.1404965,,0.06279795,0.0211001 +doc,39936,8,13,0.32627490000000003,,0.11657894999999999,0.0235643 +doc,135168,29,48,0.8179915999999998,,0.15603974999999995,0.055556400000000006 +doc,27648,5,0,0.05468535000000001,,0.055208349999999996,0.0169063 +doc,201728,2,3,0.1308127,,0.043531349999999996,0.031072899999999994 +doc,19968,0,1,0.26572500000000004,,0.05909154999999998,0.023435449999999993 +doc,50176,4,1,0.21570116666666664,,0.052497800000000004,0.022289599999999993 +doc,22016,1,2,0.035571,,0.029510250000000005,0.014097900000000005 +doc,26112,1,0,0.05101471428571428,,0.039829250000000004,0.014579250000000002 +doc,33280,8,13,0.503084,,0.06043754999999999,0.022093599999999998 +doc,103424,223,411,2.5103613333333334,,0.57397915,0.19988145000000002 +doc,27136,1,0,0.0697840909090909,,0.03830820000000001,0.015400050000000002 +doc,28672,14,9,0.11479647058823528,,0.05901044999999999,0.017902099999999997 +doc,44544,34,56,0.6447082857142857,,0.24401679999999995,0.0423937 +doc,27136,7,2,0.10150005000000002,,0.059614600000000004,0.019295800000000002 +doc,244736,314,290,5.892847,,2.813784666666667,0.6879979000000002 +doc,888832,482,443,4.809000333333333,,2.0310187777777777,0.58664585 +doc,55296,1,1,0.49107291666666675,,0.04696865000000001,0.02736035 +doc,9216,11,0,0.06103329999999999,,0.03907909999999999,0.018681249999999996 +doc,22528,7,0,0.055499900000000005,,0.05155835000000001,0.018608350000000003 +doc,28160,7,0,0.25459149999999997,,0.06161449999999998,0.02110205 +doc,11264,4,0,0.1318543,,0.06884155,0.024506399999999998 +doc,104448,4,5,0.09426675000000001,,0.058479249999999997,0.0296271 +doc,22528,36,39,0.5380591428571428,,0.10823964999999998,0.0412688 +doc,98304,8,4,0.1808335,,0.053920749999999996,0.029502049999999995 +doc,27648,6,5,0.11659564999999998,,0.046702149999999984,0.01754165 +doc,31744,3,3,0.1744212,,0.06483554999999999,0.022791600000000002 +doc,424448,927,1220,11.682104500000001,,3.1423545,0.7216521499999999 +doc,24065,1,0,0.11839933333333336,,0.050047699999999994,0.01688525 +doc,9728,2,0,0.06875824999999999,,0.05780215,0.0172458 +doc,23552,33,11,0.21337740000000002,,0.1159729,0.04782284999999999 +doc,222720,0,9,0.40445133333333333,,0.15119370000000001,0.13192495000000004 +doc,27136,6,0,0.060227050000000004,,0.0553748,0.0203522 +doc,22528,3,0,0.055472750000000015,,0.0362375,0.015474949999999998 +doc,32768,19,5,0.6315456,,0.08613954999999998,0.0185394 +doc,202240,63,39,1.2931598333333332,,0.6181249999999999,0.1459417 +doc,98816,121,98,1.5690831428571428,,0.6957667999999999,0.33839169999999996 +dot,76800,57,41,1.3104236666666669,,0.5060602999999999,0.20271034999999996 +dot,40960,23,16,0.37498214285714276,,0.5036479,0.06396255 +dotx,10008,1,0,0.1303604,0.21420414999999995,0.12385195,0.06768335 +dotx,14726,4,3,0.33927766666666664,0.34392079999999997,0.3666313,0.20589170000000007 +dotx,6065439,420,339,12.124666999999999,4.650792,2.6120415714285707,4.87948975 +dotx,11338,1,0,0.15673955,0.2875770000000001,0.13686245,0.08411455 +dotx,59583,26,19,1.446944,0.8741200588235294,0.6144792499999999,0.68274785 +dotx,36463,37,34,0.6522836,0.917911375,0.6440041000000001,0.8878430588235295 +docm,12709,1,0,0.1677232,0.21600625,0.13511655,0.06911244999999999 +docm,20187,1,0,0.293009705882353,0.37894175,0.2620688,0.15640199999999999 +docm,15617,3,0,0.16869379999999998,0.19698119999999997,0.13431885,0.07224169999999999 +docm,17322,1,2,0.19725215000000001,0.30251455,0.16018994999999997,0.09508950000000002 +docm,49596,25,56,3.075180666666667,2.3481612500000004,1.1112145384615386,3.375402833333333 +docm,15517,1,0,0.15113565,0.19808969999999998,0.11865010000000002,0.06576244999999999 +dotm,30486,31,35,0.6946941666666667,2.6055840000000003,1.0682855714285713,1.4822310909090914 +dotm,35063,8,9,0.8073334000000001,2.15842725,0.790453894736842,0.8645048823529411 +dotm,27882,9,4,0.6565556666666666,1.3817948461538463,0.5806790000000002,0.7209124 +dotm,16205,2,0,0.4982498888888889,0.47568145000000006,0.24229775,0.2588521 +dotm,65527,513,0,1.5241965714285715,2.7000415714285717,2.4811300000000003,5.86570825 +dotm,30809,2,1,0.5712706666666666,1.931398333333333,0.6736814499999999,0.7105728499999999 +dotm,17429,1,0,0.35791199999999995,0.48997494999999996,0.23513734999999997,0.25938739999999993 +dotm,29582,16,12,0.5809653333333333,2.1478853750000004,0.8823303124999999,1.2634106428571428 +dotm,25165,4,1,0.481174,1.3390211666666667,0.5588374500000001,0.6533729000000001 +dotm,32766,4,1,0.5282501428571428,2.139458444444444,0.6899437500000001,0.72096465 +dotm,143114,1,0,0.5606851111111111,0.4751146000000001,0.25067505,0.19345004999999996 +dotm,14944,2,0,0.318140375,0.41795225,0.22391069999999996,0.2100021 +docx-regressions,157830,4,6,0.4807021428571429,0.4667897,1.1855711,0.3936126 +docx-regressions,13646,0,1,0.15883064285714288,0.2869832999999999,0.15973549999999997,0.0966728 +docx-regressions,18504,35,2,0.2985605454545454,0.6102854500000001,0.9962653684210524,0.53421045 +docx-regressions,18606,20,59,0.8926767499999999,0.7441124499999999,0.4979854,0.6508541000000001 +docx-regressions,13339,1,0,0.177421,0.3100689,0.35090619999999995,0.0931561 +docx-regressions,15982,31,21,0.4272918333333333,0.9265129375000001,0.5164207,1.1896554666666666 +docx-regressions,15648,0,0,0.28720133333333336,0.2664124,0.23930609999999994,0.13669590000000004 +docx-regressions,11946,4,4,0.19238978571428575,0.34989584999999995,0.17246874999999998,0.15313529999999997 +docx-regressions,12167,0,1,0.17387513333333335,0.3141001,0.15054580000000004,0.10751659999999999 +docx-regressions,15746,49,48,0.51957825,1.5047688181818182,0.6238522,0.7582208500000001 +docx-regressions,16849,2,0,0.28925781818181817,0.4082751000000001,0.21600425000000004,0.15957285000000002 +docx-regressions,31303,4,3,0.17626118181818182,0.15865205,0.17984345000000002,0.19343970000000002 +docx-regressions,24696,59,58,0.5714332,0.9299119411764707,0.53219595,0.743273 +docx-regressions,12980,4,2,0.16120550000000003,0.34568334999999994,0.1738834,0.1349063 +docx-regressions,18245,1,0,0.27944747368421047,0.32026855,0.19094819999999998,0.08449170000000002 +docx-regressions,52399,6,21,0.8245912000000001,0.7889913157894736,0.5009002,0.6736583500000001 +docx-regressions,11115,0,8,0.20202230769230772,0.27651675000000003,0.15962070000000003,0.16698944999999996 +docx-regressions,2284,1,0,0.029379250000000006,0.022770749999999996,0.04316239999999999,0.0212417 +docx-regressions,113242,1,1,0.325083375,0.2432458,0.15151674999999998,0.1132834 +docx-regressions,14047,2,0,0.22855266666666668,0.3387812,0.17094160000000003,0.12960835 +docx-regressions,13219,1,1,0.1826066666666667,0.22771454999999996,0.13628365,0.08926869999999999 +docx-regressions,2631,0,0,0.043656200000000006,0.023489549999999998,0.05681250000000001,0.027472950000000003 +docx-regressions,14464,8,30,0.6279501999999999,0.6467145500000001,0.29704165000000005,0.7404373499999999 +docx-regressions,16206,0,1,0.4558261666666666,0.30474999999999997,0.33485209999999993,0.1243562 +docx-regressions,15887,1,2,0.33938899999999994,0.2640334,0.1579833,0.15091449999999998 +docx-regressions,21535,20,15,0.48348257142857143,0.6406395000000001,0.37297104999999997,0.43833335000000007 +docx-regressions,3481,0,1,0.06558739999999999,0.05578545,0.0767,0.048245899999999994 +docx-regressions,13436,20,22,0.2746956,0.45556244999999995,0.29996485,0.35988954999999995 +docx-regressions,13394,20,20,0.22024100000000005,0.32693115,0.27226039999999996,0.20146665000000002 +docx-regressions,33037,2,2,0.2676045,0.333975,0.2229872,0.27691675000000004 +docx-regressions,14541,0,1,0.1931986923076923,0.2736499,0.1455334,0.07787295000000001 +docx-regressions,23175,0,0,0.3611875,1.8888584000000002,0.60949575,0.5953437499999998 +docx-regressions,11019,2,0,0.15470224999999999,0.25419984999999995,0.12984159999999997,0.07773750000000001 +docx-regressions,2125,4,0,0.11399369230769232,0.0353625,0.0534791,0.060058349999999996 +docx-regressions,10409,3,4,0.17264075000000004,0.2229625,0.1381916,0.10502924999999999 +docx-regressions,2270,3,1,0.07045411111111112,0.027466699999999993,0.052548,0.04548105 +docx-regressions,25271,4,3,0.3907224999999999,0.34719785000000003,0.22911465,0.27547105 +docx-regressions,1980,1,0,0.027025049999999995,0.0213062,0.04350830000000001,0.021618599999999998 +docx-regressions,31592,9,13,0.4923664,1.0318195333333333,0.4896812,0.48667499999999997 +docx-regressions,63294,3,2,1.9441916000000004,0.35837090000000005,0.3577312,0.2347918 +docx-regressions,19731,0,1,0.24188349999999997,0.41733950000000003,0.20658334999999997,0.12478544999999999 +docx-regressions,12823,2,0,0.17355629999999997,0.216677,0.14440200000000003,0.086552 +docx-regressions,17913,0,0,0.30408822222222226,0.30504585000000006,0.18525409999999995,0.1438729 +docx-regressions,61769,1,3,0.2359165,0.3607188,0.34775835000000005,0.15719385000000002 +docx-regressions,33186,38,39,0.9007,4.2086418000000005,1.4581783636363639,7.797041666666668 +docx-regressions,12912,1,3,0.15632715000000003,0.23012085,0.13597925000000002,0.07283954999999999 +docx-regressions,10429,2,1,0.145567625,0.22245215000000002,0.12604354999999998,0.07618135000000001 +docx-regressions,15993,1,0,0.27486577777777776,0.36685015,0.20821475,0.1500959 +docx-regressions,11047,1,4,0.1567792,0.32254774999999997,0.15768749999999998,0.11404585 +docx-regressions,12523,1,0,0.17030206250000002,0.31391035,0.16240395,0.10748945000000001 +docx-regressions,8169,4,0,0.13875916666666666,0.19879785,0.25293960000000004,0.22348765000000004 +docx-regressions,22867,7,7,0.7679166666666667,0.5653770500000002,0.3478603,0.6107604 +docx-regressions,9672,1,0,0.12703319999999999,0.333577,0.17205194999999998,0.10566255 +docx-regressions,27082,1,1,0.3216195,0.39597295,0.23238755000000003,0.17935830000000003 +docx-regressions,49860,1,0,0.3599402222222222,0.32074995,0.17759805,0.10361040000000002 +docx-regressions,29018,8,0,0.36586250000000003,0.3062145,0.21543954999999998,0.18010429999999994 +docx-regressions,2328,1,0,0.028235450000000002,0.04042905,0.0426166,0.018604199999999998 +docx-regressions,18506,72,18,0.28368261538461537,1.2832627692307694,0.678451789473684,1.3134326153846154 +docx-regressions,8325,1,1,0.10756845454545454,0.08144589999999999,0.10023125000000002,0.08400415000000001 +docx-regressions,16550,1,1,0.18987482352941173,0.26492720000000003,0.17895025000000003,0.09043749999999999 +docx-regressions,12382,1,4,0.17153917647058825,0.31066884999999994,0.1525916,0.11288120000000001 +docx-regressions,3070,0,0,0.04480185,0.023843800000000005,0.05965220000000001,0.026860450000000008 +docx-regressions,2306,1,0,0.034199999999999994,0.02243965,0.040823,0.019700000000000002 +docx-regressions,11032,5,5,0.133824,0.18198534999999996,0.16538334999999998,0.09310615000000001 +docx-regressions,10148,2,2,0.15893533333333335,0.2126979,0.13366234999999999,0.08296655 +docx-regressions,13984,0,1,0.180878,0.28071235,0.15468125000000002,0.14776055000000002 +docx-regressions,3366,0,11,0.15161454999999996,0.08141665,0.08939559999999999,0.10295000000000001 +docx-regressions,99389,1,1,0.28735912499999994,0.21947710000000004,0.1376105,0.10137290000000002 +docx-regressions,3562,0,3,0.08088750000000001,0.06887925,0.07874579999999998,0.07035425 +docx-regressions,10312,0,0,0.14561566666666667,0.21870610000000004,0.12958134999999998,0.07229375000000002 +docx-regressions,130189,0,9,0.4208331666666667,0.4019875999999999,0.30475209999999997,0.26822714999999997 +docx-regressions,19239,2,3,0.37155839999999996,0.3931626,0.32737705,0.22097709999999995 diff --git a/crates/docx-core/benchmarks/optimized-20261102/latency.json b/crates/docx-core/benchmarks/optimized-20261102/latency.json new file mode 100644 index 0000000..710482b --- /dev/null +++ b/crates/docx-core/benchmarks/optimized-20261102/latency.json @@ -0,0 +1,162 @@ +{ + "label": "unreleased optimizations", + "measured_date": "2026-11-02", + "measured_commit": "dcce836072cb01a24f6490c97643f825d2d6902e", + "binary_sha256": [ + "f15b9150db8ed9136311995e8f554ed16a55ebf300aee446ff9e7b34a35b9220" + ], + "versions": { + "docx-rs": "0.4.22", + "office_oxide": "0.1.15", + "undoc": "0.27.1" + }, + "scope": "Warm native parse + model/IR; core also projects structured blocks. Common successful inputs per format. Five samples, 1\u201320 parses/sample. Per-document medians; p95 across documents. Excludes I/O, startup, JSON encoding, destruction, reference conversion. Shared development machine.", + "groups": { + "docx": { + "common_inputs": 999, + "attempted_inputs": 1000, + "engines": { + "docx-core": { + "median_ms": 0.6729685, + "p95_ms": 5.080422949999991 + }, + "docx-rs": { + "median_ms": 1.192551153846154, + "p95_ms": 9.498645799999966 + }, + "office_oxide": { + "median_ms": 0.7496617777777778, + "p95_ms": 5.379519375 + }, + "undoc": { + "median_ms": 1.1304833333333333, + "p95_ms": 14.260343949999996 + } + } + }, + "doc": { + "common_inputs": 211, + "attempted_inputs": 217, + "engines": { + "docx-core": { + "median_ms": 0.265642, + "p95_ms": 5.016135416666666 + }, + "office_oxide": { + "median_ms": 0.07659374999999999, + "p95_ms": 1.289615369047619 + }, + "undoc": { + "median_ms": 0.02679170000000001, + "p95_ms": 0.42453890357142854 + } + } + }, + "dot": { + "common_inputs": 2, + "attempted_inputs": 2, + "engines": { + "docx-core": { + "median_ms": 0.8427029047619048, + "p95_ms": 1.2636515904761907 + }, + "office_oxide": { + "median_ms": 0.5048541, + "p95_ms": 0.5059396799999999 + }, + "undoc": { + "median_ms": 0.13333645, + "p95_ms": 0.19577295999999994 + } + } + }, + "dotx": { + "common_inputs": 6, + "attempted_inputs": 6, + "engines": { + "docx-core": { + "median_ms": 0.49578063333333333, + "p95_ms": 9.455236249999999 + }, + "docx-rs": { + "median_ms": 0.6090204294117647, + "p95_ms": 3.71757184375 + }, + "office_oxide": { + "median_ms": 0.49055527499999996, + "p95_ms": 2.1200322035714283 + }, + "undoc": { + "median_ms": 0.4443197750000001, + "p95_ms": 3.8815780772058828 + } + } + }, + "docm": { + "common_inputs": 6, + "attempted_inputs": 6, + "engines": { + "docx-core": { + "median_ms": 0.18297297499999998, + "p95_ms": 2.3796379264705885 + }, + "docx-rs": { + "median_ms": 0.2592604, + "p95_ms": 1.8558563750000003 + }, + "office_oxide": { + "median_ms": 0.14765324999999999, + "p95_ms": 0.898928103846154 + }, + "undoc": { + "median_ms": 0.0836656, + "p95_ms": 2.5706526249999997 + } + } + }, + "dotm": { + "common_inputs": 12, + "attempted_inputs": 12, + "engines": { + "docx-core": { + "median_ms": 0.5659778888888889, + "p95_ms": 1.1299218271428568 + }, + "docx-rs": { + "median_ms": 1.6565965897435897, + "p95_ms": 2.6480899071428574 + }, + "office_oxide": { + "median_ms": 0.627180225, + "p95_ms": 1.7040655642857132 + }, + "undoc": { + "median_ms": 0.715742625, + "p95_ms": 3.454795812499997 + } + } + }, + "docx-regressions": { + "common_inputs": 72, + "attempted_inputs": 75, + "engines": { + "docx-core": { + "median_ms": 0.2111316538461539, + "p95_ms": 0.7934202066666669 + }, + "docx-rs": { + "median_ms": 0.310368875, + "p95_ms": 1.1449689894871802 + }, + "office_oxide": { + "median_ms": 0.175740725, + "p95_ms": 0.6484220152631579 + }, + "undoc": { + "median_ms": 0.1271969, + "p95_ms": 0.7499995325000001 + } + } + } + } +} diff --git a/crates/docx-core/benchmarks/optimized-20261102/validation.json b/crates/docx-core/benchmarks/optimized-20261102/validation.json new file mode 100644 index 0000000..e14c917 --- /dev/null +++ b/crates/docx-core/benchmarks/optimized-20261102/validation.json @@ -0,0 +1,601 @@ +{ + "accuracy": { + "engine_input_records": 5316, + "changes": [] + }, + "output_parity": { + "core_inputs": 1329, + "exact_original_baseline": 1323, + "exact_baseline_rerun": 6, + "regressions": [], + "by_format": { + "doc": { + "inputs": 228, + "exact_original_baseline": 222, + "exact_baseline_rerun": 6 + }, + "docx": { + "inputs": 1075, + "exact_original_baseline": 1075 + }, + "docm": { + "inputs": 6, + "exact_original_baseline": 6 + }, + "dotx": { + "inputs": 6, + "exact_original_baseline": 6 + }, + "dotm": { + "inputs": 12, + "exact_original_baseline": 12 + }, + "dot": { + "inputs": 2, + "exact_original_baseline": 2 + } + }, + "baseline_binary_sha256": "3a8d0e2f668bbc1cd283daeeb0bf9582c3ae9aed2dc6d6c9704ca65f95aedea9", + "note": "Original-baseline differences are checked against reruns of the unmodified binary. Unmodified baseline binary reruns exactly reproduce each candidate full-model and full-block digest, including IDs. The original mismatches remain retained in output-parity.json." + }, + "concurrent_validated_parses": 1088, + "paired": { + "doc": { + "inputs": 216, + "baseline": { + "median_ms": 0.26049957222222225, + "p95_ms": 4.769786 + }, + "candidate": { + "median_ms": 0.25499695555555557, + "p95_ms": 4.245916833333333 + }, + "median_paired_ratio": 0.9342218717353656, + "p95_paired_ratio": 1.0453759932865336 + }, + "docm": { + "inputs": 6, + "baseline": { + "median_ms": 0.17854692499999997, + "p95_ms": 2.334735472222222 + }, + "candidate": { + "median_ms": 0.17450312500000004, + "p95_ms": 2.269368613970588 + }, + "median_paired_ratio": 0.9931098049538675, + "p95_paired_ratio": 1.0343359214957832 + }, + "docx": { + "inputs": 1075, + "baseline": { + "median_ms": 0.54214225, + "p95_ms": 4.125308733333332 + }, + "candidate": { + "median_ms": 0.5512566666666666, + "p95_ms": 4.130672699999997 + }, + "median_paired_ratio": 0.9990689944155422, + "p95_paired_ratio": 1.1249533921527806 + }, + "dot": { + "inputs": 2, + "baseline": { + "median_ms": 0.9198514523809525, + "p95_ms": 1.4370786452380953 + }, + "candidate": { + "median_ms": 0.8096285833333333, + "p95_ms": 1.2438192583333332 + }, + "median_paired_ratio": 0.90624114297788, + "p95_paired_ratio": 0.9437927360581421 + }, + "dotm": { + "inputs": 12, + "baseline": { + "median_ms": 0.557482873015873, + "p95_ms": 1.0470677559523804 + }, + "candidate": { + "median_ms": 0.5286873750000001, + "p95_ms": 1.0106973199999996 + }, + "median_paired_ratio": 0.9716403894646084, + "p95_paired_ratio": 1.0388406298556712 + }, + "dotx": { + "inputs": 6, + "baseline": { + "median_ms": 0.5345405666666666, + "p95_ms": 7.017807937500001 + }, + "candidate": { + "median_ms": 0.5606717083333334, + "p95_ms": 7.009425125 + }, + "median_paired_ratio": 1.021946721010218, + "p95_paired_ratio": 1.1155564564717066 + } + }, + "stages": { + "doc": { + "inputs": 216, + "stages": { + "package_ms": { + "baseline": 0.134729, + "candidate": 0.1191255, + "median_paired_ratio": 0.8908799637185691 + }, + "model_ms": { + "baseline": 0.115729, + "candidate": 0.121479, + "median_paired_ratio": 1.0167041902830225 + }, + "blocks_ms": { + "baseline": 0.012542, + "candidate": 0.012875, + "median_paired_ratio": 1.0175720311020573 + }, + "total_ms": { + "baseline": 0.27464500000000003, + "candidate": 0.263542, + "median_paired_ratio": 0.9506402991713869 + }, + "doc.cfb": { + "baseline": 0.006479, + "candidate": 0.005396, + "median_paired_ratio": 0.8385997163764745 + }, + "doc.emit_xml": { + "baseline": 0.10091649999999999, + "candidate": 0.088729, + "median_paired_ratio": 0.8871462137172001 + }, + "doc.open_including_cfb": { + "baseline": 0.0078125, + "candidate": 0.006291000000000001, + "median_paired_ratio": 0.7664376101096096 + }, + "doc.setup": { + "baseline": 0.0137295, + "candidate": 0.013042000000000002, + "median_paired_ratio": 1.017388954887429 + }, + "model.body": { + "baseline": 0.0294795, + "candidate": 0.031896, + "median_paired_ratio": 1.01021961426492 + }, + "model.normalize": { + "baseline": 0.023104, + "candidate": 0.0234795, + "median_paired_ratio": 1.005922615775282 + }, + "model.stories": { + "baseline": 0.0071665, + "candidate": 0.0071665, + "median_paired_ratio": 1.0127638936816128 + }, + "model.styles": { + "baseline": 0.0028539999999999998, + "candidate": 0.0029584999999999998, + "median_paired_ratio": 1.0 + }, + "model.xml_index": { + "baseline": 0.0007085, + "candidate": 0.000709, + "median_paired_ratio": 1.0 + } + } + }, + "docm": { + "inputs": 6, + "stages": { + "package_ms": { + "baseline": 0.078625, + "candidate": 0.07772899999999999, + "median_paired_ratio": 1.0146388317717272 + }, + "model_ms": { + "baseline": 0.1205205, + "candidate": 0.1197295, + "median_paired_ratio": 1.0044340238035245 + }, + "blocks_ms": { + "baseline": 0.0013745, + "candidate": 0.001271, + "median_paired_ratio": 1.0624610169491524 + }, + "total_ms": { + "baseline": 0.197417, + "candidate": 0.2036035, + "median_paired_ratio": 1.0089190364754164 + }, + "model.body": { + "baseline": 0.0020625, + "candidate": 0.002125, + "median_paired_ratio": 1.0178036792308567 + }, + "model.normalize": { + "baseline": 0.054166, + "candidate": 0.0520415, + "median_paired_ratio": 1.0073638879836535 + }, + "model.stories": { + "baseline": 0.0020625, + "candidate": 0.0021869999999999997, + "median_paired_ratio": 1.0791843698744483 + }, + "model.styles": { + "baseline": 0.0042295, + "candidate": 0.003979, + "median_paired_ratio": 1.0084634304207118 + }, + "model.xml_index": { + "baseline": 0.0011250000000000001, + "candidate": 0.001167, + "median_paired_ratio": 1.0114500626294944 + } + } + }, + "docx": { + "inputs": 30, + "stages": { + "package_ms": { + "baseline": 0.0930005, + "candidate": 0.09693750000000001, + "median_paired_ratio": 1.0068328303680072 + }, + "model_ms": { + "baseline": 0.4505835, + "candidate": 0.4494165, + "median_paired_ratio": 1.0028281845505957 + }, + "blocks_ms": { + "baseline": 0.054854, + "candidate": 0.0549165, + "median_paired_ratio": 0.9835807607218687 + }, + "total_ms": { + "baseline": 0.5865005, + "candidate": 0.596833, + "median_paired_ratio": 1.001061393106156 + }, + "model.body": { + "baseline": 0.1394375, + "candidate": 0.14158300000000001, + "median_paired_ratio": 1.0004803073967339 + }, + "model.normalize": { + "baseline": 0.1542915, + "candidate": 0.15670800000000001, + "median_paired_ratio": 1.0059971352978256 + }, + "model.stories": { + "baseline": 0.04375, + "candidate": 0.0536245, + "median_paired_ratio": 1.011798984259026 + }, + "model.styles": { + "baseline": 0.008146, + "candidate": 0.0085, + "median_paired_ratio": 0.9938958906402566 + }, + "model.xml_index": { + "baseline": 0.0018960000000000001, + "candidate": 0.0019585, + "median_paired_ratio": 0.9746779993240959 + } + } + }, + "dot": { + "inputs": 2, + "stages": { + "package_ms": { + "baseline": 0.6564795, + "candidate": 0.500354, + "median_paired_ratio": 0.8139520901406176 + }, + "model_ms": { + "baseline": 0.210167, + "candidate": 0.2175625, + "median_paired_ratio": 1.0306992784756308 + }, + "blocks_ms": { + "baseline": 0.059417000000000005, + "candidate": 0.06375, + "median_paired_ratio": 1.0513024118146155 + }, + "total_ms": { + "baseline": 0.9198759999999999, + "candidate": 0.7767705, + "median_paired_ratio": 0.8815581852741912 + }, + "doc.cfb": { + "baseline": 0.008625, + "candidate": 0.0074164999999999995, + "median_paired_ratio": 0.8685742397137746 + }, + "doc.emit_xml": { + "baseline": 0.5926665, + "candidate": 0.4395205, + "median_paired_ratio": 0.7960258903218467 + }, + "doc.open_including_cfb": { + "baseline": 0.010708000000000002, + "candidate": 0.008312, + "median_paired_ratio": 0.7795110291710459 + }, + "doc.setup": { + "baseline": 0.045937500000000006, + "candidate": 0.045541000000000005, + "median_paired_ratio": 1.0010135149034207 + }, + "model.body": { + "baseline": 0.0739165, + "candidate": 0.076333, + "median_paired_ratio": 1.0288169560605727 + }, + "model.normalize": { + "baseline": 0.04666650000000001, + "candidate": 0.0484995, + "median_paired_ratio": 1.05493077708601 + }, + "model.stories": { + "baseline": 0.030125, + "candidate": 0.0297705, + "median_paired_ratio": 1.015971181169048 + }, + "model.styles": { + "baseline": 0.0048965, + "candidate": 0.004937, + "median_paired_ratio": 1.0083807619667873 + }, + "model.xml_index": { + "baseline": 0.000771, + "candidate": 0.0008960000000000001, + "median_paired_ratio": 1.1444668814177148 + } + } + }, + "dotm": { + "inputs": 12, + "stages": { + "package_ms": { + "baseline": 0.0960625, + "candidate": 0.1024795, + "median_paired_ratio": 0.9825492071684956 + }, + "model_ms": { + "baseline": 0.45570849999999996, + "candidate": 0.475188, + "median_paired_ratio": 0.959245234852075 + }, + "blocks_ms": { + "baseline": 0.025709000000000003, + "candidate": 0.0302705, + "median_paired_ratio": 0.9602508748025114 + }, + "total_ms": { + "baseline": 0.605416, + "candidate": 0.593459, + "median_paired_ratio": 0.9668243794133229 + }, + "model.body": { + "baseline": 0.0934795, + "candidate": 0.102208, + "median_paired_ratio": 1.021105322294202 + }, + "model.normalize": { + "baseline": 0.1639585, + "candidate": 0.15300049999999998, + "median_paired_ratio": 0.9698027400351961 + }, + "model.stories": { + "baseline": 0.065729, + "candidate": 0.0633955, + "median_paired_ratio": 0.967719775439178 + }, + "model.styles": { + "baseline": 0.010395999999999999, + "candidate": 0.009229, + "median_paired_ratio": 0.9584510409882578 + }, + "model.xml_index": { + "baseline": 0.0022085000000000004, + "candidate": 0.0022915, + "median_paired_ratio": 1.006871794871795 + } + } + }, + "dotx": { + "inputs": 6, + "stages": { + "package_ms": { + "baseline": 0.0874585, + "candidate": 0.08612500000000001, + "median_paired_ratio": 0.9925187265450958 + }, + "model_ms": { + "baseline": 0.422083, + "candidate": 0.397084, + "median_paired_ratio": 1.021523454109718 + }, + "blocks_ms": { + "baseline": 0.022167000000000003, + "candidate": 0.0231045, + "median_paired_ratio": 1.0644453643798175 + }, + "total_ms": { + "baseline": 0.539396, + "candidate": 0.5011680000000001, + "median_paired_ratio": 1.0084623050271389 + }, + "model.body": { + "baseline": 0.07985450000000001, + "candidate": 0.07589599999999999, + "median_paired_ratio": 1.038237276860714 + }, + "model.normalize": { + "baseline": 0.18518749999999998, + "candidate": 0.17468699999999998, + "median_paired_ratio": 0.98388892079741 + }, + "model.stories": { + "baseline": 0.058229, + "candidate": 0.057124999999999995, + "median_paired_ratio": 1.001262455103128 + }, + "model.styles": { + "baseline": 0.0106875, + "candidate": 0.0097915, + "median_paired_ratio": 0.9884714131607335 + }, + "model.xml_index": { + "baseline": 0.002354, + "candidate": 0.0023955, + "median_paired_ratio": 1.1306570338058888 + } + } + } + }, + "concurrent": [ + { + "case": "mixed", + "workers": 1, + "allocation": { + "throughput_per_second": 622.4087160102915, + "median_ms": 0.7139169999999999, + "p95_ms": 6.036299999999957 + }, + "actor": { + "throughput_per_second": 619.5133142123129, + "median_ms": 0.7126874999999999, + "p95_ms": 5.952816699999992 + }, + "median_paired_throughput_change_pct": 0.7389831646653855 + }, + { + "case": "mixed", + "workers": 4, + "allocation": { + "throughput_per_second": 1464.5920398068965, + "median_ms": 0.812354, + "p95_ms": 7.887837849999999 + }, + "actor": { + "throughput_per_second": 1508.6720209392317, + "median_ms": 0.7996875, + "p95_ms": 7.721666349999993 + }, + "median_paired_throughput_change_pct": 3.0097105497136845 + }, + { + "case": "mixed", + "workers": 8, + "allocation": { + "throughput_per_second": 2236.6838414875915, + "median_ms": 0.9201254999999999, + "p95_ms": 10.052099699999996 + }, + "actor": { + "throughput_per_second": 2306.790484392231, + "median_ms": 0.873354, + "p95_ms": 9.746372949999992 + }, + "median_paired_throughput_change_pct": 0.040448893357702964 + }, + { + "case": "hot-doc", + "workers": 1, + "allocation": { + "throughput_per_second": 1435.9129888601108, + "median_ms": 0.6456660000000001, + "p95_ms": 0.7794253 + }, + "actor": { + "throughput_per_second": 1418.2360586198802, + "median_ms": 0.6518125, + "p95_ms": 0.8010481999999999 + }, + "median_paired_throughput_change_pct": -0.8125956345382157 + }, + { + "case": "hot-doc", + "workers": 4, + "allocation": { + "throughput_per_second": 3860.668102127411, + "median_ms": 0.9351875, + "p95_ms": 1.09471425 + }, + "actor": { + "throughput_per_second": 3803.1559681631156, + "median_ms": 0.9634375000000001, + "p95_ms": 1.2016270999999998 + }, + "median_paired_throughput_change_pct": -2.2850918613881 + }, + { + "case": "hot-doc", + "workers": 8, + "allocation": { + "throughput_per_second": 6469.19613920396, + "median_ms": 1.075958, + "p95_ms": 1.4291291499999998 + }, + "actor": { + "throughput_per_second": 6541.890932142897, + "median_ms": 1.07225, + "p95_ms": 1.4433666499999995 + }, + "median_paired_throughput_change_pct": -1.9524867124385725 + }, + { + "case": "hot-docx", + "workers": 1, + "allocation": { + "throughput_per_second": 740.1039643697762, + "median_ms": 1.2307080000000001, + "p95_ms": 1.43975625 + }, + "actor": { + "throughput_per_second": 756.6025399435721, + "median_ms": 1.2494375, + "p95_ms": 2.197493749999998 + }, + "median_paired_throughput_change_pct": -1.203496730125131 + }, + { + "case": "hot-docx", + "workers": 4, + "allocation": { + "throughput_per_second": 2111.775038518117, + "median_ms": 1.6401455, + "p95_ms": 2.7061131 + }, + "actor": { + "throughput_per_second": 2202.2986230354973, + "median_ms": 1.565875, + "p95_ms": 2.1286381499999996 + }, + "median_paired_throughput_change_pct": 8.056912255383475 + }, + { + "case": "hot-docx", + "workers": 8, + "allocation": { + "throughput_per_second": 2876.7061671235842, + "median_ms": 2.2480835, + "p95_ms": 3.7467461499999994 + }, + "actor": { + "throughput_per_second": 3284.61370184304, + "median_ms": 2.1661875, + "p95_ms": 3.4192148999999996 + }, + "median_paired_throughput_change_pct": -0.8069952007797143 + } + ] +} diff --git a/crates/docx-core/src/doc/build.rs b/crates/docx-core/src/doc/build.rs index 6ac54d1..0a4a82a 100644 --- a/crates/docx-core/src/doc/build.rs +++ b/crates/docx-core/src/doc/build.rs @@ -10,7 +10,7 @@ use crate::package::{OoxmlPackage, OoxmlPart}; use super::chp::Chp; use super::fib::fcidx; use super::fkp; -use super::fmt::escape_xml; +use super::fmt::{escape_xml, escape_xml_into}; use super::fonts::FontTable; use super::images::{extract_image, InlineImage}; use super::lists::ListTables; @@ -118,6 +118,7 @@ pub struct DocBuilder<'a> { fonts: FontTable, lists: ListTables, char_runs: Vec, + char_run_max_ends: Vec, papx_bins: Vec<(u32, u32, u32)>, papx_pages: HashMap>, sections: Vec<(u32, Sep)>, // (exclusive cp end, properties) @@ -178,6 +179,7 @@ impl<'a> DocBuilder<'a> { fonts, lists, char_runs: Vec::new(), + char_run_max_ends: Vec::new(), papx_bins: Vec::new(), papx_pages: HashMap::new(), sections: Vec::new(), @@ -266,6 +268,22 @@ impl<'a> DocBuilder<'a> { } } self.char_runs.sort_by_key(|run| run.cp_start); + self.char_run_max_ends = self + .char_runs + .iter() + .scan(0, |end, run| { + *end = (*end).max(run.cp_end); + Some(*end) + }) + .collect(); + } + + fn char_run_range(&self, start: u32, end: u32) -> std::ops::Range { + let first = self + .char_run_max_ends + .partition_point(|max_end| *max_end <= start); + let last = self.char_runs.partition_point(|run| run.cp_start < end); + first.min(last)..last } fn prm_grpprl(&self, prm: u16) -> Option> { @@ -507,6 +525,7 @@ impl<'a> DocBuilder<'a> { paragraphs.push(self.build_paragraph( para_start, cp + 1, + &text[(para_start - cp_start) as usize..=index], unit, context, &mut fields, @@ -515,7 +534,14 @@ impl<'a> DocBuilder<'a> { } } if para_start < cp_end { - paragraphs.push(self.build_paragraph(para_start, cp_end, 0x0D, context, &mut fields)); + paragraphs.push(self.build_paragraph( + para_start, + cp_end, + &text[(para_start - cp_start) as usize..], + 0x0D, + context, + &mut fields, + )); } paragraphs } @@ -524,6 +550,7 @@ impl<'a> DocBuilder<'a> { &mut self, cp_start: u32, cp_end: u32, + text: &[u16], mark_char: u16, context: &NoteContext, fields: &mut Vec, @@ -556,18 +583,24 @@ impl<'a> DocBuilder<'a> { 0 }; - let mut runs_xml = self.build_runs(cp_start, mark_cp, pap.istd, context, fields); + let mut runs_xml = self.build_runs( + cp_start, + mark_cp, + &text[..text.len().saturating_sub(1)], + pap.istd, + context, + fields, + ); if let Some(markers) = self.bookmarks.get(&mark_cp) { runs_xml.push_str(markers); } // Paragraph-mark formatting (empty-paragraph strut height). let mark_rpr = { - let grpprl = self - .char_runs + let grpprl = self.char_runs[self.char_run_range(mark_cp, cp_end)] .iter() .find(|run| mark_cp >= run.cp_start && mark_cp < run.cp_end) - .map(|run| run.grpprl.clone()) + .map(|run| run.grpprl.as_slice()) .unwrap_or_default(); let char_istd = super::sprm::iterate(&grpprl) .find(|sprm| sprm.opcode == 0x4A30) @@ -598,6 +631,7 @@ impl<'a> DocBuilder<'a> { &mut self, cp_start: u32, cp_end: u32, + text: &[u16], para_istd: u16, context: &NoteContext, fields: &mut Vec, @@ -608,28 +642,29 @@ impl<'a> DocBuilder<'a> { } // Collect runs intersecting the range; fall back to one unformatted run. - let mut intervals: Vec<(u32, u32, Vec)> = self - .char_runs + let range = self.char_run_range(cp_start, cp_end); + let mut intervals: Vec<(u32, u32, Option)> = self.char_runs[range.clone()] .iter() - .filter(|run| run.cp_end > cp_start && run.cp_start < cp_end) - .map(|run| { + .enumerate() + .filter(|(_, run)| run.cp_end > cp_start && run.cp_start < cp_end) + .map(|(index, run)| { ( run.cp_start.max(cp_start), run.cp_end.min(cp_end), - run.grpprl.clone(), + Some(range.start + index), ) }) .collect(); if intervals.is_empty() { - intervals.push((cp_start, cp_end, Vec::new())); + intervals.push((cp_start, cp_end, None)); } intervals.sort_by_key(|(start, _, _)| *start); // Fill any gaps so every CP is covered. let mut covered = cp_start; - let mut complete: Vec<(u32, u32, Vec)> = Vec::new(); + let mut complete: Vec<(u32, u32, Option)> = Vec::new(); for (start, end, grpprl) in intervals { if start > covered { - complete.push((covered, start, Vec::new())); + complete.push((covered, start, None)); } let clamped_start = start.max(covered); if clamped_start < end { @@ -638,11 +673,15 @@ impl<'a> DocBuilder<'a> { } } if covered < cp_end { - complete.push((covered, cp_end, Vec::new())); + complete.push((covered, cp_end, None)); } let mut linked = Vec::new(); for (start, end, grpprl) in complete { + if self.spanning_links.is_empty() { + linked.push((start, end, grpprl, None)); + continue; + } let mut boundaries = vec![start, end]; for link in &self.spanning_links { boundaries.extend( @@ -659,10 +698,13 @@ impl<'a> DocBuilder<'a> { .iter() .find(|link| pair[0] >= link.start && pair[1] <= link.end) .map(|link| link.instruction.clone()); - linked.push((pair[0], pair[1], grpprl.clone(), instruction)); + linked.push((pair[0], pair[1], grpprl, instruction)); } } - for (start, end, grpprl, instruction) in linked { + for (start, end, run_index, instruction) in linked { + let grpprl = run_index + .map(|index| self.char_runs[index].grpprl.as_slice()) + .unwrap_or_default(); if let Some(instruction) = &instruction { xml.push_str(&format!( r#""#, @@ -684,7 +726,7 @@ impl<'a> DocBuilder<'a> { self.emit_run_content( &mut deleted, start, - end, + &text[(start - cp_start) as usize..(end - cp_start) as usize], &chp, &rpr, &mut deleted_fields, @@ -695,7 +737,15 @@ impl<'a> DocBuilder<'a> { .replace("", ""); xml.push_str(&format!(r#"{deleted}"#)); } else { - self.emit_run_content(&mut xml, start, end, &chp, &rpr, fields, context); + self.emit_run_content( + &mut xml, + start, + &text[(start - cp_start) as usize..(end - cp_start) as usize], + &chp, + &rpr, + fields, + context, + ); } if instruction.is_some() { xml.push_str(""); @@ -709,30 +759,39 @@ impl<'a> DocBuilder<'a> { &mut self, xml: &mut String, cp_start: u32, - cp_end: u32, + text: &[u16], chp: &Chp, rpr: &str, fields: &mut Vec, context: &NoteContext, ) { - let text = self.doc.text(cp_start, cp_end); let mut buffer = String::new(); let flush = |buffer: &mut String, xml: &mut String, state: FieldState, rpr: &str| { if buffer.is_empty() { return; } - let escaped = escape_xml(buffer); - let body = if state == FieldState::Instruction { - format!(r#"{escaped}"#) + xml.push_str(""); + xml.push_str(rpr); + let instruction = state == FieldState::Instruction; + xml.push_str(if instruction { + r#""# } else { - format!(r#"{escaped}"#) - }; - xml.push_str(&format!("{rpr}{body}")); + r#""# + }); + escape_xml_into(buffer, xml); + xml.push_str(if instruction { + "" + } else { + "" + }); buffer.clear(); }; let single = |xml: &mut String, rpr: &str, inner: &str| { - xml.push_str(&format!("{rpr}{inner}")); + xml.push_str(""); + xml.push_str(rpr); + xml.push_str(inner); + xml.push_str(""); }; for (index, &unit) in text.iter().enumerate() { @@ -1019,7 +1078,7 @@ impl<'a> DocBuilder<'a> { let index = self.image_count; let file_name = format!("media/image{index}.{}", image.extension); let part_name = format!("word/{file_name}"); - self.media.insert(part_name, image.bytes.clone()); + self.media.insert(part_name, image.bytes); self.media_extensions.insert(image.extension); // The rel id is fixed up by the caller part's RelSet via placeholder @@ -1128,14 +1187,14 @@ impl<'a> DocBuilder<'a> { } index += consumed; } else { - xml.push_str(&self.paragraph_xml(para)); + self.append_paragraph_xml(para, &mut xml); index += 1; } } xml } - fn paragraph_xml(&self, para: &ParaOut) -> String { + fn append_paragraph_xml(&self, para: &ParaOut, xml: &mut String) { let mut ppr_inner = para .pap .to_ppr_inner_xml(&|istd| self.styles.style_id(istd)); @@ -1143,11 +1202,14 @@ impl<'a> DocBuilder<'a> { if let Some(sect_index) = para.sect_index { ppr_inner.push_str(&self.sect_pr_xml(sect_index)); } - if ppr_inner.is_empty() { - format!("{}", para.runs_xml) - } else { - format!("{ppr_inner}{}", para.runs_xml) + xml.push_str(""); + if !ppr_inner.is_empty() { + xml.push_str(""); + xml.push_str(&ppr_inner); + xml.push_str(""); } + xml.push_str(¶.runs_xml); + xml.push_str(""); } /// Builds one table at `level` from the head of `paragraphs`. Returns the @@ -1421,7 +1483,7 @@ impl<'a> DocBuilder<'a> { let (table_xml, _, _) = self.assemble_table(&owned, 1); xml.push_str(&table_xml); } else { - xml.push_str(&self.paragraph_xml(para)); + self.append_paragraph_xml(para, &mut xml); index += 1; } } diff --git a/crates/docx-core/src/doc/cfb.rs b/crates/docx-core/src/doc/cfb.rs index 8e50db5..294fbf0 100644 --- a/crates/docx-core/src/doc/cfb.rs +++ b/crates/docx-core/src/doc/cfb.rs @@ -58,6 +58,13 @@ pub fn is_cfb(bytes: &[u8]) -> bool { impl CompoundFile { pub fn parse(bytes: &[u8]) -> Result { + Self::parse_selected(bytes, None) + } + + pub(crate) fn parse_selected( + bytes: &[u8], + selected: Option<&[&str]>, + ) -> Result { if !is_cfb(bytes) { return Err("CFB: missing compound file signature".to_string()); } @@ -113,6 +120,13 @@ impl CompoundFile { if entry.object_type != OBJECT_TYPE_STREAM { continue; } + if selected.is_some_and(|names| { + !names + .iter() + .any(|name| entry.name.eq_ignore_ascii_case(name)) + }) { + continue; + } let data = if entry.size < mini_cutoff { match (&mini_stream, &mini_fat) { @@ -142,6 +156,10 @@ impl CompoundFile { self.try_stream(name).ok().flatten() } + pub(crate) fn take_stream(&mut self, name: &str) -> Result>, String> { + self.streams.remove(&name.to_ascii_lowercase()).transpose() + } + pub fn try_stream(&self, name: &str) -> Result, String> { self.streams .get(&name.to_ascii_lowercase()) diff --git a/crates/docx-core/src/doc/fmt.rs b/crates/docx-core/src/doc/fmt.rs index 8b747f6..7b85332 100644 --- a/crates/docx-core/src/doc/fmt.rs +++ b/crates/docx-core/src/doc/fmt.rs @@ -3,19 +3,28 @@ pub fn escape_xml(text: &str) -> String { let mut out = String::with_capacity(text.len()); - for ch in text.chars() { - match ch { - '&' => out.push_str("&"), - '<' => out.push_str("<"), - '>' => out.push_str(">"), - '"' => out.push_str("""), - '\'' => out.push_str("'"), - _ => out.push(ch), - } - } + escape_xml_into(text, &mut out); out } +pub(super) fn escape_xml_into(text: &str, out: &mut String) { + let mut start = 0; + for (index, byte) in text.bytes().enumerate() { + let escaped = match byte { + b'&' => "&", + b'<' => "<", + b'>' => ">", + b'"' => """, + b'\'' => "'", + _ => continue, + }; + out.push_str(&text[start..index]); + out.push_str(escaped); + start = index + 1; + } + out.push_str(&text[start..]); +} + /// Classic 17-entry Word color palette (ico values 1-16; 0 = auto). const ICO_PALETTE: [&str; 17] = [ "auto", "000000", "0000FF", "00FFFF", "00FF00", "FF00FF", "FF0000", "FFFF00", "FFFFFF", @@ -106,7 +115,10 @@ pub struct Border { impl Border { pub fn to_xml(&self, tag: &str) -> String { - let mut xml = format!(r#"<{tag} w:val="{}" w:sz="{}" w:space="{}""#, self.val, self.sz, self.space); + let mut xml = format!( + r#"<{tag} w:val="{}" w:sz="{}" w:space="{}""#, + self.val, self.sz, self.space + ); xml.push_str(&format!( r#" w:color="{}""#, self.color.as_deref().unwrap_or("auto") diff --git a/crates/docx-core/src/doc/mod.rs b/crates/docx-core/src/doc/mod.rs index d830cc7..19908b8 100644 --- a/crates/docx-core/src/doc/mod.rs +++ b/crates/docx-core/src/doc/mod.rs @@ -42,26 +42,24 @@ pub struct DocFile { impl DocFile { pub fn open(bytes: &[u8]) -> Result { - let compound = CompoundFile::parse(bytes)?; + let mut compound = CompoundFile::parse_selected( + bytes, + Some(&["WordDocument", "0Table", "1Table", "Data"]), + )?; let word = compound - .try_stream("WordDocument")? - .ok_or_else(|| "Invalid .doc: missing WordDocument stream".to_string())? - .to_vec(); + .take_stream("WordDocument")? + .ok_or_else(|| "Invalid .doc: missing WordDocument stream".to_string())?; let fib = Fib::parse(&word)?; - let table = match compound.try_stream(fib.table_stream_name())? { + let table = match compound.take_stream(fib.table_stream_name())? { Some(table) => Some(table), - None => compound.try_stream(if fib.f_which_tbl_stm { + None => compound.take_stream(if fib.f_which_tbl_stm { "0Table" } else { "1Table" })?, } - .ok_or_else(|| "Invalid .doc: missing Table stream".to_string())? - .to_vec(); - let data = compound - .try_stream("Data")? - .map(|s| s.to_vec()) - .unwrap_or_default(); + .ok_or_else(|| "Invalid .doc: missing Table stream".to_string())?; + let data = compound.take_stream("Data")?.unwrap_or_default(); let clx = fib .table_slice(&table, fib::fcidx::CLX) diff --git a/crates/docx-core/src/parse/cache.rs b/crates/docx-core/src/parse/cache.rs new file mode 100644 index 0000000..c6345fd --- /dev/null +++ b/crates/docx-core/src/parse/cache.rs @@ -0,0 +1,72 @@ +use std::sync::{ + mpsc::{sync_channel, SyncSender}, + Arc, OnceLock, +}; + +use arc_swap::ArcSwap; + +type Update = Box; + +fn writer() -> Option<&'static SyncSender> { + static WRITER: OnceLock>> = OnceLock::new(); + WRITER + .get_or_init(|| { + let (send, receive) = sync_channel::(8); + std::thread::Builder::new() + .name("docx-cache".into()) + .spawn(move || { + while let Ok(update) = receive.recv() { + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(update)); + } + }) + .ok()?; + Some(send) + }) + .as_ref() +} + +pub(super) struct Snapshot { + value: ArcSwap, +} + +impl Snapshot { + pub(super) fn new(value: T) -> Self { + Self { + value: ArcSwap::from_pointee(value), + } + } + + pub(super) fn read(&self, read: impl FnOnce(&T) -> R) -> R { + read(&self.value.load()) + } + + pub(super) fn reader(&'static self) -> arc_swap::cache::Cache<&'static ArcSwap, Arc> { + arc_swap::cache::Cache::new(&self.value) + } + + pub(super) fn publish(&'static self, update: impl FnOnce(&T) -> T + Send + 'static) -> bool { + let Some(writer) = writer() else { return false }; + writer + .try_send(Box::new(move || { + let next = update(&self.value.load()); + self.value.store(Arc::new(next)); + })) + .is_ok() + } + + pub(super) fn update( + &'static self, + update: impl FnOnce(&T) -> (T, R) + Send + 'static, + ) -> Option { + let writer = writer()?; + let (send, receive) = sync_channel(1); + writer + .send(Box::new(move || { + let (next, result) = update(&self.value.load()); + self.value.store(Arc::new(next)); + let _ = send.send(result); + })) + .ok()?; + receive.recv().ok() + } +} diff --git a/crates/docx-core/src/parse/mod.rs b/crates/docx-core/src/parse/mod.rs index ddf9ff1..9f172d5 100644 --- a/crates/docx-core/src/parse/mod.rs +++ b/crates/docx-core/src/parse/mod.rs @@ -1,3 +1,5 @@ +#[cfg(not(target_arch = "wasm32"))] +mod cache; mod body; mod build; mod colors; diff --git a/crates/docx-core/src/parse/namespaces.rs b/crates/docx-core/src/parse/namespaces.rs index 214ce48..83bc430 100644 --- a/crates/docx-core/src/parse/namespaces.rs +++ b/crates/docx-core/src/parse/namespaces.rs @@ -438,6 +438,38 @@ struct RootPlan { type RootPlanEntry = (String, String, std::sync::Arc); +#[cfg(not(target_arch = "wasm32"))] +fn cached_root_plan( + key: (&str, &str), + build: impl FnOnce() -> Option, +) -> Option> { + use std::collections::VecDeque; + use std::sync::{Arc, LazyLock}; + static PLANS: LazyLock>>> = + LazyLock::new(|| super::cache::Snapshot::new(VecDeque::new())); + if key.0.len() > 64 || key.1.len() > 8192 { + return build().map(Arc::new); + } + if let Some(plan) = PLANS.read(|plans| plans.iter() + .find(|entry| entry.0 == key.0 && entry.1 == key.1) + .map(|entry| Arc::clone(&entry.2))) { + return Some(plan); + } + let plan = Arc::new(build()?); + let entry = Arc::new((key.0.to_owned(), key.1.to_owned(), Arc::clone(&plan))); + PLANS.publish(move |plans| { + if plans.iter().any(|existing| existing.0 == entry.0 && existing.1 == entry.1) { + return plans.clone(); + } + let mut next = plans.clone(); + if next.len() == 32 { next.pop_back(); } + next.push_front(entry); + next + }); + Some(plan) +} + +#[cfg(target_arch = "wasm32")] fn cached_root_plan( key: (&str, &str), build: impl FnOnce() -> Option, @@ -1229,45 +1261,40 @@ pub(crate) fn worker_pool() -> Option<&'static rayon::ThreadPool> { mod definition_indexes { use super::*; use std::collections::VecDeque; - use std::sync::{Mutex, OnceLock}; + use std::sync::{Arc, LazyLock}; - static PREPARED: OnceLock>> = OnceLock::new(); + type CachedPart = (Option, Option>); + type Entry = Arc<(String, CachedPart)>; + static PREPARED: LazyLock>> = + LazyLock::new(|| super::super::cache::Snapshot::new(VecDeque::new())); - fn copy(prepared: &PreparedPart) -> PreparedPart { - ( - prepared - .0 - .as_ref() - .map(crate::xml_index::Index::shared_copy), - prepared.1.clone(), - ) + fn copy(prepared: &CachedPart) -> PreparedPart { + (prepared.0.as_ref().map(crate::xml_index::IndexedSource::local_index), prepared.1.clone()) } pub(super) fn prepare<'a>( part: &'a crate::package::OoxmlPart, prologs: &mut PrologCache<'a>, ) -> PreparedPart { - let prepared = PREPARED.get_or_init(|| Mutex::new(VecDeque::new())); - { - let entries = prepared.lock().unwrap_or_else(|error| error.into_inner()); - if let Some((_, result)) = entries.iter().find(|(key, _)| key == &part.content) { - return copy(result); - } + if let Some(result) = PREPARED.read(|entries| entries.iter() + .find(|entry| entry.0 == part.content).map(|entry| copy(&entry.1))) { + return result; } let result = prepare_uncached_part(part, true, prologs); if result.0.is_some() && !matches!(result.1, Some(Err(_))) { - let entry = (part.content.clone(), copy(&result)); - let mut entries = prepared.lock().unwrap_or_else(|error| error.into_inner()); - if !entries.iter().any(|(key, _)| key == &part.content) { - let mut retained = entries.iter().map(|(key, _)| key.len()).sum::(); - while entries.len() >= 8 || retained + part.content.len() > 1024 * 1024 { - let Some((key, _)) = entries.pop_front() else { - break; - }; - retained -= key.len(); + let entry = Arc::new((part.content.clone(), (result.0.as_ref().map(crate::xml_index::Index::shared_source), result.1.clone()))); + PREPARED.publish(move |entries| { + let mut next = entries.clone(); + if !next.iter().any(|existing| existing.0 == entry.0) { + let mut retained = next.iter().map(|entry| entry.0.len()).sum::(); + while next.len() >= 8 || retained + entry.0.len() > 1024 * 1024 { + let Some(old) = next.pop_front() else { break }; + retained -= old.0.len(); + } + next.push_back(entry); } - entries.push_back(entry); - } + next + }); } result } diff --git a/crates/docx-core/src/parse/numbering.rs b/crates/docx-core/src/parse/numbering.rs index 3dfb8d6..aaf5dd5 100644 --- a/crates/docx-core/src/parse/numbering.rs +++ b/crates/docx-core/src/parse/numbering.rs @@ -427,29 +427,27 @@ struct IntermediateAbstractDefinition { mod numbering_templates { use super::*; use std::collections::VecDeque; - use std::sync::{Arc, Mutex, OnceLock}; + use std::sync::{Arc, LazyLock}; - type Entry = (String, Arc>); - static TEMPLATES: OnceLock>> = OnceLock::new(); + type Entry = Arc<(String, Arc>)>; + static TEMPLATES: LazyLock>> = + LazyLock::new(|| super::super::cache::Snapshot::new(VecDeque::new())); pub(super) fn parse(xml: &str) -> Option { - let templates = TEMPLATES.get_or_init(|| Mutex::new(VecDeque::new())); - let cached = { - let entries = templates.lock().unwrap_or_else(|error| error.into_inner()); - entries.iter().find(|(key, _)| key == xml).map(|(_, value)| Arc::clone(value)) - }; - if let Some(value) = cached { + if let Some(value) = TEMPLATES.read(|entries| entries.iter() + .find(|entry| entry.0 == xml).map(|entry| Arc::clone(&entry.1))) { return (*value).clone(); } let parsed = parse_numbering_xml(xml, &HashMap::new()); - let entry = (xml.to_owned(), Arc::new(parsed.clone())); - let mut entries = templates.lock().unwrap_or_else(|error| error.into_inner()); - if !entries.iter().any(|(key, _)| key == xml) { - if entries.len() == 4 { - entries.pop_front(); + let entry = Arc::new((xml.to_owned(), Arc::new(parsed.clone()))); + TEMPLATES.publish(move |entries| { + let mut next = entries.clone(); + if !next.iter().any(|existing| existing.0 == entry.0) { + if next.len() == 4 { next.pop_front(); } + next.push_back(entry); } - entries.push_back(entry); - } + next + }); parsed } } diff --git a/crates/docx-core/src/parse/re.rs b/crates/docx-core/src/parse/re.rs index bbcda8f..c81cbf6 100644 --- a/crates/docx-core/src/parse/re.rs +++ b/crates/docx-core/src/parse/re.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; -use std::sync::{LazyLock, Mutex}; +use std::sync::LazyLock; +#[cfg(target_arch = "wasm32")] +use std::sync::Mutex; use regex::Regex; +#[cfg(target_arch = "wasm32")] static REGEX_CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); @@ -46,19 +49,55 @@ fn ascii_token_pattern(pattern: &str) -> String { result } +#[cfg(not(target_arch = "wasm32"))] +static REGEX_CACHE: LazyLock>> = + LazyLock::new(|| super::cache::Snapshot::new(HashMap::new())); + +#[cfg(not(target_arch = "wasm32"))] +thread_local! { + static REGEX_READER: std::cell::RefCell>, + std::sync::Arc>, + >> = std::cell::RefCell::new(REGEX_CACHE.reader()); +} + pub fn get(pattern: &str) -> Option<&'static Regex> { - if let Ok(cache) = REGEX_CACHE.lock() { - if let Some(regex) = cache.get(pattern) { + #[cfg(not(target_arch = "wasm32"))] + { + if let Some(regex) = + REGEX_READER.with(|cache| cache.borrow_mut().load().get(pattern).copied()) + { return Some(regex); } + let compiled = Regex::new(&ascii_token_pattern(pattern)).ok()?; + let key = pattern.to_owned(); + let result = REGEX_CACHE.update(move |cache| { + let mut next = cache.clone(); + let regex = *next + .entry(key) + .or_insert_with(|| Box::leak(Box::new(compiled))); + (next, regex) + }); + result.or_else(|| { + Regex::new(&ascii_token_pattern(pattern)) + .ok() + .map(|regex| &*Box::leak(Box::new(regex))) + }) } - - let compiled = Regex::new(&ascii_token_pattern(pattern)).ok()?; - let leaked: &'static Regex = Box::leak(Box::new(compiled)); - if let Ok(mut cache) = REGEX_CACHE.lock() { - cache.insert(pattern.to_string(), leaked); + #[cfg(target_arch = "wasm32")] + { + if let Ok(cache) = REGEX_CACHE.lock() { + if let Some(regex) = cache.get(pattern) { + return Some(regex); + } + } + let compiled = Regex::new(&ascii_token_pattern(pattern)).ok()?; + let leaked: &'static Regex = Box::leak(Box::new(compiled)); + if let Ok(mut cache) = REGEX_CACHE.lock() { + cache.insert(pattern.to_string(), leaked); + } + Some(leaked) } - Some(leaked) } pub fn get_unchecked(pattern: &str) -> &'static Regex { diff --git a/crates/docx-core/src/parse/styles.rs b/crates/docx-core/src/parse/styles.rs index f25378c..9b71282 100644 --- a/crates/docx-core/src/parse/styles.rs +++ b/crates/docx-core/src/parse/styles.rs @@ -2258,14 +2258,14 @@ struct CachedDocumentStyleSheet { #[cfg(not(target_arch = "wasm32"))] static DOCUMENT_STYLE_CACHE: std::sync::LazyLock< - std::sync::Mutex>, -> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::VecDeque::new())); + super::cache::Snapshot>>, +> = std::sync::LazyLock::new(|| super::cache::Snapshot::new(std::collections::VecDeque::new())); #[cfg(not(target_arch = "wasm32"))] pub(crate) fn cached_document_style_identity(styles: &ParsedStyleSheet) -> Option { - DOCUMENT_STYLE_CACHE.lock().ok()?.iter() + DOCUMENT_STYLE_CACHE.read(|cache| cache.iter() .find(|entry| std::ptr::eq(entry.parsed.as_ref(), styles)) - .map(|entry| std::sync::Arc::clone(&entry.parsed)) + .map(|entry| std::sync::Arc::clone(&entry.parsed))) } pub(crate) fn acquire_document_style_sheet(pkg: &OoxmlPackage) -> DocumentStyleSheet { @@ -2300,35 +2300,35 @@ pub(crate) fn acquire_document_style_sheet(pkg: &OoxmlPackage) -> DocumentStyleS && entry.theme.as_deref() == theme && entry.references == references }; - if let Ok(cache) = DOCUMENT_STYLE_CACHE.lock() { - if let Some(entry) = cache.iter().find(|entry| matches(entry)) { - return entry.parsed.clone(); - } + if let Some(parsed) = DOCUMENT_STYLE_CACHE.read(|cache| cache.iter() + .find(|entry| matches(entry)).map(|entry| entry.parsed.clone())) { + return parsed; } let parsed = std::sync::Arc::new(parse_referenced_style_sheet( - pkg, - references.iter().cloned().collect(), + pkg, references.iter().cloned().collect(), )); - if let Ok(mut cache) = DOCUMENT_STYLE_CACHE.lock() { - if let Some(entry) = cache.iter().find(|entry| matches(entry)) { - return entry.parsed.clone(); - } - if source_bytes > 512 * 1024 { - return parsed; + if source_bytes > 512 * 1024 { return parsed; } + let entry = std::sync::Arc::new(CachedDocumentStyleSheet { + styles: styles.map(str::to_owned), + theme: theme.map(str::to_owned), + references, + source_bytes, + parsed: parsed.clone(), + }); + DOCUMENT_STYLE_CACHE.publish(move |cache| { + if cache.iter().any(|existing| existing.styles == entry.styles + && existing.theme == entry.theme && existing.references == entry.references) { + return cache.clone(); } - let mut retained = cache.iter().map(|entry| entry.source_bytes).sum::(); - while cache.len() >= 4 || retained + source_bytes > 512 * 1024 { - let Some(old) = cache.pop_front() else { break }; + let mut next = cache.clone(); + let mut retained = next.iter().map(|entry| entry.source_bytes).sum::(); + while next.len() >= 4 || retained + entry.source_bytes > 512 * 1024 { + let Some(old) = next.pop_front() else { break }; retained -= old.source_bytes; } - cache.push_back(CachedDocumentStyleSheet { - styles: styles.map(str::to_owned), - theme: theme.map(str::to_owned), - references, - source_bytes, - parsed: parsed.clone(), - }); - } + next.push_back(entry); + next + }); parsed } } @@ -3151,9 +3151,10 @@ mod bounded_large_definition_tests { let expected = parse_document_style_sheet(&pkg); for _ in 0..2 { assert_eq!(*acquire_document_style_sheet(&pkg), expected); - let cache = DOCUMENT_STYLE_CACHE.lock().unwrap(); - assert!(cache.len() <= 4); - assert!(cache.iter().map(|entry| entry.source_bytes).sum::() <= 512 * 1024); + DOCUMENT_STYLE_CACHE.read(|cache| { + assert!(cache.len() <= 4); + assert!(cache.iter().map(|entry| entry.source_bytes).sum::() <= 512 * 1024); + }); } } } diff --git a/crates/docx-core/src/xml_index.rs b/crates/docx-core/src/xml_index.rs index 09bfbbf..82ec692 100644 --- a/crates/docx-core/src/xml_index.rs +++ b/crates/docx-core/src/xml_index.rs @@ -445,13 +445,11 @@ impl Index { } #[cfg(not(target_arch = "wasm32"))] - pub(crate) fn shared_copy(&self) -> Self { - Self { + pub(crate) fn shared_source(&self) -> IndexedSource { + IndexedSource { address: self.address, len: self.len, data: std::sync::Arc::clone(&self.data), - tag_cache: RefCell::new(TagCache::default()), - name_cache: RefCell::new(Vec::new()), } } @@ -836,12 +834,25 @@ thread_local! { } #[cfg(not(target_arch = "wasm32"))] -struct IndexedSource { +pub(crate) struct IndexedSource { address: usize, len: usize, data: std::sync::Arc, } +#[cfg(not(target_arch = "wasm32"))] +impl IndexedSource { + pub(crate) fn local_index(&self) -> Index { + Index { + address: self.address, + len: self.len, + data: std::sync::Arc::clone(&self.data), + tag_cache: RefCell::new(TagCache::default()), + name_cache: RefCell::new(Vec::new()), + } + } +} + // Source borrows and thread affinity keep address-based lookups valid for the scope. pub(crate) struct Scope<'a> { previous: Vec, @@ -1427,7 +1438,7 @@ mod shared_index_tests { for _ in 0..4 { let copied = original.clone(); assert_ne!(original.as_ptr(), copied.as_ptr()); - let scope = scope([("part", copied.as_str())].into_iter(), HashMap::from([("part".to_owned(), index.shared_copy())])); + let scope = scope([("part", copied.as_str())].into_iter(), HashMap::from([("part".to_owned(), index.shared_source().local_index())])); let token = tag(&copied, "item").flatten().unwrap(); assert_eq!(token, ""); assert_eq!(token.as_ptr(), copied[6..].as_ptr()); diff --git a/docs/benchmarks-optimized-20261102.md b/docs/benchmarks-optimized-20261102.md new file mode 100644 index 0000000..dda78d0 --- /dev/null +++ b/docs/benchmarks-optimized-20261102.md @@ -0,0 +1,105 @@ +# Legacy Word parser allocation and conversion optimization + +This candidate reduces unnecessary work in the Word 97–2003 adapter while preserving its generated OOXML package and complete parsed output. It does not replace the adapter with a separate direct-to-model implementation. + +The compound-file reader loads only the streams consumed by the DOC parser and transfers ownership into the document. Public unfiltered compound-file parsing remains available. Formatting lookup uses a start-sorted index with prefix maximum ends, retaining the original precedence for overlapping runs. Paragraphs and runs borrow one decoded UTF-16 story buffer and formatting property slices. XML escaping and paragraph/run emission append directly to destination buffers. Extracted media payloads move into the package. + +## Measurement + +All four libraries were rerun on the same 1,329 distinct inputs, with frozen references and unchanged scoring. The three cohorts comprise 257 multi-format development inputs, 72 additional public regression inputs, and a separate seeded 1,000-DOCX corpus. The 72-input set became a regression corpus after previous fixes; it is not an untouched holdout. + +Competitors are docx-rs 0.4.22, office_oxide 0.1.15, and undoc 0.27.1. The common-success latency cohort differs from the accuracy denominator. Failed parses retain a zero accuracy score where a reference exists. Pre-Word-97 and encrypted inputs remain visible outside declared support. + +Native optimized timing includes parsing and model/IR construction, plus structured-block extraction for core. Each document has five warm samples, each averaging 1–20 parses. Input reads, startup, JSON serialization, destruction, and reference conversion are excluded. The p95 is across per-document medians. Shared-machine measurements are directional, especially for small template/macro cohorts. + +A second run pairs the previous and candidate binaries on every input, with seeded randomized input order and seeded randomized binary order for each pair. It excludes serialization and retains raw samples and binary hashes. This controls drift more closely than comparing separately timed release runs. + +The detailed stage profile uses temporary instrumentation and seven warm samples per input. Its 272-input selection includes every successfully parsed supported non-DOCX input and 30 DOCX inputs spread across the baseline latency distribution. Nested stage timings are inclusive and must not be added together. Instrumentation is absent from the production candidate. + +## Accuracy checks + +Text five-gram F1, ordered-text F1, both feature score sets, source-reference audits, and reference denominators are compared record by record. Full model and block output digests additionally retain all serialized values, including source XML, binary assets, formatting, warnings, IDs, and source references. Only JSON object key order and order of paragraph/table style definitions are normalized. Package identity also hashes source XML and asset bytes. + +These checks establish parity on the measured corpus, not perfect fidelity. The existing text, table, field, equation, story, and visual-layout limitations remain. Binary references are LibreOffice conversion proxies, and text F1 does not measure visual layout. Reference content is never inserted into parser output. + +## Native shared-state changes + +Native cache hits read immutable snapshots instead of taking the five parser-owned cache mutexes. The regex snapshot reader is local to each thread and revalidates against the shared snapshot. Cache updates run on one actor with an eight-slot mailbox. Optional document-cache admission uses try_send: a full queue skips memoization, and the caller still returns its fully parsed result. Regex registration uses a reply channel to deduplicate retained compiled expressions before returning a stable reference. + +The actor alone publishes new snapshots. Snapshot entries share immutable plans, style sheets, and XML-index data through Arc. XML-index lookup scratch storage remains local to each parser. Cache entry/source-byte limits remain in place. Thread-local regex readers can retain an older snapshot until their next lookup or thread exit. The WebAssembly path retains its existing single-threaded implementation. + +The shared Rayon pool retains its existing scheduling. A trial atomic admission guard reduced throughput on the mixed cohort and was not retained. + +This does not make the entire library lock-free: initialization, actor mailboxes, and dependencies have their own synchronization. The hot cache-read paths no longer acquire the parser's old process-wide mutexes. + +## Current comparison + +Measured source: `dcce836072cb01a24f6490c97643f825d2d6902e`. Each cell is body-text five-gram F1 followed by median / p95 milliseconds. DOCX uses the seeded 1,000-input corpus; other formats pool both multi-format cohorts. + +| Format | docx-core | docx-rs | office_oxide | undoc | +| --- | --- | --- | --- | --- | +| DOCX | 99.983% · 0.673 / 5.080 | 99.740% · 1.193 / 9.499 | 99.917% · 0.750 / 5.380 | 99.988% · 1.130 / 14.260 | +| DOC | 98.748% · 0.266 / 5.016 | Unsupported | 97.979% · 0.077 / 1.290 | 95.293% · 0.027 / 0.425 | +| DOT | 100.000% · 0.843 / 1.264 | Unsupported | 99.902% · 0.505 / 0.506 | 100.000% · 0.133 / 0.196 | +| DOTX | 100.000% · 0.496 / 9.455 | 99.756% · 0.609 / 3.718 | 99.813% · 0.491 / 2.120 | 99.911% · 0.444 / 3.882 | +| DOCM | 100.000% · 0.183 / 2.380 | 100.000% · 0.259 / 1.856 | 100.000% · 0.148 / 0.899 | 100.000% · 0.084 / 2.571 | +| DOTM | 100.000% · 0.566 / 1.130 | 100.000% · 1.657 / 2.648 | 100.000% · 0.627 / 1.704 | 100.000% · 0.716 / 3.455 | + +Common successful latency cohorts contain 999 DOCX, 211 DOC, 2 DOT, 6 DOTX, 6 DOCM, and 12 DOTM inputs. Accuracy retains all reference-bearing inputs, including failed parses; supported DOC has 217 inputs and 215 usable references. The 75 additional DOCX inputs are kept separate in the charts. Template and macro cohorts are too small to establish general performance. + +## Validation results + +All 5,316 engine/input accuracy records are unchanged, including original body scores, ordered text, both feature metric sets, source-reference audits, statuses, and reference denominators. Full parsed-model and structured-block digests match the original baseline for 1,323 inputs/statuses. Six binary inputs have existing source-identity variability; rerunning the unmodified baseline binary reproduces each candidate model and block digest exactly, including IDs. No output fields were discarded from that comparison except object-key and style-definition ordering. + +An additional 1,088 concurrent parses across 272 inputs, at four and eight callers, match sequential results. This concurrency audit normalizes the known document-ID variability while retaining reference paths and all content, formatting, assets, and warnings. It is separate from the strict baseline digest comparison. + +The 329 existing Rust tests pass, with one opt-in corpus test ignored. The WASM target check passes, and PR CI passes JavaScript, Rust/package build, browser smoke, and preview deployment. No new unit tests or corpus documents were added. + +Complete interactive reports retain all feature tables and selectable complexity/performance axes under `output/doc-actor-perf-20261102/formats`, `holdout`, and `docx1k`. Original release and allocation-only runs are preserved separately. Charts and sanitized numeric data are linked from the [docx-core README](../crates/docx-core/README.md#parser-latency-comparisons). + +## Paired before/after measurements + +The same-machine paired run covers all 1,329 acquired inputs and compares successful, declared-supported core parses. DOCX here pools all 1,075 DOCX inputs, so its absolute timings should not be compared directly with the separate 1,000-input comparison table above. Each pair randomizes binary order; values below are the median of per-input candidate/baseline latency ratios. Negative changes are faster. + +| Format | Paired successful inputs | Median per-input latency change | Baseline p95 (ms) | Optimized p95 (ms) | +| --- | ---: | ---: | ---: | ---: | +| DOCX | 1075 | -0.1% | 4.125 | 4.131 | +| DOC | 216 | -6.6% | 4.770 | 4.246 | +| DOT | 2 | -9.4% | 1.437 | 1.244 | +| DOTX | 6 | +2.2% | 7.018 | 7.009 | +| DOCM | 6 | -0.7% | 2.335 | 2.269 | +| DOTM | 12 | -2.8% | 1.047 | 1.011 | + +DOC improves 6.6% on the median per-input ratio and 11.0% at the cohort p95. DOCX is effectively unchanged in the paired run. The separate four-library run should not be used to attribute its full DOCX timing difference from the older release run to code changes. Small template/macro cohorts and shared-machine variance limit interpretation of differences of a few percent. + +## Stage profile + +Temporary instrumentation was removed from production. The 272-input paired stage sample uses all 242 successful supported non-DOCX inputs plus 30 DOCX inputs spread across baseline latency. The table shows median per-input stage latency changes for 216 DOC inputs. + +| DOC stage | Baseline median (ms) | Optimized median (ms) | Median per-input change | +| --- | ---: | ---: | ---: | +| Compound-file reading | 0.0065 | 0.0054 | -16.1% | +| Document opening, including compound-file reading | 0.0078 | 0.0063 | -23.4% | +| XML construction | 0.1009 | 0.0887 | -11.3% | +| Total package adaptation | 0.1347 | 0.1191 | -10.9% | +| Model construction | 0.1157 | 0.1215 | +1.7% | + +The main gains are reduced copying/decoding and XML construction. The XML-to-model bridge remains; normalization and full model construction still account for substantial work. Nested stages are inclusive and should not be summed. + +## Concurrent cache comparison + +This isolates the cache changes against the allocation-optimized build. Five paired trials use ten passes per workload at 1, 4, and 8 callers. The mixed workload contains 272 inputs; repeated DOC and DOCX workloads each contain 64 instances of one input. Throughput includes allocation/destruction and all passes; latency below excludes the first pass. Queue contention, cache reuse, and shared-machine scheduling affect these results. + +| Workload | Callers | Median paired throughput change | Baseline / optimized p95 (ms) | +| --- | ---: | ---: | ---: | +| mixed | 1 | +0.7% | 6.036 / 5.953 | +| mixed | 4 | +3.0% | 7.888 / 7.722 | +| mixed | 8 | +0.0% | 10.052 / 9.746 | +| hot-doc | 1 | -0.8% | 0.779 / 0.801 | +| hot-doc | 4 | -2.3% | 1.095 / 1.202 | +| hot-doc | 8 | -2.0% | 1.429 / 1.443 | +| hot-docx | 1 | -1.2% | 1.440 / 2.197 | +| hot-docx | 4 | +8.1% | 2.706 / 2.129 | +| hot-docx | 8 | -0.8% | 3.747 / 3.419 | + +Cache improvements are workload-dependent, with regressions in some repeated-input tails; there is no universal actor speedup. Synchronous actor writes and a global parallel-admission gate were tested and rejected because they reduced mixed-workload throughput. The retained implementation keeps reads independent of the writer and uses bounded, best-effort cache admission. diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-doc.png b/docs/readme-assets/docx-core-optimized-20261102-latency-doc.png new file mode 100644 index 0000000..2fce7e4 Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-doc.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-docm.png b/docs/readme-assets/docx-core-optimized-20261102-latency-docm.png new file mode 100644 index 0000000..ef75bc7 Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-docm.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-docx-regressions.png b/docs/readme-assets/docx-core-optimized-20261102-latency-docx-regressions.png new file mode 100644 index 0000000..89c42a3 Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-docx-regressions.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-docx.png b/docs/readme-assets/docx-core-optimized-20261102-latency-docx.png new file mode 100644 index 0000000..1b0005e Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-docx.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-dot.png b/docs/readme-assets/docx-core-optimized-20261102-latency-dot.png new file mode 100644 index 0000000..3d4312d Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-dot.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-dotm.png b/docs/readme-assets/docx-core-optimized-20261102-latency-dotm.png new file mode 100644 index 0000000..77f54ad Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-dotm.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-dotx.png b/docs/readme-assets/docx-core-optimized-20261102-latency-dotx.png new file mode 100644 index 0000000..6d6701e Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-dotx.png differ diff --git a/docs/readme-assets/docx-core-optimized-20261102-latency-overview.png b/docs/readme-assets/docx-core-optimized-20261102-latency-overview.png new file mode 100644 index 0000000..92f4c8f Binary files /dev/null and b/docs/readme-assets/docx-core-optimized-20261102-latency-overview.png differ diff --git a/packages/wasm/src/docx_wasm_bg.wasm b/packages/wasm/src/docx_wasm_bg.wasm index 6cd631e..a8e8198 100644 Binary files a/packages/wasm/src/docx_wasm_bg.wasm and b/packages/wasm/src/docx_wasm_bg.wasm differ diff --git a/scripts/parser-benchmark/Cargo.lock b/scripts/parser-benchmark/Cargo.lock index 29a25b2..d7031ee 100644 --- a/scripts/parser-benchmark/Cargo.lock +++ b/scripts/parser-benchmark/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", +] + [[package]] name = "atoi_simd" version = "0.18.1" @@ -176,6 +185,7 @@ dependencies = [ name = "docx-core" version = "0.1.0" dependencies = [ + "arc-swap", "base64", "base64-simd", "itoa", diff --git a/scripts/parser-benchmark/readme_charts.py b/scripts/parser-benchmark/readme_charts.py index 8165077..8c2c488 100644 --- a/scripts/parser-benchmark/readme_charts.py +++ b/scripts/parser-benchmark/readme_charts.py @@ -21,6 +21,10 @@ def main(): parser.add_argument('--docx-run', type=Path, required=True) parser.add_argument('--output', type=Path, required=True) parser.add_argument('--images-output', type=Path, default=Path(__file__).resolve().parents[2] / 'docs/readme-assets') + parser.add_argument('--label', default='release 0.13.0') + parser.add_argument('--asset-prefix', default='docx-core-0.13.0') + parser.add_argument('--measured-date', default='2026-11-02') + parser.add_argument('--measured-commit', default='9ae335abdc79f7defe5a2843065a3ff13c6e5cea') args = parser.parse_args() pooled = [r for run in args.runs for r in json.loads((run / 'format-scores.json').read_text())] docx = json.loads((args.docx_run / 'format-scores.json').read_text()) @@ -48,8 +52,10 @@ def main(): } args.output.mkdir(parents=True, exist_ok=True) args.images_output.mkdir(parents=True, exist_ok=True) - receipt = {'release': '0.13.0', 'measured_date': '2026-11-02', - 'measured_commit': '9ae335abdc79f7defe5a2843065a3ff13c6e5cea', + receipt = {'label': args.label, 'measured_date': args.measured_date, + 'measured_commit': args.measured_commit, + 'binary_sha256': sorted({json.loads((run / 'format-environment.json').read_text())['binary_sha256'] + for run in [*args.runs, args.docx_run]}), 'versions': {'docx-rs': '0.4.22', 'office_oxide': '0.1.15', 'undoc': '0.27.1'}, 'scope': 'Warm native parse + model/IR; core also projects structured blocks. Common successful inputs per format. Five samples, 1–20 parses/sample. Per-document medians; p95 across documents. Excludes I/O, startup, JSON encoding, destruction, reference conversion. Shared development machine.', 'groups': groups} @@ -86,8 +92,8 @@ def main(): ax.set_xlabel('Milliseconds · lower is faster') ax.grid(axis='x', alpha=.15) ax.set_axisbelow(True) - fig.suptitle('Native parser latency · release 0.13.0\nSolid: median Light: p95 across documents', fontsize=18) - fig.savefig(args.images_output / 'docx-core-0.13.0-latency-overview.png', dpi=145) + fig.suptitle(f'Native parser latency · {args.label}\nSolid: median Light: p95 across documents', fontsize=18) + fig.savefig(args.images_output / f'{args.asset_prefix}-latency-overview.png', dpi=145) plt.close(fig) for fmt, group in groups.items(): fig, axes = plt.subplots(2, 2, figsize=(13, 9), layout='constrained') @@ -114,8 +120,8 @@ def main(): axes[0, 0].grid(alpha=.16) axes[0, 0].legend(loc='lower right') title = 'DOCX · additional regression cohort' if fmt == 'docx-regressions' else fmt.upper() - fig.suptitle(f'{title} · {group["common_inputs"]} common successful inputs\nNative parser latency · release 0.13.0', fontsize=17) - fig.savefig(args.images_output / f'docx-core-0.13.0-latency-{fmt}.png', dpi=145) + fig.suptitle(f'{title} · {group["common_inputs"]} common successful inputs\nNative parser latency · {args.label}', fontsize=17) + fig.savefig(args.images_output / f'{args.asset_prefix}-latency-{fmt}.png', dpi=145) plt.close(fig) print({fmt: {e: {k: v for k, v in stats.items() if k != 'points'} for e, stats in g['engines'].items()} for fmt, g in groups.items()}) diff --git a/scripts/parser-benchmark/src/bin/concurrent.rs b/scripts/parser-benchmark/src/bin/concurrent.rs new file mode 100644 index 0000000..6584a04 --- /dev/null +++ b/scripts/parser-benchmark/src/bin/concurrent.rs @@ -0,0 +1,144 @@ +use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; +use std::{ + fs, + hint::black_box, + sync::{Arc, Barrier}, + time::Instant, +}; + +fn canonicalize(value: &mut Value, document_id: &str) { + match value { + Value::Array(values) => values + .iter_mut() + .for_each(|value| canonicalize(value, document_id)), + Value::Object(values) => { + for (key, value) in values.iter_mut() { + if matches!( + key.as_str(), + "documentId" | "id" | "blockId" | "nodeId" | "parentBlockId" + ) { + if let Some(id) = value.as_str() { + if !document_id.is_empty() { + if id == document_id { + *value = Value::String("document".into()); + } else if let Some(suffix) = + id.strip_prefix(&format!("docx:{document_id}/")) + { + *value = Value::String(format!("docx:document/{suffix}")); + } + } + } + } + canonicalize(value, document_id); + } + let mut sorted: Vec<_> = std::mem::take(values).into_iter().collect(); + sorted.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + values.extend(sorted); + } + _ => {} + } +} + +fn output_digest(model: &docx_core::DocModel, blocks: &docx_core::StructuredDocument) -> [u8; 32] { + let mut model = serde_json::to_value(model).unwrap(); + let mut blocks = serde_json::to_value(blocks).unwrap(); + let id = model["metadata"]["documentId"] + .as_str() + .unwrap_or("") + .to_owned(); + canonicalize(&mut model, &id); + canonicalize(&mut blocks, &id); + for key in ["paragraphStyles", "tableStyles"] { + if let Some(styles) = model["metadata"][key].as_array_mut() { + styles.sort_by_cached_key(|value| serde_json::to_string(value).unwrap()); + } + } + let mut digest = Sha256::new(); + digest.update(serde_json::to_vec(&model).unwrap()); + digest.update(serde_json::to_vec(&blocks).unwrap()); + digest.finalize().into() +} + +fn run() { + let args: Vec<_> = std::env::args().collect(); + let manifest: Value = serde_json::from_slice(&fs::read(&args[1]).unwrap()).unwrap(); + let workers: usize = args[2].parse().unwrap(); + let rounds: usize = args[3].parse().unwrap(); + let inputs: Arc>> = Arc::new( + manifest["entries"] + .as_array() + .unwrap() + .iter() + .map(|entry| fs::read(entry["path"].as_str().unwrap()).unwrap()) + .collect(), + ); + assert!(workers > 0 && rounds > 0); + let audit = args.get(4).is_some_and(|arg| arg == "audit"); + let expected = Arc::new(if audit { + inputs + .iter() + .map(|bytes| { + let package = docx_core::parse_document_bytes(bytes).unwrap(); + let model = docx_core::build_doc_model(&package); + output_digest(&model, &docx_core::extract_document_blocks(&model)) + }) + .collect::>() + } else { + Vec::new() + }); + let barrier = Arc::new(Barrier::new(workers + 1)); + let mut threads = Vec::new(); + for worker in 0..workers { + let inputs = Arc::clone(&inputs); + let barrier = Arc::clone(&barrier); + let expected = Arc::clone(&expected); + threads.push( + std::thread::Builder::new() + .stack_size(64 * 1024 * 1024) + .spawn(move || { + barrier.wait(); + let mut samples = Vec::new(); + for round in 0..rounds { + for index in (worker..inputs.len()).step_by(workers) { + let start = Instant::now(); + let package = docx_core::parse_document_bytes(&inputs[index]).unwrap(); + let model = docx_core::build_doc_model(&package); + let blocks = docx_core::extract_document_blocks(&model); + black_box((&package, &model, &blocks)); + let ms = start.elapsed().as_secs_f64() * 1000.0; + if audit { + assert_eq!( + output_digest(&model, &blocks), + expected[index], + "concurrent output changed at input {index}" + ); + } + samples.push(json!({"input":index,"round":round,"ms":ms})); + } + } + samples + }) + .unwrap(), + ); + } + let start = Instant::now(); + barrier.wait(); + let samples: Vec<_> = threads + .into_iter() + .flat_map(|thread| thread.join().unwrap()) + .collect(); + println!( + "{}", + json!({"audit":audit,"workers":workers,"rounds":rounds,"inputs":inputs.len(),"wall_ms":start.elapsed().as_secs_f64()*1000.0,"samples":samples}) + ); +} + +fn main() { + std::thread::Builder::new() + .stack_size(64 * 1024 * 1024) + .spawn(run) + .unwrap() + .join() + .unwrap(); +}