使移动位更有效率

时间:2013-10-23 06:33:53

标签: c

在我们公司的最新项目中,我们希望将一个字符移到左半个字节,例如

char buf[] = {0x12, 0x34, 0x56, 0x78, 0x21} 

我们想要像

那样使用buf
0x23, 0x45, 0x67, 0x82, 0x10

如何使过程更有效率,如果要处理N个字节,是否可以使时间复杂度小于O(N)?

SOS...

4 个答案:

答案 0 :(得分:3)

如果没有更多的上下文,我甚至会质疑对实际数组的需求。如果您有4个字节,可以使用uint32_t轻松表示,然后您可以执行O(1)班次操作:

uint32_t x = 0x12345678;
uint32_t offByHalf = x << 4;

这样,您可以使用位掩码替换数组访问,如下所示:

array[i]

等同于

(x >> 8 * (3 - i)) & 0xff

谁知道,算术甚至可能比内存访问更快。 但是不要相信我的话,对它进行基准测试。

答案 1 :(得分:2)

不,如果你想实际移动数组,你需要至少按一次每个元素,所以它将是O(n)。没有解决这个问题。您可以使用以下内容执行此操作:

#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;
}

给你:

0x23 0x45 0x67 0x80

这并不是说你不能提高效率(a)。如果您改为修改提取代码以使其行为不同,则可以避免转换操作。

换句话说,不要移动数组,只需设置偏移量变量并使用它来修改提取过程。检查以下代码:

#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;
}

shiftSz设置为0,就好像数组没有移位一样。通过将shiftSz设置为非零,O(1)操作,getByte()实际上将返回元素,就好像已将其移动了该数量。输出正如您所期望的那样:

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

现在这看起来似乎是一个人为的例子(因为它确实如此),但是使用这样的技巧有很多先例可以避免可能代价高昂的操作。您可能还想添加一些边界检查以捕获数组外引用的问题。

请记住,这是一种权衡。不必移动阵列所获得的收益可能会在某种程度上被提取过程中的计算所抵消。它是否真的值得取决于您如何使用数据。如果数组很大但你没有从中提取那么多值,那么这个技巧可能是值得的。


作为使用“技巧”来防止代价高昂的操作的另一个例子,我已经看到了文本编辑器,这些编辑器也不会改变行的内容(例如删除字符时)。相反,他们只是将字符设置为0代码点,并在显示行时处理它(忽略0代码点)。

它们通常会最终清理,但通常会在后台不会干扰您的编辑速度。


(a)虽然你可能想要确保这是必要的。

你的一条评论说你的阵列大约有500个条目,我可以告诉你,我的非极端发展盒子可以将这个阵列向左移动一个左右,每个大约五十万次单秒。

因此,即使您的探查器声明在那里花费了大量的比例时间,这并不一定意味着它的时间量 。< / p>

如果存在特定的,已识别的瓶颈,您应该只考虑优化代码。

答案 2 :(得分:0)

我将解决问题中唯一客观上可回答的部分,即:

  如果要处理N个字节,

是否可以使时间复杂度小于O(N)?

如果你需要整个输出数组,那么你不能做到比O(N)更好。

如果您只需要输出数组的某些元素,那么您可以只计算那些元素。

答案 3 :(得分:-2)

由于对齐可能无法很好地编译,但您可以尝试在结构中使用位域偏移。

struct __attribute__((packed)) shifted{
 char offset:4; // dump data
 char data[N]; // rest of data
};

或某些系统

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

或者你可以试着把它变成一个联盟

union __attribute__((packed)) {
  char old[N];
  struct{
    char offset:4;
    char buf[N];
    char last:4; // to make an even byte
  }shifted;
}data;

替代方法是为每个int转换为int和&lt;&lt; 4的数组,将其减少为N / 4,但这取决于字节顺序。