Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/vba/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 将位解包为单精度浮点的最快方法_C++_C_Visual Studio_Optimization_64 Bit - Fatal编程技术网

C++ 将位解包为单精度浮点的最快方法

C++ 将位解包为单精度浮点的最快方法,c++,c,visual-studio,optimization,64-bit,C++,C,Visual Studio,Optimization,64 Bit,这是一个特定于平台的问题。速度至关重要。 将一个字节解压成8个单精度浮点数组,从而使0映射为0,1映射为1的最快方法是什么 最后,我使用8位掩码和7位移位解压成8个int32,然后使用AVX指令将int32转换成浮点 我的平台是在支持AVX(但没有AVX2)的CPU上运行的Windows 64位。编译器:Visual Studio 2013 谢谢 void byteToFloat(const uint8_t byteIn,

这是一个特定于平台的问题。速度至关重要。 将一个字节解压成8个单精度浮点数组,从而使0映射为0,1映射为1的最快方法是什么

最后,我使用8位掩码和7位移位解压成8个int32,然后使用AVX指令将int32转换成浮点

我的平台是在支持AVX(但没有AVX2)的CPU上运行的Windows 64位。编译器:Visual Studio 2013

谢谢

void byteToFloat(const uint8_t               byteIn, 
                       float *const restrict floatOut)
{
     floatOut[0]=(byteIn&0x01)?1.0f:0.0f;
     floatOut[1]=(byteIn&0x02)?1.0f:0.0f;
     floatOut[2]=(byteIn&0x04)?1.0f:0.0f;
     floatOut[3]=(byteIn&0x08)?1.0f:0.0f;
     floatOut[4]=(byteIn&0x10)?1.0f:0.0f;
     floatOut[5]=(byteIn&0x20)?1.0f:0.0f;
     floatOut[6]=(byteIn&0x40)?1.0f:0.0f;
     floatOut[7]=(byteIn&0x80)?1.0f:0.0f;
}
在Intel和AMD的x86-64体系结构中,分支预测 可以通过使用条件移动操作来执行 (cmove):源操作数有条件地移动到目标 取决于标志寄存器值的操作数


循环、条件和遍历内存中的实际数组当然不是矢量方式。所以这里有另一个想法,尽管仅在AVX中有点烦人。由于没有AVX2,使用ymm寄存器几乎什么都做不了(反正也没什么用处),所以只需使用两个xmm寄存器,然后在最后使用高位部分来构成整个寄存器。只要xmm寄存器上的操作使用VEX编码的指令,这样的混合就可以了(所以“v”出现在所有指令前面,即使它看起来不必要)

无论如何,我们的想法是在每个dword中放置一个字节副本,每个通道使用正确的位,并与表单掩码进行比较。最后,我们可以做一个单位运算,将掩码转换为0f或1f

所以,首先,让我们假设它在
eax
中,到处都是字节,这并不重要:

vmovd xmm0, eax
vpshufd xmm0, xmm0, 0
提取正确的位:

vpand xmm0, xmm0, [low_mask]
vpand xmm1, xmm0, [high_mask]
遮罩是
1,2,4,8
16,32,64,128
(这是内存顺序,如果使用
\u mm\u set\u epi32
,则必须相反)

与模板进行比较:

vpxor xmm2, xmm2, xmm2
vpcmpgtd xmm0, xmm0, xmm2
vpcmpgtd xmm1, xmm1, xmm2
合并:

vinsertf128 ymm0, ymm0, xmm1, 1
变成0f或1f:

vandps ymm0, ymm0, [ones]
one
只需1f复制8次

我不知道这是否更快,但值得一试。此外,这些都没有经过测试

我试图将其转换为本质,但我不知道我在做什么(而且它没有经过测试)。另外,请注意它使用VEX前缀编译,否则会导致昂贵的模式切换

// broadcast
__m128i low = _mm_set1_epi32(mask);
__m128i high = _mm_set1_epi32(mask);
// extract bits
low = _mm_and_si128(low, _mm_set_epi32(8, 4, 2, 1));
high = _mm_and_si128(high, _mm_set_epi32(128, 64, 32, 16));
// form masks
low = _mm_cmpgt_epi32(low, _mm_setzero_si128());
high = _mm_cmpgt_epi32(high, _mm_setzero_si128());
// stupid no-op casts
__m256 low2 = _mm256_castps128_ps256(_mm_castsi128_ps(low));
__m128 high2 = _mm_castsi128_ps(high);
// merge
__m256 total = _mm256_insertf128_ps(low2, high2, 1);
// convert to 0f or 1f
total = _mm256_and_ps(total, _mm256_set1_ps(1.0f));
至少对于GCC,这会生成OK代码。它将
vbroadcastss
用于
set1
(而不是我使用的
vpshufd
),我不确定这个想法有多好(这意味着它必须通过内存反弹该int)

使用AVX2,它可以简单得多:

__m256i x = _mm256_set1_epi32(mask); 
x = _mm256_and_si256(x, _mm256_set_epi32(128, 64, 32, 16, 8, 4, 2, 1));
x = _mm256_cmpgt_epi32(x, _mm256_setzero_si256());
x = _mm256_and_si256(x, _mm256_set1_epi32(0x3F800000));
return _mm256_castsi256_ps(x);

预处理不是更快吗?2^8的可能性差不多,但再一次,把它分成两部分,它只有2^4=16个变量

使数组由16个“值”组成,其中每个值由4个具有正确值的浮点数填充。那么您的成本将仅为2*(将数据从预处理数组复制到新数组)

我对汇编不太深入,但两个副本应该比一些循环等更快

unsigned char myByte; // input byte (pattern to create floats)
float preprocessingArrays[16][4] = {
    { 0.0f, 0.0f, 0.0f, 0.0f }, // 0000
    // ...
    { 1.0f, 1.0f, 1.0f, 1.0f }  // 1111
};

float result[8];
std::memcpy(&result[0], &preprocessingArrays[myByte >> 4][0], 16);
std::memcpy(&result[4], &preprocessingArrays[myByte & 15][0], 16);
// 16 = platform-specific -> floats should be 32bits -> 4bytes * 4 floats = 16
这是手写的,但正如您所看到的,我的循环将由两个memcpy组成,一个位移位和一个二进制and操作(或者只有一个,但更大的,memcpy,如果您想对2^8个值进行预处理)

对于纯C(++)代码,我认为这将击败循环等,但汇编代码可能更快,我不太确定也许您可以使用汇编程序执行
memcpy
操作,一次读取全部4个浮点值,然后在另一个调用中写入。
AVX似乎支持多达16个256位寄存器,因此可以只计算从哪个寄存器(16个可能值)将值复制到哪里,这将非常快


也不要自己编写太多代码,只需编写一个简单的程序,为您打印预处理值,复制并粘贴到原始程序中即可:)

