eendebakpt's Description of Work
x_mul() checks for signals on every row, x_divrem() on every quotient digit and the decimal string conversion on every digit. PyErr_CheckSignals() costs about as much as a hundred digit operations, so for small operands the checks cost more than the arithmetic.
SIGCHECK() now takes the loop index and checks on every 64th iteration, the same pattern _sre uses.
| Benchmark | main | PR |
|---|:---:|:---:|
| a * b (10 x 10 digits) | 159 ns | 97.2 ns: 1.64x faster |
| a * b (60 x 60 digits) | 2.61 us | 2.22 us: 1.18x faster |
| a * b (500 x 500 digits) | 99.1 us | 79.7 us: 1.24x faster |
| a // b (1000 // 2 digits) | 13.8 us | 10.3 us: 1.34x faster |
| a // b (1000 // 500 digits) | 204 us | not significant |
| pow(3, 5000) | 10.7 us | 8.38 us: 1.28x faster |
| math.factorial(2000) | 97.7 us | 81.6 us: 1.20x faster |
| str(7**1000) | 8.07 us | 7.34 us: 1.10x faster |
Benchmark script
```python
import pyperf
runner = pyperf.Runner()
def ints(na, nb):
return (f"import random; r = random.Random(1); "
f"a = r.getrandbits({na*30-1}) | (1 << {na*30-2}); "
f"b = r.getrandbits({nb*30-1}) | (1 << {nb*30-2})")
runner.timeit("a * b (10 x 10 digits)", "a * b", setup=ints(10, 10))
runner.timeit("a * b (60 x 60 digits)", "a * b", setup=ints(60, 60))
runner.timeit("a * b (500 x 500 digits)", "a * b", setup=ints(500, 500))
runner.timeit("a // b (1000 // 2 digits)", "a // b", setup=ints(1000, 2))
runner.timeit("a // b (1000 // 500 digits)", "a // b", setup=ints(1000, 500))
runner.timeit("pow(3, 5000)", "pow(3, 5000)")
runner.timeit("math.factorial(2000)", "factorial(2000)",
setup="from math import factorial")
runner.timeit("str(7**1000)", "str(x)", setup="x = 7**1000")
```
Generated with Claude Code
- Issue: gh-157742
<!-- /gh-issue-number -->