C++ C+中的有效整数层函数+;
我想定义一个有效的整数楼层函数,即从float或double到负无穷大执行截断的转换 我们可以假设这些值不会发生整数溢出。到目前为止,我有几个选择C++ C+中的有效整数层函数+;,c++,performance,x86-64,processing-efficiency,floor,C++,Performance,X86 64,Processing Efficiency,Floor,我想定义一个有效的整数楼层函数,即从float或double到负无穷大执行截断的转换 我们可以假设这些值不会发生整数溢出。到目前为止,我有几个选择 铸造到int;这需要对负值进行特殊处理,因为强制转换将向零截断 I= int(F); if (I < 0 && I != F) I--; 通过大移位将其转换为int以获得正值(对于大值,这可能返回错误的结果) 转换为int是出了名的慢。if测试也是如此。我还没有为楼层功能计时,但看到一些帖子声称它也很慢 在速度、准确度
- 铸造到int;这需要对负值进行特殊处理,因为强制转换将向零截断
I= int(F); if (I < 0 && I != F) I--;
- 通过大移位将其转换为int以获得正值(对于大值,这可能返回错误的结果)
Performing 10000000 times:
simple cast convert 3186 ms i.e. fi = (f*65536);
fistp convert 3031 ms i.e. fi = FISTToInt(f*65536);
xs_ToFix 622 ms i.e. fi = xs_Fix<16>::ToFix(f);
执行10000000次:
简单转换3186毫秒,即fi=(f*65536);
fistp转换3031毫秒,即fi=FISTINT(f*65536);
xs_-ToFix 622 ms,即fi=xs_-Fix::ToFix(f);
简要说明“幻数”方法的工作原理:
“基本上,为了添加两个浮点数,处理器将数字的小数点“排列”起来,这样就可以轻松地添加位。它通过“规范化”数字来实现这一点,以便保留最重要的位,即较小的数字“规范化”以匹配较大的数字。因此,“幻数”的原理xs_CRoundToInt()使用的转换是这样的:我们将一个足够大的浮点数(一个大到只有小数点以下才有有效数字的数字,后面没有有效数字)添加到要转换的浮点数上,以便:(a)处理器将该数字标准化为其等效整数;(b)将这两个数字相加不会擦除您试图转换的数字中的整数有效位(即XX00+00YY=XXYY)。”
引用取自同一网页。如果您批量执行此操作,编译器可能会自动矢量化它,如果您知道您正在执行的操作。例如,下面是一个小型实现,它在GCC上自动矢量化浮点到整数的转换:
#include <cmath>
// Compile with -O3 and -march=native to see autovectorization
__attribute__((optimize("-fno-trapping-math")))
void testFunction(float* input, int* output, int length) {
// Assume the input and output are aligned on a 32-bit boundary.
// Of course, you have to ensure this when calling testFunction, or else
// you will have problems.
input = static_cast<float*>(__builtin_assume_aligned(input, 32));
output = static_cast<int*>(__builtin_assume_aligned(output, 32));
// Also assume the length is a multiple of 32.
if (length & 31) __builtin_unreachable();
// Do the conversion
for (int i = 0; i < length; ++i) {
output[i] = floor(input[i]);
}
}
如果您的目标不支持AVX512,它仍将使用SSE4.1指令(假设您有这些指令)进行自动矢量化。这是带有-O3-msse4.1
的输出:
testFunction(float*,int*,int):
测试edx,edx
jle.L1
shr-edx,2
异或eax,eax
sal rdx,4岁
.L3:
roundps xmm0,xmmwordptr[rdi+rax],1
cvttps2dq xmm0,xmm0
movaps XMMWORD PTR[rsi+rax],xmm0
加上rax,16
cmp-rax,rdx
jne.L3
.L1:
ret
转换为int是出了名的慢
也许您从x86-64开始就一直生活在一块岩石下,或者在x86上错过了这一事实
SSE/SSE2有一条使用截断进行转换的指令(而不是默认舍入模式)。ISA有效地支持此操作,正是因为在实际代码库中使用C语义的转换并不少见。x86-64代码使用SSE/SSE2 XMM寄存器进行标量FP数学运算,而不是x87,因为这一点和其他一些因素使其更为有效。即使是现代32位代码也使用XMM寄存器进行标量数学运算
在为x87编译时(没有SSE3fisttp
),编译器过去必须将x87舍入模式更改为截断,将FP存储更改为内存,然后再次更改舍入模式。(然后从内存重新加载整数,通常是从堆栈上的本地重新加载整数,如果对其进行进一步操作的话。)x87对此非常糟糕
是的,速度非常慢,例如在2006年,当@Kirjain的答案中的链接被写入时,如果您仍然有一个32位CPU,或者正在使用x86-64 CPU运行32位代码
不直接支持使用截断或默认(最接近)以外的舍入模式进行转换,并且直到SSE4.1
roundps
/roundpd
您最好的选择是像@Kirjain的答案中的数字魔术
这里有一些不错的技巧,但只适用于double
->32位整数。如果您有float
,则不太值得扩展到double
或者更常见的情况是,简单地添加一个较大的数值以触发舍入,然后再减去它以返回到原始范围。这可以用于浮点
,而不会扩展到双精度
,但我不确定让下限
工作有多容易
无论如何,这里最明显的解决方案是
\u mm256\u floor\u ps()
和\u mm256\u cvtps\u epi32
(vroundps
和vcvtps2dq
)。非AVX版本可以与SSE4.1一起使用。
我不确定我们是否能做得更好;如果您有一个庞大的数组要处理(并且无法将此工作与其他工作交叉进行),您可以将MXCSR舍入模式设置为“朝向-Inf”(地板),只需使用vcvtps2dq
(使用当前舍入模式)。然后将其设置回原位。但最好是缓存阻止转换,或者在生成数据时动态执行转换,可能是从需要将FP舍入模式设置为默认最接近模式的其他FP计算中生成的
roundps
/pd/ss/sd在英特尔CPU上是2个uop,但在AMD Ryzen上只有1个uop(每128位通道)。cvtps2dq
也是1个uop。压缩双精度->整数转换还包括一个随机数。标量FP->整数转换(复制到整数寄存器)通常也需要额外的uop
这就是roo
Performing 10000000 times:
simple cast 2819 ms i.e. i = (long)f;
xs_ToInt 1242 ms i.e. i = xs_ToInt(f); //numerically same as above
bit-twiddle(full) 1093 ms i.e. i = BitConvertToInt(f); //rounding from Fluid
fistp 676 ms i.e. i = FISTToInt(f); //Herf, et al x86 Assembly rounding
bit-twiddle(limited) 623 ms i.e. i = FloatTo23Bits(f); //Herf, rounding only in the range (0...1]
xs_CRoundToInt 609 ms i.e. i = xs_CRoundToInt(f); //rounding with "magic" numbers
Performing 10000000 times:
simple cast convert 3186 ms i.e. fi = (f*65536);
fistp convert 3031 ms i.e. fi = FISTToInt(f*65536);
xs_ToFix 622 ms i.e. fi = xs_Fix<16>::ToFix(f);
#include <cmath>
// Compile with -O3 and -march=native to see autovectorization
__attribute__((optimize("-fno-trapping-math")))
void testFunction(float* input, int* output, int length) {
// Assume the input and output are aligned on a 32-bit boundary.
// Of course, you have to ensure this when calling testFunction, or else
// you will have problems.
input = static_cast<float*>(__builtin_assume_aligned(input, 32));
output = static_cast<int*>(__builtin_assume_aligned(output, 32));
// Also assume the length is a multiple of 32.
if (length & 31) __builtin_unreachable();
// Do the conversion
for (int i = 0; i < length; ++i) {
output[i] = floor(input[i]);
}
}
#include <cmath>
auto floor_(float const x) noexcept
{
int const t(x);
return t - (t > x);
}
#include <assert.h>
#include <cmath>
#include <stddef.h>
#include <stdint.h>
#define ALIGNMENT alignof(max_align_t)
using std::floor;
// Compiled with: -std=c++17 -Wall -Wextra -Wpedantic -Wconversion -fno-trapping-math -O -march=cannonlake -mprefer-vector-width=512
void testFunction(const float in[], int32_t out[], const ptrdiff_t length)
{
static_assert(sizeof(float) == sizeof(int32_t), "");
assert((uintptr_t)(void*)in % ALIGNMENT == 0);
assert((uintptr_t)(void*)out % ALIGNMENT == 0);
assert((size_t)length % (ALIGNMENT/sizeof(int32_t)) == 0);
alignas(ALIGNMENT) const float* const input = in;
alignas(ALIGNMENT) int32_t* const output = out;
// Do the conversion
for (int i = 0; i < length; ++i) {
output[i] = static_cast<int32_t>(floor(input[i]));
}
}
.L7:
vrndscaleps zmm0, ZMMWORD PTR [rdi+rax], 1
vcvttps2dq zmm0, zmm0
vmovdqu32 ZMMWORD PTR [rsi+rax], zmm0
add rax, 64
cmp rax, rcx
jne .L7
test rdx, rdx
jle .LBB0_14
cmp rdx, 63
ja .LBB0_6
xor eax, eax
jmp .LBB0_13
.LBB0_6:
mov rax, rdx
and rax, -64
lea r9, [rax - 64]
mov r10, r9
shr r10, 6
add r10, 1
mov r8d, r10d
and r8d, 1
test r9, r9
je .LBB0_7
mov ecx, 1
sub rcx, r10
lea r9, [r8 + rcx]
add r9, -1
xor ecx, ecx
.LBB0_9: # =>This Inner Loop Header: Depth=1
vrndscaleps zmm0, zmmword ptr [rdi + 4*rcx], 9
vrndscaleps zmm1, zmmword ptr [rdi + 4*rcx + 64], 9
vrndscaleps zmm2, zmmword ptr [rdi + 4*rcx + 128], 9
vrndscaleps zmm3, zmmword ptr [rdi + 4*rcx + 192], 9
vcvttps2dq zmm0, zmm0
vcvttps2dq zmm1, zmm1
vcvttps2dq zmm2, zmm2
vmovups zmmword ptr [rsi + 4*rcx], zmm0
vmovups zmmword ptr [rsi + 4*rcx + 64], zmm1
vmovups zmmword ptr [rsi + 4*rcx + 128], zmm2
vcvttps2dq zmm0, zmm3
vmovups zmmword ptr [rsi + 4*rcx + 192], zmm0
vrndscaleps zmm0, zmmword ptr [rdi + 4*rcx + 256], 9
vrndscaleps zmm1, zmmword ptr [rdi + 4*rcx + 320], 9
vrndscaleps zmm2, zmmword ptr [rdi + 4*rcx + 384], 9
vrndscaleps zmm3, zmmword ptr [rdi + 4*rcx + 448], 9
vcvttps2dq zmm0, zmm0
vcvttps2dq zmm1, zmm1
vcvttps2dq zmm2, zmm2
vcvttps2dq zmm3, zmm3
vmovups zmmword ptr [rsi + 4*rcx + 256], zmm0
vmovups zmmword ptr [rsi + 4*rcx + 320], zmm1
vmovups zmmword ptr [rsi + 4*rcx + 384], zmm2
vmovups zmmword ptr [rsi + 4*rcx + 448], zmm3
sub rcx, -128
add r9, 2
jne .LBB0_9
test r8, r8
je .LBB0_12
.LBB0_11:
vrndscaleps zmm0, zmmword ptr [rdi + 4*rcx], 9
vrndscaleps zmm1, zmmword ptr [rdi + 4*rcx + 64], 9
vrndscaleps zmm2, zmmword ptr [rdi + 4*rcx + 128], 9
vrndscaleps zmm3, zmmword ptr [rdi + 4*rcx + 192], 9
vcvttps2dq zmm0, zmm0
vcvttps2dq zmm1, zmm1
vcvttps2dq zmm2, zmm2
vcvttps2dq zmm3, zmm3
vmovups zmmword ptr [rsi + 4*rcx], zmm0
vmovups zmmword ptr [rsi + 4*rcx + 64], zmm1
vmovups zmmword ptr [rsi + 4*rcx + 128], zmm2
vmovups zmmword ptr [rsi + 4*rcx + 192], zmm3
.LBB0_12:
cmp rax, rdx
je .LBB0_14
.LBB0_13: # =>This Inner Loop Header: Depth=1
vmovss xmm0, dword ptr [rdi + 4*rax] # xmm0 = mem[0],zero,zero,zero
vroundss xmm0, xmm0, xmm0, 9
vcvttss2si ecx, xmm0
mov dword ptr [rsi + 4*rax], ecx
add rax, 1
cmp rdx, rax
jne .LBB0_13
.LBB0_14:
pop rax
vzeroupper
ret
.LBB0_7:
xor ecx, ecx
test r8, r8
jne .LBB0_11
jmp .LBB0_12