正如@RippeR所建议的,索引也是我的第一个猜测

我的第二个猜测是这样的:

switch(theChar){
 break; case   0: result[0] = 0; ... result[7] = 0;
 break; case   1: result[0] = 0; ... result[7] = 1;
 ...
 break; case 255: result[0] = 1; ... result[7] = 1;
}
这是冗长的代码,但你可以让预处理器来帮助你编写它

这可能更快的原因是开关应该变成一个跳转表,并且移动应该优化得很好

补充:如果您想知道预处理器如何提供帮助,请看以下内容:

#define FOO(x,i) result[i] = !!((x) & (1<<(i)))
#define BAR(x) break; case x: FOO(x,0);FOO(x,1); ... FOO(x,7)
switch(theChar){
 BAR(0);
 BAR(1);
 ...
 BAR(255);
}

#定义FOO(x,i)结果[i]=!!(十)及(1)这适用于32位或64位浮点数?32位浮点数为什么不在CPU reg 1中准备
1.0f
,在CPU reg 2中准备
0.0f
,以便您可以使用单个
MOV
指令来“创建”新的浮点数?
1
在32位浮点中是
0x3f80000
0
0x0
与您尝试的方法相比,我会尝试使用掩码/移位在每个位上循环,如果位“1”在相应的插槽中写入
0x3F800000
,否则会将数组置零。看起来不错,但要小心该字符,它可能会被签名,然后
myByte>>4
可能会引起麻烦谢谢,我一直忘记它t即使是位运算,它对负数的作用也不同。:)顺便说一句,你不能从一个变量寄存器中读取数据,至少如果有两个以上的变量寄存器,就不容易读取了choices@harold:您可能无法索引到SSE寄存器表,但索引到1级缓存的速度非常快。只需确保
preprocessingarray
已过度对齐即可。好吧,这是一个遗憾,但正如@BenVoigt所说,应该是这样的快速复制(据我所知,即使std::memcpy也被优化为使用sse 128位寄存器进行复制,这将导致人们相信只要
preprocessingarray
能够快速检索,那么它将是非常快速的操作。我认为这会更慢。让我们假设在这两种情况下,哪种情况下它的速度都一样快,在我的情况下,我执行2个memc。)py是通过SSE 128位寄存器优化的,在您的情况下,您可以执行8个单浮点数副本。我想说,如果您使用较长时间,从内存中复制的数据可能会保存在缓存中,比分配8个val更快