Visitar URL original
sum() several times slower on Python 3 64-bit · Issue #68264 · python/cpython · GitHub
Skip to content

sum() several times slower on Python 3 64-bit #68264

Description

@ambv
BPO 24076
Nosy @gvanrossum, @rhettinger, @mdickinson, @scoder, @stevendaprano, @ambv, @serhiy-storchaka, @pablogsal
PRs
  • bpo-24076: Inline single digit unpacking in the integer fastpath of sum() #28469
  • bpo-24076: Fix reference in sum() introduced by GH-28469 #28493
  • Files
  • pylong_freelist.patch
  • unpack_single_digits.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2021-09-21.11:22:53.873>
    created_at = <Date 2015-04-29.18:36:58.330>
    labels = ['interpreter-core', '3.11', 'performance']
    title = 'sum() several times slower on Python 3 64-bit'
    updated_at = <Date 2021-09-22.10:30:16.952>
    user = 'https://github.com/ambv'

    bugs.python.org fields:

    activity = <Date 2021-09-22.10:30:16.952>
    actor = 'pablogsal'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-09-21.11:22:53.873>
    closer = 'scoder'
    components = ['Interpreter Core']
    creation = <Date 2015-04-29.18:36:58.330>
    creator = 'lukasz.langa'
    dependencies = []
    files = ['39245', '47748']
    hgrepos = []
    issue_num = 24076
    keywords = ['patch']
    message_count = 35.0
    messages = ['242238', '242241', '242242', '242243', '242244', '242259', '242260', '242262', '242294', '242300', '242302', '242310', '242357', '242914', '323443', '402136', '402201', '402210', '402281', '402282', '402285', '402286', '402295', '402298', '402302', '402306', '402310', '402313', '402315', '402325', '402326', '402328', '402334', '402410', '402421']
    nosy_count = 8.0
    nosy_names = ['gvanrossum', 'rhettinger', 'mark.dickinson', 'scoder', 'steven.daprano', 'lukasz.langa', 'serhiy.storchaka', 'pablogsal']
    pr_nums = ['28469', '28493']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'performance'
    url = 'https://bugs.python.org/issue24076'
    versions = ['Python 3.11']

    Activity

    1. ambv commented on Apr 29, 2015

      @ambv
      ContributorAuthor

      I got a report that summing numbers is noticably slower on Python 3. This is easily reproducible:

      $ time python2.7 -c "print sum(xrange(3, 10**9, 3)) + sum(xrange(5, 10**9, 5)) - sum(xrange(15, 10**9, 15))"
      233333333166666668

      real 0m6.165s
      user 0m6.100s
      sys 0m0.032s

      $ time python3.4 -c "print(sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15)))"
      233333333166666668

      real 0m16.413s
      user 0m16.086s
      sys 0m0.089s

      I can't tell from initial poking what's the core issue here. Both examples produce equivalent bytecode, the builtin_sum() function is only noticably different in the fact that it uses PyLong_* across the board, including PyLong_AsLongAndOverlow. We'll need to profile this, which I didn't have time for yet.

    2. added
      interpreter-core(Objects, Python, Grammar, and Parser dirs)
      performancePerformance or resource usage
      on Apr 29, 2015
    3. serhiy-storchaka commented on Apr 29, 2015

      @serhiy-storchaka
      Member

      Can't reproduce on 32-bit Linux.

      $ time python2.7 -c "print sum(xrange(3, 10**9, 3)) + sum(xrange(5, 10**9, 5)) - sum(xrange(15, 10**9, 15))"
      233333333166666668

      real 1m11.614s
      user 1m11.376s
      sys 0m0.056s
      $ time python3.4 -c "print(sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15)))"
      233333333166666668

      real 1m11.658s
      user 1m10.980s
      sys 0m0.572s

      $ python2.7 -m timeit -n1 -r1 "sum(xrange(3, 10**9, 3)) + sum(xrange(5, 10**9, 5)) - sum(xrange(15, 10**9, 15))"
      1 loops, best of 1: 72 sec per loop
      $ python3.4 -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loops, best of 1: 72.5 sec per loop
      
      $ python2.7 -m timeit -s "a = list(range(10**6))" -- "sum(a)"
      10 loops, best of 3: 114 msec per loop
      $ python3.4 -m timeit -s "a = list(range(10**6))" -- "sum(a)"
      10 loops, best of 3: 83.5 msec per loop

      What is sys.int_info on your build?

    4. pitrou commented on Apr 29, 2015

      @pitrou
      Member

      I reproduce under 64-bit Linux. So this may be because the Python long digit (30 bits) is smaller than the C long (64 bits).

      Lukasz: is there a specific use case? Note you can use Numpy for such calculations.

    5. ambv commented on Apr 29, 2015

      @ambv
      ContributorAuthor

      Serhiy, this is 64-bit specific. Antoine, as far as I can tell, the main use case is: "Don't make it look like migrating to Python 3 is a terrible performance downgrade."

      As we discussed on the language summit this year [1], we have to be at least not worse to look appealing. This might be a flawed benchmark but people will make them anyway. In this particular case, there's internal usage at Twitter that unearthed it. The example is just a simplified repro.

      Some perf degradations were expected, like switching text to Unicode. In this case, the end result computed by both 2.7 and 3.4 is the same so we should be able to address this.

      [1] http://lwn.net/Articles/640224/

    6. pitrou commented on Apr 29, 2015

      @pitrou
      Member

      If that's due to the different representation of Python 2's int type and Python 3's int type then I don't see an easy solution to this.

    7. mdickinson commented on Apr 30, 2015

      @mdickinson
      Member

      Łukasz: there are three ingredients here - sum, (x)range and the integer addition that sum will be performing at each iteration. Is there any chance you can separate the effects on your machine?

      On my machine (OS X, 64-bit), I'm seeing *some* speed difference in the integer arithmetic, but not enough to explain the whole of the timing mismatch.

      One thing we've lost in Python 3 is the fast path for small-int addition *inside* the ceval loop. It may be possible to restore something there.

    8. mdickinson commented on Apr 30, 2015

      @mdickinson
      Member

      Throwing out sum, I'm seeing significant slowdown simply from xrange versus range:

      taniyama:Desktop mdickinson$ python2 -m timeit -s 'x = xrange(3, 10**9, 3)' 'for e in x: pass'
      10 loops, best of 3: 5.01 sec per loop
      taniyama:Desktop mdickinson$ python3 -m timeit -s 'x = range(3, 10**9, 3)' 'for e in x: pass'
      10 loops, best of 3: 8.62 sec per loop

    9. scoder commented on Apr 30, 2015

      @scoder
      Contributor

      there are three ingredients here - sum, (x)range and the integer addition that sum will be performing at each iteration.

      ... not to forget the interpreter startup time on his machine. :)

      I did a tiny bit of profiling and about 90% of the time seems to be spent creating and deallocating throw-away PyLong objects. My guess is that it simply lacks a free-list in _PyLong_New().

    10. pitrou commented on Apr 30, 2015

      @pitrou
      Member

      It seems we (like the benchmarks posted) are spending a whole lot of time on something that's probably not relevant to any real-world situation.

      If someone has actual code that suffers from this, it would be good to know about it.
      (note by the way that summing on a range() can be done O(1): it's just a variation on a arithmetic series)

    11. scoder commented on May 1, 2015

      @scoder
      Contributor

      I don't think it's irrelevant. Throw-away integers are really not uncommon. For-loops use them quite often, non-trivial arithmetic expressions can create a lot of intermediate temporaries. Speeding up the create-delete cycle of PyLong sounds like a very obvious thing to do.

      Imagine some code that iterates over a list of integers, applies some calculation to them, and then stores them in a new list, maybe even using a list comprehension or so. If you could speed up the intermediate calculation by avoiding overhead in creating temporary PyLong objects, such code could benefit a lot.

      I suspect that adding a free-list for single-digit PyLong objects (the most common case) would provide some visible benefit.

    12. pitrou commented on May 1, 2015

      @pitrou
      Member

      Le 01/05/2015 08:09, Stefan Behnel a écrit :

      I don't think it's irrelevant. Throw-away integers are really not
      uncommon. For-loops use them quite often, non-trivial arithmetic
      expressions can create a lot of intermediate temporaries. Speeding up
      the create-delete cycle of PyLong sounds like a very obvious thing to do.

      That may be a good thing indeed. I'm just saying that the benchmarks
      people are worried about here are completely pointless.

    13. scoder commented on May 1, 2015

      @scoder
      Contributor

      I tried implementing a freelist. Patch attached, mostly adapted from the one in dictobject.c, but certainly needs a bit of cleanup.

      The results are not bad, about 10-20% faster:

      Original:

      $ ./python -m timeit 'sum(range(1, 100000))'
      1000 loops, best of 3: 1.86 msec per loop
      
      $ ./python -m timeit -s 'l = list(range(1000, 10000))' '[(i*2+5) // 7 for i in l]'
      1000 loops, best of 3: 1.05 msec per loop

      With freelist:

      $ ./python -m timeit 'sum(range(1, 100000))'
      1000 loops, best of 3: 1.52 msec per loop
      
      $ ./python -m timeit -s 'l = list(range(1000, 10000))' '[(i*2+5) // 7 for i in l]'
      1000 loops, best of 3: 931 usec per loop
    14. stevendaprano commented on May 1, 2015

      @stevendaprano
      Member

      Antoine asked:

      If someone has actual code that suffers from this, it would be good to know about it.

      You might have missed Łukasz' earlier comment: "In this particular case, there's internal usage at Twitter that unearthed it. The example is just a simplified repro."

    15. 31 remaining items

    16. l1t1 commented on Oct 26, 2022

      @l1t1

      3.11

      D:\python311>python
      Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32
      >>> import time
      >>> t=time.time();sum(range(1,pow(10,8)+1));print(time.time()-t)
      5000000050000000
      4.157237768173218

      vs
      3.10

      D:\python310>python
      Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
      >>> import time
      >>> t=time.time();sum(range(1,pow(10,8)+1));print(time.time()-t)
      5000000050000000
      4.183239221572876
    17. l1t1 commented on Oct 26, 2022

      @l1t1

      pypy 7.3.9

      D:\pypy3.8-v7.3.9-win64>pypy
      Python 3.8.12 (0089b4a7ab2306925a251b35912885d52ead1aba, Mar 16 2022, 13:51:04)
      [PyPy 7.3.9 with MSC v.1929 64 bit (AMD64)] on win32
      Type "help", "copyright", "credits" or "license" for more information.
      >>>> import time
      >>>> t=time.time();sum(range(1,pow(10,8)+1));print(time.time()-t)
      5000000050000000
      0.1780109405517578
    18. StanFromIreland commented on Apr 24, 2025

      @StanFromIreland
      Member

      This can be closed. On 3.14:

      $ time python3.14 -c "print(sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15)))"
      233333333166666668
      
      real	0m3.706s
      user	0m3.687s
      sys	0m0.005s
      

      Much faster than @ambv benchmarks.

      real 0m6.165s
      user 0m6.100s
      sys 0m0.032s

      real 0m16.413s
      user 0m16.086s
      sys 0m0.089s

      Discussion of further optimization belongs elsewhere (faster-cpython).

    19. added
      type-featureA feature request or enhancement
      and removed
      3.11only security fixes
      on Apr 24, 2025
    20. skirpichev commented on Apr 25, 2025

      @skirpichev
      Member

      This can be closed. On 3.14:

      I'm not sure. Absolute numbers in benchmarks aren't relevant here. OP probably run py2 tests on a different system than you.

      At least, you should run test for py2 and py3 on same system. Here are my tests.

      Py2:

      $ python2.7 -m timeit -n1 -r1 "sum(xrange(3, 10**9, 3)) + sum(xrange(5, 10**9, 5)) - sum(xrange(15, 10**9, 15))"
      1 loops, best of 1: 9.85 sec per loop
      

      Py3.13:

      $ python3.13  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 39.9 sec per loop
      

      Py3.14a7:

      $ python3.14  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 22.3 sec per loop
      

      For me it looks like issue is valid and it's a regression from py2, not a feature request. @picnixz ? @ambv ?

    21. picnixz commented on Apr 25, 2025

      @picnixz
      Member

      Honestly, considering 2.7 has been EOL for a long time, I don't think we need to keep this specific issue open. I don't think we can do anything now and I would indeed prefer if faster-cpython comes up with a solution.

      Now, for performance loss, we sometimes treat them as bug, sometimes not. I categorized it as a FR because we won't backport the change I think.

    22. skirpichev commented on Apr 25, 2025

      @skirpichev
      Member

      Honestly, considering 2.7 has been EOL for a long time, I don't think we need to keep this specific issue open.

      Why not? If v2.7 is better somewhere - it's still a regression.

      I don't think we can do anything now and I would indeed prefer if faster-cpython comes up with a solution.

      I'm not sure that closing issue is a right thing even if we can't do anything now. Work is ongoing, e.g.: https://discuss.python.org/t/87950. I think it's a good thing to keep eye on this issue for people involved.

      BTW, Py3.14 has impressive speedup, but I doubt it's related to integer arithmetic.

    23. gvanrossum commented on Apr 25, 2025

      @gvanrossum
      Member

      I'm with @skirpichev. Can someone at least summarize an explanation of the difference between 2.7 and 3.x?

    24. picnixz commented on Apr 25, 2025

      @picnixz
      Member

      I think it was analyzed by #68264 (comment) but AFAICT, it's the call to PyLong_AsLongAndOverflow that introduced the regression.

      Work is ongoing, e.g.: discuss.python.org/t/87950

      I wasn't aware of this one so thanks. By the way, feel free to re-open issues if I close them wrongly!

    25. skirpichev commented on Apr 25, 2025

      @skirpichev
      Member

      Can someone at least summarize an explanation of the difference between 2.7 and 3.x?

      My 2c:

      1. one issue was mentioned by @mdickinson: sum() several times slower on Python 3 64-bit #68264 (comment) and gone ~3.11: sum() several times slower on Python 3 64-bit #68264 (comment)
      2. second one was missing specialization for single-digit integers (in a loop): sum() several times slower on Python 3 64-bit #68264 (comment). This was added by GH-101291: Rearrange the size bits in PyLongObject #102464.
      3. something happened on 3.14 (as my benchmarks suggests). I don't know yet.

      Here more tests from my zoo:

      $ python2.7 -m timeit -n1 -r1 "sum(xrange(3, 10**9, 3)) + sum(xrange(5, 10**9, 5)) - sum(xrange(15, 10**9, 15))"
      1 loops, best of 1: 9.85 sec per loop
      $ python3.9  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 27.7 sec per loop
      $ python3.10  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 28.4 sec per loop
      $ python3.11  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 26.1 sec per loop
      $ python3.12  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 31.3 sec per loop
      $ python3.13  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 33.1 sec per loop
      $ python3.14  -m timeit -n1 -r1 "sum(range(3, 10**9, 3)) + sum(range(5, 10**9, 5)) - sum(range(15, 10**9, 15))"
      1 loop, best of 1: 21.8 sec per loop
      
    26. picnixz commented on Jun 29, 2025

      @picnixz
      Member

      AFACIT, what changed between 3.13 and main (not 3.14, I haven't built 3.14 locally) is the time to iterate over:

      $ python3.13 -m timeit -n1 -r1 -s 'r1 = range(3,10**9,3)' 'for _ in r1: pass'
      1 loop, best of 1: 2.2 sec per loop
      $ python3.15 -m timeit -n1 -r1 -s 'r1 = range(3,10**9,3)' 'for _ in r1: pass'
      1 loop, best of 1: 1.57 sec per loop

      We're roughly 30% faster between 3.13 and 3.15 just for iterations, which is roughly the gain between 3.13 and 3.14 for the benchmark above. I don't have a 2.7 that I can test but one possibility is that it's not just sum() that is impacted.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      interpreter-core(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagetype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions