On 5/23/2021 7:32 AM, pozz wrote:
> Il 22/05/2021 01:05, Don Y ha scritto:
>> On 5/20/2021 12:22 AM, pozz wrote:
>>> Many times I need to pass to functions or serialize an array of bits. If
>>> they are just a few (8-16 bits), I decide to use a standard array:
>>>
>>> uint8_t my_short_array_of_bits[16];
>>> void abits_set_bit(uint8_t *array, size_t size, uint8_t nbit);
>>> uint8_t abits_get_bit(uint8_t *arrray, size_t size, uint8_t nbit);
>>
>> Why would you want to allocate a BYTE (uint8_t) for EACH BIT?
>> 8 bits would fit into:
>> uint8_t my_short_array_of_bits[1];
>> while 16 would fit in:
>> uint8_t my_short_array_of_bits[2];
>> generally:
>> uint8_t my_short_array_of_bits[(NUMBITS+7)/8];
>
> Because the bits are just a few, so I don't lost many space and loops for
> operations on bit are very fast.
>
> Of course, this decision is made when the bit are completely indipendent data
> and I don't interested in classical "bit operations".
>
> For example, when you have 10 boolean options for your software. Why should you
> pack those options in a bitmask if they are unrelated?
Why should you pack them into a bit_array_t -- if they are unrelated?
Bool flagX;
Bool flag3;
Bool running;
Bool OK;
Putting them into an array suggests that they are related to each other
more than simply by the fact that they are of the same *type*.
You don't put all of your "integers" into a int_array_t, do you?
int count;
int books;
int iteration;
int bank_balance;
>>> Do you have some suggestions for this? Maybe in the same project I need to
>>> use an array of 100 bits and an array of 200 bits. I would prefer a typedef
>>> for both types, for example:
>>
>> Ideally, you would conditionally pick your underlying implementation
>> (i.e., whether to use uint8 vs unint32 vs uint64) based on whatever the
>> native hardware best supports.
>
> Yes, but my question was related to a "generic" approach that can be used if
> the bits are 32, 64 or 256. In the latter case, arrays are necessary.
Whether or not an array is necessary depends on the size of the "base type"
on which the array will be built. If your processor handles unit8_t's
efficiently, you'd pack 8 bits into a uint8_t and then number_of_bits/8
of these into a bit_array_t. If your processor handles uint32_t's
efficiently, you'd pack 32 of them into a uint32_t and then pack
number_of_bits/32 of these into a bit_array_t.
Note that "handles efficiently" may not be the same as the
native data size for your processor. E.g., a 32b CPU with
an 8 bit data path would likely handle uint8_t's more efficiently
than uint32_t's -- because it could reduce the array member access
to a single "byte access" (memory cycle). OTOH, if the cost of
accessing a uint32_t is the same (or lower!), then you'd opt
for that as a base type.
Once you have selected a base type, the code is the same
(except for adjustments in manifest constants related to
the number of bits packed in each byte)
>> Using a wider base type can be less efficient on targets that have narrower
>> natural data sizes. E.g., support for a uint32 on an 8 bit processor may
>> ADD code where a "more natural" 8 bit type would better fit with the
>> instruction set/architecture.
>
> Yes, something similar. I only asked if there was a public domain code with
> this kind of approach.
<shrug> It would likely take longer to FIND than it would take to WRITE.
Pseudocode:
// note that this relies on BASE_T being the "expected" width
// AND sizeof yielding that value!
// if it is greater, then bits of memory get wasted
#define BASE_T ... // unit8_t, uint16_t, uint32_t, etc.
#define BASE_BITS (8 * sizeof(BASE_T))
result_t
test_bit(
uint bit, // [0,size)
BASE_T *array, // (size+7)/BASE_BITS elements
uint size // (0,UINT_MAX]
) {
ASSERT( size > 0 ); // makes no sense to have zero or fewer bits!
// ensure the referenced bit resides within the structure
if ( (bit < 0) || (bit >= size) ) {
return ERROR;
}
if ( array[bit/8] & (1 << (bit % BASE_BITS)) ) {
return (1);
} else {
return (0);
}
}
[No guarantees as I just rolled out of bed! :> ]
This implementation counts bits "the normal way"
(from LSb upward).
Pick different BASE_T's and see what the code looks like.
Or, profile it's execution on YOUR hardware to see if there
are advantages to one BASE_T over others.
Note that the larger the BASE_T, the greater the chance for
"wasted bits" (e.g., if you only need 5 bits, then using
a ulonglong will "cost" you considerably more than you *need*!)
By comparison, an implementation that uses uint8_t's for each "bit"
would likely look like:
result_t
test_bit(
uint bit, // [0,size)
BASE_T *array, // size elements
uint size // (0,UINT_MAX]
) {
ASSERT( size > 0 ); // makes no sense to have zero or fewer bits!
// ensure the referenced bit resides within the structure
if ( (bit < 0) || (bit >= size) ) {
return ERROR;
}
if ( array[bit] ) {
return (1);
} else {
return (0);
}
}
i.e., once you start considering the overhead of error checking,
there's little performance gain.
>> OTOH, if you just treat them all as "bit_array_t" -- and assume responsibility
>> for manually ensuring that you've specified the correct size for each
>> argument processed, then you can share a parameterized (instead of templatized)
>> implementation.
>
> Yes, this is another solution.
As implemented, above. But, now the compiler can't tell you if you've used
the wrong type in a particular line of code.
Time for breakfast (which most people call "lunch"!). Sunday... The Pork Dish!