Added lfs_parity intrinsic

We're using parity a lot more than popc now (actually, now that we don't
use CTZ skip-lists, do we use popc at all?), so it makes sense to the
compiler's __builtin_parity intrinsic when possible.

On some processors parity can be much cheaper than popc. Notably, the
8080 family just includes a parity flag in the set of carry flags that
are implicitly updated on most ALU operations. Though I think this
approach didn't scale, you don't really see parity flags on most >8-bit
architectures...

Unfortunately, ARM thumb, our test arch, does not have a popc or parity
instruction. I guess because thanks to implicit shifts in most
instructions, the tree-reduction solution is surprisingly cheap:

  ea80 4010   eor.w   r0, r0, r0, lsr #16
  ea80 2010   eor.w   r0, r0, r0, lsr #8
  ea80 1010   eor.w   r0, r0, r0, lsr #4
  ea80 00c0   eor.w   r0, r0, r0, lsr #2
  ea80 0050   eor.w   r0, r0, r0, lsr #1
  f000 0001   and.w   r0, r0, #1

Both popc and parity benefit from this (GCC 11):

                 code
  __popcountsi2:   40
  __paritysi2:     32 (-20.0%)

So, thumb is not an arch where we see much benefit:

           code          stack
  before: 33908           2824
  after:  33924 (+0.0%)   2824 (+0.0%)

Not really sure where the +16 bytes come from, we removed several masks,
so I guess it's just bool vs in compiler noise?

Still, this may be useful for other archs with parity instructions/
hardware.
This commit is contained in:
Christopher Haster
2024-05-01 11:04:17 -05:00
parent 1c9cc63994
commit dbe503776d
2 changed files with 16 additions and 8 deletions
+9
View File
@@ -274,6 +274,15 @@ static inline uint32_t lfs_popc(uint32_t a) {
#endif
}
// Returns true if there is an odd number of binary ones in a
static inline bool lfs_parity(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_parity(a);
#else
return lfs_popc(a) & 1;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs_scmp(uint32_t a, uint32_t b) {