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
+7 -8
View File
@@ -2223,7 +2223,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
// temporary state until we validate a cksum
uint32_t cksum_ = cksum;
bool parity_ = lfs_popc(cksum) & 1;
bool parity_ = lfs_parity(cksum);
lfs_size_t off = sizeof(uint32_t);
lfs_size_t trunk_ = 0;
lfs_size_t trunk__ = 0;
@@ -2255,7 +2255,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
break;
}
tag &= 0x7fff;
parity_ ^= lfs_popc(cksum_ ^ cksum__) & 1;
parity_ ^= lfs_parity(cksum_ ^ cksum__);
cksum_ = cksum__;
// tag goes out of range?
@@ -2277,7 +2277,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
}
return err;
}
parity_ ^= lfs_popc(cksum_ ^ cksum__) & 1;
parity_ ^= lfs_parity(cksum_ ^ cksum__);
cksum_ = cksum__;
// found an ecksum? save for later
@@ -2631,7 +2631,7 @@ static int lfsr_rbyd_appendrev(lfs_t *lfs, lfsr_rbyd_t *rbyd, uint32_t rev) {
// update eoff, xor cksum parity
rbyd->eoff
+= ((lfs_popc(rbyd->cksum ^ cksum_) & 1)
+= ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_)
<< (8*sizeof(lfs_size_t)-1))
+ sizeof(uint32_t);
rbyd->cksum = cksum_;
@@ -2655,7 +2655,7 @@ static int lfsr_rbyd_appendtag(lfs_t *lfs, lfsr_rbyd_t *rbyd,
// update eoff, xor cksum parity
rbyd->eoff
+= ((lfs_popc(rbyd->cksum ^ cksum_) & 1)
+= ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_)
<< (8*sizeof(lfs_size_t)-1))
+ d;
rbyd->cksum = cksum_;
@@ -2674,7 +2674,7 @@ static int lfsr_rbyd_appenddata(lfs_t *lfs, lfsr_rbyd_t *rbyd,
// update eoff, xor cksum parity
rbyd->eoff
+= ((lfs_popc(rbyd->cksum ^ cksum_) & 1)
+= ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_)
<< (8*sizeof(lfs_size_t)-1))
+ lfsr_data_size(data);
rbyd->cksum = cksum_;
@@ -3506,8 +3506,7 @@ static int lfsr_rbyd_appendcksum(lfs_t *lfs, lfsr_rbyd_t *rbyd) {
// xor in the tag parity
cksum_buf[0] ^= (uint8_t)lfsr_rbyd_parity(rbyd) << 7;
// find the new parity
bool parity_ = lfsr_rbyd_parity(rbyd)
^ (lfs_popc(rbyd->cksum ^ cksum_) & 1);
bool parity_ = lfsr_rbyd_parity(rbyd) ^ lfs_parity(rbyd->cksum ^ cksum_);
// and intentionally perturb the commit so the next tag appears invalid
if ((e >> 7) == parity_) {
cksum_buf[1] ^= 0x01;
+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) {