所以,我想设置一个__m256i
寄存器的单个位。
说,我的__m256i
包含:[ 1 0 1 0 | 1 0 1 0 | ... | 1 0 1 0 ]
,如何设置和取消设置第n位?
答案 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);