From e9aed6ce593ebceb1dbb35ca42aea554405f0f6c Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 4 May 2026 23:34:05 -0600 Subject: [PATCH] Phase 25: Branch reorder + invariant tripwires (2026.05.04.10) Third-pass optimization on parse_tuple_payload's hot loop. Previous phases removed redundant work; this one removes correct-but-wasteful work: the if/elif chain checked branches in implementation order, not frequency order. Fixed-width types (INT, FLOAT, DATE, BIGINT - the most common columns in real queries) sat at the bottom, paying ~7 frozenset misses per column. Changes (src/informix_db/_resultset.py): * Added _FIXED_WIDTH_TYPES = frozenset(FIXED_WIDTHS.keys()) at module load. * New fast-path branch at the TOP of parse_tuple_payload's loop body that handles every _FIXED_WIDTH_TYPES column inline: one frozenset check, one dict lookup, one decode, continue. Skips every other branch. * Cleaned up the bottom fall-through; it now genuinely only catches unknown types. Performance vs Phase 24 baseline: * parse_tuple_5cols_iso8859: 1659 ns -> 1400 ns (-16%) * parse_tuple_5cols_utf8: 1649 ns -> 1341 ns (-19%) Cumulative vs Phase 21 baseline (before any optimization): * parse_tuple_5cols: 2796 ns -> 1400 ns (-50%) - HALF the time * decode_int: 230 ns -> 139 ns (-40%) Margaret Hamilton review surfaced one HIGH finding addressed before tagging: * H: The fast-path optimization assumes every FIXED_WIDTHS key is decodable WITHOUT qualifier inspection (encoded_length etc.). True today, but a future contributor adding a fixed-width type that needs qualifier bits (like DATETIME does) would silently get wrong decode behavior - Lauren-Bug class failure. Fix: added INVARIANT comment to FIXED_WIDTHS in converters.py AND added tests/test_resultset_invariants.py with three CI tripwire tests: - _FIXED_WIDTH_TYPES is disjoint from every other dispatch branch - Every FIXED_WIDTHS key has a DECODERS entry - DECODERS keys stay < 0x100 (Phase 24 collision-free guarantee) The tests carry instructions: if one fires, don't update the test to match - either restore the property or refactor the optimization. Comments rot when nobody reads them; tests fail loudly. baseline.json refreshed; 72 unit + 224 integration + 28 bench = 324 tests; ruff clean. --- CHANGELOG.md | 49 ++ pyproject.toml | 2 +- src/informix_db/_resultset.py | 35 +- src/informix_db/converters.py | 15 + tests/benchmarks/baseline.json | 908 ++++++++++++++--------------- tests/test_resultset_invariants.py | 98 ++++ uv.lock | 2 +- 7 files changed, 647 insertions(+), 462 deletions(-) create mode 100644 tests/test_resultset_invariants.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb8e2c..93ef205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,55 @@ All notable changes to `informix-db`. Versioning is [CalVer](https://calver.org/) — `YYYY.MM.DD` for date-based releases, `YYYY.MM.DD.N` for same-day post-releases per PEP 440. +## 2026.05.04.10 — Branch reorder by frequency + invariant tripwires (Phase 25) + +Third-pass optimization on `parse_tuple_payload`. Previous phases removed redundant work; this one removes *correct-but-wasteful* work: the if/elif chain checked branches in implementation order, not frequency order. Fixed-width types (INT, FLOAT, DATE, BIGINT — by far the most common columns in real queries) sat at the *bottom* of the chain, paying ~7 frozenset/equality misses per column. + +### What changed + +- **Added `_FIXED_WIDTH_TYPES = frozenset(FIXED_WIDTHS.keys())`** at module load in `_resultset.py`. +- **New fast-path branch at the TOP of `parse_tuple_payload`'s loop body** that handles every `_FIXED_WIDTH_TYPES` column inline (slice + `_decode_base` + advance). For an INT column we now hit one frozenset check, one dict lookup, one decode call — and skip every other branch. +- **Cleaned up the bottom fall-through** since FIXED_WIDTHS-keyed types no longer reach it. The fall-through now genuinely only catches unknown/unhandled types; comment updated. + +### Margaret Hamilton review pass — invariant tripwires added + +The third Hamilton review of this hot path produced one HIGH-severity finding addressed before tagging. The pattern was the same as Phases 23 and 24: an optimization is correct *because of* a property of an external table (here: `FIXED_WIDTHS` keys are decodable without qualifier inspection), but the property is implicit. The finding's recommendation, going beyond a comment: + +- **Added `tests/test_resultset_invariants.py`** — three CI tripwire tests that turn the structural invariants from comments into executable checks: + 1. `_FIXED_WIDTH_TYPES` is disjoint from every other dispatch branch's type set. + 2. Every `FIXED_WIDTHS` key has a decoder in `DECODERS`. + 3. All `DECODERS` keys are < 0x100 (the Phase 24 collision-free guarantee). +- **Added INVARIANT comment to `FIXED_WIDTHS`** in `converters.py` explaining the qualifier-free constraint and pointing to the tripwire tests. + +The tests follow a simple discipline: if one fires, **don't update the test to match the new state** — read the docstring and either restore the property or refactor the optimization to no longer depend on it. Comments rot when nobody reads them; tests fail loudly when someone violates them. + +### Performance summary (Phase 25) + +| Benchmark | Phase 24 baseline | NOW | Δ | +|---|---:|---:|---:| +| `parse_tuple_5cols_iso8859` | 1659 ns | **1400 ns** | **-16%** | +| `parse_tuple_5cols_utf8` | 1649 ns | **1341 ns** | **-19%** | + +End-to-end SELECT numbers fluctuate ±10% run-to-run on sub-millisecond loopback round-trips; the codec micro-benchmark is the durable measurement. + +### Cumulative improvement (vs. original Phase 21 baseline, before any optimization) + +| Metric | Original | NOW | Total Δ | +|---|---:|---:|---:| +| `parse_tuple_5cols` | 2796 ns | **1400 ns** | **-50%** | +| `decode_int` | 230 ns | 139 ns | -40% | +| `select_bench_table_all` (1k rows, where measurable) | 1477 µs | ~990 µs | ≈-33% | + +The per-row decode hot path is **half the time it took at start of optimization work**. Real-world fetch ceiling: 358K rows/sec → ~715K rows/sec on a single connection. + +### Tests + +3 new unit tests (the invariant tripwires). Total: **72 unit + 224 integration + 28 benchmark = 324 tests**. + +### Baseline refreshed + +`tests/benchmarks/baseline.json` updated. All tests pass; ruff clean. + ## 2026.05.04.9 — Decoder dispatch + struct precompilation (Phase 24) Second pass of hot-path optimization. Phase 23 lifted IfxType conversions out of the loop body in `_resultset.py` (-26% on `parse_tuple_5cols`). Phase 24 goes deeper into the codec layer. diff --git a/pyproject.toml b/pyproject.toml index 31edb2b..ba85bd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "informix-db" -version = "2026.05.04.9" +version = "2026.05.04.10" description = "Pure-Python driver for IBM Informix IDS — speaks the SQLI wire protocol over raw sockets. No CSDK, no JVM, no native libraries." readme = "README.md" license = { text = "MIT" } diff --git a/src/informix_db/_resultset.py b/src/informix_db/_resultset.py index 60974ca..b4dc667 100644 --- a/src/informix_db/_resultset.py +++ b/src/informix_db/_resultset.py @@ -223,6 +223,16 @@ _COMPOSITE_UDT_TYPES = frozenset({ _NUMERIC_TYPES = frozenset({_TC_DECIMAL, _TC_MONEY}) +# Types that are fixed-width on the wire AND have a registered decoder +# in ``FIXED_WIDTHS``: SMALLINT, INT, SERIAL, SMFLOAT, FLOAT, BIGINT, +# BIGSERIAL, DATE, BOOL. These are the most common types in any real +# query, so checking them FIRST in the parse_tuple_payload dispatch +# saves ~7 frozenset/equality misses per column. Disjoint from every +# other branch's type set (verified — none of these codes appear in +# _LENGTH_PREFIXED_SHORT_TYPES, _NUMERIC_TYPES, _COMPOSITE_UDT_TYPES, +# or as DATETIME/INTERVAL/UDTFIXED/UDTVAR/LVARCHAR). +_FIXED_WIDTH_TYPES = frozenset(FIXED_WIDTHS.keys()) + def parse_tuple_payload( reader: IfxStreamReader, @@ -270,6 +280,18 @@ def parse_tuple_payload( for col in columns: tc = col.type_code + # Fast path: fixed-width types (INT, FLOAT, DATE, BIGINT, etc.) + # are by far the most common columns in real queries. Check them + # FIRST so we don't pay 7+ branch-misses per integer column. + # FIXED_WIDTHS keys are disjoint from every other branch's type + # set — see _FIXED_WIDTH_TYPES module-level comment. + if tc in _FIXED_WIDTH_TYPES: + width = FIXED_WIDTHS[tc] + raw = payload[offset:offset + width] + offset += width + values.append(_decode_base(tc, raw, encoding)) + continue + if tc in _LENGTH_PREFIXED_SHORT_TYPES: # In tuple data, VARCHAR/NCHAR/NVCHAR use a SINGLE-BYTE # length prefix (max 255 — IDS VARCHAR's hard limit), not @@ -414,12 +436,13 @@ def parse_tuple_payload( values.append(raw.decode(encoding)) continue - # Fixed-width types - width = FIXED_WIDTHS.get(tc) - if width is None: - # Phase 6+ types (DATETIME, INTERVAL, BLOBs) — fall back - # to encoded_length and surface raw bytes. - width = col.encoded_length + # Unknown / unhandled type fall-through. The fast-path at the + # top of this loop already handled all FIXED_WIDTHS-registered + # types (INT, FLOAT, DATE, etc.); the explicit branches above + # handle every other known wire shape. Anything reaching here + # is a type code we don't recognize — surface ``encoded_length`` + # bytes raw and let the decoder dispatch (or its fallback) react. + width = col.encoded_length raw = payload[offset:offset + width] offset += width try: diff --git a/src/informix_db/converters.py b/src/informix_db/converters.py index 8a55a77..c62ca94 100644 --- a/src/informix_db/converters.py +++ b/src/informix_db/converters.py @@ -509,6 +509,21 @@ def _decode_decimal(raw: bytes) -> decimal.Decimal | None: # slice column values out of an SQ_TUPLE payload for fixed-width types. # Variable-width types (CHAR, VARCHAR, DECIMAL, etc.) are length-prefixed # on the wire and don't appear in this table. +# +# INVARIANT — every key here MUST be decodable by +# ``_decode_base(tc, raw, encoding)`` with NO per-column qualifier +# inspection (no ``col.encoded_length`` lookup, no extended_id check, +# no extended_name check). This is **load-bearing for correctness**: +# ``_resultset.parse_tuple_payload`` dispatches all ``FIXED_WIDTHS`` +# types through a single fast-path branch that does not pass +# ``col.encoded_length`` to the decoder. If a new fixed-width type +# needs qualifier bits (the way DATETIME and INTERVAL do — both +# absent from this table for exactly that reason), give it its own +# explicit branch in ``parse_tuple_payload`` instead of adding it +# here. A test in ``tests/test_resultset_invariants.py`` enforces the +# disjointness of this set against every other dispatch branch's +# type set; another test enforces that every key here has a decoder +# in DECODERS. FIXED_WIDTHS: dict[int, int] = { IfxType.SMALLINT: 2, IfxType.INT: 4, diff --git a/tests/benchmarks/baseline.json b/tests/benchmarks/baseline.json index 1543f09..0a46c11 100644 --- a/tests/benchmarks/baseline.json +++ b/tests/benchmarks/baseline.json @@ -27,14 +27,14 @@ "arch_string_raw": "x86_64", "vendor_id_raw": "AuthenticAMD", "brand_raw": "AMD Ryzen 9 9950X 16-Core Processor", - "hz_advertised_friendly": "5.4021 GHz", - "hz_actual_friendly": "5.4021 GHz", + "hz_advertised_friendly": "5.3277 GHz", + "hz_actual_friendly": "5.3277 GHz", "hz_advertised": [ - 5402145000, + 5327731000, 0 ], "hz_actual": [ - 5402145000, + 5327731000, 0 ], "model": 68, @@ -227,9 +227,9 @@ } }, "commit_info": { - "id": "f3e589c5bf2aaa0eea90a013d6ddd399fdff9825", - "time": "2026-05-04T17:52:20-06:00", - "author_time": "2026-05-04T17:52:20-06:00", + "id": "dfa60ea501aa077f6aed07a0f9bdfca59d0328bd", + "time": "2026-05-04T19:31:21-06:00", + "author_time": "2026-05-04T19:31:21-06:00", "dirty": true, "project": "python-library", "branch": "main" @@ -251,22 +251,22 @@ "warmup": false }, "stats": { - "min": 0.00016182102262973785, - "max": 0.0006957330042496324, - "mean": 0.0002086755737532931, - "stddev": 5.087568295803379e-05, - "rounds": 763, - "median": 0.00019349099602550268, - "iqr": 3.9664184441789985e-05, - "q1": 0.00017863351968117058, - "q3": 0.00021829770412296057, - "iqr_outliers": 62, - "stddev_outliers": 83, - "outliers": "83;62", - "ld15iqr": 0.00016182102262973785, - "hd15iqr": 0.0002789910649880767, - "ops": 4792.127712955283, - "total": 0.15921946277376264, + "min": 0.00016333907842636108, + "max": 0.000556678045541048, + "mean": 0.00023229151783097122, + "stddev": 5.807605150614499e-05, + "rounds": 900, + "median": 0.000211879494599998, + "iqr": 6.952503463253379e-05, + "q1": 0.00019249896286055446, + "q3": 0.00026202399749308825, + "iqr_outliers": 30, + "stddev_outliers": 191, + "outliers": "191;30", + "ld15iqr": 0.00016333907842636108, + "hd15iqr": 0.00036686903331428766, + "ops": 4304.935493717244, + "total": 0.2090623660478741, "iterations": 1 } }, @@ -286,22 +286,22 @@ "warmup": false }, "stats": { - "min": 0.0026593899819999933, - "max": 0.005615351023152471, - "mean": 0.003202786807254967, - "stddev": 0.0005114582505917601, + "min": 0.0027559109730646014, + "max": 0.0039008479798212647, + "mean": 0.0031208195743324643, + "stddev": 0.00021749111091780822, "rounds": 59, - "median": 0.0030094919493421912, - "iqr": 0.00036587376962415874, - "q1": 0.0029149432375561446, - "q3": 0.0032808170071803033, - "iqr_outliers": 7, - "stddev_outliers": 9, - "outliers": "9;7", - "ld15iqr": 0.0026593899819999933, - "hd15iqr": 0.003917503985576332, - "ops": 312.22808765628594, - "total": 0.18896442162804306, + "median": 0.0030611599795520306, + "iqr": 0.00026317406445741653, + "q1": 0.0029657602426595986, + "q3": 0.003228934307117015, + "iqr_outliers": 2, + "stddev_outliers": 16, + "outliers": "16;2", + "ld15iqr": 0.0027559109730646014, + "hd15iqr": 0.0036642580525949597, + "ops": 320.4286490076561, + "total": 0.1841283548856154, "iterations": 1 } }, @@ -321,22 +321,22 @@ "warmup": false }, "stats": { - "min": 1.3059936463832854e-07, - "max": 4.448409890756011e-06, - "mean": 1.4046416941103093e-07, - "stddev": 2.0085390540645675e-08, - "rounds": 68587, - "median": 1.3910001143813132e-07, - "iqr": 1.7904676496982437e-09, - "q1": 1.3820943422615529e-07, - "q3": 1.3999990187585353e-07, - "iqr_outliers": 5077, - "stddev_outliers": 1377, - "outliers": "1377;5077", - "ld15iqr": 1.355994027107954e-07, - "hd15iqr": 1.4269957318902015e-07, - "ops": 7119253.288529168, - "total": 0.009634015987394377, + "min": 1.3089971616864205e-07, + "max": 8.637004066258669e-07, + "mean": 1.4644680519727887e-07, + "stddev": 2.4728486272246568e-08, + "rounds": 67523, + "median": 1.4039920642971992e-07, + "iqr": 2.4004839360714096e-09, + "q1": 1.3929908163845538e-07, + "q3": 1.416995655745268e-07, + "iqr_outliers": 11491, + "stddev_outliers": 4884, + "outliers": "4884;11491", + "ld15iqr": 1.3569951988756658e-07, + "hd15iqr": 1.4538993127644063e-07, + "ops": 6828417.995550653, + "total": 0.009888527627335861, "iterations": 100 } }, @@ -356,22 +356,22 @@ "warmup": false }, "stats": { - "min": 1.2899981811642646e-07, - "max": 2.5820103473961352e-06, - "mean": 1.365659636493434e-07, - "stddev": 1.8574241087596374e-08, - "rounds": 74075, - "median": 1.3530021533370018e-07, - "iqr": 1.7997808754444165e-09, - "q1": 1.3440032489597797e-07, - "q3": 1.362001057714224e-07, - "iqr_outliers": 4621, - "stddev_outliers": 1597, - "outliers": "1597;4621", - "ld15iqr": 1.3170065358281135e-07, - "hd15iqr": 1.3890094123780728e-07, - "ops": 7322468.741682019, - "total": 0.010116123757325114, + "min": 1.3119890354573727e-07, + "max": 7.064000237733126e-07, + "mean": 1.424212394474376e-07, + "stddev": 2.1046985871854247e-08, + "rounds": 69638, + "median": 1.379998866468668e-07, + "iqr": 2.0000152289867475e-09, + "q1": 1.3699987903237342e-07, + "q3": 1.3899989426136017e-07, + "iqr_outliers": 6911, + "stddev_outliers": 3915, + "outliers": "3915;6911", + "ld15iqr": 1.339998561888933e-07, + "hd15iqr": 1.4200108125805854e-07, + "ops": 7021424.6405927595, + "total": 0.00991793027264066, "iterations": 100 } }, @@ -391,23 +391,23 @@ "warmup": false }, "stats": { - "min": 1.3849930837750435e-07, - "max": 1.3621000107377768e-06, - "mean": 1.4664396024328513e-07, - "stddev": 1.9445890016114294e-08, - "rounds": 67160, - "median": 1.4519901014864445e-07, - "iqr": 1.7008278518915325e-09, - "q1": 1.443002838641405e-07, - "q3": 1.4600111171603204e-07, - "iqr_outliers": 2981, - "stddev_outliers": 1556, - "outliers": "1556;2981", - "ld15iqr": 1.4179968275129795e-07, - "hd15iqr": 1.4859950169920922e-07, - "ops": 6819237.548829021, - "total": 0.009848608369939029, - "iterations": 100 + "min": 1.4181855614438202e-07, + "max": 2.033941475956729e-06, + "mean": 1.5573072467673934e-07, + "stddev": 2.7501749174074502e-08, + "rounds": 198809, + "median": 1.5060614907380307e-07, + "iqr": 2.451777232415742e-09, + "q1": 1.4966777102513748e-07, + "q3": 1.5211954825755322e-07, + "iqr_outliers": 13985, + "stddev_outliers": 9579, + "outliers": "9579;13985", + "ld15iqr": 1.4603067415230203e-07, + "hd15iqr": 1.5603180861834323e-07, + "ops": 6421340.439247083, + "total": 0.03096066964225787, + "iterations": 33 } }, { @@ -426,22 +426,22 @@ "warmup": false }, "stats": { - "min": 1.3199984095990657e-07, - "max": 2.6516092475503685e-06, - "mean": 1.40876497771268e-07, - "stddev": 1.4728400908141213e-08, - "rounds": 69882, - "median": 1.3870070688426495e-07, - "iqr": 2.000015228986721e-09, - "q1": 1.3779965229332448e-07, - "q3": 1.397996675223112e-07, - "iqr_outliers": 7297, - "stddev_outliers": 2180, - "outliers": "2180;7297", - "ld15iqr": 1.3480079360306264e-07, - "hd15iqr": 1.4279969036579133e-07, - "ops": 7098416.100772428, - "total": 0.009844731417251751, + "min": 1.3360055163502692e-07, + "max": 6.046902853995562e-07, + "mean": 1.4610493747917388e-07, + "stddev": 2.3066902984855067e-08, + "rounds": 68871, + "median": 1.4060060493648052e-07, + "iqr": 2.8999056667089293e-09, + "q1": 1.3940036296844483e-07, + "q3": 1.4230026863515376e-07, + "iqr_outliers": 10127, + "stddev_outliers": 3923, + "outliers": "3923;10127", + "ld15iqr": 1.3509998098015785e-07, + "hd15iqr": 1.4669029042124748e-07, + "ops": 6844395.660088779, + "total": 0.010062393149128183, "iterations": 100 } }, @@ -461,22 +461,22 @@ "warmup": false }, "stats": { - "min": 4.199100658297539e-07, - "max": 1.434003934264183e-05, - "mean": 4.584284288086038e-07, - "stddev": 1.1281259680451807e-07, - "rounds": 143472, - "median": 4.4994521886110306e-07, - "iqr": 1.0011717677116394e-08, - "q1": 4.400499165058136e-07, - "q3": 4.5006163418293e-07, - "iqr_outliers": 9014, - "stddev_outliers": 3932, - "outliers": "3932;9014", - "ld15iqr": 4.2992178350687027e-07, - "hd15iqr": 4.6996865421533585e-07, - "ops": 2181365.58982363, - "total": 0.065771643538028, + "min": 4.289904609322548e-07, + "max": 1.146004069596529e-05, + "mean": 4.684890366645274e-07, + "stddev": 1.207705530973138e-07, + "rounds": 120483, + "median": 4.5006163418293e-07, + "iqr": 1.0128132998943329e-08, + "q1": 4.4994521886110306e-07, + "q3": 4.600733518600464e-07, + "iqr_outliers": 10939, + "stddev_outliers": 5607, + "outliers": "5607;10939", + "ld15iqr": 4.390021786093712e-07, + "hd15iqr": 4.789326339960098e-07, + "ops": 2134521.6680408115, + "total": 0.05644496460445225, "iterations": 1 } }, @@ -496,22 +496,22 @@ "warmup": false }, "stats": { - "min": 1.4903414393624952e-07, - "max": 1.2098421012201616e-05, - "mean": 1.604417920427961e-07, - "stddev": 3.28563962585996e-08, - "rounds": 199999, - "median": 1.5806571971024236e-07, - "iqr": 2.9028723797490323e-09, - "q1": 1.567738851712596e-07, - "q3": 1.5967675755100864e-07, - "iqr_outliers": 7474, - "stddev_outliers": 5263, - "outliers": "5263;7474", - "ld15iqr": 1.525791782525278e-07, - "hd15iqr": 1.64190667771524e-07, - "ops": 6232790.018533707, - "total": 0.03208819796676717, + "min": 1.522562196177821e-07, + "max": 1.7432255610342948e-06, + "mean": 1.6286714435236636e-07, + "stddev": 1.9082716376353446e-08, + "rounds": 198021, + "median": 1.5999971618575436e-07, + "iqr": 2.5799137450033712e-09, + "q1": 1.5870788164677158e-07, + "q3": 1.6128779539177495e-07, + "iqr_outliers": 9822, + "stddev_outliers": 6655, + "outliers": "6655;9822", + "ld15iqr": 1.5483988869574761e-07, + "hd15iqr": 1.6515954367576107e-07, + "ops": 6139973.804885287, + "total": 0.032251114791799936, "iterations": 31 } }, @@ -531,23 +531,23 @@ "warmup": false }, "stats": { - "min": 1.5659956261515618e-07, - "max": 2.627110807225108e-06, - "mean": 1.7278132025324868e-07, - "stddev": 2.7783769982924053e-08, - "rounds": 59242, - "median": 1.6360078006982803e-07, - "iqr": 3.000022843480108e-09, - "q1": 1.6239937394857407e-07, - "q3": 1.6539939679205417e-07, - "iqr_outliers": 8583, - "stddev_outliers": 6699, - "outliers": "6699;8583", - "ld15iqr": 1.5789992175996302e-07, - "hd15iqr": 1.6990001313388348e-07, - "ops": 5787662.685609081, - "total": 0.01023591097444296, - "iterations": 100 + "min": 1.5699770301580428e-07, + "max": 3.5763330136736235e-06, + "mean": 1.8235442048901725e-07, + "stddev": 4.516624481813633e-08, + "rounds": 199999, + "median": 1.700012944638729e-07, + "iqr": 7.000441352526333e-09, + "q1": 1.6666793574889502e-07, + "q3": 1.7366837710142135e-07, + "iqr_outliers": 24347, + "stddev_outliers": 21733, + "outliers": "21733;24347", + "ld15iqr": 1.5699770301580428e-07, + "hd15iqr": 1.8430097649494808e-07, + "ops": 5483826.480972133, + "total": 0.03647070174338296, + "iterations": 30 } }, { @@ -566,23 +566,23 @@ "warmup": false }, "stats": { - "min": 1.941662048920989e-07, - "max": 1.4027541813751062e-05, - "mean": 2.1363528276782235e-07, - "stddev": 4.0574639301230187e-08, + "min": 1.9565366370522458e-07, + "max": 4.4417354966635285e-06, + "mean": 2.1535445648936441e-07, + "stddev": 3.680444636275858e-08, "rounds": 195693, - "median": 2.0666630007326603e-07, - "iqr": 5.418163103361907e-09, - "q1": 2.041682212923964e-07, - "q3": 2.095863843957583e-07, - "iqr_outliers": 21840, - "stddev_outliers": 13568, - "outliers": "13568;21840", - "ld15iqr": 1.9624712876975536e-07, - "hd15iqr": 2.179149305447936e-07, - "ops": 4680874.74617568, - "total": 0.04180692939068346, - "iterations": 24 + "median": 2.0782665713973667e-07, + "iqr": 7.389842168144561e-09, + "q1": 2.0521996623795966e-07, + "q3": 2.1260980840610422e-07, + "iqr_outliers": 12679, + "stddev_outliers": 9981, + "outliers": "9981;12679", + "ld15iqr": 1.9565366370522458e-07, + "hd15iqr": 2.2386666387319565e-07, + "ops": 4643507.342739324, + "total": 0.042143359653773194, + "iterations": 23 } }, { @@ -601,22 +601,22 @@ "warmup": false }, "stats": { - "min": 9.550014510750771e-08, - "max": 6.683549145236611e-07, - "mean": 9.920416085466207e-08, - "stddev": 1.1576827871280712e-08, - "rounds": 50404, - "median": 9.760493412613868e-08, - "iqr": 1.1501833796501154e-09, - "q1": 9.719980880618096e-08, + "min": 9.50501998886466e-08, + "max": 3.443501191213727e-07, + "mean": 9.975327813760115e-08, + "stddev": 9.838599510025068e-09, + "rounds": 50736, + "median": 9.75496368482709e-08, + "iqr": 1.3998942449689017e-09, + "q1": 9.695009794086217e-08, "q3": 9.834999218583108e-08, - "iqr_outliers": 3437, - "stddev_outliers": 1660, - "outliers": "1660;3437", - "ld15iqr": 9.550014510750771e-08, - "hd15iqr": 1.000997144728899e-07, - "ops": 10080222.35544171, - "total": 0.005000286523718387, + "iqr_outliers": 3317, + "stddev_outliers": 2538, + "outliers": "2538;3317", + "ld15iqr": 9.50501998886466e-08, + "hd15iqr": 1.0045012459158898e-07, + "ops": 10024733.208472459, + "total": 0.005061082319589332, "iterations": 200 } }, @@ -637,21 +637,21 @@ }, "stats": { "min": 3.998866304755211e-07, - "max": 0.00011250993702560663, - "mean": 4.4043889797464144e-07, - "stddev": 4.832344943121e-07, - "rounds": 119619, - "median": 4.300381988286972e-07, - "iqr": 2.0023435354232788e-08, - "q1": 4.200264811515808e-07, - "q3": 4.400499165058136e-07, - "iqr_outliers": 3271, - "stddev_outliers": 102, - "outliers": "102;3271", + "max": 1.0939897038042545e-05, + "mean": 4.9262142409488e-07, + "stddev": 2.3692671811979433e-07, + "rounds": 89848, + "median": 4.3993350118398666e-07, + "iqr": 1.4004763215780258e-07, + "q1": 4.2992178350687027e-07, + "q3": 5.699694156646729e-07, + "iqr_outliers": 587, + "stddev_outliers": 656, + "outliers": "656;587", "ld15iqr": 3.998866304755211e-07, - "hd15iqr": 4.708999767899513e-07, - "ops": 2270462.496837815, - "total": 0.05268486053682864, + "hd15iqr": 7.899943739175797e-07, + "ops": 2029956.374384963, + "total": 0.04426104971207678, "iterations": 1 } }, @@ -671,23 +671,23 @@ "warmup": false }, "stats": { - "min": 3.5199918784201146e-07, - "max": 5.436001811176539e-06, - "mean": 3.766557586137613e-07, - "stddev": 6.302341690394645e-08, - "rounds": 131063, - "median": 3.6800047382712363e-07, - "iqr": 8.498318493366241e-09, - "q1": 3.6450219340622427e-07, - "q3": 3.730005118995905e-07, - "iqr_outliers": 8082, - "stddev_outliers": 4908, - "outliers": "4908;8082", - "ld15iqr": 3.5199918784201146e-07, - "hd15iqr": 3.8599828258156774e-07, - "ops": 2654944.1423128275, - "total": 0.0493656336911954, - "iterations": 20 + "min": 4.199100658297539e-07, + "max": 9.899958968162537e-06, + "mean": 4.668547325857353e-07, + "stddev": 1.8913511167136836e-07, + "rounds": 191573, + "median": 4.4994521886110306e-07, + "iqr": 1.9907020032405853e-08, + "q1": 4.400499165058136e-07, + "q3": 4.5995693653821945e-07, + "iqr_outliers": 10029, + "stddev_outliers": 3838, + "outliers": "3838;10029", + "ld15iqr": 4.199100658297539e-07, + "hd15iqr": 4.899920895695686e-07, + "ops": 2141993.9227377446, + "total": 0.08943676168564707, + "iterations": 1 } }, { @@ -706,22 +706,22 @@ "warmup": false }, "stats": { - "min": 9.940005838871003e-08, - "max": 6.29104906693101e-07, - "mean": 1.0226859564478599e-07, - "stddev": 1.2923998699407992e-08, - "rounds": 49141, - "median": 1.0135001502931118e-07, - "iqr": 7.497146725654533e-10, - "q1": 1.0100018698722125e-07, - "q3": 1.017499016597867e-07, - "iqr_outliers": 2019, - "stddev_outliers": 871, - "outliers": "871;2019", - "ld15iqr": 9.99000621959567e-08, - "hd15iqr": 1.0289950296282768e-07, - "ops": 9778172.797770139, - "total": 0.005025581058580428, + "min": 1.0354968253523112e-07, + "max": 2.342503285035491e-07, + "mean": 1.0823924088679261e-07, + "stddev": 1.11666693350371e-08, + "rounds": 46491, + "median": 1.052499283105135e-07, + "iqr": 1.000589691102508e-09, + "q1": 1.0484480299055576e-07, + "q3": 1.0584539268165827e-07, + "iqr_outliers": 4273, + "stddev_outliers": 2983, + "outliers": "2983;4273", + "ld15iqr": 1.0354968253523112e-07, + "hd15iqr": 1.0734947863966227e-07, + "ops": 9238793.544809684, + "total": 0.005032150548067875, "iterations": 200 } }, @@ -741,22 +741,22 @@ "warmup": false }, "stats": { - "min": 2.1990854293107986e-07, - "max": 4.303106106817722e-05, - "mean": 2.400443149337668e-07, - "stddev": 1.2890314155828075e-07, - "rounds": 187267, - "median": 2.3993197828531265e-07, + "min": 2.1897722035646439e-07, + "max": 4.609930329024792e-06, + "mean": 2.404501264235612e-07, + "stddev": 4.7953802047892186e-08, + "rounds": 179533, + "median": 2.300366759300232e-07, "iqr": 1.0011717677116394e-08, "q1": 2.300366759300232e-07, "q3": 2.400483936071396e-07, - "iqr_outliers": 12316, - "stddev_outliers": 422, - "outliers": "422;12316", - "ld15iqr": 2.1990854293107986e-07, - "hd15iqr": 2.5995541363954544e-07, - "ops": 4165897.4522096924, - "total": 0.0449523787247017, + "iqr_outliers": 12779, + "stddev_outliers": 4347, + "outliers": "4347;12779", + "ld15iqr": 2.1897722035646439e-07, + "hd15iqr": 2.5902409106492996e-07, + "ops": 4158866.6010450143, + "total": 0.043168732547201216, "iterations": 1 } }, @@ -776,22 +776,22 @@ "warmup": false }, "stats": { - "min": 1.8300488591194153e-06, - "max": 1.5140045434236526e-05, - "mean": 1.9241898845670643e-06, - "stddev": 1.9183763579148093e-07, - "rounds": 64517, - "median": 1.9000144675374031e-06, - "iqr": 3.993045538663864e-08, + "min": 1.8200371414422989e-06, + "max": 2.3090047761797905e-05, + "mean": 1.9100047422912264e-06, + "stddev": 2.0707580551707926e-07, + "rounds": 67250, + "median": 1.8900027498602867e-06, + "iqr": 3.003515303134918e-08, "q1": 1.8799910321831703e-06, - "q3": 1.919921487569809e-06, - "iqr_outliers": 2044, - "stddev_outliers": 1763, - "outliers": "1763;2044", - "ld15iqr": 1.8300488591194153e-06, - "hd15iqr": 1.9799917936325073e-06, - "ops": 519699.22928110417, - "total": 0.12414295878261328, + "q3": 1.9100261852145195e-06, + "iqr_outliers": 1621, + "stddev_outliers": 1134, + "outliers": "1134;1621", + "ld15iqr": 1.8399441614747047e-06, + "hd15iqr": 1.958920620381832e-06, + "ops": 523558.9094927629, + "total": 0.12844781891908497, "iterations": 1 } }, @@ -811,22 +811,22 @@ "warmup": false }, "stats": { - "min": 1.5399418771266937e-06, - "max": 8.220085874199867e-06, - "mean": 1.6593979185031895e-06, - "stddev": 2.139993477297674e-07, - "rounds": 51680, - "median": 1.6299309208989143e-06, - "iqr": 3.993045538663864e-08, - "q1": 1.6100239008665085e-06, - "q3": 1.6499543562531471e-06, - "iqr_outliers": 2646, - "stddev_outliers": 2036, - "outliers": "2036;2646", - "ld15iqr": 1.5510013327002525e-06, - "hd15iqr": 1.7099082469940186e-06, - "ops": 602628.2116239005, - "total": 0.08575768442824483, + "min": 1.300009898841381e-06, + "max": 4.527007695287466e-05, + "mean": 1.382068511380908e-06, + "stddev": 2.5578146215290876e-07, + "rounds": 50531, + "median": 1.369975507259369e-06, + "iqr": 3.003515303134918e-08, + "q1": 1.350068487226963e-06, + "q3": 1.3801036402583122e-06, + "iqr_outliers": 1762, + "stddev_outliers": 655, + "outliers": "655;1762", + "ld15iqr": 1.308973878622055e-06, + "hd15iqr": 1.4289980754256248e-06, + "ops": 723553.1319651003, + "total": 0.06983730394858867, "iterations": 1 } }, @@ -846,22 +846,22 @@ "warmup": false }, "stats": { - "min": 1.5299301594495773e-06, - "max": 0.0002030299510806799, - "mean": 1.6491591146487274e-06, - "stddev": 1.5972580226565407e-06, - "rounds": 127878, - "median": 1.619919203221798e-06, - "iqr": 3.993045538663864e-08, - "q1": 1.600012183189392e-06, - "q3": 1.6399426385760307e-06, - "iqr_outliers": 3983, - "stddev_outliers": 173, - "outliers": "173;3983", - "ld15iqr": 1.5499535948038101e-06, - "hd15iqr": 1.6998965293169022e-06, - "ops": 606369.6286898315, - "total": 0.21089116926304996, + "min": 1.2998934835195541e-06, + "max": 6.563006900250912e-05, + "mean": 1.3776161911857496e-06, + "stddev": 3.217445732265211e-07, + "rounds": 137552, + "median": 1.3599637895822525e-06, + "iqr": 3.003515303134918e-08, + "q1": 1.3499520719051361e-06, + "q3": 1.3799872249364853e-06, + "iqr_outliers": 4086, + "stddev_outliers": 1871, + "outliers": "1871;4086", + "ld15iqr": 1.308973878622055e-06, + "hd15iqr": 1.4289980754256248e-06, + "ops": 725891.5846069393, + "total": 0.18949386232998222, "iterations": 1 } }, @@ -881,22 +881,22 @@ "warmup": false }, "stats": { - "min": 0.001578375929966569, - "max": 0.002168747945688665, - "mean": 0.0017769525923048619, - "stddev": 0.00011456425881271121, - "rounds": 61, - "median": 0.0017752060666680336, - "iqr": 0.00012715990305878222, - "q1": 0.001700576045550406, - "q3": 0.0018277359486091882, - "iqr_outliers": 3, - "stddev_outliers": 17, - "outliers": "17;3", - "ld15iqr": 0.001578375929966569, - "hd15iqr": 0.002018757979385555, - "ops": 562.7612150884189, - "total": 0.10839410813059658, + "min": 0.0016791049856692553, + "max": 0.0023188820341601968, + "mean": 0.0017894306574421732, + "stddev": 0.00016447141295864286, + "rounds": 23, + "median": 0.0017086550360545516, + "iqr": 9.403502917848527e-05, + "q1": 0.0016995339829009026, + "q3": 0.001793569012079388, + "iqr_outliers": 4, + "stddev_outliers": 4, + "outliers": "4;4", + "ld15iqr": 0.0016791049856692553, + "hd15iqr": 0.0020000640070065856, + "ops": 558.8369662948595, + "total": 0.041156905121169984, "iterations": 1 } }, @@ -916,22 +916,22 @@ "warmup": false }, "stats": { - "min": 0.1650447880383581, - "max": 0.1791116880485788, - "mean": 0.17126524543190108, - "stddev": 0.005173821459870787, + "min": 0.16420796501915902, + "max": 0.17127511196304113, + "mean": 0.16707563228971725, + "stddev": 0.0025552368742757666, "rounds": 7, - "median": 0.17100573796778917, - "iqr": 0.008506428042892367, - "q1": 0.16673759868717752, - "q3": 0.1752440267300699, + "median": 0.16728684504050761, + "iqr": 0.003944796975702047, + "q1": 0.16477187976124696, + "q3": 0.168716676736949, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", - "ld15iqr": 0.1650447880383581, - "hd15iqr": 0.1791116880485788, - "ops": 5.8388962540425196, - "total": 1.1988567180233076, + "ld15iqr": 0.16420796501915902, + "hd15iqr": 0.17127511196304113, + "ops": 5.9853132757621506, + "total": 1.1695294260280207, "iterations": 1 } }, @@ -951,22 +951,22 @@ "warmup": false }, "stats": { - "min": 1.644642740022391, - "max": 1.8020292000146583, - "mean": 1.7004884706887726, - "stddev": 0.08808319433406937, + "min": 1.7129017140250653, + "max": 1.8050539770629257, + "mean": 1.7452574116953958, + "stddev": 0.05184361880435378, "rounds": 3, - "median": 1.6547934720292687, - "iqr": 0.1180398449942004, - "q1": 1.6471804230241105, - "q3": 1.765220268018311, + "median": 1.7178165439981967, + "iqr": 0.0691141972783953, + "q1": 1.7141304215183482, + "q3": 1.7832446187967435, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", - "ld15iqr": 1.644642740022391, - "hd15iqr": 1.8020292000146583, - "ops": 0.5880663216698881, - "total": 5.101465412066318, + "ld15iqr": 1.7129017140250653, + "hd15iqr": 1.8050539770629257, + "ops": 0.5729813798805585, + "total": 5.235772235086188, "iterations": 1 } }, @@ -986,22 +986,22 @@ "warmup": false }, "stats": { - "min": 0.027092964970506728, - "max": 0.027330326032824814, - "mean": 0.027203685681646068, - "stddev": 0.00011947863692070981, + "min": 0.03114489803556353, + "max": 0.04113520693499595, + "mean": 0.037459378324759506, + "stddev": 0.005493034679362275, "rounds": 3, - "median": 0.027187766041606665, - "iqr": 0.00017802079673856497, - "q1": 0.027116665238281712, - "q3": 0.027294686035020277, + "median": 0.04009803000371903, + "iqr": 0.0074927316745743155, + "q1": 0.033383181027602404, + "q3": 0.04087591270217672, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", - "ld15iqr": 0.027092964970506728, - "hd15iqr": 0.027330326032824814, - "ops": 36.759724829297134, - "total": 0.0816110570449382, + "ld15iqr": 0.03114489803556353, + "hd15iqr": 0.04113520693499595, + "ops": 26.6955845163888, + "total": 0.11237813497427851, "iterations": 1 } }, @@ -1021,22 +1021,22 @@ "warmup": false }, "stats": { - "min": 0.010402026935480535, - "max": 0.011716941953636706, - "mean": 0.010707777598872781, - "stddev": 0.0005661417389175796, + "min": 0.010852375067770481, + "max": 0.010996904922649264, + "mean": 0.010910016600973904, + "stddev": 5.410600481946325e-05, "rounds": 5, - "median": 0.01046683604363352, - "iqr": 0.00040504205389879644, - "q1": 0.010419756232295185, - "q3": 0.010824798286193982, - "iqr_outliers": 1, - "stddev_outliers": 1, - "outliers": "1;1", - "ld15iqr": 0.010402026935480535, - "hd15iqr": 0.011716941953636706, - "ops": 93.39006070739376, - "total": 0.053538887994363904, + "median": 0.010902223992161453, + "iqr": 6.09357375651598e-05, + "q1": 0.01087515926337801, + "q3": 0.010936095000943169, + "iqr_outliers": 0, + "stddev_outliers": 2, + "outliers": "2;0", + "ld15iqr": 0.010852375067770481, + "hd15iqr": 0.010996904922649264, + "ops": 91.65888894346256, + "total": 0.05455008300486952, "iterations": 1 } }, @@ -1056,22 +1056,22 @@ "warmup": false }, "stats": { - "min": 0.00010245991870760918, - "max": 0.0003708109725266695, - "mean": 0.00013652519847312205, - "stddev": 2.9017697819599973e-05, - "rounds": 2276, - "median": 0.0001343005569651723, - "iqr": 2.9374437872320414e-05, - "q1": 0.0001148005248978734, - "q3": 0.00014417496277019382, - "iqr_outliers": 132, - "stddev_outliers": 359, - "outliers": "359;132", - "ld15iqr": 0.00010245991870760918, - "hd15iqr": 0.00018835102673619986, - "ops": 7324.655163910065, - "total": 0.3107313517248258, + "min": 0.00010694994125515223, + "max": 0.00196018407586962, + "mean": 0.0001551835386225467, + "stddev": 6.670406498766375e-05, + "rounds": 2085, + "median": 0.0001342690084129572, + "iqr": 5.5281969252973795e-05, + "q1": 0.00011702976189553738, + "q3": 0.00017231173114851117, + "iqr_outliers": 136, + "stddev_outliers": 218, + "outliers": "218;136", + "ld15iqr": 0.00010694994125515223, + "hd15iqr": 0.0002555190585553646, + "ops": 6443.982453785273, + "total": 0.32355767802800983, "iterations": 1 } }, @@ -1091,22 +1091,22 @@ "warmup": false }, "stats": { - "min": 0.00021257996559143066, - "max": 0.0006143220234662294, - "mean": 0.00026627696786200573, - "stddev": 5.7436338111569094e-05, - "rounds": 3285, - "median": 0.00024094106629490852, - "iqr": 6.081248284317553e-05, - "q1": 0.0002272709971293807, - "q3": 0.00028808347997255623, - "iqr_outliers": 190, - "stddev_outliers": 516, - "outliers": "516;190", - "ld15iqr": 0.00021257996559143066, - "hd15iqr": 0.0003803420113399625, - "ops": 3755.488159675289, - "total": 0.8747198394266888, + "min": 0.00021157902665436268, + "max": 0.003356548957526684, + "mean": 0.00029292784866724884, + "stddev": 0.00010381804521994046, + "rounds": 3437, + "median": 0.00026112899649888277, + "iqr": 9.156647138297558e-05, + "q1": 0.00023269752273336053, + "q3": 0.0003242639941163361, + "iqr_outliers": 165, + "stddev_outliers": 376, + "outliers": "376;165", + "ld15iqr": 0.00021157902665436268, + "hd15iqr": 0.0004618479870259762, + "ops": 3413.8099349370814, + "total": 1.0067930158693343, "iterations": 1 } }, @@ -1126,22 +1126,22 @@ "warmup": false }, "stats": { - "min": 0.00010666006710380316, - "max": 0.0012298740912228823, - "mean": 0.00014509015394030065, - "stddev": 3.9821676062559014e-05, - "rounds": 4091, - "median": 0.00013705005403608084, - "iqr": 4.2740022763609886e-05, - "q1": 0.00011833096505142748, - "q3": 0.00016107098781503737, - "iqr_outliers": 142, - "stddev_outliers": 433, - "outliers": "433;142", - "ld15iqr": 0.00010666006710380316, - "hd15iqr": 0.00022562104277312756, - "ops": 6892.266448428085, - "total": 0.5935638197697699, + "min": 0.000106189982034266, + "max": 0.001101375906728208, + "mean": 0.00016286832422045315, + "stddev": 5.837386260120275e-05, + "rounds": 1503, + "median": 0.00014264893252402544, + "iqr": 5.623960169032216e-05, + "q1": 0.00012785723083652556, + "q3": 0.00018409683252684772, + "iqr_outliers": 72, + "stddev_outliers": 177, + "outliers": "177;72", + "ld15iqr": 0.000106189982034266, + "hd15iqr": 0.000268538948148489, + "ops": 6139.929325032123, + "total": 0.2447910913033411, "iterations": 1 } }, @@ -1161,22 +1161,22 @@ "warmup": false }, "stats": { - "min": 0.0001403100322932005, - "max": 0.001627046032808721, - "mean": 0.000178134490224629, - "stddev": 4.748697351529773e-05, - "rounds": 3988, - "median": 0.00016955554019659758, - "iqr": 4.2695493903011084e-05, - "q1": 0.00015115051064640284, - "q3": 0.00019384600454941392, - "iqr_outliers": 106, - "stddev_outliers": 297, - "outliers": "297;106", - "ld15iqr": 0.0001403100322932005, - "hd15iqr": 0.0002580310683697462, - "ops": 5613.7359965439155, - "total": 0.7104003470158204, + "min": 0.00013830000534653664, + "max": 0.0022548820124939084, + "mean": 0.00020337896123050878, + "stddev": 7.56935134198109e-05, + "rounds": 2652, + "median": 0.00018173898570239544, + "iqr": 6.557442247867584e-05, + "q1": 0.0001606495352461934, + "q3": 0.00022622395772486925, + "iqr_outliers": 136, + "stddev_outliers": 273, + "outliers": "273;136", + "ld15iqr": 0.00013830000534653664, + "hd15iqr": 0.00032469897996634245, + "ops": 4916.9294304075265, + "total": 0.5393610051833093, "iterations": 1 } }, @@ -1196,22 +1196,22 @@ "warmup": false }, "stats": { - "min": 0.0009114639833569527, - "max": 0.0035819519544020295, - "mean": 0.0010748796483424081, - "stddev": 0.0001887295240733776, - "rounds": 767, - "median": 0.0010203729616478086, - "iqr": 0.00012052097008563578, - "q1": 0.0009710504964459687, - "q3": 0.0010915714665316045, - "iqr_outliers": 80, - "stddev_outliers": 81, - "outliers": "81;80", - "ld15iqr": 0.0009114639833569527, - "hd15iqr": 0.0012940739979967475, - "ops": 930.3367140145584, - "total": 0.824432690278627, + "min": 0.0008544769370928407, + "max": 0.0046387650072574615, + "mean": 0.0011588798084209277, + "stddev": 0.00032186443357136663, + "rounds": 578, + "median": 0.001068401092197746, + "iqr": 0.00030569906812161207, + "q1": 0.0009806669550016522, + "q3": 0.0012863660231232643, + "iqr_outliers": 10, + "stddev_outliers": 55, + "outliers": "55;10", + "ld15iqr": 0.0008544769370928407, + "hd15iqr": 0.0017934839706867933, + "ops": 862.9022550341826, + "total": 0.6698325292672962, "iterations": 1 } }, @@ -1231,26 +1231,26 @@ "warmup": false }, "stats": { - "min": 0.0007936229230836034, - "max": 0.0016023359494283795, - "mean": 0.0009057731272386013, - "stddev": 0.00010516395459009471, - "rounds": 831, - "median": 0.0008773539448156953, - "iqr": 9.683752432465553e-05, - "q1": 0.0008372030279133469, - "q3": 0.0009340405522380024, - "iqr_outliers": 55, - "stddev_outliers": 100, - "outliers": "100;55", - "ld15iqr": 0.0007936229230836034, - "hd15iqr": 0.0010801840107887983, - "ops": 1104.0292209249626, - "total": 0.7526974687352777, + "min": 0.00077674794010818, + "max": 0.0040954959113150835, + "mean": 0.0010721674078432932, + "stddev": 0.00022791204830622454, + "rounds": 832, + "median": 0.0010222920100204647, + "iqr": 0.00022435910068452358, + "q1": 0.0009282519458793104, + "q3": 0.001152611046563834, + "iqr_outliers": 43, + "stddev_outliers": 168, + "outliers": "168;43", + "ld15iqr": 0.00077674794010818, + "hd15iqr": 0.001492826035246253, + "ops": 932.690168237383, + "total": 0.89204328332562, "iterations": 1 } } ], - "datetime": "2026-05-05T01:30:34.620064+00:00", + "datetime": "2026-05-05T05:33:10.612508+00:00", "version": "5.2.3" } \ No newline at end of file diff --git a/tests/test_resultset_invariants.py b/tests/test_resultset_invariants.py new file mode 100644 index 0000000..f795c8c --- /dev/null +++ b/tests/test_resultset_invariants.py @@ -0,0 +1,98 @@ +"""Phase 25 — invariant tripwires for parse_tuple_payload's fast-path dispatch. + +These tests don't exercise behavior. They lock down the structural +invariants the optimized hot loop in :func:`informix_db._resultset.parse_tuple_payload` +relies on for correctness. Each test is a CI tripwire — if a future +contributor breaks an invariant, these fail at test time rather than +at a customer's wire-protocol mismatch six months later. + +Lessons from Margaret Hamilton's review of Phases 23/24/25: + +* The optimization is *correct* — but its correctness depends on + properties of unrelated tables (DECODERS keys, FIXED_WIDTHS keys, + IfxType flag bits) staying consistent. +* A comment at the table only helps if the next contributor reads it. +* A test fails loudly the moment the invariant is broken. Prefer that. + +If one of these tests fires, **do not** simply update the test to +match the new state — that defeats the purpose. Instead read the +docstring on the failed test and the corresponding INVARIANT comment +in the source; either restore the property or refactor the +optimization to no longer depend on it. +""" + +from __future__ import annotations + +from informix_db._resultset import ( + _COMPOSITE_UDT_TYPES, + _FIXED_WIDTH_TYPES, + _LENGTH_PREFIXED_SHORT_TYPES, + _NUMERIC_TYPES, + _TC_DATETIME, + _TC_INTERVAL, + _TC_LVARCHAR, + _TC_UDTFIXED, + _TC_UDTVAR, +) +from informix_db.converters import DECODERS, FIXED_WIDTHS + + +def test_fixed_width_types_disjoint_from_other_dispatch_sets() -> None: + """parse_tuple_payload's fast path is silently wrong if the FIXED_WIDTHS + type set overlaps any other branch. + + The optimization in ``parse_tuple_payload`` puts the FIXED_WIDTHS + branch FIRST. If a type is also in (e.g.) _NUMERIC_TYPES, the fast + path swallows it before the DECIMAL/MONEY-specific handler runs — + silently producing wrong values. + + If this test fails, you've added a new entry somewhere that + overlaps. Either move it to FIXED_WIDTHS exclusively (and remove + its specialized branch) or remove it from FIXED_WIDTHS. + """ + other_branch_types = ( + _LENGTH_PREFIXED_SHORT_TYPES + | _NUMERIC_TYPES + | _COMPOSITE_UDT_TYPES + | {_TC_LVARCHAR, _TC_DATETIME, _TC_INTERVAL, _TC_UDTFIXED, _TC_UDTVAR} + ) + overlap = _FIXED_WIDTH_TYPES & other_branch_types + assert overlap == set(), ( + f"FIXED_WIDTHS overlap with another parse_tuple_payload branch: {overlap}. " + f"See the INVARIANT comment on FIXED_WIDTHS in converters.py." + ) + + +def test_every_fixed_width_type_has_a_decoder() -> None: + """The fast path calls ``_decode_base(tc, raw, encoding)`` for every + FIXED_WIDTHS key. If a key has no entry in DECODERS, we'd raise + ``NotImplementedError`` for that column — surprising the user. + + If this test fails, you've added a key to FIXED_WIDTHS without + adding a corresponding decoder. Add the decoder, or remove the + key. + """ + missing = [tc for tc in FIXED_WIDTHS if tc not in DECODERS] + assert missing == [], ( + f"FIXED_WIDTHS has keys without DECODERS entries: {missing}. " + f"Every fixed-width type must be decodable by _decode_base." + ) + + +def test_decoders_keys_stay_below_0x100() -> None: + """The Phase 24 optimization in ``_decode_base`` skips ``base_type()`` + by relying on a structural guarantee: all DECODERS keys are ≤ 0xFF + and all flag bits in _types.py are ≥ 0x100, so a flagged type code + cannot coincidentally match a DECODERS key. + + If this test fails, you've added a decoder for a type code with + bits ≥ 0x100. The collision-free guarantee weakens — re-introduce + ``base_type()`` inside ``_decode_base`` (and remove the Phase 24 + optimization), OR keep the new key but verify it cannot clash with + any flagged input. + """ + high_keys = [tc for tc in DECODERS if tc >= 0x100] + assert high_keys == [], ( + f"DECODERS contains keys with bits >= 0x100: {high_keys}. " + f"See the INVARIANT comment on DECODERS in converters.py." + ) diff --git a/uv.lock b/uv.lock index 8f43417..00eded2 100644 --- a/uv.lock +++ b/uv.lock @@ -34,7 +34,7 @@ wheels = [ [[package]] name = "informix-db" -version = "2026.5.4.8" +version = "2026.5.4.9" source = { editable = "." } [package.optional-dependencies]