views:

595

answers:

9

I have an Arduino application (well actually a library) that has a number of status flags in it - and originally I simply declared them as ints (well uint8_t so 8 bit unsigned chars in this case). But I could have combined them all into one integer and used bitmask operations to set and test the status.

An example of the former:

if (_shift == HIGH)
{
    _shift = LOW;
}
else
{
    _shift = HIGH;
}

an example of the latter

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
   bitWrite(_flags, SHIFT_BIT, LOW);
}
else
{
   bitWrite(_flags, SHIFT_BIT, HIGH);
}

The former reads better, but the latter is more efficient (space and time). Should the space and time efficiency always win in this situation or is this a kind of optimization that should only happen when needed?

(Added)

For completeness here's the Wiring definition of those bitWrite etc. macros:

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
+7  A: 

Check out Raymond Chen's excellent take on this issue. In summary, you need to do some detailed calculation to find out whether the latter case is actually more efficient, depending on how many objects there are vs. how many callsites actually set these states.

As far as readability, it looks like you're doing this with member variables, which means you've probably encapsulated them in nice functions. In that case, I'm not as concerned with readability because at least the code for the people using the class looks nice. However, you could always encapsulate it in private functions if it's a concern.

Drew Hoskins
I agree with most of that in the case where there are few instances of the class. But (a) he speaks as if it's unheard of to create 1000 instances of a structure, and (b) the benefits of natural word-atomicity are dubious-to-none. A field where your multi-threaded code relies on atomic reads and writes, but never needs atomic update, is considerably rarer than a struct with 1000 instances. And will in most cases break as soon as you move it to an architecture lacking cache coherency.
Steve Jessop
I'll have a look at the reference, thanks. In this case (it's a general class managing a button/led combination) I couldn't imagine instantiating more than about 20 but the point about 1000 instances is right on but not in this particular case...
Alan Moore
(Later after reading it). That's a good article. The good thing is I can directly measure this by looking at the memory footprint of the target application and the associated memory usage. In the chip I'm targetting I have only 2k of ram available for "variables" but 30k for the program. So reducing the former by increasing the latter may be a good tradeoff...
Alan Moore
When you have a system with 2k of ram and a 16Mhz processor, the practical number of instances goes down from 1000 rather quickly.
NoMoreZealots
@Pete: Quite. Chen's analysis simply doesn't apply (or claim to apply) if saving 16 bytes of data is actually valuable.
Steve Jessop
+3  A: 

It can be clearer if you remove the need to use constants HIGH and LOW, by splitting into two methods. Just make bitSet and bitClear methods. bitSet sets the bit to HIGH, and bitClear sets the bit to LOW. Then it becomes:

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
    bitClear(_flags, SHIFT_BIT);
}
else
{
    bitSet(_flags, SHIFT_BIT);
}

And of course, if you just have HIGH == 1 and LOW == 0, then you don't need the == check.

Nick Lewis
In the Arduino/Wiring implementation HIGH does equal 0x01 and LOW does equal 0x00...
Alan Moore
A: 

To my eye, even your latter code is still quite readable. By giving a name to each one of your flags, the code can be read without much effort.

A poor way to do it would be to use "magic" numbers:

if( _flags | 0x20 ) {  // What does this number mean?
   do_something();
}
Kevin Panko
A: 

For bit fields, it's better to use logical operations, so you could do:

if (flags & FLAG_SHIFT) {
   flags &= ~FLAG_SHIFT;
} else {
   flags |= FLAG_SHIFT;
}

This now has the look of the former with the efficiency of the latter. Now you could have macros rather than functions, so (if I've got this right - it would be something like):

#define bitIsSet(flags,bit) flags | bit
#define bitSet(flags,bit) flags |= bit
#define bitClear(flags,bit) flags &= ~bit

You don't have the overhead of calling functions, and the code becomes more readable again.

I've not played with Arduino (yet) but there might already be macros for this sort of thing, I don't know.

Chris J
Yes, sorry about using the Arduino/wiring defines - bitSet/bitWrite etc. is all done through macros that are exactly or similar to what you have written above. The challenge is in the readability of five different levels of macro which ends up hiding the real thing you're trying to achieve...
Alan Moore
I think the thing is bit manipulation is fairly trivial in C and anyone that's working on embedded systems (which effectively what Arduino is) would or should be familiar with the constructs. I think the problem with your second bit of code might just simply be the names of the macros ... bitRead() and bitWrite() just seemed odd conventions compared to using Set, Clear or IsSet. But ours is not to reason why :-)
Chris J
Yes, there's a bitWrite which allows to define whether to set or clear and *then* there's also bitSet and bitClear which does the same with one less parameter (i.e. variable + bit instead of variable + bit + 0 or 1). I was hoping (before I looked at the header file) that there was some magic optimization going on with those differences but it's just someone's convention... :-)
Alan Moore
A potential issue with this is if you have many flags, and thus several variables holding flags. Then 'flags' and 'bit' must come as a matching pair, and the programmer is responsible not to get them mixed up. I.e. was that bitSet(flags1, END_OF_FRAME_REACHED) or bitSet(flags2, END_OF_FRAME_REACHED)? You can mitigate it with naming conventions, e.g. bitSet(flags2, F2_END_OF_FRAME_REACHED).
Craig McQueen
Michael Burr
I've added the definition of those macros to the end of the question above. Brackets abound...
Alan Moore
+4  A: 

