Author: Carl Friedrich Bolz <cfb...@gmx.de> Branch: Changeset: r65000:902241cca7dc Date: 2013-06-26 12:07 +0200 http://bitbucket.org/pypy/pypy/changeset/902241cca7dc/
Log: merge faster-str-of-bigint speed up str(long) significantly, thanks to an algorithm proposed by Nathan Hurst on pypy-dev: http://mail.python.org/pipermail/pypy-dev/2013-May/011433.html diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py --- a/rpython/rlib/rbigint.py +++ b/rpython/rlib/rbigint.py @@ -434,11 +434,11 @@ @jit.elidable def repr(self): - return _format_decimal(self, addL=True) + return self.format(BASE10, suffix="L") @jit.elidable def str(self): - return _format_decimal(self) + return self.format(BASE10) @jit.elidable def eq(self, other): @@ -1974,45 +1974,27 @@ BASE10 = '0123456789' BASE16 = '0123456789abcdef' -def _format(a, digits, prefix='', suffix=''): - """ - Convert a bigint object to a string, using a given conversion base. - Return a string object. - """ - size_a = a.numdigits() - - base = len(digits) - assert base >= 2 and base <= 36 - - # Compute a rough upper bound for the length of the string - i = base - bits = 0 - while i > 1: - bits += 1 - i >>= 1 - i = 5 + len(prefix) + len(suffix) + (size_a*SHIFT + bits-1) // bits - s = [chr(0)] * i - p = i - j = len(suffix) - while j > 0: - p -= 1 - j -= 1 - s[p] = suffix[j] - - if a.sign == 0: - p -= 1 - s[p] = '0' - elif (base & (base - 1)) == 0: +def _format_base2_notzero(a, digits, prefix='', suffix=''): + base = len(digits) # JRH: special case for power-of-2 bases accum = 0 accumbits = 0 # # of bits in accum - basebits = 1 # # of bits in base-1 + basebits = 0 i = base - while 1: + while i > 1: + basebits += 1 i >>= 1 - if i <= 1: - break - basebits += 1 + + # Compute a rough upper bound for the length of the string + size_a = a.numdigits() + i = 5 + len(prefix) + len(suffix) + (size_a*SHIFT + basebits-1) // basebits + result = [chr(0)] * i + next_char_index = i + j = len(suffix) + while j > 0: + next_char_index -= 1 + j -= 1 + result[next_char_index] = suffix[j] i = 0 while i < size_a: @@ -2021,9 +2003,9 @@ assert accumbits >= basebits while 1: cdigit = intmask(accum & (base - 1)) - p -= 1 - assert p >= 0 - s[p] = digits[cdigit] + next_char_index -= 1 + assert next_char_index >= 0 + result[next_char_index] = digits[cdigit] accumbits -= basebits accum >>= basebits if i < size_a - 1: @@ -2032,162 +2014,82 @@ else: if accum <= 0: break - i += 1 + j = len(prefix) + while j > 0: + next_char_index -= 1 + j -= 1 + result[next_char_index] = prefix[j] + + if a.sign < 0: + next_char_index -= 1 + result[next_char_index] = '-' + + assert next_char_index >= 0 # otherwise, buffer overflow (this is also a + # hint for the annotator for the slice below) + return ''.join(result[next_char_index:]) + +_FORMAT_MINDIGITS = 5 # 36 ** 5 fits in 32 bits, there may be a better choice for this + +def _format_int(val, digits): + base = len(digits) + j = 0 + out = [] + while val: + out.append(digits[val % base]) + val //= base + out.reverse() + return "".join(out) + + +def _format_recursive(x, i, output, pts, digits, size_prefix): + # bottomed out with min_digit sized pieces + # use str of ints + if i < 0: + # this checks whether any digit has been appended yet + if output.getlength() == size_prefix: + if x.sign == 0: + pass + else: + s = _format_int(x.toint(), digits) + output.append(s) + else: + s = _format_int(x.toint(), digits) + output.append_multiple_char(digits[0], _FORMAT_MINDIGITS - len(s)) + output.append(s) else: - # Not 0, and base not a power of 2. Divide repeatedly by - # base, but for speed use the highest power of base that - # fits in a digit. - size = size_a - pin = a # just for similarity to C source which uses the array - # powbase <- largest power of base that fits in a digit. - powbase = _widen_digit(base) # powbase == base ** power - power = 1 - while 1: - newpow = powbase * base - if newpow >> SHIFT: # doesn't fit in a digit - break - powbase = newpow - power += 1 + top, bot = x.divmod(pts[i]) # split the number + _format_recursive(top, i-1, output, pts, digits, size_prefix) + _format_recursive(bot, i-1, output, pts, digits, size_prefix) - # Get a scratch area for repeated division. - scratch = rbigint([NULLDIGIT] * size, 1, size) +def _format(x, digits, prefix='', suffix=''): + if x.sign == 0: + return prefix + "0" + suffix + base = len(digits) + assert base >= 2 and base <= 36 + if (base & (base - 1)) == 0: + return _format_base2_notzero(x, digits, prefix, suffix) + negative = x.sign < 0 + if negative: + x = x.neg() + rbase = rbigint.fromint(base) + two = rbigint.fromint(2) - # Repeatedly divide by powbase. - while 1: - ntostore = power - rem = _inplace_divrem1(scratch, pin, powbase, size) - pin = scratch # no need to use a again - if pin._digits[size - 1] == NULLDIGIT: - size -= 1 + pts = [rbase.pow(rbigint.fromint(_FORMAT_MINDIGITS))] + stringsize = _FORMAT_MINDIGITS + while pts[-1].lt(x): + pts.append(pts[-1].pow(two)) + stringsize *= 2 + pts.pop() # remove first base**2**i greater than x - # Break rem into digits. - assert ntostore > 0 - while 1: - nextrem = rem // base - c = rem - nextrem * base - p -= 1 - assert p >= 0 - s[p] = digits[c] - rem = nextrem - ntostore -= 1 - # Termination is a bit delicate: must not - # store leading zeroes, so must get out if - # remaining quotient and rem are both 0. - if not (ntostore and (size or rem)): - break - if size == 0: - break + output = StringBuilder(stringsize) + if negative: + output.append('-') + output.append(prefix) + _format_recursive(x,len(pts)-1, output, pts, digits, output.getlength()) - j = len(prefix) - while j > 0: - p -= 1 - j -= 1 - s[p] = prefix[j] - - if a.sign < 0: - p -= 1 - s[p] = '-' - - assert p >= 0 # otherwise, buffer overflow (this is also a - # hint for the annotator for the slice below) - return ''.join(s[p:]) - - -DECIMAL_SHIFT = 0 # computed as max(E such that 10**E fits in a digit) -while 10 ** (DECIMAL_SHIFT + 1) <= 2 ** SHIFT: - DECIMAL_SHIFT += 1 -DECIMAL_BASE = 10 ** DECIMAL_SHIFT - -# an RPython trick: this creates a nested sequence of calls that are -# all inlined into each other, making an unrolled loop. Moreover the -# calls are done in the "wrong" order to be written as a regular loop: -# the first digit that is append-ed to the builder is the most -# significant one (corresponding to the innermost call). -_succ = specialize.memo()(lambda n: n + 1) -@specialize.arg(3) -def _add_decimal_digits(builder, value, ndigits, digit_index=1): - assert value >= 0 - if digit_index < ndigits: - assert digit_index < DECIMAL_SHIFT - _add_decimal_digits(builder, value // 10, ndigits, _succ(digit_index)) - builder.append(chr(ord('0') + value % 10)) - else: - assert value < 10 - builder.append(chr(ord('0') + value)) -_add_decimal_digits._always_inline_ = True - - -def _format_decimal(a, addL=False): - """ Optimized version of _format(a, BASE10, '', 'L' if addL else ''). """ - if a.sign == 0: - if addL: - return "0L" - else: - return "0" - - size_a = a.numdigits() - negative = a.sign < 0 - - # quick and dirty upper bound for the number of digits - # required to express a in base DECIMAL_BASE: - # - # #digits = 1 + floor(log2(a) / log2(DECIMAL_BASE)) - # - # But log2(a) < size_a * PyLong_SHIFT, and - # log2(DECIMAL_BASE) = log2(10) * DECIMAL_SHIFT - # > 3 * DECIMAL_SHIFT - - size = 1 + size_a * SHIFT // (3 * DECIMAL_SHIFT) - pout = [NULLDIGIT] * size - - # convert array of base _PyLong_BASE digits in pin to an array of - # base _PyLong_DECIMAL_BASE digits in pout, following Knuth (TAOCP, - # Volume 2 (3rd edn), section 4.4, Method 1b). - size = 0 - for i in range(size_a-1, -1, -1): - hi = a.digit(i) - for j in range(size): - z = (_widen_digit(pout[j]) << SHIFT) | hi - hi = _store_digit(z // DECIMAL_BASE) - pout[j] = _store_digit(z - _widen_digit(hi) * DECIMAL_BASE) - assert hi >= 0 - while hi: - pout[size] = hi % DECIMAL_BASE - hi //= DECIMAL_BASE - size += 1 - sizem1 = size - 1 - assert sizem1 >= 0 - - # calculate exact length of output string, and allocate - decimal_digits_in_last_part = 1 - rem = pout[sizem1] - tenpow = 10 - while rem >= tenpow: - tenpow *= 10 - decimal_digits_in_last_part += 1 - strlen = (addL + negative + - decimal_digits_in_last_part + (sizem1) * DECIMAL_SHIFT) - - builder = StringBuilder(strlen) - - # start with the negative sign, if needed - if negative: - builder.append('-') - - # pout[size-1] produces 'decimal_digits_in_last_part' digits. - # Then the remaining pout[size-2] through pout[0] contribute exactly - # DECIMAL_SHIFT digits each. - decimal_digits = decimal_digits_in_last_part - for i in range(sizem1, -1, -1): - _add_decimal_digits(builder, pout[i], decimal_digits) - decimal_digits = DECIMAL_SHIFT - - # done - if addL: - builder.append('L') - return builder.build() - + output.append(suffix) + return output.build() def _bitwise(a, op, b): # '&', '|', '^' """ Bitwise and/or/xor operations """ 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 @@ -31,11 +31,14 @@ assert rbigint.frombool(True).tolong() == 1 def test_str(self): - for i in range(100): - n = 3 ** i - r1 = rbigint.fromlong(n) + n = 1 + r1 = rbigint.fromint(1) + three = rbigint.fromint(3) + for i in range(300): + n *= 3 + r1 = r1.mul(three) assert r1.str() == str(n) - r2 = rbigint.fromlong(-n) + r2 = r1.neg() assert r2.str() == str(-n) def test_floordiv(self): _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit