[pypy-commit] pypy default: Add rarithmetic.mulmod(). Use it to improve the performance of
arigo
pypy.commits at gmail.com
Fri Jul 6 13:00:52 EDT 2018
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r94817:93f86b23bc8e
Date: 2018-07-06 18:21 +0200
http://bitbucket.org/pypy/pypy/changeset/93f86b23bc8e/
Log: Add rarithmetic.mulmod(). Use it to improve the performance of
pow(a, b, c)
if c is a large integer that still fits inside a W_IntObject.
Note that this means that pow(a, b, c) now returns an int more
systematically, and not a long, if all arguments are ints. I hope
this is not a problem. Previously we would return sometimes an int
and sometimes a long in this situation, based on the internal
algorithm, like CPython.
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -48,6 +48,7 @@
'int_lshift': 'interp_intop.int_lshift',
'int_rshift': 'interp_intop.int_rshift',
'uint_rshift': 'interp_intop.uint_rshift',
+ 'int_mulmod': 'interp_intop.int_mulmod',
}
diff --git a/pypy/module/__pypy__/interp_intop.py b/pypy/module/__pypy__/interp_intop.py
--- a/pypy/module/__pypy__/interp_intop.py
+++ b/pypy/module/__pypy__/interp_intop.py
@@ -2,7 +2,7 @@
from rpython.rtyper.lltypesystem import lltype
from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.rlib.rarithmetic import r_uint, intmask
-from rpython.rlib.rarithmetic import int_c_div, int_c_mod
+from rpython.rlib.rarithmetic import int_c_div, int_c_mod, mulmod
from rpython.rlib import jit
@@ -39,3 +39,7 @@
n = r_uint(n)
x = llop.uint_rshift(lltype.Unsigned, n, m)
return space.newint(intmask(x))
+
+ at unwrap_spec(a=int, b=int, c=int)
+def int_mulmod(space, a, b, c):
+ return space.newint(mulmod(a, b, c))
diff --git a/pypy/module/__pypy__/test/test_intop.py b/pypy/module/__pypy__/test/test_intop.py
--- a/pypy/module/__pypy__/test/test_intop.py
+++ b/pypy/module/__pypy__/test/test_intop.py
@@ -102,3 +102,7 @@
assert intop.uint_rshift(-1, 1) == sys.maxsize
assert intop.uint_rshift(-1, bits-2) == 3
assert intop.uint_rshift(-1, bits-1) == 1
+
+ def test_mulmod(self):
+ from __pypy__ import intop
+ assert intop.int_mulmod(9373891, 9832739, 2**31-1) == 1025488209
diff --git a/pypy/objspace/std/intobject.py b/pypy/objspace/std/intobject.py
--- a/pypy/objspace/std/intobject.py
+++ b/pypy/objspace/std/intobject.py
@@ -233,20 +233,23 @@
return wrapint(space, a)
- at jit.look_inside_iff(lambda space, iv, iw, iz:
- jit.isconstant(iw) and jit.isconstant(iz))
def _pow(space, iv, iw, iz):
"""Helper for pow"""
- if iw < 0:
- if iz != 0:
- raise oefmt(space.w_TypeError,
- "pow() 2nd argument cannot be negative when 3rd "
- "argument specified")
+ if iz == 0:
+ return _pow_nomod(iv, iw)
+ else:
+ return _pow_mod(space, iv, iw, iz)
+
+ at jit.look_inside_iff(lambda iv, iw: jit.isconstant(iw))
+def _pow_nomod(iv, iw):
+ if iw <= 0:
+ if iw == 0:
+ return 1
# bounce it, since it always returns float
raise ValueError
temp = iv
ix = 1
- while iw > 0:
+ while True:
if iw & 1:
try:
ix = ovfcheck(ix * temp)
@@ -259,12 +262,40 @@
temp = ovfcheck(temp * temp) # Square the value of temp
except OverflowError:
raise
- if iz:
- # If we did a multiplication, perform a modulo
- ix %= iz
- temp %= iz
- if iz:
- ix %= iz
+ return ix
+
+ at jit.look_inside_iff(lambda space, iv, iw, iz:
+ jit.isconstant(iw) and jit.isconstant(iz))
+def _pow_mod(space, iv, iw, iz):
+ from rpython.rlib.rarithmetic import mulmod
+
+ if iw <= 0:
+ if iw == 0:
+ return 1 % iz # != 1, for iz == 1 or iz < 0
+ raise oefmt(space.w_TypeError,
+ "pow() 2nd argument cannot be negative when 3rd "
+ "argument specified")
+ if iz < 0:
+ try:
+ iz = ovfcheck(-iz)
+ except OverflowError:
+ raise
+ iz_negative = True
+ else:
+ iz_negative = False
+
+ temp = iv
+ ix = 1
+ while True:
+ if iw & 1:
+ ix = mulmod(ix, temp, iz)
+ iw >>= 1 # Shift exponent down by 1 bit
+ if iw == 0:
+ break
+ temp = mulmod(temp, temp, iz)
+
+ if iz_negative and ix > 0:
+ ix -= iz
return ix
diff --git a/pypy/objspace/std/test/test_intobject.py b/pypy/objspace/std/test/test_intobject.py
--- a/pypy/objspace/std/test/test_intobject.py
+++ b/pypy/objspace/std/test/test_intobject.py
@@ -1,8 +1,9 @@
# encoding: utf-8
import py
import sys
+from pypy.interpreter.error import OperationError
from pypy.objspace.std import intobject as iobj
-from rpython.rlib.rarithmetic import r_uint, is_valid_int
+from rpython.rlib.rarithmetic import r_uint, is_valid_int, intmask
from rpython.rlib.rbigint import rbigint
@@ -186,6 +187,63 @@
assert space.isinstance_w(v, space.w_long)
assert space.bigint_w(v).eq(rbigint.fromlong(pow(10, 20)))
+ try:
+ from hypothesis import given, strategies, example
+ except ImportError:
+ pass
+ else:
+ @given(
+ a=strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint),
+ b=strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint),
+ c=strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint))
+ @example(0, 0, -sys.maxint-1)
+ @example(0, 1, -sys.maxint-1)
+ @example(1, 0, -sys.maxint-1)
+ def test_hypot_pow(self, a, b, c):
+ if c == 0:
+ return
+ #
+ # "pow(a, b, c)": if b < 0, should get an app-level TypeError.
+ # Otherwise, should always work except if c == -maxint-1
+ if b < 0:
+ expected = TypeError
+ elif b > 0 and c == -sys.maxint-1:
+ expected = OverflowError
+ else:
+ expected = pow(a, b, c)
+
+ try:
+ result = iobj._pow(self.space, a, b, c)
+ except OperationError as e:
+ assert ('TypeError: pow() 2nd argument cannot be negative '
+ 'when 3rd argument specified' == e.errorstr(self.space))
+ result = TypeError
+ except OverflowError:
+ result = OverflowError
+ assert result == expected
+
+ @given(
+ a=strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint),
+ b=strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint))
+ def test_hypot_pow_nomod(self, a, b):
+ # "a ** b": detect overflows and ValueErrors
+ if b < 0:
+ expected = ValueError
+ elif b > 128 and not (-1 <= a <= 1):
+ expected = OverflowError
+ else:
+ expected = a ** b
+ if expected != intmask(expected):
+ expected = OverflowError
+
+ try:
+ result = iobj._pow(self.space, a, b, 0)
+ except ValueError:
+ result = ValueError
+ except OverflowError:
+ result = OverflowError
+ assert result == expected
+
def test_neg(self):
space = self.space
x = 42
diff --git a/rpython/rlib/rarithmetic.py b/rpython/rlib/rarithmetic.py
--- a/rpython/rlib/rarithmetic.py
+++ b/rpython/rlib/rarithmetic.py
@@ -840,6 +840,31 @@
return z
+ at specialize.memo()
+def check_support_int128():
+ from rpython.rtyper.lltypesystem import rffi
+ return hasattr(rffi, '__INT128_T')
+
+def mulmod(a, b, c):
+ """Computes (a * b) % c.
+ Assumes c > 0, and returns a nonnegative result.
+ """
+ assert c > 0
+ if LONG_BIT < LONGLONG_BIT:
+ a = r_longlong(a)
+ b = r_longlong(b)
+ return intmask((a * b) % c)
+ elif check_support_int128():
+ a = r_longlonglong(a)
+ b = r_longlonglong(b)
+ return intmask((a * b) % c)
+ else:
+ from rpython.rlib.rbigint import rbigint
+ a = rbigint.fromlong(a)
+ b = rbigint.fromlong(b)
+ return a.mul(b).int_mod(c).toint()
+
+
# String parsing support
# ---------------------------
diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -1,6 +1,7 @@
from rpython.rlib.rarithmetic import LONG_BIT, intmask, longlongmask, r_uint, r_ulonglong
from rpython.rlib.rarithmetic import ovfcheck, r_longlong, widen
from rpython.rlib.rarithmetic import most_neg_value_of_same_type
+from rpython.rlib.rarithmetic import check_support_int128
from rpython.rlib.rstring import StringBuilder
from rpython.rlib.debug import make_sure_not_resized, check_regular_int
from rpython.rlib.objectmodel import we_are_translated, specialize, not_rpython
@@ -10,7 +11,7 @@
import math, sys
-SUPPORT_INT128 = hasattr(rffi, '__INT128_T')
+SUPPORT_INT128 = check_support_int128()
BYTEORDER = sys.byteorder
# note about digit sizes:
diff --git a/rpython/rlib/test/test_rarithmetic.py b/rpython/rlib/test/test_rarithmetic.py
--- a/rpython/rlib/test/test_rarithmetic.py
+++ b/rpython/rlib/test/test_rarithmetic.py
@@ -1,5 +1,6 @@
from rpython.rtyper.test.tool import BaseRtypingTest
from rpython.rtyper.test.test_llinterp import interpret
+from rpython.rlib import rarithmetic
from rpython.rlib.rarithmetic import *
from rpython.rlib.rstring import ParseStringError, ParseStringOverflowError
from hypothesis import given, strategies, assume
@@ -731,3 +732,17 @@
py.test.raises(OverflowError, ovfcheck_int32_sub, 2**30, -2**30)
assert ovfcheck_int32_mul(-2**16, 2**15) == -2**31
py.test.raises(OverflowError, ovfcheck_int32_mul, -2**16, -2**15)
+
+ at given(strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint),
+ strategies.integers(min_value=-sys.maxint-1, max_value=sys.maxint),
+ strategies.integers(min_value=1, max_value=sys.maxint))
+def test_mulmod(a, b, c):
+ assert mulmod(a, b, c) == (a * b) % c
+ #
+ import rpython.rlib.rbigint # import before patching check_support_int128
+ prev = rarithmetic.check_support_int128
+ try:
+ rarithmetic.check_support_int128 = lambda: False
+ assert mulmod(a, b, c) == (a * b) % c
+ finally:
+ rarithmetic.check_support_int128 = prev
diff --git a/rpython/translator/c/test/test_typed.py b/rpython/translator/c/test/test_typed.py
--- a/rpython/translator/c/test/test_typed.py
+++ b/rpython/translator/c/test/test_typed.py
@@ -962,3 +962,12 @@
f = self.getcompiled(func, [int])
res = f(2)
assert res == 1 # and not 2
+
+ def test_mulmod(self):
+ from rpython.rlib.rarithmetic import mulmod
+
+ def func(a, b, c):
+ return mulmod(a, b, c)
+ f = self.getcompiled(func, [int, int, int])
+ res = f(1192871273, 1837632879, 2001286281)
+ assert res == 1573897320
More information about the pypy-commit
mailing list