因此,我想设置一个__m256i
寄存器的单个位。
假设我的__m256i
包含:[1 0 1 0 | 1 0 1 0 |...| 1 0 1 0]
,如何设置和取消第n位?
因此,我想设置一个__m256i
寄存器的单个位。
假设我的__m256i
包含:[1 0 1 0 | 1 0 1 0 |...| 1 0 1 0]
,如何设置和取消第n位?
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;
}
set1( 1U << (k&31) )
和 set1( k & ~31U )
,因为标量移位可以在端口6上运行(不会与向量ALU uops竞争)。因此,它把一些标量指令和额外的 MOVD + VPBROADCASTD 交换成了3个向量指令。我把三个版本都放在了Godbolt上:https://godbolt.org/g/F3NqdW。你应该把最终版本作为答案的主要部分,它绝对是最好的。(不要只是“更新”,重新排列你的答案以首先展示它,如果想要的话,可以将你早期的想法作为脚注。或者只需说“请参阅早期想法的历史记录”。) - Peter Cordes这是一个可以在向量中设置单个位的函数实现:
#include <immintrin.h>
#include <assert.h>
void SetBit(__m256i & vector, size_t position, bool value)
{
assert(position <= 255);
uint8_t lut[32] = { 0 };
lut[position >> 3] = 1 << (position & 7);
__m256i mask = _mm256_loadu_si256((__m256i*)lut);
if (value)
vector = _mm256_or_si256(mask, vector);
else
vector = _mm256_andnot_si256(mask, vector);
}
int main(int argc, char* argv[])
{
__m256i a = _mm256_set1_epi8(-1);
SetBit(a, 54, false);
__m256i b = _mm256_set1_epi8(0);
SetBit(b, 54, true);
return 0;
}
还有另一种实现方法:
#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;
}
BTS
设置单个位(或BTR
清除它)。这个指令在GCC中似乎没有内置函数,因此需要使用内联汇编(仅适用于x86架构)。
0F AB /r --- BTS r/m32, r32 --- 存储所选位于CF标志中并将其设置。
它们与存储器操作数一起运行非常慢,但是这些位串指令允许超出寻址模式引用的字节或双字的位偏移量。手册解释如下:
有些汇编程序支持使用内存操作数的位移字段和立即位移字段结合使用来支持大于31的立即位移。在这种情况下,立即位移字段中存储低3位或5位(16位操作数为3位,32位操作数为5位)立即位移,组合位移字段和立即位移以及地址模式中的字节位移字段。如果不为零,则处理器将忽略高阶位。
当访问存储器中的位时,处理器可能会访问从内存地址开始的4个字节,对于32位操作数大小,使用以下关系式:
Effective Address + (4 ∗ (BitOffset DIV 32))
在纯汇编(Intel-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);
LUT[n]
加载掩码向量,然后使用_mm256_or_si256
。 - Paul R