{
  "id": "#1108",
  "pr": 1108,
  "title": "perf: SpecifierSet use tuple instead of frozenset for `_specs`",
  "base": "20f1523cf37dc5a0e85e7c7dc0070dfc4a09653a",
  "head": "f59d6ee9afe2330cc9524f825417ee1c31e5e684",
  "merged": "2026-03-07",
  "after_ultra_release": false,
  "known": [
    {
      "id": "dedup-equality",
      "match": {
        "probe": "P3",
        "detail": "eq"
      },
      "fixed_by": 1109,
      "status": "verified"
    },
    {
      "id": "dedup-str-len",
      "match": {
        "probe": "P1"
      },
      "fixed_by": 1109,
      "status": "verified"
    },
    {
      "id": "old-pickles-break-comparison",
      "match": {
        "probe": "P4"
      },
      "fixed_by": "26.2",
      "status": "verified"
    }
  ],
  "targets": [
    {
      "target": "packaging.specifiers:SpecifierSet",
      "run_id": "bt-1108-f30c47",
      "image": "052dfee2-22cb-4d9b-b081-e27b77a69db0",
      "backend": "contree",
      "usd": 0.0983,
      "harvested": 80,
      "arms": {
        "hypothesis": {
          "classes": [
            "P4|base->head eq-fresh|True|<exc:builtins.AttributeError>"
          ],
          "known": [
            "old-pickles-break-comparison"
          ],
          "seconds": 8.8,
          "error": null
        },
        "mutations": {
          "classes": [
            "P1|len|differs|plain",
            "P1|str|differs|plain",
            "P2|outcome|exc:builtins.AssertionError|ok|long-digits",
            "P2|outcome|exc:builtins.ValueError|ok|long-digits",
            "P4|base->head eq-fresh|True|<exc:builtins.AttributeError>",
            "P4|base->head eq-fresh|True|False"
          ],
          "known": [
            "dedup-str-len",
            "old-pickles-break-comparison"
          ],
          "seconds": 7.1,
          "error": null
        },
        "breakwater": {
          "classes": [
            "P1|len|differs|non-ascii+whitespace",
            "P1|len|differs|plain",
            "P1|len|differs|whitespace",
            "P1|str|differs|non-ascii+whitespace",
            "P1|str|differs|plain",
            "P1|str|differs|whitespace",
            "P2|outcome|exc:builtins.AssertionError|ok|long-digits",
            "P3|eq|True|False",
            "P3|hash_eq|True|False",
            "P4|base->head eq-fresh|True|<exc:builtins.AttributeError>",
            "P4|base->head eq-fresh|True|False"
          ],
          "known": [
            "dedup-equality",
            "dedup-str-len",
            "old-pickles-break-comparison"
          ],
          "seconds": 53.7,
          "error": null
        },
        "llm-plan": {
          "classes": [
            "P1|len|differs|plain",
            "P1|str|differs|plain",
            "P4|base->head eq-fresh|True|False"
          ],
          "known": [
            "dedup-str-len",
            "old-pickles-break-comparison"
          ],
          "seconds": 4.2,
          "error": null
        }
      },
      "findings": [
        {
          "signature": "P1|len|differs|plain",
          "probe": "P1",
          "detail": "len",
          "args": [
            [
              ">=1.0,>=1.0.0"
            ]
          ],
          "base": 1,
          "head": 2,
          "found_by": [
            "seed",
            "mutation:equivalents",
            "mutation:separators"
          ]
        },
        {
          "signature": "P1|len|differs|whitespace",
          "probe": "P1",
          "detail": "len",
          "args": [
            [
              ">= 1.0,>=1.0.0"
            ]
          ],
          "base": 1,
          "head": 2,
          "found_by": [
            "mutation:whitespace-outer",
            "mutation:whitespace-inner"
          ]
        },
        {
          "signature": "P1|len|differs|non-ascii+whitespace",
          "probe": "P1",
          "detail": "len",
          "args": [
            [
              " >=1.0,>=1.0.0 "
            ]
          ],
          "base": 1,
          "head": 2,
          "found_by": [
            "mutation:whitespace-outer"
          ]
        },
        {
          "signature": "P1|str|differs|plain",
          "probe": "P1",
          "detail": "str",
          "args": [
            [
              ">=1.0,>=1.0.0"
            ]
          ],
          "base": ">=1.0",
          "head": ">=1.0,>=1.0.0",
          "found_by": [
            "seed",
            "mutation:equivalents",
            "mutation:separators"
          ]
        },
        {
          "signature": "P1|str|differs|whitespace",
          "probe": "P1",
          "detail": "str",
          "args": [
            [
              ">= 1.0,>=1.0.0"
            ]
          ],
          "base": ">=1.0",
          "head": ">=1.0,>=1.0.0",
          "found_by": [
            "mutation:whitespace-outer",
            "mutation:whitespace-inner"
          ]
        },
        {
          "signature": "P1|str|differs|non-ascii+whitespace",
          "probe": "P1",
          "detail": "str",
          "args": [
            [
              " >=1.0,>=1.0.0 "
            ]
          ],
          "base": ">=1.0",
          "head": ">=1.0,>=1.0.0",
          "found_by": [
            "mutation:whitespace-outer"
          ]
        },
        {
          "signature": "P2|outcome|exc:builtins.AssertionError|ok|long-digits",
          "probe": "P2",
          "detail": "outcome",
          "args": [
            [
              ">=10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0,>=1.0.0"
            ]
          ],
          "base": {
            "status": "exc",
            "exc": "builtins.AssertionError"
          },
          "head": {
            "status": "ok",
            "obs": {
              "str": ">=1.0.0,>=10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0",
              "len": 2
            }
          },
          "found_by": [
            "mutation:huge-number"
          ]
        },
        {
          "signature": "P3|eq|True|False",
          "probe": "P3",
          "detail": "eq",
          "args": [
            [
              ">=1.0,>=1.0.0"
            ],
            [
              ">=1.0.0,>=1.0.0"
            ]
          ],
          "base": true,
          "head": false,
          "found_by": [
            "focus"
          ]
        },
        {
          "signature": "P3|hash_eq|True|False",
          "probe": "P3",
          "detail": "hash_eq",
          "args": [
            [
              ">=1.0,>=1.0.0"
            ],
            [
              ">=1.0.0,>=1.0.0"
            ]
          ],
          "base": true,
          "head": false,
          "found_by": [
            "focus"
          ]
        },
        {
          "signature": "P4|base->head eq-fresh|True|False",
          "probe": "P4",
          "detail": "base->head eq-fresh",
          "args": [
            [
              ">=1.0,>=1.0.0"
            ]
          ],
          "base": true,
          "head": false,
          "found_by": [
            "seed",
            "mutation:whitespace-outer",
            "mutation:equivalents",
            "mutation:whitespace-inner",
            "mutation:separators"
          ]
        },
        {
          "signature": "P4|base->head eq-fresh|True|<exc:builtins.AttributeError>",
          "probe": "P4",
          "detail": "base->head eq-fresh",
          "args": [
            [
              ""
            ]
          ],
          "base": true,
          "head": "<exc:builtins.AttributeError>",
          "found_by": [
            "tests",
            "mutation:huge-number",
            "mutation:edges",
            "mutation:equivalents",
            "mutation:separators",
            "mutation:case"
          ]
        }
      ],
      "perf": {
        "slower_families": [],
        "worst_valid_ratio": null,
        "worst_family": null,
        "speedup_ratio_small": null,
        "report": {
          "run_id": "bt-1108-f30c47",
          "target": "packaging.specifiers:SpecifierSet",
          "image": "052dfee2-22cb-4d9b-b081-e27b77a69db0",
          "backend": "contree",
          "families": [
            {
              "id": "F1",
              "kind": "suspected_slowdown",
              "family": "many_specs_hash_first",
              "why": "SpecifierSet now lazily sorts its specs on first order‑sensitive operation. __hash__ triggers a full O(n log n) sort before any __str__ call, whereas the base version hashed the frozenset in O(n). Repeated hashing of a large set before any string conversion therefore becomes slower.",
              "size_meaning": "Number of distinct specifiers in the comma‑separated string (n). Cost grows roughly as n log n for the first hash.",
              "make_source": "\ndef make(n):\n    # generate n distinct simple specifiers, e.g. \">=1.0.0\", \">=1.1.0\", …\n    specs = [f\">={i}.0.0\" for i in range(1, n+1)]\n    spec_string = \",\".join(specs)\n    # one argument list per call – the benchmark can instantiate many times\n    return [[spec_string]]",
              "example_args": [],
              "origin": "plan",
              "screen_ratio": 0.4026765225057491,
              "valid_fraction": 1.0,
              "error": null
            },
            {
              "id": "F2",
              "kind": "suspected_slowdown",
              "family": "Large SpecifierSet (≥ 50 specifiers) where the first hash/equality operation forces a full sort",
              "why": "Head lazily sorts the internal tuple on first order‑sensitive operation (__hash__, __eq__, __str__). The old version used a frozenset whose hash/equality are O(n) without sorting. For a large set the first hash now costs O(n log n) vs O(n) before.",
              "size_meaning": "Number of distinct specifiers in the comma‑separated string passed to the constructor.",
              "make_source": "\ndef make(n):\n    # Generate n distinct simple specifiers like '>=1.0', '>=2.0', …\n    specs = [f\">={i}.0\" for i in range(1, n+1)]\n    spec_string = \",\".join(specs)\n    # Each element of the returned list is an argument tuple for the constructor.\n    # The benchmark will typically call SpecifierSet(*args).\n    return [[spec_string]]",
              "example_args": [],
              "origin": "plan",
              "screen_ratio": 0.413594095426172,
              "valid_fraction": 1.0,
              "error": null
            },
            {
              "id": "F3",
              "kind": "suspected_slowdown",
              "family": "many_specifiers_hash",
              "why": "Head stores specs as a tuple and lazily sorts on first __hash__/__eq__/__str__. A SpecifierSet with many comma‑separated specifiers triggers an O(n log n) sort before the tuple can be hashed, whereas the base version kept a frozenset whose hash is cached after the first O(n) computation.",
              "size_meaning": "n = number of distinct specifiers in the set",
              "make_source": "\ndef make(n):\n    # generate n distinct simple specifiers, e.g. \">=1.0\", \">=2.0\", …\n    specs = [f\">={i}.0\" for i in range(1, n+1)]\n    spec_str = \",\".join(specs)\n    return [[spec_str]]",
              "example_args": [
                [
                  ">=1.0,>=2.0,>=3.0"
                ]
              ],
              "origin": "plan",
              "screen_ratio": 0.413594095426172,
              "valid_fraction": 1.0,
              "error": null
            },
            {
              "id": "F4",
              "kind": "suspected_slowdown",
              "family": "many_specs_equality",
              "why": "__eq__ now sorts both operand tuples (if not already sorted) before comparing, costing up to two O(n log n) sorts. The base version compared two frozensets directly in average O(n). Equality checks on large sets before any __str__ are therefore slower.",
              "size_meaning": "Number of distinct specifiers per set (n). Two equal large sets are created and compared.",
              "make_source": "def make(n):\n    specs = [f\">={i}.0.0\" for i in range(1, n+1)]\n    spec_string = \",\".join(specs)\n    # return two argument lists – the benchmark can build two SpecifierSets and compare them\n    return [[spec_string], [spec_string]]",
              "example_args": [],
              "origin": "plan",
              "screen_ratio": 0.40236402997486365,
              "valid_fraction": 1.0,
              "error": null
            },
            {
              "id": "F5",
              "kind": "suspected_slowdown",
              "family": "Large SpecifierSet where the first __str__ (or __repr__) call triggers the lazy sort",
              "why": "Same lazy‑sort mechanism as hashing. The old implementation built the string by sorting a generator each call (O(n log n) every call). The new version sorts once and caches, but the *first* call still pays the full sort, which is slower than the old frozenset hash path for a single use.",
              "size_meaning": "Number of distinct specifiers.",
              "make_source": "\ndef make(n):\n    specs = [f\"=={i}.0.*\" for i in range(1, n+1)]  # use prefix‑match specifiers to exercise the string path\n    spec_string = \",\".join(specs)\n    return [[spec_string]]",
              "example_args": [],
              "origin": "plan",
              "screen_ratio": 0.849565469365266,
              "valid_fraction": 1.0,
              "error": null
            },
            {
              "id": "F6",
              "kind": "suspected_slowdown",
              "family": "many_specifiers_eq",
              "why": "Equality now compares lengths then calls _sort_specs() on both operands. The first equality check on two large sets forces two sorts (O(n log n) each) before the O(n) tuple comparison, while the base version compared two frozensets in O(n) average time with no sorting.",
              "size_meaning": "n = number of distinct specifiers in each set",
              "make_source": "def make(n):\n    specs = [f\">={i}.0\" for i in range(1, n+1)]\n    spec_str = \",\".join(specs)\n    # two argument lists – the benchmark can construct two SpecifierSet instances from them\n    return [[spec_str], [spec_str]]",
              "example_args": [
                [
                  ">=1.0,>=2.0,>=3.0"
                ],
                [
                  ">=1.0,>=2.0,>=3.0"
                ]
              ],
              "origin": "plan",
              "screen_ratio": 0.4132495962286905,
              "valid_fraction": 1.0,
              "error": null
            }
          ],
          "findings": [],
          "seconds": {
            "plan": 18.94,
            "screen": 102.55,
            "full": 0.0,
            "total": 121.5
          }
        }
      }
    }
  ]
}