存储器中的高效矢量位数据“旋转”/“重新排列”[例如在Python中,Numpy]

时间:2016-02-03 21:44:27

标签: python c++ c arrays bit-manipulation

如何有效地转换为8个元素的长数组,例如uint8s成为一个“旋转”的对应物,例如第一个元素的原始8位作为MSB分布在所有向量元素上,第二个元素跨越第二个MSB,依此类推:工作和慢速示例:

import numpy as np

original = np.random.randint(0, 255, 8).astypye(np.uint8) # some random example vector
[np.binary_repr(i, width=8) for i in original]            # original data
=>['01111111',
   '00100111',
   '01110111',
   '00100010',
   '00111101',
   '10010000',
   '10000100',
   '10101000']

rotated = np.packbits(np.unpackbits(original).reshape(-1,8).T) # <= SLOW ROTATION
[np.binary_repr(i, width=8) for i in rotated]                  # this is should be the result
=>['00000111',                                                 # what where rows originally
   '10100000',                                                 # are now columns
   '11111001',
   '10101100',
   '10001001',
   '11101010',
   '11110000',
   '11101000']

所以最后,我想重新排列BITS如何“归档”到RAM中的布局。正如你所看到的,我在Numpy中得到了一个实际的例子,它不是超慢(这里约21μs),但是我想用~2k * 1 mio位的数据结构进行这种练习。因此,numpy或C bool dtype的使用是浪费的(因素8开销)。

欢迎任何C位改组魔术或SSE指令或一般答案!

3 个答案:

答案 0 :(得分:0)

我建议查看提供的来源here

特别是calcperm.cpp。这是一个简单的位置换问题。

答案 1 :(得分:0)

如果旋转是针对平方数量的行和列,那么这是一个解决方案,然后它只是对位进行转置。

我在问题中使用了8位元素。另外,第7位是最左边的位,而第0位是最右边的位。我将按以下格式引用列和行中的位(因为这是我最快速打印位的方式 - 因此索引比最好的情况更复杂,但可以修改适当地):

    | col : 7 6 5 4 3 2 1 0
--------------------------
row:| 0     0 1 1 1 1 1 1 1
    | 1     0 0 1 0 0 1 1 1 
    | 2     0 0 0 0 0 1 0 0
    | 3     0 0 0 0 1 0 0 1
    | 4     0 0 0 0 1 1 0 0
    | 5     0 1 1 0 0 1 0 0
    | 6     1 1 0 0 1 0 0 0
    | 7     1 0 0 1 0 1 1 0

然后我定义了以下结构来包装8位元素,并执行位操作和打印:

struct Element {
    Element(uint8_t E) : e(E) {}

    // Just for convienience
    static constexpr int size = 8;
    uint8_t e;

    // Get a bit from the element
    inline uint8_t get(uint8_t i) {
        return (e >> i & 0x01);
    }

    // Flip a bit in the element
    inline void flip(uint8_t i) {
        e ^= (0x01 << i);
    }

    // Just for convienience
    void print() {
        std::cout << std::bitset<8>(e) << "\n";
    }
};

除了以下函数来翻转两个Element中的位 - 请注意,如果它们不相同,则只需要翻转这些位,因为这些元素是二进制的。

inline void swap(Element& a, Element& b, int a_offset, int b_offset) {
    if (a.get(a_offset) != b.get(b_offset)) {
        a.flip(a_offset); b.flip(b_offset);
    }
}

然后,只需循环上三角形(对角线上方)中的元素,然后将其与下三角形(对角线下方)中的元素进行交换,如下所示:

int main() {
  std::vector<Element> array = { 127, 39, 4, 9, 12, 100, 200, 150 };

  for (auto& a : array) a.print(); std::cout << "\n"; // Before

  // Do the swapping
  for (size_t row = 0; row < array.size(); ++row) {
    for (size_t col = Element::size - 1 - row; col >= 1; --col) {
      swap(array[row], array[Element::size - col], col - 1, Element::size - 1 - row);
    }
  }

  for (auto& a : array) a.print(); // After
}  

在问题中生成转换:请参阅显示输入和输出的live demo。使用-O3进行编译的时间大约为1.1微秒(只是转换,不包括打印)。

通过修改索引,您还可以非常轻松地将转换更改为90度左右旋转。

答案 2 :(得分:0)

以下是C语言中针对8x8案例的简单实现:

#include <stdio.h>
#include <stdlib.h>

typedef unsigned char byte;

void dump(const char *name, const byte *p, int size) {
    int len = printf("%s => ['", name) - 1;
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < 8; j++) {
            putchar('0' + ((p[i] >> (7 - j)) & 1));
        }
        if (i < 7) {
            printf("',\n%*s'", len, "");
        }
    }
    printf("']\n");
}

int main(int argc, char **argv) {
    byte original[8], rotated[8];
    int repeat = 1;

    if (argc > 1)
        repeat = atoi(argv[1]);

    for (int i = 0; i < 8; i++) {
        original[i] = rand() & 255;
    }
    for (int r = 0; r < repeat; r++) {
        /*-------- this is the core of the rotation --------*/
        for (int i = 0; i < 8; i++) {
            rotated[i] = 0;
        }
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                rotated[j] |= ((original[i] >> (7 - j)) & 1) << (7 - i);
            }
        }
        /*-------- end of the rotation code --------*/
    }
    if (repeat == 1) {
        dump("original", original, 8);
        dump("rotated", rotated, 8);
    }
    return 0;
}

在没有参数的情况下运行它进行样本随机测试:

chqrlie@mac ~/dev/stackoverflow > ./rot8x8
original => ['10100111',
             '11110001',
             '11011001',
             '00101010',
             '10000010',
             '11001000',
             '11011000',
             '11111110']
rotated => ['11101111',
            '01100111',
            '11010001',
            '01100011',
            '00110111',
            '10000001',
            '10011001',
            '11100000']

使用数字参数运行它以进行计时:

chqrlie@mac ~/dev/stackoverflow > time ./rot8x8 20000000
real    0m0.986s
user    0m0.976s
sys     0m0.004s

在带有clang -O3的MacbookPro上,这个天真的程序执行单次旋转需要小于50ns 快<400> 。我确信有更快的方法,但这已经明显好了。