Skip to content

Improve performance by using reference stealing methods #150942

Description

@eendebakpt

Feature or enhancement

Proposal:

We can improve performance of cpython by using stealing methods (_PyList_AppendTakeRef, _PyDict_SetItem_Take2, PyTuple_FromPairSteal etc.) instead of the normal ones. Using stealing methods we typically avoid an incref/decref pair and sometimes error handling is simplified. We should only make changes where it makes a performance impact, the code is not more complex and the modules or methods affected are performance critical.

A list of possible cases (there might be more).

Location Module / context Helper
Modules/_csv.c:688 csv reader, per field _PyList_AppendTakeRef
Modules/pyexpat.c:472 pyexpat StartElement, per attribute _PyDict_SetItem_Take2
Modules/_elementtree.c:3351 ElementTree C accel attrib dict _PyDict_SetItem_Take2
Modules/_io/bytesio.c:577 io.BytesIO.readlines _PyList_AppendTakeRef
Modules/_io/iobase.c:756 io.IOBase.readlines (generic) _PyList_AppendTakeRef
Objects/frameobject.c:639 & 658 frame f_locals / vars() / tracing _PyList_AppendTakeRef
Modules/_pickle.c:5138 pickle Pickler memo copy _PyDict_SetItem_Take2
Modules/_io/textio.c:2337 & 2343 io.TextIOWrapper readline (multi-chunk) _PyList_AppendTakeRef
Modules/_asynciomodule.c:1213 asyncio future done-callback compaction _PyList_AppendTakeRef

Update: Most cases already have an open PR, for new ones please open a separate issue.

Linked PRs

Activity

  1. added 2 commits that reference this issue on Jun 5, 2026
  2. omkar-334 commented on Jun 5, 2026

    @omkar-334
    Contributor

    hi @eendebakpt @StanFromIreland can i work on a module , maybe csv or textio

  3. eendebakpt commented on Jun 5, 2026

    @eendebakpt
    ContributorAuthor

    hi @eendebakpt @StanFromIreland can i work on a module , maybe csv or textio

    Sure! Try out, benchmark, test. If benchmarks show significant improvements you can open a PR, if not just report here briefly.

  4. added a commit that references this issue on Jun 6, 2026
  5. omkar-334 commented on Jun 6, 2026

    @omkar-334
    Contributor

    hi @eendebakpt , i've opned a PR for csv reader, since that has 1.08x speedup, matches your PR speedups....

    however, the benchmarks for io.BytesIO.readlines show that there is not a significant improvements

    Benchmark main my branch speedup
    bytesio_readlines_1m_8b 15.15 ms 15.29 ms 0.99x
    bytesio_readlines_500k_24b 7.66 ms 7.45 ms 1.03x
    bytesio_readlines_100k_256b 3.24 ms 3.20 ms 1.01x
    bytesio_readlines_2m_0b 16.33 ms 16.54 ms 0.99x
    bytesio_readlines_2m_8b 32.58 ms 31.97 ms 1.02x
    geomean 1.01x
    Benchmark script
    """Micro-benchmark for io.BytesIO.readlines result-list building."""
    
    from __future__ import annotations
    
    import argparse
    import io
    import json
    import statistics
    import sys
    import time
    from pathlib import Path
    
    
    def make_data(lines: int, payload_size: int) -> bytes:
        payload = b"x" * payload_size
        return b"".join(payload + b"\n" for _ in range(lines))
    
    
    def time_readlines(data: bytes) -> float:
        t0 = time.perf_counter_ns()
        io.BytesIO(data).readlines()
        return (time.perf_counter_ns() - t0) / 1e9
    
    
    def main() -> int:
        ap = argparse.ArgumentParser()
        ap.add_argument("--lines", type=int, default=200_000)
        ap.add_argument("--payload-size", type=int, default=24)
        ap.add_argument("--trials", type=int, default=15)
        ap.add_argument("--warmup", type=int, default=3)
        ap.add_argument("--label", default="run")
        ap.add_argument("--outdir", default="results/bytesio-readlines")
        args = ap.parse_args()
    
        data = make_data(args.lines, args.payload_size)
    
        for _ in range(args.warmup):
            time_readlines(data)
    
        trials = [time_readlines(data) for _ in range(args.trials)]
    
        outdir = Path(args.outdir)
        outdir.mkdir(parents=True, exist_ok=True)
        summary = {
            "label": args.label,
            "python": sys.executable,
            "lines": args.lines,
            "payload_size": args.payload_size,
            "trials": args.trials,
            "warmup": args.warmup,
            "input_bytes": len(data),
            "seconds": {
                "min": min(trials),
                "median": statistics.median(trials),
                "mean": statistics.fmean(trials),
                "stdev": statistics.pstdev(trials),
                "all": trials,
            },
            "lines_per_sec_at_min": args.lines / min(trials),
        }
        out = outdir / f"bench_{args.label}.json"
        out.write_text(json.dumps(summary, indent=2))
    
        s = summary["seconds"]
        print(
            f"{args.label}: min={s['min']*1000:.2f}ms median={s['median']*1000:.2f}ms "
            f"mean={s['mean']*1000:.2f}ms stdev={s['stdev']*1000:.2f}ms "
            f"lines/s@min={summary['lines_per_sec_at_min']:.2e}"
        )
        print(f"wrote {out}")
        return 0
    
    
    if __name__ == "__main__":
        raise SystemExit(main())

    do you think i should still make a pr for this

  6. added a commit that references this issue on Jun 6, 2026
  7. tangyuan0821 commented on Jun 7, 2026

    @tangyuan0821
    Contributor

    Happy to try _asynciomodule.c — swap PyList_Append for
    _PyList_AppendTakeRef in future_add_done_callback.
    I'll benchmark first.

  8. added 2 commits that reference this issue on Jun 7, 2026
  9. added a commit that references this issue on Jun 7, 2026
  10. 26 remaining items

  11. tkim602 commented on Aug 21, 2026

    @tkim602

    Hi, I found an additional candidate in Python/modsupport.c::do_mkdict(), where Py_BuildValue() dictionary construction passes newly owned key/value references to PyDict_SetItem() and then immediately decrefs them. Using _PyDict_SetItem_Take2() avoids those INCREF/DECREF round trips. Targeted Py_BuildValue() dict microbenchmarks showed small but repeatable ~1–5% improvements across the tested cases.

    Opened #156199 for this.

  12. eendebakpt commented on Aug 22, 2026

    @eendebakpt
    ContributorAuthor

    It seems all of the obvious cases have an open PR, so I am going to close the issue. For any new cases, please open a new issue.

  13. StanFromIreland commented on Aug 22, 2026

    @StanFromIreland
    Member

    I think we should keep this issue open however, unless you plan to move all the PRs?

  14. added a commit that references this issue on Oct 2, 2026
  15. reopened this on Oct 2, 2026
  16. picnixz commented on Oct 2, 2026

    @picnixz
    Member

    Keeping it open because there are still PRs.

  17. moved this from Todo to In progress in Project_VKon Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    easyextension-modulesC modules in the Modules dirperformancePerformance or resource usagetype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions