? 辽宁福彩35选7:苏州银行五点半下班吗 - HOME-辽宁福35选7走势图
  • 动次创始人杨宝成:“格”斗快手、抖音 2018-12-20
  • 上海合作组织在京举行2018迎新招待会 2018-12-20
  • 辽宁福彩35选7:SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。

    辽宁福35选7走势图 www.rfwpk.com 小说:苏州银行五点半下班吗作者:龙文马更新时间:2019-03-24字数:75225

    “三个吗?那么既然如此的话本田你和我们去,杏子和爷爷麻烦你们去找刘皓?!庇蜗匪档?,进入虚拟游戏可是很危险的,而且去海马娱乐集团的话还可能会遇到什么麻烦,让杏子跟着来的话可是很危险,还不如让她去找刘皓。

    孩子内向怎么办

    正在思考时,一名侍卫进来禀报:“陛下,杨相国来了,说有要事禀报”
    面对红衣凌厉的一击蕴藏的恐怖气劲就算苍穹血尊也不敢大意,没办法玄阴杀葵星可是不能用常理来衡量的,尤其是他察觉到红衣这一招的厉害,连忙一掌迎了上去。

    “帅哥,看来你的运气是不错啊,让姐姐陪你玩怎么样?”美女荷官抛了一个媚眼说道。

      在做图像处理的SSE优化时,也会经常遇到一些小的过程、数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家。

    一、快速求对数运算

      对数运算在图像处理中也是个经?;嵊龅降墓?,特备是在一些数据压缩和空间转换时常?;嵊玫?,而且是个比较耗时的函数,标准的SSE库里并没有提供该函数的实现,如果需要高精度的SSE版本,网络上已经有了,参考:https://github.com/to-miz/sse_mathfun_extension/blob/master/sse_mathfun.h,这个的精度和标准库的精度基本一致了,稍作整理后的代码如下:

    //    对数函数的SSE实现,高精度版
    inline __m128 _mm_log_ps(__m128 x)
    {
        static const __declspec(align(16)) int _ps_min_norm_pos[4] = { 0x00800000, 0x00800000, 0x00800000, 0x00800000 };
        static const __declspec(align(16)) int _ps_inv_mant_mask[4] = { ~0x7f800000, ~0x7f800000, ~0x7f800000, ~0x7f800000 };
        static const __declspec(align(16)) int _pi32_0x7f[4] = { 0x7f, 0x7f, 0x7f, 0x7f };
        static const __declspec(align(16)) float _ps_1[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
        static const __declspec(align(16)) float _ps_0p5[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
        static const __declspec(align(16)) float _ps_sqrthf[4] = { 0.707106781186547524f, 0.707106781186547524f, 0.707106781186547524f, 0.707106781186547524f };
        static const __declspec(align(16)) float _ps_log_p0[4] = { 7.0376836292E-2f, 7.0376836292E-2f, 7.0376836292E-2f, 7.0376836292E-2f };
        static const __declspec(align(16)) float _ps_log_p1[4] = { -1.1514610310E-1f, -1.1514610310E-1f, -1.1514610310E-1f, -1.1514610310E-1f };
        static const __declspec(align(16)) float _ps_log_p2[4] = { 1.1676998740E-1f, 1.1676998740E-1f, 1.1676998740E-1f, 1.1676998740E-1f };
        static const __declspec(align(16)) float _ps_log_p3[4] = { -1.2420140846E-1f, -1.2420140846E-1f, -1.2420140846E-1f, -1.2420140846E-1f };
        static const __declspec(align(16)) float _ps_log_p4[4] = { 1.4249322787E-1f, 1.4249322787E-1f, 1.4249322787E-1f, 1.4249322787E-1f };
        static const __declspec(align(16)) float _ps_log_p5[4] = { -1.6668057665E-1f, -1.6668057665E-1f, -1.6668057665E-1f, -1.6668057665E-1f };
        static const __declspec(align(16)) float _ps_log_p6[4] = { 2.0000714765E-1f, 2.0000714765E-1f, 2.0000714765E-1f, 2.0000714765E-1f };
        static const __declspec(align(16)) float _ps_log_p7[4] = { -2.4999993993E-1f, -2.4999993993E-1f, -2.4999993993E-1f, -2.4999993993E-1f };
        static const __declspec(align(16)) float _ps_log_p8[4] = { 3.3333331174E-1f, 3.3333331174E-1f, 3.3333331174E-1f, 3.3333331174E-1f };
        static const __declspec(align(16)) float _ps_log_q1[4] = { -2.12194440e-4f, -2.12194440e-4f, -2.12194440e-4f, -2.12194440e-4f };
        static const __declspec(align(16)) float _ps_log_q2[4] = { 0.693359375f, 0.693359375f, 0.693359375f, 0.693359375f };
    
        __m128 one = *(__m128*)_ps_1;
        __m128 invalid_mask = _mm_cmple_ps(x, _mm_setzero_ps());
        /* cut off denormalized stuff */
        x = _mm_max_ps(x, *(__m128*)_ps_min_norm_pos);
        __m128i emm0 = _mm_srli_epi32(_mm_castps_si128(x), 23);
    
        /* keep only the fractional part */
        x = _mm_and_ps(x, *(__m128*)_ps_inv_mant_mask);
        x = _mm_or_ps(x, _mm_set1_ps(0.5f));
    
        emm0 = _mm_sub_epi32(emm0, *(__m128i *)_pi32_0x7f);
        __m128 e = _mm_cvtepi32_ps(emm0);
        e = _mm_add_ps(e, one);
    
        __m128 mask = _mm_cmplt_ps(x, *(__m128*)_ps_sqrthf);
        __m128 tmp = _mm_and_ps(x, mask);
        x = _mm_sub_ps(x, one);
        e = _mm_sub_ps(e, _mm_and_ps(one, mask));
        x = _mm_add_ps(x, tmp);
    
        __m128 z = _mm_mul_ps(x, x);
        __m128 y = *(__m128*)_ps_log_p0;
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p1);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p2);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p3);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p4);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p5);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p6);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p7);
        y = _mm_mul_ps(y, x);
        y = _mm_add_ps(y, *(__m128*)_ps_log_p8);
        y = _mm_mul_ps(y, x);
    
        y = _mm_mul_ps(y, z);
        tmp = _mm_mul_ps(e, *(__m128*)_ps_log_q1);
        y = _mm_add_ps(y, tmp);
        tmp = _mm_mul_ps(z, *(__m128*)_ps_0p5);
        y = _mm_sub_ps(y, tmp);
        tmp = _mm_mul_ps(e, *(__m128*)_ps_log_q2);
        x = _mm_add_ps(x, y);
        x = _mm_add_ps(x, tmp);
        x = _mm_or_ps(x, invalid_mask); // negative arg will be NAN
    
        return x;
    }

      看上去有一大堆代码,不过实测这个的速度越是标准库(本文是指启动增强指令集选项设置为:未设置,设计上编译器在此种情况下会自动设置为SSE2增强,这可以从反编译logf函数看到,因此,这里的速度比较还不是和纯Fpu实现的比较)的2倍,如果稍微降低点精度,比如_ps_log_p5到_ps_log_p8之间的代码,还能提高点速度。

      另外,在很多场合我们还可以使用另外一种低精度的log函数,其C代码如下所示:

    //https://stackoverflow.com/questions/9411823/fast-log2float-x-implementation-c
    inline float IM_Flog(float val)
    {
        union
        {
            float val;
            int x;
        } u = { val };
        float log_2 = (float)(((u.x >> 23) & 255) - 128);
        u.x &= ~(255 << 23);
        u.x += (127 << 23);
        log_2 += ((-0.34484843f) * u.val + 2.02466578f) * u.val - 0.67487759f;
        return log_2 * 0.69314718f;
    }

      这个函数大概有小数点后2位精度。

      上述代码大约也是标准函数的2倍速度左右。但是上述函数是可以向量化的,我们来尝试实现。

      我们首先来看联合体,其实这个东西就是两个东西占同一个内存空间,然后外部用不同的规则去读取他,在SSE里,有着丰富的cast函数,他也是干这个事情的,比如这里的联合体就可以用_mm_castps_si128来转换,而实际上这个Intrinsic并不会产生任何的汇编语句。

      那么后面的那些移位、或运算、非运算、加减乘除之类的就是直接翻译了,毫无难处,完整的代码如下所示:

    inline __m128 _mm_flog_ps(__m128 x)
    {
        __m128i I = _mm_castps_si128(x);
        __m128 log_2 = _mm_cvtepi32_ps(_mm_sub_epi32(_mm_and_si128(_mm_srli_epi32(I, 23), _mm_set1_epi32(255)), _mm_set1_epi32(128)));
        I = _mm_and_si128(I, _mm_set1_epi32(-2139095041));        //    255 << 23
        I = _mm_add_epi32(I, _mm_set1_epi32(1065353216));        //    127 << 23
        __m128 F = _mm_castsi128_ps(I);
        __m128 T = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(-0.34484843f), F), _mm_set1_ps(2.02466578f));
        T = _mm_sub_ps(_mm_mul_ps(T, F), _mm_set1_ps(0.67487759f));
        return _mm_mul_ps(_mm_add_ps(log_2, T), _mm_set1_ps(0.69314718f));
    }

      经过实测,这个速度可以达到标准库的7到8倍的优势。

    二、快速求幂运算

      一般图像编程中有log出现的地方就会有exp出现,因此exp的优化也尤为重要,同样在sse_mathfun.h中也有exp的优化(还有sin,cos的SSE优化语句呢),我这里就不贴那个的代码了,我们同样关注下用联合体实现的近似快速算法,其C代码如下所示:

    inline float IM_Fexp(float Y)            
    {
        union
        {
            double Value;
            int X[2];
        } V;
        V.X[1] = (int)(Y * 1512775 + 1072632447 + 0.5F);
        V.X[0] = 0;
        return (float)V.Value;
    }

      测试这个和标准的exp库函数速度居然差不多,不晓得为啥,但我们来试下他的SSE优化版本了。

     V.X[1] = (int)(Y * 1512775 + 1072632447 + 0.5F);这句话没啥难度,直接翻译就可以了,注意几个强制类型转化就可以了,如下所示:
    __m128i T = _mm_cvtps_epi32(_mm_add_ps(_mm_mul_ps(Y, _mm_set1_ps(1512775)), _mm_set1_ps(1072632447)));

      由于我们想一次性处理4个float类型的数据,因此也就需要4个union的空间,这样就需要2个__m128i变量来保存数据,每个XMM寄存器的数据应该分别为:

      T1 ? ?0 ? ?T0 ? ?0 ? ? ? ? + ? ??T3 ? 0 ? ?T2 ? ?0?   ?。ǜ呶?---》低位)

      这个可以使用unpack来实现,具体如下:

        __m128i TL = _mm_unpacklo_epi32(_mm_setzero_si128(), T);
        __m128i TH = _mm_unpackhi_epi32(_mm_setzero_si128(), T);

      最后我们认为__m128i里的数据是double数据,直接一个cast就可以了,然后因为我们只需要单精度的数据,再使用_mm_cvtpd_ps将double转换为float类型,注意这个时候还需要将他们连接再一起形成一个完整的__m128变量,最终的代码如下:

    inline __m128 _mm_fexp_ps(__m128 Y)
    {
        __m128i T = _mm_cvtps_epi32(_mm_add_ps(_mm_mul_ps(Y, _mm_set1_ps(1512775)), _mm_set1_ps(1072632447)));
        __m128i TL = _mm_unpacklo_epi32(_mm_setzero_si128(), T);
        __m128i TH = _mm_unpackhi_epi32(_mm_setzero_si128(), T);
        return _mm_movelh_ps(_mm_cvtpd_ps(_mm_castsi128_pd(TL)), _mm_cvtpd_ps(_mm_castsi128_pd(TH)));
    }

      实测这个的提速大概有10倍。

      如果要求double的exp,其SSE代码你会了吗?

    三、pow函数的优化。

      一种常用的近似算法如下所示:

    inline float IM_Fpow(float a, float b) 
    {
        union
        {
            double Value;
            int X[2];
        } V;
        V.X[1] = (int)(b * (V.X[1] - 1072632447) + 1072632447);
        V.X[0] = 0;
        return (float)V.Value;
    }

      和exp很类似,留给有兴趣的人自己实现。

    四:两个求倒数函数的优化误区

      SSE提供了连个快速求倒数的函数,_mm_rcp_ps,_mm_rsqrt_ps,他们都是近似值,只有12bit的精度,如果想通过他们得到精确的倒数值,需要牛顿 - 拉弗森方法,比如利用_mm_rcp_ps求精确倒数的代码如下:

    __forceinline __m128 _mm_prcp_ps(__m128 a)
    {
        __m128 rcp = _mm_rcp_ps(a);            //    此函数只有12bit的精度.                    
        return _mm_sub_ps(_mm_add_ps(rcp, rcp), _mm_mul_ps(a, _mm_mul_ps(rcp, rcp)));    //    x1 = x0 * (2 - d * x0) = 2 * x0 - d * x0 * x0,使用牛顿 - 拉弗森方法这种方法可以提高精度到23bit
    }

      但是实测这个还不如直接用_mm_div_ps的速度,即使是下面的函数:

    __forceinline __m128 _mm_fdiv_ps(__m128 a, __m128 b)
    {
        return _mm_mul_ps(a, _mm_rcp_ps(b));
    }

      似乎速度也不够好,而且精度还低了。

      特别低,如果使用_mm_rcp_ps和_mm_rsqrt_ps联合求近似sqrt,即如下代码,速度好像还慢了,真搞不明白为什么。

    __forceinline __m128 _mm_fsqrt_ps(__m128 a)
    {
        return _mm_rcp_ps(_mm_rsqrt_ps(a));
    }

    五:避免除数为0时的获得无效效果

      在SSE指令中,没有提供整数的除法指令,不知道这是为什么,所以整数除法一般只能借用浮点版本的指令,同时,除法存在的一个问题就是如果除数为0,可能会触发异常,不过SSE在这种情况下不会抛出异常,但是我们应该避免,避免的方式有很多,比如判断如果除数为0,就做特殊处理,或者如果除数为0就除以一个很小的数,不过大部分的需求是,除数为0,则返回0,此时就可以使用下面的SSE指令代替_mm_div_ps:

    //    四个浮点数的除法a/b,如果b中某个分量为0,则对应位置返回0值
    inline __m128 _mm_divz_ps(__m128 a, __m128 b)
    {
        __m128 Mask = _mm_cmpeq_ps(b, _mm_setzero_ps());
        return _mm_blendv_ps(_mm_div_ps(a, b), _mm_setzero_ps(), Mask);
    }

      即先把除数和0进行比较,然后在把_mm_div_ps的返回值中,除数为0的部分用0代替,当然,这会带来一定的性能下降。

      实际上,利用位运算,上述代码还可以稍作优化如下:

    inline __m128 _mm_divz_ps(__m128 a, __m128 b)
    {
        return _mm_and_ps(_mm_div_ps(a, b), _mm_cmpneq_ps(b, _mm_setzero_ps()));
    }

    六、将4个32位整数转换为字节数并保存

      很多情况下,我们的算法计算需要将字节类型扩展到32位,计算完成后再保存的字节数据中,这个时候使用SSE的话是没有直接的指令的,不过SSE4提供了一条_mm_cvtsi128_si32指令,可以将XMM寄存器的4个int32数据转换为4个字节数据并保存到一个普通的寄存器中,因此,我们只要调用几个合适的pack语句就可以实现这个功能了,如下所示:

    //    将4个32位整形变量数据打包到4个字节数据中
    inline void _mm_storesi128_4char(unsigned char *Dest, __m128i P)
    {
        __m128i T = _mm_packs_epi32(P, P);
        *((int *)Dest) = _mm_cvtsi128_si32(_mm_packus_epi16(T, T));
    }

    七、读取12个字节数到一个XMM寄存器中

      XMM寄存器是16个字节大小的,而且SSE的很多计算是以4的整数倍字节位单位进行的,但是在图像处理中,70%情况下处理的是彩色的24位图像,即一个像素占用3个字节,如果直接使用load指令载入数据,一次性可载入5加1/3个像素,这对算法的处理是很不方便的,一般状况下都是加载4个像素,即12个字节,然后扩展成16个字节(给每个像素增加一个Alpha值),我们当然可以直接使用load加载16个字节,然后每次跳过12个字节在进行load加载,但是其实也可以使用下面的加载12个字节的函数:

    //    从指针p处加载12个字节数据到XMM寄存器中,寄存器最高32位清0
    inline __m128i _mm_loadu_epi96(const __m128i * p)
    {
        return _mm_unpacklo_epi64(_mm_loadl_epi64(p), _mm_cvtsi32_si128(((int *)p)[2]));
    }

      保存当然也可以只保存XMM的低12位:

    //     将寄存器Q的低位12个字节数据写入到指针P中。
    inline void _mm_storeu_epi96(__m128i *P, __m128i Q)
    {
        _mm_storel_epi64(P, Q);
        ((int *)P)[2] = _mm_cvtsi128_si32(_mm_srli_si128(Q, 8));
    }

      不过实际测试,可能还是直接使用_mm_loadu_si128和_mm_storeu_si128快点,但是要注意循环的结束为止。

    八、整除255

      整除255在图像处理是非常非常常见的操作,前面说了,SSE里没有整数除法的指令,如果转换到浮点在除那就慢的死了,一般情况下如果要求精度不高可以使用右移8位实现,如果非要精确值可以使用如下的C代码:

    //    计算整数整除255的四舍五入结果。
    inline int IM_Div255(int V)
    {
        return (((V >> 8) + V + 1) >> 8);        //    似乎V可以是负数
    }

      翻译为SSE为:

    //    返回16位无符号整形数据整除255后四舍五入的结果: x = ((x + 1) + (x >> 8)) >> 8:
    inline __m128i _mm_div255_epu16(__m128i x)
    {
        return _mm_srli_epi16(_mm_adds_epu16(_mm_adds_epu16(x, _mm_set1_epi16(1)), _mm_srli_epi16(x, 8)), 8);
    }

    九、求XMM寄存器内所有元素的累加值

      这也是个常见的需求,我们可能把某个结果重复的结果保存在寄存器中,最后结束时在把寄存器中的每个元素想加,你当然可以通过访问__m128i变量的内部的元素实现,但是据说这样会降低循环内的优化,一种方式是直接用SSE指令实现,比如对8个有符号的short类型的相加代码如下所示:

    //    8个有符号的16位的数据相加的和。
    //    https://stackoverflow.com/questions/31382209/computing-the-inner-product-of-vectors-with-allowed-scalar-values-0-1-and-2-usi/31382878#31382878
    inline int _mm_hsum_epi16(__m128i V)                            //    V7 V6 V5 V4 V3 V2 V1 V0
    {
        //    V = _mm_unpacklo_epi16(_mm_hadd_epi16(V, _mm_setzero_si128()), _mm_setzero_si128());    也可以用这句,_mm_hadd_epi16似乎对计算结果超出32768能获得正确结果
        __m128i T = _mm_madd_epi16(V, _mm_set1_epi16(1));   //    V7+V6                        V5+V4            V3+V2    V1+V0
        T = _mm_add_epi32(T, _mm_srli_si128(T, 8));            //    V7+V6+V3+V2                    V5+V4+V1+V0        0        0        
        T = _mm_add_epi32(T, _mm_srli_si128(T, 4));            //    V7+V6+V3+V2+V5+V4+V1+V0        V5+V4+V1+V0        0        0    
        return _mm_cvtsi128_si32(T);                        //    提取低位    
    }

      对于epi32或者ps类型也是使用类似的过程的。

    10、求16个字节的最小值

      比如我们要求一个字节序列的最小值,我们肯定会使用_mm_min_epi8这样的函数保存每隔16个字节的最小值,这样最终我们得到16个字节的一个XMM寄存器,整个序列的最小值肯定在这个16个字节里面,这个时候我们可以巧妙的借用下面的SSE语句得到这16个字节的最小值:

    //    求16个字节数据的最小值, 只能针对字节数据。
    inline int _mm_hmin_epu8(__m128i a)
    {
        __m128i L = _mm_unpacklo_epi8(a, _mm_setzero_si128());
        __m128i H = _mm_unpackhi_epi8(a, _mm_setzero_si128());
        return _mm_extract_epi16(_mm_min_epu16(_mm_minpos_epu16(L), _mm_minpos_epu16(H)), 0);
    }

      SSE3提供了_mm_minpos_epu16函数,他能获取8个无符号数的的最小值及其最小值的索引,放置在寄存器的低16和低32位,我们把字节数据扩展到16位,然后在通过两次比较就可以获得相应的最小值了。

      那如果是求最大值呢,可惜SSE没有提供_mm_maxpos_epu16函数,但是也无妨,稍微修改下上面的代码就可以了,如下所示:

    //    求16个字节数据的最大值, 只能针对字节数据。
    inline int _mm_hmax_epu8(__m128i a)
    {
        __m128i b = _mm_subs_epu8(_mm_set1_epi8(255), a);
        __m128i L = _mm_unpacklo_epi8(b, _mm_setzero_si128());
        __m128i H = _mm_unpackhi_epi8(b, _mm_setzero_si128());
        return 255 - _mm_extract_epi16(_mm_min_epu16(_mm_minpos_epu16(L), _mm_minpos_epu16(H)), 0);
    }

    十一、其他一些优化技巧

      在//www.alfredklomp.com/programming/sse-intrinsics/?以及?//www.itkeyword.com/doc/0326039046115117x827/c++-sse2-intrinsics-comparing-unsigned-integers等网站上还有很多参考的资料,希望大家自己去学习下。

    编辑:丁扁

    发布:2019-03-24 05:14:28

    当前文章://www.rfwpk.com/news/201810/23/content_89361.html

    克州零距离微信平台 男生做什么工作有前途 小鹅通 2018信用卡推广技巧 2018干什么最赚钱农村 2018年赚钱项目排行 成功淘宝客推广方法 打码兼职怎么做

    54790 17720 93748 47312 89847 5233997073 47682 43779

    我要说两句: (0人参与)

    发布
  • 动次创始人杨宝成:“格”斗快手、抖音 2018-12-20
  • 上海合作组织在京举行2018迎新招待会 2018-12-20