What you are facing is the classic time-memory tradeoff. The bit fields will be smaller in memory, but will take more time to operate on. You can count that no matter what processor, the bit fields will be slower.
You use the word efficient, but that word has no certain meaning without a metric for what is good or bad. When you only have 8 k of RAM, using memory is bad, time may be cheap. If you have real time constraints, then using time is bad and memory may be cheap. In general, you can only buy your way out of this tradeoff. In other words, when you find both time and memory bad, spend cash and use a bigger chip. There is no single answer for what is good or bad. This is part of why there are so many choices for microcontrollers, people fit the chip to the application and application to the chip.
Populating the bits of a bit field will be slower than filling of complete bytes. Take for example
x = 5;
asimplestruct.len = x;
abitfield.len = x;
The first simple case will just:
- load value of x to a register
- store it to the byte for len
The second does something like:
- loads the current value of abitfield
- loads a mask
- clears the bits for len
- loads current value of x
- loads a mask
- clears off unused bits of x
- shifts the bits of x
- or's x with the abitfield
- stores the current value back to memory
If all your operations are packing data into the bit field, or unpacking out of the bitfield, you should expect slower execution. Bit fields are a type of compression - they cost ticks.
But moving around bit fields will be faster because there are less bytes to load and store. If you where sorting this array, the smaller byte count could be an advantage. If you where transferring them over a serial port, the compressed size of the bitfield might be a winner.
So with respect to your question:
Is there a good way to test this?
The only good way to test this is to write test cases for both approaches using a pattern that closely matches your application. It really matters what mix of operations you perform to decide whether the difference is negligible or significant.
When doing this sort of optimization experiment, definitely use source control on your project. You can create a local GIT or Mercurial repository with just a few clicks. Keeping a chain of checkpoints allows you to tear up your code exploring the effects of different implementations. If you take a wrong turn, the repository allows you to simply go back to the last good point and try another path.
(Side note: This time memory tradeoff exists in the opposite direction also. If you look through the compiler options for desktop class processors, you will find something called structure packing. This option allow you to add blank bytes between single byte fields such that they stay aligned at word or double word boundaries. This may seem crazy to throw away RAM on purpose, but on processors with 16 or 32 bit wide registers and buses, so called word or dword aligned memory operations can be faster than byte wise operations.)