gh-157742: Let compilers vectorize the int shift loops #157751

Open
eendebakpt opened 7:25am on September 18, 2026 wants to merge 119 Ξ” into python/cpython main from
longobject-vshift-vectorize

Pull Request Overview

  • Opened on September 18, 2026
  • Status Open
  • Commit count 2 with first commit September 18, 2026

Total Delta

119 Total Diff Delta

Open Days

Open 0 weekdays

Test Delta

0 Diff Delta in Test Files
Breakdown by Phase

How long has this pull request spent in each phase of its lifecycle?

Fraction of total time Business days Phase
 
0.0 days Authoring 2 commits before pull request opened for review
 
0.1 days Awaiting first review
 
0.9 days Revising work with 0 commits in response to 0 reviews that left 2 comments

Total time for pull request still awaiting merge: 1.0 business day

Author avatar

gh-157742: Let compilers vectorize the int shift loops

v_lshift() and v_rshift() pass a carry from one loop iteration to the next, which prevents vectorization. The carry into a digit is just the bits shifted out of its neighbour, so each output digit can be built from two input digits instead, with nothing carried.

These helpers do the normalization in x_divrem() (three passes that dominate
when the quotient is short) and the scaling in true division.

| Benchmark | main | PR |
|---|:---:|:---:|
| a << 7 (10 digits) | 35.2 ns | 31.9 ns: 1.10x faster |
| a << 7 (100 digits) | 87.3 ns | 42.8 ns: 2.04x faster |
| a >> 7 (100 digits) | 90.2 ns | 42.4 ns: 2.13x faster |
| a << 7 (1000 digits) | 639 ns | 213 ns: 2.99x faster |
| a >> 7 (1000 digits) | 631 ns | 209 ns: 3.02x faster |
| 10 // 2 digits | 160 ns | 149 ns: 1.08x faster |
| 1000 // 2 digits | 14.7 us | 13.9 us: 1.05x faster |
| 100 // 99 digits | 535 ns | 349 ns: 1.54x faster |
| 1000 // 999 digits | 4.51 us | 2.95 us: 1.53x faster |
| 1000 % 999 digits | 4.53 us | 2.94 us: 1.54x faster |
| divmod, 1000 by 999 digits | 4.55 us | 2.98 us: 1.52x faster |
| 1000 / 999 digits | 5.22 us | 3.18 us: 1.64x faster |
| pyperformance pidigits | 161 ms | 129 ms: 1.24x faster |

Benchmark script

```python
import pyperf

runner = pyperf.Runner()
def ints(na, nb=1):
return (f"import random; r = random.Random(1234); "
f"a = r.getrandbits({na*30-1}) | (1 << {na*30-2}); "
f"b = r.getrandbits({nb*30-3}) | (1 << {nb*30-4})")
for na in (2, 10, 100, 1000):
runner.timeit(f"a << 7 ({na} digits)", "a << 7", setup=ints(na))
runner.timeit(f"a >> 7 ({na} digits)", "a >> 7", setup=ints(na))
runner.timeit("-a >> 7 (1000 digits)", "a >> 7", setup=ints(1000) + "; a = -a")
for na, nb in [(2, 2), (10, 2), (1000, 2), (10, 9), (100, 99), (1000, 999)]:
runner.timeit(f"{na} // {nb} digits", "a // b", setup=ints(na, nb))
runner.timeit("1000 % 999 digits", "a % b", setup=ints(1000, 999))
runner.timeit("divmod, 1000 by 999 digits", "divmod(a, b)", setup=ints(1000, 999))
runner.timeit("1000 / 999 digits", "a / b", setup=ints(1000, 999))
```

Note: when disabling vectorization I get a 20% slowdown. I think that is ok, since modern cpu's/compilers can do this, if not we could modify the PR to gate the change behind some macro.

Generated with Claude Code


  • Issue: gh-157742
    <!-- /gh-issue-number -->

Comments Threads Pending Resolution

Resolved Comment Threads

No resolved comments have been left on this PR.