设置AVX寄存器中的个别位(__m256i),需要"随机访问"操作者

时间:2016-09-13 17:10:36

标签: x86 bit-manipulation simd intrinsics avx

所以,我想设置一个__m256i寄存器的单个位。

说,我的__m256i包含:[ 1 0 1 0 | 1 0 1 0 | ... | 1 0 1 0 ],如何设置和取消设置第n位?

4 个答案:

答案 0 :(得分:4)

这是一个函数的实现,可以在向量中设置单个位:

decimal

答案 1 :(得分:4)

还有另一种实施方式:

#include <immintrin.h>
#include <assert.h>

template <bool value> void SetMask(const __m256i & mask, __m256i & vector);

template <> inline void SetMask<true>(const __m256i & mask, __m256i & vector)
{
    vector = _mm256_or_si256(mask, vector);
}

template <> inline void SetMask<false>(const __m256i & mask, __m256i & vector)
{
    vector = _mm256_andnot_si256(mask, vector);
}

template <int position, bool value> void SetBit(__m256i & vector)
{
    const uint8_t mask8 = 1 << (position & 7);
    const __m128i mask128 = _mm_insert_epi8(_mm_setzero_si128(), mask8, (position >> 3)&15);
    const __m256i mask256 = _mm256_inserti128_si256(_mm256_setzero_si256(), mask128, position >> 7);
    SetMask<value>(mask256, vector);
}

int main(int argc, char* argv[])
{
    __m256i a = _mm256_set1_epi8(-1);
    SetBit<50, false>(a);

    __m256i b = _mm256_set1_epi8(0);
    SetBit<50, true>(b);

    return 0;
}

答案 2 :(得分:4)

如果您想避免LUT和/或存储转发停顿,您可以执行此操作来设置 avx-256寄存器的第k位:

inline __m256i setbit_256(__m256i x,int k){
// constants that will (hopefully) be hoisted out of a loop after inlining  
  __m256i indices = _mm256_set_epi32(224,192,160,128,96,64,32,0);
  __m256i one = _mm256_set1_epi32(-1);
  one = _mm256_srli_epi32(one, 31);    // set1(0x1)


  __m256i kvec = _mm256_set1_epi32(k);  
// if 0<=k<=255 then kvec-indices has exactly one element with a value between 0 and 31
  __m256i shiftcounts = _mm256_sub_epi32(kvec, indices);
  __m256i kbit        = _mm256_sllv_epi32(one, shiftcounts);   // shift counts outside 0..31 shift the bit out of the element
                                                               // kth bit set, all 255 other bits zero.
  return _mm256_or_si256(kbit, x);                             // use _mm256_andnot_si256 to unset the k-th bit
}


以下是我以前的回答,不太直接,现在已经过时了。

#include <immintrin.h>

inline __m256i setbit_256(__m256i x,int k){
  __m256i c1, c2, c3;
  __m256i t, y, msk;

  // constants that will (hopefully) be hoisted out of a loop after inlining
  c1=_mm256_set_epi32(7,6,5,4,3,2,1,0);
  c2=_mm256_set1_epi32(-1);
  c3=_mm256_srli_epi32(c2,27);     // set1(0x1f) mask for the shift within elements
  c2=_mm256_srli_epi32(c2,31);     // set1(0x1)

  // create a vector with the kth bit set
  t=_mm256_set1_epi32(k);
  y=_mm256_and_si256(c3,t);        // shift count % 32: distance within each elem
  y=_mm256_sllv_epi32(c2,y);       // set1( 1<<(k%32) )

  t=_mm256_srli_epi32(t,5);        // set1( k>>5 )
  msk=_mm256_cmpeq_epi32(t,c1);    // all-ones in the selected element
  y=_mm256_and_si256(y,msk);       // kth bit set, all 255 other bits zero.

  x=_mm256_or_si256(y,x);   /* use _mm256_andnot_si256 to unset the k-th bit */
  return x;
}

我不确定这是否会比其他答案中提出的方法更快。

使用clang或gcc (Godbolt compiler explorer)编译为非常好的asm,考虑到常量将从循环中提升。像往常一样,clang失败了在运行中生成常量的尝试,并从内存广播加载它们(这在现代CPU上非常有效)。

答案 3 :(得分:2)

如果您想避开LUT,可以使用BTS设置一个位(或BTR分别清除它)。这条指令似乎没有固有的(至少在GCC中),因此需要内联汇编(仅适用于x86架构)。

  

0F AB / r --- BTS r / m32,r32 ---将所选位存储在CF标志中并设置。

对于内存操作数,它们非常慢,但这些位串指令允许位偏移超出​​寻址模式引用的字节或双字。手册解释说:

  

通过将立即位偏移字段与内存操作数的位移字段结合使用,某些汇编程序支持大于31的立即位偏移。在这种情况下,立即位偏移的低位3或5位(16位操作数为3,32位操作数为5)存储在立即位偏移字段中,高位位为移位并与汇编器在寻址模式中的字节位移相结合。如果处理器不为零,则处理器将忽略高位。

     

当访问内存中的某个位时,处理器可以从32位操作数大小的内存地址开始访问4个字节,使用以下关系:

     

有效地址+(4 *(BitOffset DIV 32))

在纯汇编程序(Intel-MASM-syntax)中,这将如下所示:

.data
  .align 16
  save db 32 dup(0)    ; 256bit = 32 byte YMM/__m256i temp variable space
  bitNumber dd 254     ; use an UINT for the bit to set (here the second to last)
.code
  mov eax, bitNumber
  ...
  lea edx, save
  movdqa xmmword ptr [edx], xmm0    ; save __m256i to to memory
  bts dword ptr [edx], eax          ; set the 255st bit
  movdqa xmm0, xmmword ptr [edx]    ; read __m256i back to register
  ...

如果变量已经在内存中,那就更容易了。

使用内联汇编,这将产生以下功能:

static inline
void set_m256i_bit(__m256i * value, uint32_t bit)
{
    // doesn't need to be volatile: we only want to run this for its effect on *value.
    __asm__ ("btsl %[bit], %[memval]\n\t"
             : [memval] "+m" (*value) : [bit] "ri" (bit));
}

static inline
void clear_m256i_bit(__m256i * value, uint32_t bit)
{
    __asm__ ( "btrl %[bit], %[memval]\n\t"
              : [memval] "+m" (*value) : [bit] "ri" (bit));
}

这些编译成您期望的on the Godbolt compiler explorer

和一些类似于上面汇编程序代码的测试代码:

__m256i value = _mm256_set_epi32(0,0,0,0,0,0,0,0);
set_m256i_bit(&value,254);
clear_m256i_bit(&value,254);