ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

c – 快速计算两个数组之间相等的字节数

2019-09-30 19:06:48  阅读:208  来源: 互联网

标签:c-3 sse2 c sse simd


参见英文答案 > Can counting byte matches between two strings be optimized using SIMD?                                    3个
我写了函数int compare_16bytes(__ m128i lhs,__ m128i rhs),以便使用SSE指令比较两个16字节数:此函数返回执行比较后相等的字节数.

现在我想使用上面的函数来比较任意长度的两个字节数组:长度可能不是16字节的倍数,所以我需要处理这个问题.我怎样才能完成下面这个功能的实现?我怎样才能改进下面的功能?

int fast_compare(const char* s, const char* t, int length)
{
    int result = 0;

    const char* sPtr = s;
    const char* tPtr = t;

    while(...)
    {
        const __m128i* lhs = (const __m128i*)sPtr;
        const __m128i* rhs = (const __m128i*)tPtr;

        // compare the next 16 bytes of s and t
        result += compare_16bytes(*lhs,*rhs);

        sPtr += 16;
        tPtr += 16;
    }

    return result;
}

解决方法:

正如@Mysticial在上面的评论中所说,做垂直比较和求和,然后在主循环结束时水平求和:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <emmintrin.h>

// reference implementation
int fast_compare_ref(const char *s, const char *t, int length)
{
    int result = 0;
    int i;

    for (i = 0; i < length; ++i)
    {
        if (s[i] == t[i])
            result++;
    }
    return result;
}

// optimised implementation
int fast_compare(const char *s, const char *t, int length)
{
    int result = 0;
    int i;

    __m128i vsum = _mm_set1_epi32(0);
    for (i = 0; i < length - 15; i += 16)
    {
        __m128i vs, vt, v, vh, vl, vtemp;

        vs = _mm_loadu_si128((__m128i *)&s[i]); // load 16 chars from input
        vt = _mm_loadu_si128((__m128i *)&t[i]);
        v = _mm_cmpeq_epi8(vs, vt);             // compare
        vh = _mm_unpackhi_epi8(v, v);           // unpack compare result into 2 x 8 x 16 bit vectors
        vl = _mm_unpacklo_epi8(v, v);
        vtemp = _mm_madd_epi16(vh, vh);         // accumulate 16 bit vectors into 4 x 32 bit partial sums
        vsum = _mm_add_epi32(vsum, vtemp);
        vtemp = _mm_madd_epi16(vl, vl);
        vsum = _mm_add_epi32(vsum, vtemp);
    }

    // get sum of 4 x 32 bit partial sums
    vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 8));
    vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4));
    result = _mm_cvtsi128_si32(vsum);

    // handle any residual bytes ( < 16)
    if (i < length)
    {
        result += fast_compare_ref(&s[i], &t[i], length - i);
    }

    return result;
}

// test harness
int main(void)
{
    const int n = 1000000;
    char *s = malloc(n);
    char *t = malloc(n);
    int i, result_ref, result;

    srand(time(NULL));

    for (i = 0; i < n; ++i)
    {
        s[i] = rand();
        t[i] = rand();
    }

    result_ref = fast_compare_ref(s, t, n);
    result = fast_compare(s, t, n);

    printf("result_ref = %d, result = %d\n", result_ref, result);;

    return 0;
}

编译并运行上面的测试工具:

$gcc -Wall -O3 -msse3 fast_compare.c -o fast_compare
$./fast_compare
result_ref = 3955, result = 3955
$./fast_compare
result_ref = 3947, result = 3947
$./fast_compare
result_ref = 3945, result = 3945

注意,在上面的SSE代码中有一个可能非显而易见的技巧,我们使用_mm_madd_epi16来解包并将16位0 / -1值累加到32位部分和.我们利用-1 * -1 = 1(当然0 * 0 = 0)这一事实 – 我们在这里并没有真正进行乘法,只需在一条指令中解包和求和.

更新:如下面的评论所述,这个解决方案并不是最优的 – 我只采用了一个相当优化的16位解决方案,并添加了8位到16位的解包,使其适用于8位数据.然而,对于8位数据,存在更有效的方法,例如,使用psadbw/_mm_sad_epu8.我将把这个答案留给后人,以及任何想要用16位数据做这种事情的人,但实际上其中一个不需要解压缩输入数据的答案应该被接受回答.

标签:c-3,sse2,c,sse,simd
来源: https://codeday.me/bug/20190930/1836512.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有