X86 在AVX寄存器(m256i)中设置单个位,需要;“随机存取”;操作人员

X86 在AVX寄存器(m256i)中设置单个位,需要;“随机存取”;操作人员,x86,bit-manipulation,simd,intrinsics,avx,X86,Bit Manipulation,Simd,Intrinsics,Avx,因此,我想设置\uuum256i寄存器的单个位 比如说,我的uu m256i包含:[1 0 1 0 | 1 0 1 0 |························· #包括 #包括 无效设置(uuu m256i&vector,大小\u t位置,布尔值) { assert(position>3]=1如果您想避免LUT,可以使用它来设置单个位(或者分别用于清除位)。此指令似乎没有内在属性(至少在GCC中是这样),因此需要内联汇编(仅适用于x86体系结构) 0F AB/r---BTS r/m3

因此,我想设置
\uuum256i
寄存器的单个位


比如说,我的
uu m256i
包含:
[1 0 1 0 | 1 0 1 0 |·························

#包括
#包括
无效设置(uuu m256i&vector,大小\u t位置,布尔值)
{

assert(position>3]=1如果您想避免LUT,可以使用它来设置单个位(或者分别用于清除位)。此指令似乎没有内在属性(至少在GCC中是这样),因此需要内联汇编(仅适用于x86体系结构)

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

内存操作数的速度非常慢,但这些位字符串指令允许位偏移量超出寻址模式引用的字节或dword。手册解释:

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

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

有效地址+(4)∗ (位偏移分区32))

在纯汇编程序(英特尔MASM语法)中,如下所示:

.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));
}
这些将编译为您所期望的

还有一些测试代码类似于上面的汇编代码:

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

还有另一个实现:

#包括
#包括
模板无效设置掩码(常数m256i和掩码、常数m256i和向量);
模板内联无效设置掩码(常量m256i和掩码、uuu m256i和向量)
{
向量=_mm256_或_si256(掩码,向量);
}
模板内联无效设置掩码(常量m256i和掩码、uuu m256i和向量)
{
向量=_mm256_和非_si256(掩码,向量);
}
模板无效设置(uuu m256i&vector)
{
const uint8_t mask8=1>3)和15);
常数m256i mask256=_mm256_inserti128_si256(_mm256_setzero_si256(),mask128,位置>>7);
设置掩码(mask256,向量);
}
int main(int argc,char*argv[])
{
__m256i a=_mm256_set1_epi8(-1);
立法会(a);
__m256i b=_mm256_set1_epi8(0);
立法会(b);
返回0;
}

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

inline\uuuum256i setbit\u256(\uuuum256i x,int k){
//内联后将(希望)从循环中提升出来的常量
__m256i指数=_mm256_set_epi32(224192160128,96,64,32,0);
__m256i一个=_mm256_set1_epi32(-1);
一=_mm256_srli_epi32(一,31);//集1(0x1)
__m256i kvec=_mm256_set1_epi32(k);

//如果0最简单的方法是为256个不同的掩码值创建一个查找表,并使用n作为索引来获取用于设置/清除位的掩码,您有没有给我举个例子?只需从
LUT[n]加载掩码向量即可
然后使用
\u mm256\u或_si256
。为了让其他人清楚地看到这一点,由于存储转发暂停,这不是一种有效的方法。因此,您不希望将其放入性能关键循环中。不幸的是,SIMD不是为此而设计的。因此,可能没有有效的方法来实现这一点。我认为shift+permute可能会更快,但也要复杂得多。在Fortran90中有IBSET、IBSHFT、IBTEST等内部函数。因此,这是一个值得使用混合语言解决方案的地方。存储转发主要是延迟问题,而不是吞吐量问题,对吗?你知道,在最近的Intel CPU上,具有内存操作数的BTS超过10 UOP,对吗?正是因为它疯狂的位字符串寻址,要修改的字节(或dword)的地址并不仅仅由寻址模式决定。而且它在重新加载时仍然会导致存储转发暂停。不过,值得指出的是,我很有意思。我敢肯定,通过使用向量移位+洗牌,AVX2可以轻松克服这一问题(使用整数指令生成的洗牌掩码,并用pmovzx或其他东西展开)。避免存储转发停顿是一个巨大的问题。让我们,在这里,我们移动了关于向下投票的离题评论。干杯,继续做好你的回答。很好地使用广播+cmpeq来选择应该包含
1
位的向量元素。这可能至少在延迟方面是最佳的,对于位位置n不是编译时常量。(当它是时,ermlg的模板答案有望编译成单个VPANDN或带有预先计算的常量的VPOR)。我对您的代码进行了注释,因为没有描述性变量名的情况下,遵循这些代码是非常重要的。请随意回滚编辑。因此,我没有将其作为建议留给您批准的选项,所以我只是继续操作了。@PeterCordes:感谢您对我的答案进行了编辑和注释!更新很好,值得一提向量移位的饱和行为(与计数被屏蔽的标量移位不同)。我很确定这是最优的,除了可能使用64位元素来简化常量。(因此编译器可以在从循环中提升出来时使用PMOVZXBQ而不是BD来加载它)。您不需要显式地编写
广播的_epi32(…)
,只需使用set1,编译器将使用vpbroadcasted。如果
k
在内存中,它可以直接从内存(b)进行广播