Skip to content

Use the popcount64c bit-counting sequence in Dar_WordCountOnes - #572

Open
TrevorHansen wants to merge 1 commit into
berkeley-abc:masterfrom
TrevorHansen:popcount64c-in-darcut
Open

TrevorHansen wants to merge 1 commit into
berkeley-abc:masterfrom
TrevorHansen:popcount64c-in-darcut

Conversation

@TrevorHansen

Copy link
Copy Markdown

This reopens #533, which was closed by accident when the fork it came from was deleted. GitHub cannot reopen a pull request whose source repository is gone, so this is the same change from a new fork, rebased onto the current master. The description below is unchanged from #533.

The popcount (hamming weight) instruction has been common on x86 processors since about 2008. It returns the number of 1-bits in a register with 3-cycle latency and 1-cycle throughput.

ABC uses hand rolled SIMD within a register (SWAR) to achieve the same.

static inline int Dar_WordCountOnes( unsigned uWord )
{
    uWord = (uWord & 0x55555555) + ((uWord>>1) & 0x55555555);
    uWord = (uWord & 0x33333333) + ((uWord>>2) & 0x33333333);
    uWord = (uWord & 0x0F0F0F0F) + ((uWord>>4) & 0x0F0F0F0F);
    uWord = (uWord & 0x00FF00FF) + ((uWord>>8) & 0x00FF00FF);
    return  (uWord & 0x0000FFFF) + (uWord>>16);
}

When passed the -mpopcnt flag, clang and GCC will pattern match and replace some matching SWAR with a popcount instruction.

Unfortunately, the SWAR above is not recognised by any GCC I tested (7 to 16), or any clang (9 to 22), so it emits 23 instructions instead of 1.

Popcounts are in other places in ABC (e.g. #436), so other users would benefit if the other uses were sped up. Only this function is in our hot-path - some CNF generation problems have 7% of their runtime in Dar_WordCountOnes, so I've focused on it.

The above function is the naive implementation (popcount64a) from:
https://en.wikipedia.org/wiki/Hamming_weight

This PR changes to the best implementation (popcount64c) from the same page:

uWord = uWord - ((uWord >> 1) & 0x55555555);
uWord = (uWord & 0x33333333) + ((uWord >> 2) & 0x33333333);
uWord = (uWord + (uWord >> 4)) & 0x0F0F0F0F;
return (int)((uWord * 0x01010101) >> 24);

Which is converted by GCC 10+ and clang 10+ to a single instruction, and is 8 fewer instructions if not compiled with -mpopcnt.

The sequence currently used is popcount64a, which no GCC from 7 to 16 and no
clang from 9 to 22 recognises as a population count, so it always emits 23
instructions. popcount64c is recognised by GCC 10 and later and clang 10 and
later, which emit a single popcnt where the target has the instruction, and is
8 instructions shorter where it does not.
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.

1 participant