[pypy-commit] pypy math-improvements: Make rshift invert (in most cases) in place, this makes a huge speedup for rshift with negative numbers as it avoids two extra copies, also make an rqshift for the power of twos

stian pypy.commits at gmail.com
Sat Nov 4 14:19:22 EDT 2017


Author: stian
Branch: math-improvements
Changeset: r92929:f30c2f38b0b5
Date: 2017-11-04 19:18 +0100
http://bitbucket.org/pypy/pypy/changeset/f30c2f38b0b5/

Log:	Make rshift invert (in most cases) in place, this makes a huge
	speedup for rshift with negative numbers as it avoids two extra
	copies, also make an rqshift for the power of twos

diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -787,7 +787,7 @@
             if digit == 1:
                 return rbigint(self._digits[:self.numdigits()], 1, self.numdigits())
             elif digit and digit & (digit - 1) == 0:
-                return self.rshift(ptwotable[digit])
+                return self.rqshift(ptwotable[digit])
 
         div, mod = _divrem(self, other)
         if mod.sign * other.sign == -1:
@@ -816,7 +816,7 @@
             if digit == 1:
                 return self
             elif digit & (digit - 1) == 0:
-                return self.rshift(ptwotable[digit])
+                return self.rqshift(ptwotable[digit])
             
         div, mod = _divrem1(self, digit)
 
@@ -1267,31 +1267,85 @@
             raise ValueError("negative shift count")
         elif int_other == 0:
             return self
+        invert = False
         if self.sign == -1 and not dont_invert:
-            a = self.invert().rshift(int_other)
-            return a.invert()
+            first = self.digit(0)
+            if first == 0:
+                a = self.invert().rshift(int_other)
+                return a.invert()
+            invert = True
 
         wordshift = int_other / SHIFT
+        loshift = int_other % SHIFT
         newsize = self.numdigits() - wordshift
         if newsize <= 0:
-            return NULLRBIGINT
-
-        loshift = int_other % SHIFT
+            if invert:
+                return ONENEGATIVERBIGINT
+            else:
+                return NULLRBIGINT
+
+        
         hishift = SHIFT - loshift
         z = rbigint([NULLDIGIT] * newsize, self.sign, newsize)
         i = 0
         while i < newsize:
-            newdigit = (self.udigit(wordshift) >> loshift)
+            digit = self.udigit(wordshift)
+            if i == 0 and invert and wordshift == 0:
+                digit -= 1
+            newdigit = (digit >> loshift)
             if i+1 < newsize:
                 newdigit |= (self.udigit(wordshift+1) << hishift)
             z.setdigit(i, newdigit)
             i += 1
             wordshift += 1
+        if invert:
+            z.setdigit(0, z.digit(0)+1)
         z._normalize()
         return z
     rshift._always_inline_ = 'try' # It's so fast that it's always benefitial.
 
     @jit.elidable
+    def rqshift(self, int_other):
+        wordshift = int_other / SHIFT
+        loshift = int_other % SHIFT
+        newsize = self.numdigits() - wordshift
+
+        invert = False
+        if self.sign == -1:
+            first = self.digit(0)
+            if first == 0:
+                a = self.invert().rqshift(int_other)
+                return a.invert()
+            invert = True
+            
+        if newsize <= 0:
+            if invert:
+                return ONENEGATIVERBIGINT
+            else:
+                return NULLRBIGINT
+                
+        
+        hishift = SHIFT - loshift
+        z = rbigint([NULLDIGIT] * newsize, self.sign, newsize)
+        i = 0
+        inverted = False
+        while i < newsize:
+            digit = self.udigit(wordshift)
+            if invert and i == 0 and wordshift == 0:
+                digit -= 1
+            newdigit = (digit >> loshift)
+            if i+1 < newsize:
+                newdigit |= (self.udigit(wordshift+1) << hishift)
+            z.setdigit(i, newdigit)
+            i += 1
+            wordshift += 1
+        if invert:
+            z.setdigit(0, z.digit(0)+1)      
+        z._normalize()
+        return z
+    rshift._always_inline_ = 'try' # It's so fast that it's always benefitial.
+    
+    @jit.elidable
     def abs_rshift_and_mask(self, bigshiftcount, mask):
         assert isinstance(bigshiftcount, r_ulonglong)
         assert mask >= 0
diff --git a/rpython/rlib/test/test_rbigint.py b/rpython/rlib/test/test_rbigint.py
--- a/rpython/rlib/test/test_rbigint.py
+++ b/rpython/rlib/test/test_rbigint.py
@@ -598,6 +598,33 @@
                     res3 = f1.abs_rshift_and_mask(r_ulonglong(y), mask)
                     assert res3 == (abs(x) >> y) & mask
 
+    def test_qshift(self):
+        for x in range(10):
+            for y in range(1, 161, 16):
+                num = (x << y) + x
+                f1 = rbigint.fromlong(num)
+                nf1 = rbigint.fromlong(-num)
+                
+                for z in range(1, 31):
+                    res1 = f1.lqshift(z).tolong() 
+                    res2 = f1.rqshift(z).tolong() 
+                    res3 = nf1.lqshift(z).tolong() 
+                    res4 = nf1.rqshift(z).tolong() 
+                    
+                    assert res1 == num << z
+                    assert res2 == num >> z
+                    assert res3 == -num << z
+                    assert res4 == -num >> z
+                    
+        # Large digit
+        for x in range((1 << SHIFT) - 10, (1 << SHIFT) + 10):
+            f1 = rbigint.fromlong(x)
+            nf1 = rbigint.fromlong(-x)
+            assert f1.rqshift(SHIFT).tolong() == x >> SHIFT 
+            assert nf1.rqshift(SHIFT).tolong() == -x >> SHIFT
+            assert f1.rqshift(SHIFT+1).tolong() == x >> (SHIFT+1)
+            assert nf1.rqshift(SHIFT+1).tolong() == -x >> (SHIFT+1)
+                    
     def test_from_list_n_bits(self):
         for x in ([3L ** 30L, 5L ** 20L, 7 ** 300] +
                   [1L << i for i in range(130)] +


More information about the pypy-commit mailing list