I'm working on my master's thesis (computer science) on code which is written for post-quantum-secure signatures. The whole thing can be found here but is not important here. For my thesis I tried to explain a 'simple' function, which is not so simple at all.
The function tests, if a variable is non-zero in the galois-field GF(16). (GF(16) here can be understood as 4-bit unsigned integers). This function looks as follows:
static inline uint8_t gf16_is_nonzero(uint8_t a) {
unsigned a4 = a & 0xf; // mask lowest 4 bits of a
unsigned r = 0u - a4; // set 4 high bits if a is nonzero
r >>= 4; // right-shift high bits into low bits
return r & 1; // return lowest bit
}
I understood how it works but I don't understand why this function needs to be this complex. Could there be a good reason for that? Good reasons could be performance or secureness (e.g. safety against timing attacks) benefits. Because if there are no such benefits, wouldn't it be smarter to write that function in an easy manner like:
static inline uint8_t gf16_is_nonzero(uint8_t a) {
return (a & 15) != 0;
}
This code is not written by me, it is written by crypto-researches, who are trying to get their PQ-algorithm standardized by NIST.
An easier approach for the second code snippet was suggested by TonyDelroy in the comments.
The reason for this code is because it is branchless.
Testing for a condition tends to be an expensive operation, whereas addition, subtraction, and bitwise operators are not.
This however is premature optimization. With -O3
, the first function compiles to this:
andl $15, %edi
negl %edi
shrl $31, %edi
movl %edi, %eax
ret
While the second function compiles to this:
andl $15, %edi
setne %al
ret
The moral of the story: write code that clearly states your intentions and let the compiler figure out the rest.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With