◐ Shell
reader mode source ↗
Skip to content

bpo-29882: Add an efficient popcount method for integers#771

Merged
mdickinson merged 13 commits into
python:masterfrom
niklasf:bpo-29882-popcount
May 29, 2020
Merged

bpo-29882: Add an efficient popcount method for integers#771
mdickinson merged 13 commits into
python:masterfrom
niklasf:bpo-29882-popcount

Conversation

@niklasf

@niklasf niklasf commented Mar 22, 2017

Copy link
Copy Markdown
Contributor

@mention-bot

Copy link
Copy Markdown

@niklasf, thanks for your PR! By analyzing the history of the files in this pull request, we identified @tim-one, @birkenfeld and @ncoghlan to be potential reviewers.

@ilevkivskyi

Copy link
Copy Markdown
Member

Some compilers (gcc) provide hardware optimized __builtin_popcount function(s). Could these speed-up the implementation?

@niklasf

niklasf commented Mar 22, 2017

Copy link
Copy Markdown
Contributor Author

@ilevkivskyi: Yes, using hardware popcnt makes this ever so slightly faster.

$ ./python -m timeit "bin(123456789).count('1')"  # equivalent
1000000 loops, best of 5: 286 nsec per loop
$ ./python -m timeit "(123456789).bit_count()"  # gnuc builtin
5000000 loops, best of 5: 44.8 nsec per loop
$ ./python -m timeit "(123456789).bit_count()"  # swar32
5000000 loops, best of 5: 46.3 nsec per loop

Previously I have submitted #594, but so far there is no consensus on wether or how to use these intrinsics. So I think it's best to consider this seperately.

Edit: I should also mention that there is a faster software implementation, using complete lookup tables for 16bit integers. This one is commonly used in chess engines if hardware popcnt is not available, but seems like a rather large overhead for a general purpose interpreter.

@methane methane left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

Code looks OK.
But it seems no consensus about adding this method or not.

@niklasf niklasf force-pushed the bpo-29882-popcount branch from 1c6e9fa to b6e46da Compare June 2, 2019 09:21
@niklasf niklasf force-pushed the bpo-29882-popcount branch from b6e46da to 74e3ea6 Compare June 2, 2019 09:33
@niklasf

niklasf commented Jun 2, 2019

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing. All done, in case we do decide to add this.

@mdickinson

Copy link
Copy Markdown
Member

@niklasf I'm interested in getting this updated and merged, for Python 3.10. May I merge master into this branch and make the appropriate fixes (changing the "whats new" entry from 3.8 to 3.10)? Or would you prefer to do that yourself?

@niklasf

niklasf commented May 25, 2020

Copy link
Copy Markdown
Contributor Author

@mdickinson Of course, feel free to modify as needed.

@mdickinson mdickinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

LGTM. (Admittedly that's not entirely an independent judgement at this point, but I haven't changed any of the core code or tests.)

32 hidden items Load more…

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

LGTM.

LGTM: the implementation is correct. I'm not strongly excited by having this method since I never needed it, but it seems like some other people need it. So well, it looks reasonable to add it. Also, in general, I completely trust @mdickinson :-)

@mdickinson mdickinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

The current implementation of popcount_digit isn't quite as portable as it should be.

The fix should be trivial. I'll look into it.

@bedevere-bot

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@mdickinson mdickinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide comment

Re-approving after fixing my own portability objection.

@mdickinson

Copy link
Copy Markdown
Member

Merging. @niklasf: Thank you!

@mdickinson mdickinson merged commit 8bd216d into python:master May 29, 2020
@bedevere-bot

Copy link
Copy Markdown

@mdickinson: Please replace # with GH- in the commit message next time. Thanks!

@vstinner

Copy link
Copy Markdown
Member

OMG, I was merging the PR (I was editing the commit message) while @mdickinson merged it :-D Thanks @mdickinson ;-)

@niklasf

niklasf commented May 29, 2020

Copy link
Copy Markdown
Contributor Author

Thanks for getting this over the finish line!

@vstinner

Copy link
Copy Markdown
Member

PR created in 2017! It has an identifier lower than 1000 rather than the latest PR got the identifier 20517! Thanks @niklasf!

@vstinner

Copy link
Copy Markdown
Member

I wrote PR #20518 which adds a _Py_popcount32() static inline function based on popcount_digit().

CuriousLearner added a commit to CuriousLearner/cpython that referenced this pull request May 30, 2020
* 'master' of github.com:python/cpython: (497 commits)
  bpo-40061: Fix a possible refleak in _asynciomodule.c (pythonGH-19748)
  bpo-40798: Generate a different message for already removed elements (pythonGH-20483)
  closes bpo-29017: Update the bindings for Qt information with PySide2 (pythonGH-20149)
  bpo-39885: Make IDLE context menu cut and copy work again (pythonGH-18951)
  bpo-29882: Add an efficient popcount method for integers (python#771)
  Further de-linting of zoneinfo module (python#20499)
  bpo-40780: Fix failure of _Py_dg_dtoa to remove trailing zeros (pythonGH-20435)
  Indicate that abs() method accept argument that implement __abs__(), just like call() method in the docs (pythonGH-20509)
  bpo-39040: Fix parsing of email mime headers with whitespace between encoded-words. (pythongh-17620)
  bpo-40784: Fix sqlite3 deterministic test (pythonGH-20448)
  bpo-30064: Properly skip unstable loop.sock_connect() racing test (pythonGH-20494)
  Note the output ordering of combinatoric functions (pythonGH-19732)
  bpo-40474: Updated coverage.yml to better report coverage stats (python#19851)
  bpo-40806: Clarify that itertools.product immediately consumes its inpt (pythonGH-20492)
  bpo-1294959: Try to clarify the meaning of platlibdir (pythonGH-20332)
  bpo-37878: PyThreadState_DeleteCurrent() was not removed (pythonGH-20489)
  bpo-40777: Initialize PyDateTime_IsoCalendarDateType.tp_base at run-time (pythonGH-20493)
  bpo-40755: Add missing multiset operations to Counter() (pythonGH-20339)
  bpo-25920: Remove socket.getaddrinfo() lock on macOS (pythonGH-20177)
  bpo-40275: Fix test.support.threading_helper (pythonGH-20488)
  ...
@HannesGitH

HannesGitH commented Jan 26, 2023

Copy link
Copy Markdown

bit_count() vs bin() count('1')

code
import random
import timeit

lens = range(1, 10000)
maxs = [2**i for i in lens]

# array with random numbers in [0, maxs[i]-1]
randos = [max + random.randrange(max) for max in maxs]

# time bin().count('1') vs bit_count() for each random number
bin_times = [timeit.timeit(lambda: bin(rando).count('1'), number=100) for rando in randos]
bit_count_times = [timeit.timeit(lambda: rando.bit_count(), number=100) for rando in randos]

# create plot
from matplotlib import pyplot as plt

plt.loglog(lens, bin_times, label='bin().count(\'1\')')
plt.loglog(lens, bit_count_times, label='bit_count()')
plt.legend()
plt.show()

@mdickinson

Copy link
Copy Markdown
Member

@HannesGitH Why the comment? Is there some action that needs to be taken?

@HannesGitH

HannesGitH commented Jan 26, 2023

Copy link
Copy Markdown

@HannesGitH Why the comment? Is there some action that needs to be taken?

nope, all fine, sorry

just wanted to add the speedup graph for anyone who stumbles upon this and is curious

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.