write the most efficient code to get the index of that '1' bit.
The most efficient code would be to somehow map the value of ch to its bit index, i.e.:
0x01 -> 0
0x02 -> 1
0x04 -> 2
0x08 -> 3
...
Naive Mapping Table
The most simple and naive solution would require a lookup in a mapping table with all possible values of ch. For 8-bit numbers (char) we need a table with 28= 256 elements:
char naive_table[256];
naive_table[0x01] = 0;
naive_table[0x02] = 1;
naive_table[0x04] = 2;
naive_table[0x08] = 3;
naive_table[0x10] = 4;
naive_table[0x20] = 5;
naive_table[0x40] = 6;
naive_table[0x80] = 7;
The lookup in this table is also simple:
index = naive_table[ch];
Hash Function + Mapping Table
The previous solution is simple and fast, but most of the element of naive_table are wasted. Taking into account that ch is a power of two, for any n-bit number there are just n possible indexes.
So, instead of using a mapping table with 28 elements, we could use a table with just 8 elements and a hash function which would map the value of ch to a unique index of the mapping table.
The perfect candidate for such a hash function would be a function using the de Bruijn sequence. There is a paper "Using de Bruijn Sequences to Index a 1 in a Computer Word" which states:
A length-n de Bruijn sequence, where n is an exact power of 2, is a cyclic sequence of n 0's and 1's such that every 0-1 sequence of length lg n occurs exactly once as a contiguous substring.
For example, a length-8 de Bruijn sequence is 00011101. Each 3-bit number occurs exactly once as a contiguous substring: starting from the leftmost 3 bits and moving a 3-bit window right one bit at a time, we have 000, 001, 011, 111, 110, 101, 010 (wrapping around), 100 (also wrapping around).
The hash function is computed by: h(x)=(x * deBruijn)>>(n - lg n)
So, let us try this hash function to get a unique index in our compact lookup table:
h(ch) = ((ch * 00011101b) >> (8 - 3)) & 0x7
h(ch) = ((ch * 29) >> 5) & 0x7
Let us calculate the hashes for all values of ch and make sure the hash function works as expected, i.e. all the hashes are unique:
ch h(ch)
0x01 ((1 * 29) >> 5) & 0x7 = 0
0x02 ((2 * 29) >> 5) & 0x7 = 1
0x04 ((4 * 29) >> 5) & 0x7 = 3
0x08 ((8 * 29) >> 5) & 0x7 = 7
0x10 ((16 * 29) >> 5) & 0x7 = 6
0x20 ((32 * 29) >> 5) & 0x7 = 5
0x40 ((64 * 29) >> 5) & 0x7 = 2
0x80 ((64 * 29) >> 5) & 0x7 = 4
So the hash function works fine and produces unique hashes for each power of two value of ch.
Now let us create a compact mapping table using the hash values from the table above:
char compact_table[8];
compact_table[0] = 0;
compact_table[1] = 1;
compact_table[3] = 2;
compact_table[7] = 3;
compact_table[6] = 4;
compact_table[5] = 5;
compact_table[2] = 6;
compact_table[4] = 7;
Now for the lookup we use a hash value as an index:
h = ((ch * 29) >> 5) & 0x7;
index = compact_table[h];
Hash Function + Bit String
The previous version is nearly perfect: there are no more wasted elements in the mapping table. But since all the indexes are within 0-7 (i.e. just 3-bit values), there is still a room for improvement. Let us use a bit string instead of the mapping table so the most significant bits of each element are not wasted.
First, let us create such a bit string using all the values of ch and the hash values from the previous version:
ch h(sh) index
0x01 0 0 (000b)
0x02 1 1 (001b)
0x04 3 2 (010b)
0x08 7 3 (011b)
0x10 6 4 (100b)
0x20 5 5 (101b)
0x40 2 6 (110b)
0x80 4 7 (111b)
Now let us order this table by the hash value:
ch h(sh) index
0x01 0 0 (000b)
0x02 1 1 (001b)
0x40 2 6 (110b)
0x04 3 2 (010b)
0x80 4 7 (111b)
0x20 5 5 (101b)
0x10 6 4 (100b)
0x08 7 3 (011b)
So the bit string will be a reversed concatenation of those 3-bit indexes:
011 100 101 111 010 110 001 000 = 0x72f588
Now let us lookup in this bit string just like we did previously. Note that our indexes are 3-bit, so we need to multiply our hash value by 3:
h = ((ch * 29) >> 5) & 0x7; // just like before
bit_string = 0x72f588;
index = (bit_string >> (h * 3)) & 0x7;
Or in short:
index = (0x72f588 >> ((((ch * 29) >> 5) & 0x7) * 3)) & 0x7;
There are no divisions/modulos/conditions in the code, so it should perform fast on any CPU.
The prove of concept code:
unsigned char ch;
for (ch = 1; ch; ch <<= 1) {
int index = (0x72f588 >> ((((ch * 29) >> 5) & 7) * 3)) & 7;
printf("ch = 0x%02x index = %d\n", ch, index);
}
return 0;