depending on the compliance of avr-gcc, which i'm unsure about, you can do something like this and keep things nice and clean.

struct flags {
    unsigned int flag1 : 1;  //1 sets the length of the field in bits
    unsigned int flag2 : 4;
}; 

flags data;

data.flag1 = 0;
data.flag2 = 12;

if (data.flag1 == 1)
{
    data.flag1 = 0;
}
else
{
    data.flag1 = 1;
}

if you also want access to the entire flag int at once then:

union 
{
    struct {
        unsigned int flag1 : 1;  //1 sets the length of the field in bits
        unsigned int flag2 : 4;
    } bits;
    unsigned int val;
} flags;

you can then access bit with 2 levels of indirection: variable.bits.flag1<--returns single bit flag or with a single level to get the entire int worth of flags: variable.val <--returns int

Mark
Shoot, yes, I'd bizarrely forgotten about that technique in my foray back to C++
Alan Moore
In addition to the above, you could also define, for each flag:#define f_EndOfFrameReached (data.endOfFrameReached)and that increases readability. I've worked on embedded projects that used that method consistently, and it was both maintainable and RAM-conserving. The programmers just have to be aware of the convention and its gotchas (such as, beware of the non-atomic read-modify write which means you probably shouldn't use these with interrupt routines).
Craig McQueen
Bitfield structs are notorious for causing trouble, because the compiler can pack the structs in unexpected ways. This approach should only be used if you are (1) positive of your compiler's behavior, and (2) never going to change compilers, ever (e.g. specialty processor with only one compiler).
msemack
@msemack: this shouldn't be a problem, as long as you're not trying to use structs to "decode" a byte stream, either by type casting or union. This issue applies not just to bitfield packing, but also endianness and alignment issues as well. Or am I misunderstanding your meaning?
Craig McQueen
+1  A: 

If you don't need to optimize, don't do it and use the simplest solution.

If you do need to optimize, you should know for what:

  • The first version would be minimally faster if you only set or clear the bit instead of toggling it, because then you don't need to read the memory.

  • The first version is better w.r.t. concurrency. In the second you have read-modify-write, so you need to make sure that the memory byte is not accessed concurrently. Usually you would disable interrupts, which increases your interrupt latency somewhat. Also, forgetting to disable interrupts can lead to very nasty and hard to find bugs (the nastiest bug I encountered so far was of exactly this kind).

  • The first version is minimally better w.r.t. code size (less flash usage), because each access is a single load or store operation. The second approach needs additional bit operations.

  • The second version is uses less RAM, especially if you have a lot of these bits.

  • The second version is also faster if you want to test several bits at once (e.g. is one of the bits set).

starblue
+1  A: 

If you're talking readability, bit-sets and C++, why don't I find anything on std::bitset in there? I understand that the embedded programmer race is quite comfortable with bitmasks, and has evolved a blindness for its sheer uglyness (the masks, not the races:), but apart from masks and bitfields, the standard library has a quite elegant solution, too.

An example:

#include <bitset>

enum tFlags { c_firstflag, c_secondflag, c_NumberOfFlags };

...

std::bitset<c_NumberOfFlags> bits;

bits.set( c_firstflag );
if( bits.test( c_secondflag ) ) {
  bits.clear();
}

// even has a pretty print function!
std::cout << bits << std::endl;// does a "100101" representation.
xtofl
Not sure if the std library is supported on this compiler (or would take up too much memory as to be unusable). And there's no standard out by default. There's no screen, only perhaps a serial port interface if you enable it...
Alan Moore
@Alan Moore: the cout bit was only to show-off what it _can_ do. Very useful for testing!
xtofl
A: 

I would say the first thing I am concerned with is: "#define SHIFT 0" Why not make use of a constant rather than a macro? As far as efficiency get, the constant allow to determine the type, thus making sure than no conversion will be needed afterwards.

As for the efficiency of your technic: - first, get rid of the else clause (why set the bit to HIGH if its value is already HIGH ?) - second, prefer to have something readable first, inlined setters / getters using bit masking internally would do the job, be efficient and be readable.

As for the storage, for C++ I would tend to use a bitset (combined with an enum).

A: 

Is it too simple just to say:

flags ^= bit;
Mike Dunlavey