我想使用SIMD指令将字符串加速浮点(此处为long double
,扩展精度IEEE 754)。
std::numeric_limits< long double >::digits10 == 18
对于练习而言,只需阅读18个重要char
s(即首先非'0'
之后的那些),制作significand
- 二元 - 来自它们的编码十进制值(9字节“整数”),计算整数exponent
值(10的幂,从点位置,也许,指数部分),最后通过significand * 10^exponent
操作融合。< / p>
significand
:
1。)逐个收集重要char
并更新64位无符号值(首先是零初始化)significand = significand * 10 + (c - '0');
(它不应该克服(std::numeric_limits< std::int64_t >::max() - nextc) / 10
(带符号的64位)在每一步都是故意的)。它可以使用fild qword ptr ...
指令加载。缺点是需要使用慢速64位整数运算。
2.。)首先(最多)将18个重要char
收集到小数组中并将它们融合成二进制编码的十进制形式,以使用fbld tbyte ...
指令将它们转换为内部x87表示。下行最后损失了几位(1E18 vs 9223372036854775807~ = 9.2E18)。
我的可行实施(与std::istream
结果的相对差异大约是5E-18):
template< typename F >
void read_float(F & result)
{
static_assert(std::is_floating_point< F >::value, "!");
char c = *cursor;
std::uint8_t significand[18];
auto s = significand;
std::int32_t after = 0;
std::int32_t before = 0;
char sign = c;
switch (c) {
case '-' :
case '+' :
c = *++cursor;
break;
default : {
break;
}
}
[&]
{
bool d = false;
for (;;) {
switch (c) {
case '.' :
before = 1;
break;
case '0' ... '9' : {
if (c != '0') {
d = true;
}
if (0 < before) {
++before;
}
if (d) {
*s++ = (c - '0');
if (s == significand + sizeof significand) {
std::int32_t a = 0;
for (;;) {
switch ((c = *++cursor)) {
case '0' ... '9' :
++a;
break;
case '.' : {
after = a;
break;
}
default : {
if ((before == 0) && (after == 0)) {
after = a;
}
return;
}
}
}
}
}
break;
}
default : {
if (!d) {
*s++ = 0;
}
return;
}
}
c = *++cursor;
}
}();
if (0 < before) {
after -= (before - 1);
}
std::uint32_t exponent = 0;
switch (c) {
case 'e' :
case 'E' : {
c = *++cursor;
char esign = c;
switch (c) {
case '-' :
case '+' :
c = *++cursor;
break;
}
[&]
{
for (;;) {
switch (c) {
case '0' ... '9' :
exponent = (exponent << 3) + (exponent << 1) + (c - '0');
break;
default : {
return;
}
}
c = *++cursor;
}
}();
if (esign == '-') {
after -= exponent;
} else {
after += exponent;
}
break;
}
default : {
break;
}
}
alignas(32) std::uint8_t bcd[10] = {};
std::uint32_t b = 0;
do {
--s;
if ((b % 2) == 0) {
bcd[b / 2] = *s;
} else {
bcd[b / 2] |= (*s << 4);
}
++b;
} while (s != significand);
if (sign == '-') {
bcd[9] = (1 << 7);
}
asm(
"fldl2t\n"
"fildl %[exp10]\n"
"fmulp\n"
"fld %%st\n"
"frndint\n"
"fxch\n"
"fsub %%st(1), %%st\n"
"f2xm1\n"
"fld1\n"
"faddp\n"
"fscale\n"
"fstp %%st(1)\n"
"fbld %[tbyte]\n"
"fmulp\n"
: "=t"(result)
: [exp10]"m"(after), [tbyte]"m"(bcd)
: "memory", "st(1)", "st(2)"
);
}
对于f2xm1
预先计算的10的幂(对于扩展精度为15位指数),使用约〜9.8k长度的查找表可以避免真正取幂(fscale
,long double
),但是有利它的使用是可以讨论的。
3.)读取有效字节的8字节块并对它们应用位操作。然后按照以前的方式将它们组装成BCD。下行是64位整数运算,但可能更好,然后在上面的情况下逐字循环。
std::uint32_t atoi(const char s[])
{
using c8 = char [8];
static c8 z = {'0', '0', '0', '0', '0', '0', '0', '0'};
auto string = reinterpret_cast< const std::uint64_t & >(*s) - reinterpret_cast< const std::uint64_t & >(z);
string = (((string >> 4) & 0x00F000F000F000F0LL) | (string & 0x000F000F000F000FLL));
c8 & c = reinterpret_cast< c8 & >(string);
c[1] = c[2];
c[2] = c[4];
c[3] = c[6];
return reinterpret_cast< const std::uint32_t & >(c);
};
auto bcd = atoi("01234567");
要生成小变量输入,请使用以下函数:
char input[1 << 24];
auto cursor = input;
auto size = []
{
char x[] = "+1.5E+1";
for (auto s : {"", "-", "+"}) {
auto y = x;
while (*s) {
*y++ = *s++;
}
for (auto m : {"1", "1.", ".1", "1.1"}) {
auto z = y;
while (*m) {
*z++ = *m++;
}
cursor = std::copy(x, z, cursor);
*cursor++ = ' ';
for (auto E : {'E', 'e'}) {
*z = E;
for (auto S : {"", "-", "+"}) {
auto t = z + 1;
if (*S) {
*t++ = *S++;
}
*t++ = '1';
assert(t < x + sizeof x);
cursor = std::copy(x, t, cursor);
*cursor++ = ' ';
}
}
}
}
return cursor - input;
}();
有没有办法使用SSE 4.2或其他方法加速从字符串到64位整数值的转换?