Reputation: 13
in our company's latest project, we want to move a char arry to left half a byte, e.g.
char buf[] = {0x12, 0x34, 0x56, 0x78, 0x21}
we want to make the buf like
0x23, 0x45, 0x67, 0x82, 0x10
how do I make the process more efficient, can you make the time complexity less than O(N) if there are N bytes to be processed?
SOS...
Upvotes: 0
Views: 154
Reputation: 7812
It may not compile well due to alignment, but you can try using a bitfield offset in a struct.
struct __attribute__((packed)) shifted{
char offset:4; // dump data
char data[N]; // rest of data
};
or on some systems
struct __attribute__((packed)) shifted{
char offset:4; // dump data
char data[N]; // rest of data
char last:4; // to make an even byte
};
struct shifted *shifted_buf=&buf;
//now operate on shifted_buf->data
Or you can try making it a union
union __attribute__((packed)) {
char old[N];
struct{
char offset:4;
char buf[N];
char last:4; // to make an even byte
}shifted;
}data;
The alternative would be to cast to an array of int and <<4 for each int, reducing it to N/4, but this is dependent on endianness.
Upvotes: -2
Reputation: 882166
No, if you want to actually shift the array, you'll need to hit every element at least once so it'll be O(n). There's no getting around that. You can do it with something like the following:
#include <stdio.h>
void shiftNybbleLeft (unsigned char *arr, size_t sz) {
for (int i = 1; i < sz; i++)
arr[i-1] = ((arr[i-1] & 0x0f) << 4) | (arr[i] >> 4);
arr[sz-1] = (arr[sz-1] & 0x0f) << 4;
}
int main (int argc, char *argv[]) {
unsigned char buf[] = {0x12, 0x34, 0x56, 0x78};
shiftNybbleLeft (buf, sizeof (buf));
for (int i = 0; i < sizeof (buf); i++)
printf ("0x%02x ", buf[i]);
putchar ('\n');
return 0;
}
which gives you:
0x23 0x45 0x67 0x80
That's not to say you can't make it more efficient (a). If you instead modify your extraction code so that it behaves differently, you can avoid the shifting operation.
In other words, don't shift the array, simply set an offset variable and use that to modify the extraction process. Examine the following code:
#include <stdio.h>
unsigned char getByte (unsigned char *arr, size_t index, size_t shiftSz) {
if ((shiftSz % 2) == 0)
return arr[index + shiftSz / 2];
return ((arr[index + shiftSz / 2] & 0x0f) << 4)
| (arr[index + shiftSz / 2 + 1] >> 4);
}
int main (int argc, char *argv[]) {
unsigned char buf[] = {0x12, 0x34, 0x56, 0x78};
//shiftNybbleLeft (buf, sizeof (buf));
for (int i = 0; i < 4; i++)
printf ("buf[1] with left shift %d nybbles -> 0x%02x\n",
i, getByte (buf, 1, i));
return 0;
}
With shiftSz
set to 0, it's as if the array isn't shifted. By setting shiftSz
to non-zero, an O(1) operation, getByte()
will actually return the element as if you had shifted it by that amount. The output is as you would expect:
Index 1 with left shift 0 nybbles -> 0x34
Index 1 with left shift 1 nybbles -> 0x45
Index 1 with left shift 2 nybbles -> 0x56
Index 1 with left shift 3 nybbles -> 0x67
Now that may seem a contrived example (because it is) but there's ample precedent in using tricks like that to avoid potentially costly operations. You'd probably also want to add some bounds checking to catch problems with referencing outside the array.
Keep in mind that there's a trade-off. What you gain by not having to shift the array may be offset to some degree by the calculations done during extraction. Whether it's actually worth it depends on how you use the data. If the arrays is large but you don't extract that many values from it, this trick may be worth it.
As another example of using "tricks" to prevent costly operations, I've seen text editors that don't bother shifting the contents of lines either (when deleting a character for example). Instead they simply set the character to a 0 code point and take care of it when displaying the line (ignoring the 0 code points).
They'll generally clean up eventually but often in the background where it won't interfere with your editing speed.
(a) Though you may want to actually make sure this is necessary.
One of your comments stated that your arrays are about 500 entries in length and I can tell you that my not-supremely-grunty development box can shift that array one nybble to the left at the rate of about half a million times every single second.
So, even if your profiler states that a large proportion of time is being spent in there, that doesn't necessarily mean it's a large amount of time.
You should only look into optimising code if there's a specific, identified bottleneck.
Upvotes: 2
Reputation:
Without more context, I would go even as far as questioning the need for an actual array. If you have 4 bytes, that can easily be represented using a uint32_t
, and then you can perform an O(1)
shift operation:
uint32_t x = 0x12345678;
uint32_t offByHalf = x << 4;
This way, you would replace array access with bit masking, like this:
array[i]
would be equivalent with
(x >> 8 * (3 - i)) & 0xff
And who knows, arithmetic may even be faster than memory access. But don't take my word for it, benchmark it.
Upvotes: 3
Reputation: 500753
I'll tackle the only objectively answerable part of the question, which is:
can you make the time complexity less than O(N) if there are N bytes to be processed?
If you need the entire output array then no, you cannot do better than O(N)
.
If you only need certain elements of the output array, then you can compute just those.
Upvotes: 0