Pull Request Overview
- Opened on September 18, 2026
- Status Open
- Commit count 2 with first commit September 18, 2026
Total Delta
Open Days
Test Delta
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
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.