使用SIMD提高性能

时间:2018-01-25 11:52:59

标签: c++ x86 sse simd string-conversion

我想使用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长度的查找表可以避免真正取幂(fscalelong 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位整数值的转换?

0 个答案:

没有答案