【问题标题】:Fast way to "down-scale" a three-dimensional tensor index“缩小”三维张量索引的快速方法
【发布时间】:2013-08-05 03:07:48
【问题描述】:

对于 C 或 C++ 来说,这是一个有点棘手的问题。我在 Ubuntu 12.04.2 下运行 GCC 4.6.3。

我有一个内存访问索引p 用于具有以下形式的三维张量:

p = (i<<(2*N)) + (j<<N) + k

这里是0 &lt;= i,j,k &lt; (1&lt;&lt;N)N 一些正整数。

现在我想用0 &lt; S &lt; N 计算i&gt;&gt;S, j&gt;&gt;S, k&gt;&gt;S 的“缩小”内存访问索引,即:

q = ((i>>S)<<(2*(N-S))) + ((j>>S)<<(N-S)) + (k>>S)

p 计算q 的最快方法是什么(事先不知道i,j,k)?我们可以假设0 &lt; N &lt;= 10(即p是一个32位整数)。我对N=8 的快速方法特别感兴趣(即i,j,k 是8 位整数)。 NS 都是编译时常量。

N=8S=4 的示例:

unsigned int p = 240407; // this is (3<<16) + (171<<8) + 23;
unsigned int q = 161; // this is (0<<8) + (10<<4) + 1

【问题讨论】:

  • 不应该是q = ((i&gt;&gt;S)&lt;&lt;(2*(N-S))) + ((j&gt;&gt;S)&lt;&lt;(N-S)) + (k&gt;&gt;S)吗?否则你可能没有足够的位。
  • 完全正确,我解决了这个问题。谢谢!
  • NS 是不变的吗? IE。如果计算涉及N 和/或S 的复杂表达式,它会影响缩减函数的速度吗?
  • N 和 S 是编译时常量。

标签: c++ c bit-manipulation


【解决方案1】:

直截了当,8个操作(其他是对常量的操作):

M = (1<<(N-S)) - 1;                     // A mask with S lowest bits.
q = (  ((p & (M<<(2*N+S))) >> (3*S))    // Mask 'i', shift to new position.
     + ((p & (M<<(  N+S))) >> (2*S))    // Likewise for 'j'.
     + ((p & (M<<     S))  >>    S));   // Likewise for 'k'.

看起来很复杂,但实际上并非如此,只是不容易(至少对我而言)正确地获取所有常量。

要创建具有较少操作的公式,我们观察到将数字向左移动U 位与乘以1&lt;&lt;U 相同。因此,由于乘法分布性,乘以((1&lt;&lt;U1) + (1&lt;&lt;U2) + ...) 与向左移动U1U2 ......然后将所有内容相加。

因此,我们可以尝试屏蔽 ijk 的所需部分,通过一次乘法将它们全部“移位”到相对于彼此的正确位置,然后将结果向右移位,最终的目的地。这为我们提供了三个操作来从 p 计算 q

不幸的是,存在一些限制,尤其是在我们试图同时获得所有三个的情况下。当我们将数字相加时(间接地,通过将​​几个乘数相加),我们必须确保只能在一个数字中设置位,否则我们会得到错误的结果。如果我们尝试一次添加(间接)三个正确移位的数字,我们会得到:

iiiii...........jjjjj...........kkkkk.......
 N-S      S      N-S      S      N-S
.....jjjjj...........kkkkk................
 N-S  N-S      S      N-S
..........kkkkk...............
 N-S  N-S  N-S

请注意,第二个和第三个数字的左侧是ij 的位,但我们忽略它们。为此,我们假设乘法在 x86 上工作:将两个类型 T 相乘得到一个类型为 T 的数字,只有实际结果的最低位(如果没有溢出,则等于结果).

所以,为了确保第三个数字中的k 位不与第一个数字中的j 位重叠,我们需要3*(N-S) &lt;= N,即S &gt;= 2*N/3,对于N = 8,我们将其限制为@987654342 @(移位后每个组件只有一到两位;不知道您是否使用过那么低的精度)。

但是,如果S &gt;= 2*N/3,我们只能使用 3 个操作:

// Constant multiplier to perform three shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+S)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination.
q = (((p & ((M<<(2*N+S)) + (M<<(N+S)) + (M<<S))) * F)
     >> (32-3*(N-S)));

如果S 的约束太严格(可能是这样),我们可以结合第一个和第二个公式:用第二种方法计算ik,然后从第一个方法添加j公式。这里我们需要以下数字中的位不要重叠:

iiiii...............kkkkk.......
 N-S   S   N-S   S   N-S
..........kkkkk...............
 N-S  N-S  N-S

3*(N-S) &lt;= 2*N,它给出了S &gt;= N / 3,或者,对于N = 8,更不严格的S &gt;= 3。公式如下:

// Constant multiplier to perform two shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination
// and then add 'j' from the straightforward formula.
q = ((((p & ((M<<(2*N+S)) + (M<<S))) * F) >> (32-3*(N-S)))
     + ((p & (M<<(N+S))) >> (2*S)));

这个公式也适用于S = 4 的示例。

这是否比直接方法更快取决于架构。另外,我不知道 C++ 是否保证假设的乘法溢出行为。最后,您需要确保值是无符号的并且完全为 32 位,这样公式才能正常工作。

【讨论】:

    【解决方案2】:

    如果您不关心兼容性,对于 N = 8,您可以像这样得到 i、j、k:

     int p = .... 
     unsigned char *bytes = (char *)&p;
    

    现在kbytes[0]jbytes[1]ibytes[2](我在我的机器上发现了小端序)。但我认为更好的方法是……。像那样(我们有 N_MASK = 2^N - 1)

     int q;
     q = ( p & N_MASK ) >> S;
     p >>= N;
     q |= ( ( p & N_MASK ) >> S ) << S;
     p >>= N;
     q |= ( ( p & N_MASK ) >> S ) << 2*S;
    

    【讨论】:

      【解决方案3】:

      它符合您的要求吗?

      #include <cstdint>
      #include <iostream>
      
      uint32_t to_q_from_p(uint32_t p, uint32_t N, uint32_t S)
      {
         uint32_t mask = ~(~0 << N);
         uint32_t k = p &mask;
         uint32_t j = (p >> N)& mask;
         uint32_t i = (p >> 2*N)&mask;
         return ((i>>S)<<(2*(N-S))) + ((j>>S)<<(N-S)) + (k>>S);;
      }
      
      int main()
      {
         uint32_t p = 240407;
      
         uint32_t q = to_q_from_p(p, 8, 4);
      
         std::cout << q << '\n';
      
      }
      

      如果你假设 N 总是 8 并且整数是小端序,那么它可以是

      uint32_t to_q_from_p(uint32_t p, uint32_t S)
      {
         auto ptr = reinterpret_cast<uint8_t*>(&p);
         return ((ptr[2]>>S)<<(2*(8-S))) + ((ptr[1]>>S)<<(8-S)) + (ptr[0]>>S);
      }
      

      【讨论】:

        猜你喜欢
        • 2019-09-15
        • 2020-09-26
        • 1970-01-01
        • 2012-12-25
        • 1970-01-01
        • 2021-12-30
        • 1970-01-01
        • 2020-11-01
        • 2021-10-14
        相关资源
        最近更新 更多