[pypy-commit] pypy improve-rbigint: Attempt to improve division by porting Cpythons new algorithm, it cuts division time by 30%. And also improve divrem1 by just casting the value, a small speed increase when // 3.
stian
noreply at buildbot.pypy.org
Wed Aug 1 01:06:20 CEST 2012
Author: stian
Branch: improve-rbigint
Changeset: r56517:e377b170d0ea
Date: 2012-08-01 01:05 +0200
http://bitbucket.org/pypy/pypy/changeset/e377b170d0ea/
Log: Attempt to improve division by porting Cpythons new algorithm, it
cuts division time by 30%. And also improve divrem1 by just casting
the value, a small speed increase when // 3.
Note: The CPython algorithm currently doesn't pass tests and need to
be looked at
diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py
--- a/pypy/rlib/rbigint.py
+++ b/pypy/rlib/rbigint.py
@@ -109,6 +109,8 @@
hop.exception_cannot_occur()
class rbigint(object):
+ _immutable_ = True
+ _immutable_fields_ = ["_digits"]
"""This is a reimplementation of longs using a list of digits."""
def __init__(self, digits=[NULLDIGIT], sign=0, size=0):
@@ -403,7 +405,7 @@
if other.sign == 0:
return self
if self.sign == 0:
- return rbigint(other._digits[:], -other.sign, other.size)
+ return rbigint(other._digits[:other.size], -other.sign, other.size)
if self.sign == other.sign:
result = _x_sub(self, other)
else:
@@ -428,7 +430,7 @@
if a._digits[0] == NULLDIGIT:
return NULLRBIGINT
elif a._digits[0] == ONEDIGIT:
- return rbigint(b._digits[:], a.sign * b.sign, b.size)
+ return rbigint(b._digits[:b.size], a.sign * b.sign, b.size)
elif bsize == 1:
res = b.widedigit(0) * a.widedigit(0)
carry = res >> SHIFT
@@ -466,7 +468,7 @@
if self.sign == 1 and other.numdigits() == 1 and other.sign == 1:
digit = other.digit(0)
if digit == 1:
- return rbigint(self._digits[:], 1, self.size)
+ return rbigint(self._digits[:self.size], 1, self.size)
elif digit and digit & (digit - 1) == 0:
return self.rshift(ptwotable[digit])
@@ -491,7 +493,7 @@
if digit == 1:
return NULLRBIGINT
elif digit == 2:
- modm = self.digit(0) % digit
+ modm = self.digit(0) & 1
if modm:
return ONENEGATIVERBIGINT if other.sign == -1 else ONERBIGINT
return NULLRBIGINT
@@ -1329,12 +1331,12 @@
size -= 1
while size >= 0:
- rem = (rem << SHIFT) + pin.widedigit(size)
+ rem = (rem << SHIFT) | pin.widedigit(size)
hi = rem // n
pout.setdigit(size, hi)
rem -= hi * n
size -= 1
- return rem & MASK
+ return rffi.cast(lltype.Signed, rem)
def _divrem1(a, n):
"""
@@ -1439,13 +1441,13 @@
* result in z[0:m], and return the d bits shifted out of the bottom.
"""
- carry = 0
+ carry = _widen_digit(0)
acc = _widen_digit(0)
mask = (1 << d) - 1
assert 0 <= d and d < SHIFT
for i in range(m-1, 0, -1):
- acc = carry << SHIFT | a.digit(i)
+ acc = (carry << SHIFT) | a.widedigit(i)
carry = acc & mask
z.setdigit(i, acc >> d)
@@ -1453,84 +1455,93 @@
def _x_divrem(v1, w1):
""" Unsigned bigint division with remainder -- the algorithm """
-
+ size_v = v1.numdigits()
size_w = w1.numdigits()
- d = (UDIGIT_TYPE(MASK)+1) // (w1.udigit(abs(size_w-1)) + 1)
- assert d <= MASK # because the first digit of w1 is not zero
- d = UDIGIT_MASK(d)
- v = _muladd1(v1, d)
- w = _muladd1(w1, d)
- size_v = v.numdigits()
- size_w = w.numdigits()
- assert size_w > 1 # (Assert checks by div()
+ assert size_v >= size_w and size_w > 1
+
+ v = rbigint([NULLDIGIT] * (size_v + 1), 1, size_v + 1)
+ w = rbigint([NULLDIGIT] * size_w, 1, size_w)
+
+ """/normalize: shift w1 left so that its top digit is >= PyLong_BASE/2.
+ shift v1 left by the same amount. Results go into w and v. """
+
+ d = SHIFT - bits_in_digit(w1.digit(size_w-1))
+ carry = _v_lshift(w, w1, size_w, d)
+ assert carry == 0
+ carry = _v_lshift(v, v1, size_v, d)
+ if carry != 0 or v.digit(abs(size_v-1)) >= w.digit(abs(size_w-1)):
+ v.setdigit(size_v, carry)
+ size_v += 1
+
+ """ Now v->ob_digit[size_v-1] < w->ob_digit[size_w-1], so quotient has
+ at most (and usually exactly) k = size_v - size_w digits. """
size_a = size_v - size_w + 1
- assert size_a > 0
a = rbigint([NULLDIGIT] * size_a, 1, size_a)
-
+
wm1 = w.widedigit(abs(size_w-1))
wm2 = w.widedigit(abs(size_w-2))
+
j = size_v
k = size_a - 1
- carry = _widen_digit(0)
+ assert k > 0
while k >= 0:
- assert j > 1
+ assert j >= 0
+ """ inner loop: divide vk[0:size_w+1] by w0[0:size_w], giving
+ single-digit quotient q, remainder in vk[0:size_w]. """
+
+ # estimate quotient digit q; may overestimate by 1 (rare)
if j >= size_v:
- vj = 0
+ vtop = 0
else:
- vj = v.widedigit(j)
+ vtop = v.widedigit(j)
+ assert vtop <= wm1
+ vv = (vtop << SHIFT | v.widedigit(abs(j-1)))
+ q = vv / wm1
+ r = vv - (wm1 * q)
+ while wm2 * q > (r << SHIFT | v.widedigit(abs(j-2))):
+ q -= 1
+ r += wm1
+ if r > MASK:
+ break
+
+ assert q < MASK
- if vj == wm1:
- q = MASK
- else:
- q = ((vj << SHIFT) + v.widedigit(abs(j-1))) // wm1
-
- while (wm2 * q >
- ((
- (vj << SHIFT)
- + v.widedigit(abs(j-1))
- - q * wm1
- ) << SHIFT)
- + v.widedigit(abs(j-2))):
- q -= 1
+ # subtract q*w0[0:size_w] from vk[0:size_w+1]
+ zhi = 0
i = 0
- while i < size_w and i+k < size_v:
- z = w.widedigit(i) * q
- zz = z >> SHIFT
- carry += v.widedigit(i+k) - z + (zz << SHIFT)
- v.setdigit(i+k, carry)
- carry >>= SHIFT
- carry -= zz
+ while i < size_w:
+ z = v.widedigit(k+i) + zhi - q * w.widedigit(i)
+ v.setdigit(k+i, z)
+ zhi = z >> SHIFT
i += 1
-
- if i+k < size_v:
- carry += v.widedigit(i+k)
- v.setdigit(i+k, 0)
-
- if carry == 0:
- a.setdigit(k, q)
- assert not q >> SHIFT
- else:
- assert carry == -1
- q -= 1
- a.setdigit(k, q)
- assert not q >> SHIFT
-
- carry = 0
+
+ # add w back if q was too large (this branch taken rarely)
+ assert vtop+zhi == -1 or vtop + zhi == 0
+ if vtop + zhi < 0:
+ carry = _widen_digit(0)
i = 0
- while i < size_w and i+k < size_v:
- carry += v.udigit(i+k) + w.udigit(i)
- v.setdigit(i+k, carry)
+ while i < size_w:
+ carry += v.widedigit(k+i) + w.widedigit(i)
+ v.setdigit(k+i, carry)
carry >>= SHIFT
i += 1
+ q -= 1
+
+ # store quotient digit
+ a.setdigit(k, q)
+ k -= 1
j -= 1
- k -= 1
- carry = 0
-
+
+
+
+ carry = _v_rshift(w, v, size_w, d)
+ assert carry == 0
+
a._normalize()
- _inplace_divrem1(v, v, d, size_v)
- v._normalize()
- return a, v
+ w._normalize()
+
+ return a, w
def _divrem(a, b):
""" Long division with remainder, top-level routine """
diff --git a/pypy/rlib/test/test_rbigint.py b/pypy/rlib/test/test_rbigint.py
--- a/pypy/rlib/test/test_rbigint.py
+++ b/pypy/rlib/test/test_rbigint.py
@@ -533,6 +533,9 @@
y = long(randint(1, 1 << 60))
y <<= 60
y += randint(1, 1 << 60)
+ if y > x:
+ x <<= 100
+
f1 = rbigint.fromlong(x)
f2 = rbigint.fromlong(y)
div, rem = lobj._x_divrem(f1, f2)
@@ -540,6 +543,21 @@
assert div.tolong() == _div
assert rem.tolong() == _rem
+ def test__x_divrem2(self):
+ Rx = 1 << 130
+ Rx2 = 1 << 150
+ Ry = 1 << 127
+ Ry2 = 1<< 130
+ for i in range(10):
+ x = long(randint(Rx, Rx2))
+ y = long(randint(Ry, Ry2))
+ f1 = rbigint.fromlong(x)
+ f2 = rbigint.fromlong(y)
+ div, rem = lobj._x_divrem(f1, f2)
+ _div, _rem = divmod(x, y)
+ assert div.tolong() == _div
+ assert rem.tolong() == _rem
+
def test_divmod(self):
x = 12345678901234567890L
for i in range(100):
diff --git a/pypy/translator/goal/targetbigintbenchmark.py b/pypy/translator/goal/targetbigintbenchmark.py
--- a/pypy/translator/goal/targetbigintbenchmark.py
+++ b/pypy/translator/goal/targetbigintbenchmark.py
@@ -2,7 +2,7 @@
import os, sys
from time import time
-from pypy.rlib.rbigint import rbigint, _k_mul, _tc_mul
+from pypy.rlib.rbigint import rbigint
# __________ Entry point __________
@@ -35,24 +35,24 @@
Sum: 142.686547
Pypy with improvements:
- mod by 2: 0.006321
- mod by 10000: 3.143117
- mod by 1024 (power of two): 0.009611
- Div huge number by 2**128: 2.138351
- rshift: 2.247337
- lshift: 1.334369
- Floordiv by 2: 1.555604
- Floordiv by 3 (not power of two): 4.275014
- 2**500000: 0.033836
- (2**N)**5000000 (power of two): 0.049600
- 10000 ** BIGNUM % 100 1.326477
- i = i * i: 3.924958
- n**10000 (not power of two): 6.335759
- Power of two ** power of two: 0.013380
- v = v * power of two 3.497662
- v = v * v 6.359251
- v = v + v 2.785971
- Sum: 39.036619
+ mod by 2: 0.004325
+ mod by 10000: 3.152204
+ mod by 1024 (power of two): 0.009776
+ Div huge number by 2**128: 1.393527
+ rshift: 2.222866
+ lshift: 1.360271
+ Floordiv by 2: 1.499409
+ Floordiv by 3 (not power of two): 4.027997
+ 2**500000: 0.033296
+ (2**N)**5000000 (power of two): 0.045644
+ 10000 ** BIGNUM % 100 1.217218
+ i = i * i: 3.962458
+ n**10000 (not power of two): 6.343562
+ Power of two ** power of two: 0.013249
+ v = v * power of two 3.536149
+ v = v * v 6.299587
+ v = v + v 2.767121
+ Sum: 37.888659
With SUPPORT_INT128 set to False
mod by 2: 0.004103
@@ -77,32 +77,6 @@
"""
sumTime = 0.0
-
- """t = time()
- by = rbigint.fromint(2**62).lshift(1030000)
- for n in xrange(5000):
- by2 = by.lshift(63)
- _tc_mul(by, by2)
- by = by2
-
-
- _time = time() - t
- sumTime += _time
- print "Toom-cook effectivity _Tcmul 1030000-1035000 digits:", _time
-
- t = time()
- by = rbigint.fromint(2**62).lshift(1030000)
- for n in xrange(5000):
- by2 = by.lshift(63)
- _k_mul(by, by2)
- by = by2
-
-
- _time = time() - t
- sumTime += _time
- print "Toom-cook effectivity _kMul 1030000-1035000 digits:", _time"""
-
-
V2 = rbigint.fromint(2)
num = rbigint.pow(rbigint.fromint(100000000), rbigint.fromint(1024))
t = time()
More information about the pypy-commit
mailing list