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

Posted

技术标签:

【中文标题】内存中的高效矢量位数据“旋转”/“重新排列”[例如在 Python 中,Numpy] [关闭]【英文标题】:Efficient Vector Bit-Data "Rotation" / "Rearrangement" in Memory [e.g. in Python, Numpy] [closed] 【发布时间】:2016-02-03 21:44:26 【问题描述】:

如何有效地从一个 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 指令或一般答案!

【问题讨论】:

你想用python、c还是c++解决你的作业?或者也许你只想要这三个? 您所需要的只是一个重定向访问的装饰器,这样您就不必完全打乱数据了。也就是说,对于较大的数据结构,您可以先将 N 的瓦片按其中心旋转 N 位,然后围绕数据结构的中心旋转它们。类似于首先交换高半和低半然后反转它们的位顺序反转。 @4386427 感谢您的提议,但这与家庭作业无关。 太糟糕了,您只能添加五个标签。为了缩小您的问题范围,您应该添加 C#、Fortran、Algol 和 OcamML 以及您听说过的任何其他 PL。 @Ulrich Eckhardt 所以为了澄清,我确实想访问位,而不是字节。我不知道如何使用 python 装饰器有效地做到这一点...... 【参考方案1】:

我建议查看here提供的来源

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

【讨论】:

感谢您的链接,我正在寻找的那种,效果很好。还为我指明了“8x8 位矩阵转置”的方向,这是我一直在寻找的关键字。【参考方案2】:

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

我在问题中使用了 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";
    
;

以及以下用于翻转两个Elements 中的位的函数——请注意,由于元素是二进制的,因此您只需要在它们不相同时翻转位。

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 度,只需修改索引即可。

【讨论】:

感谢 nabla,提供详细示例。看起来比我原来的版本快。如果您对根据 cpu 指令数量计算出最快可能的解决方案(使用 SSE 和 AVX 功能)的理论方法感兴趣,请查看@haiandbaii 的答案或搜索“8x8 位矩阵反转”。【参考方案3】:

下面是 8x8 情况下的 C 语言简单实现:

#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/*** > ./rot8x8
original => ['10100111',
             '11110001',
             '11011001',
             '00101010',
             '10000010',
             '11001000',
             '11011000',
             '11111110']
rotated => ['11101111',
            '01100111',
            '11010001',
            '01100011',
            '00110111',
            '10000001',
            '10011001',
            '11100000']

使用数字参数运行它来表示计时:

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

在带有clang -O3 的 MacbookPro 上,这个幼稚的程序执行单次旋转需要不到 50ns,比您的 Numpy 示例快 400 倍。我确信有更快的方法,但这已经明显更好了。

【讨论】:

以上是关于内存中的高效矢量位数据“旋转”/“重新排列”[例如在 Python 中,Numpy] [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

数据对齐以实现矢量化/高效缓存访问

UICollectionView 在旋转时重新排列单元格

如何在 iPad 中自动旋转期间重新排列视图

如何为大数据创建高效的位集结构?

python - 如何按python中的因子级别对pandas数据框中的行进行重新排序?

Numpy基础:数组和矢量计算