用于左包装字节元素的高效 sse shuffle mask 生成

2024-01-31

使用 sse 优化以下代码的有效方法是什么?

uint16_t change1= ... ;
uint8_t* pSrc   = ... ;
uint8_t* pDest  = ... ;

if(change1 & 0x0001) *pDest++ = pSrc[0];
if(change1 & 0x0002) *pDest++ = pSrc[1];
if(change1 & 0x0004) *pDest++ = pSrc[2];
if(change1 & 0x0008) *pDest++ = pSrc[3];

if(change1 & 0x0010) *pDest++ = pSrc[4];
if(change1 & 0x0020) *pDest++ = pSrc[5];
if(change1 & 0x0040) *pDest++ = pSrc[6];
if(change1 & 0x0080) *pDest++ = pSrc[7];

if(change1 & 0x0100) *pDest++ = pSrc[8];
if(change1 & 0x0200) *pDest++ = pSrc[9];
if(change1 & 0x0400) *pDest++ = pSrc[10];
if(change1 & 0x0800) *pDest++ = pSrc[11];

if(change1 & 0x1000) *pDest++ = pSrc[12];
if(change1 & 0x2000) *pDest++ = pSrc[13];
if(change1 & 0x4000) *pDest++ = pSrc[14];
if(change1 & 0x8000) *pDest++ = pSrc[15];

到目前为止,我正在使用一个相当大的查找表,但我真的想摆脱它:

SSE3Shuffle::Entry& e0 = SSE3Shuffle::g_Shuffle.m_Entries[change1];
_mm_storeu_si128((__m128i*)pDest, _mm_shuffle_epi8(*(__m128i*)pSrc, e0.mask));
pDest += e0.offset;

假设:

change1 = _mm_movemask_epi8(bytemask);
offset = popcnt(change1);

在大型缓冲区上,使用两次洗牌和 1 KiB 表仅比使用 1 次洗牌和 1MiB 表慢约 10%。我尝试通过前缀和和位旋转来生成洗牌掩码,大约是基于表的方法速度的一半 (解决方案使用pext/pdep没有探索过)。

减少表大小:对 2 KiB 表使用两次查找,而不是对 1 MiB 表进行 1 次查找。始终保留最上面的字节 - 如果要丢弃该字节,那么该位置是什么字节并不重要(低至 7 位索引或 1 KiB 表)。通过手动打包每个 16 位通道中的两个字节(减少到 216 字节表),进一步减少可能的组合。

以下示例使用以下方法从文本中去除空格SSE4.1。要是SSSE3那么可用blendv可以效仿。 64 位的一半通过重叠写入内存来重新组合,但它们可以在xmm注册(如在AVX2例子)。

#include <stdint.h>
#include <smmintrin.h> // SSE4.1

size_t despacer (void* dst_void, void* src_void, size_t length)
{
    uint8_t* src = (uint8_t*)src_void;
    uint8_t* dst = (uint8_t*)dst_void;

    if (length >= 16) {
        // table of control characters (space, tab, newline, carriage return)
        const __m128i lut_cntrl = _mm_setr_epi8(' ', 0, 0, 0, 0, 0, 0, 0, 0, '\t', '\n', 0, 0, '\r', 0, 0);

        // bits[4:0] = index -> ((trit_d * 0) + (trit_c * 9) + (trit_b * 3) + (trit_a * 1))
        // bits[15:7] = popcnt
        const __m128i sadmask = _mm_set1_epi64x(0x8080898983838181);

        // adding 8 to each shuffle index is cheaper than extracting the high qword
        const __m128i offset = _mm_cvtsi64_si128(0x0808080808080808);

        // shuffle control indices
        static const uint64_t table[27] = {
            0x0000000000000706, 0x0000000000070600, 0x0000000007060100, 0x0000000000070602,
            0x0000000007060200, 0x0000000706020100, 0x0000000007060302, 0x0000000706030200,
            0x0000070603020100, 0x0000000000070604, 0x0000000007060400, 0x0000000706040100,
            0x0000000007060402, 0x0000000706040200, 0x0000070604020100, 0x0000000706040302,
            0x0000070604030200, 0x0007060403020100, 0x0000000007060504, 0x0000000706050400,
            0x0000070605040100, 0x0000000706050402, 0x0000070605040200, 0x0007060504020100,
            0x0000070605040302, 0x0007060504030200, 0x0706050403020100
        };

        const uint8_t* end = &src[length & ~15];
        do {
            __m128i v = _mm_loadu_si128((__m128i*)src);
            src += 16;

            // detect spaces
            __m128i mask = _mm_cmpeq_epi8(_mm_shuffle_epi8(lut_cntrl, v), v);

            // shift w/blend: each word now only has 3 states instead of 4
            // which reduces the possiblities per qword from 128 to 27
            v = _mm_blendv_epi8(v, _mm_srli_epi16(v, 8), mask);

            // extract bitfields describing each qword: index, popcnt
            __m128i desc = _mm_sad_epu8(_mm_and_si128(mask, sadmask), sadmask);
            size_t lo_desc = (size_t)_mm_cvtsi128_si32(desc);
            size_t hi_desc = (size_t)_mm_extract_epi16(desc, 4);

            // load shuffle control indices from pre-computed table
            __m128i lo_shuf = _mm_loadl_epi64((__m128i*)&table[lo_desc & 0x1F]);
            __m128i hi_shuf = _mm_or_si128(_mm_loadl_epi64((__m128i*)&table[hi_desc & 0x1F]), offset);

            // store an entire qword then advance the pointer by how ever
            // many of those bytes are actually wanted. Any trailing
            // garbage will be overwritten by the next store.
            // note: little endian byte memory order
            _mm_storel_epi64((__m128i*)dst, _mm_shuffle_epi8(v, lo_shuf));
            dst += (lo_desc >> 7);
            _mm_storel_epi64((__m128i*)dst, _mm_shuffle_epi8(v, hi_shuf));
            dst += (hi_desc >> 7);
        } while (src != end);
    }

    // tail loop
    length &= 15;
    if (length != 0) {
        const uint64_t bitmap = 0xFFFFFFFEFFFFC1FF;
        do {
            uint64_t c = *src++;
            *dst = (uint8_t)c;
            dst += ((bitmap >> c) & 1) | ((c + 0xC0) >> 8);
        } while (--length);
    }

    // return pointer to the location after the last element in dst
    return (size_t)(dst - ((uint8_t*)dst_void));
}

尾循环是否应该向量化或使用cmov留给读者作为练习。当输入不可预测时,无条件/无分支地写入每个字节的速度很快。


Using AVX2使用寄存器内表生成洗牌控制掩码仅比使用大型预计算表慢一点。

#include <stdint.h>
#include <immintrin.h>

// probably needs improvment...
size_t despace_avx2_vpermd(const char* src_void, char* dst_void, size_t length)
{
    uint8_t* src = (uint8_t*)src_void;
    uint8_t* dst = (uint8_t*)dst_void;

    const __m256i lut_cntrl2    = _mm256_broadcastsi128_si256(_mm_setr_epi8(' ', 0, 0, 0, 0, 0, 0, 0, 0, '\t', '\n', 0, 0, '\r', 0, 0));
    const __m256i permutation_mask = _mm256_set1_epi64x( 0x0020100884828180 );
    const __m256i invert_mask = _mm256_set1_epi64x( 0x0020100880808080 ); 
    const __m256i zero = _mm256_setzero_si256();
    const __m256i fixup = _mm256_set_epi32(
        0x08080808, 0x0F0F0F0F, 0x00000000, 0x07070707,
        0x08080808, 0x0F0F0F0F, 0x00000000, 0x07070707
    );
    const __m256i lut = _mm256_set_epi32(
        0x04050607, // 0x03020100', 0x000000'07
        0x04050704, // 0x030200'00, 0x0000'0704
        0x04060705, // 0x030100'00, 0x0000'0705
        0x04070504, // 0x0300'0000, 0x00'070504
        0x05060706, // 0x020100'00, 0x0000'0706
        0x05070604, // 0x0200'0000, 0x00'070604
        0x06070605, // 0x0100'0000, 0x00'070605
        0x07060504  // 0x00'000000, 0x'07060504
    );

    // hi bits are ignored by pshufb, used to reject movement of low qword bytes
    const __m256i shuffle_a = _mm256_set_epi8(
        0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x07, 0x16, 0x25, 0x34, 0x43, 0x52, 0x61, 0x70,
        0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x07, 0x16, 0x25, 0x34, 0x43, 0x52, 0x61, 0x70
    );

    // broadcast 0x08 then blendd...
    const __m256i shuffle_b = _mm256_set_epi32(
        0x08080808, 0x08080808, 0x00000000, 0x00000000,
        0x08080808, 0x08080808, 0x00000000, 0x00000000
    );

    for( uint8_t* end = &src[(length & ~31)]; src != end; src += 32){
        __m256i r0,r1,r2,r3,r4;
        unsigned int s0,s1;

        r0 = _mm256_loadu_si256((__m256i *)src); // asrc

        // detect spaces
        r1 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(lut_cntrl2, r0), r0);

        r2 = _mm256_sad_epu8(zero, r1);
        s0 = (unsigned)_mm256_movemask_epi8(r1);
        r1 = _mm256_andnot_si256(r1, permutation_mask);

        r1 = _mm256_sad_epu8(r1, invert_mask); // index_bitmap[0:5], low32_spaces_count[7:15]

        r2 = _mm256_shuffle_epi8(r2, zero);

        r2 = _mm256_sub_epi8(shuffle_a, r2); // add space cnt of low qword
        s0 = ~s0;

        r3 = _mm256_slli_epi64(r1, 29); // move top part of index_bitmap to high dword
        r4 = _mm256_srli_epi64(r1, 7); // number of spaces in low dword 

        r4 = _mm256_shuffle_epi8(r4, shuffle_b);
        r1 = _mm256_or_si256(r1, r3);

        r1 = _mm256_permutevar8x32_epi32(lut, r1);
        s1 = _mm_popcnt_u32(s0);
        r4 = _mm256_add_epi8(r4, shuffle_a);
        s0 = s0 & 0xFFFF; // isolate low oword

        r2 = _mm256_shuffle_epi8(r4, r2);
        s0 = _mm_popcnt_u32(s0);

        r2 = _mm256_max_epu8(r2, r4); // pin low qword bytes

        r1 = _mm256_xor_si256(r1, fixup);

        r1 = _mm256_shuffle_epi8(r1, r2); // complete shuffle mask

        r0 = _mm256_shuffle_epi8(r0, r1); // despace!

        _mm_storeu_si128((__m128i*)dst, _mm256_castsi256_si128(r0));
        _mm_storeu_si128((__m128i*)&dst[s0], _mm256_extracti128_si256(r0,1));
        dst += s1;
    }
    // tail loop
    length &= 31;
    if (length != 0) {
        const uint64_t bitmap = 0xFFFFFFFEFFFFC1FF;
        do {
            uint64_t c = *src++;
            *dst = (uint8_t)c;
            dst += ((bitmap >> c) & 1) | ((c + 0xC0) >> 8);
        } while (--length);
    }
    return (size_t)(dst - ((uint8_t*)dst_void));
}

对于后代,1 KiB 版本(生成表格留给读者作为练习)。

static const uint64_t table[128] __attribute__((aligned(64))) = {
    0x0706050403020100, 0x0007060504030201, ..., 0x0605040302010700, 0x0605040302010007 
};
const __m128i mask_01 = _mm_set1_epi8( 0x01 );

__m128i vector0 = _mm_loadu_si128((__m128i*)src);
__m128i vector1 = _mm_shuffle_epi32( vector0, 0x0E );

__m128i bytemask0 = _mm_cmpeq_epi8( ???, vector0); // detect bytes to omit

uint32_t bitmask0 = _mm_movemask_epi8(bytemask0) & 0x7F7F;
__m128i hsum = _mm_sad_epu8(_mm_add_epi8(bytemask0, mask_01), _mm_setzero_si128());

vector0 = _mm_shuffle_epi8(vector0, _mm_loadl_epi64((__m128i*) &table[(uint8_t)bitmask0]));
_mm_storel_epi64((__m128i*)dst, vector0);
dst += (uint32_t)_mm_cvtsi128_si32(hsum);

vector1 = _mm_shuffle_epi8(vector1, _mm_loadl_epi64((__m128i*) &table[bitmask0 >> 8]));
_mm_storel_epi64((__m128i*)dst, vector1);
dst += (uint32_t)_mm_cvtsi128_si32(_mm_unpackhi_epi64(hsum, hsum));

https://github.com/InstLatx64/AVX512_VPCOMPRESSB_Emu https://github.com/InstLatx64/AVX512_VPCOMPRESSB_Emu有一些基准。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用于左包装字节元素的高效 sse shuffle mask 生成 的相关文章

  • Visual Studio 2012 本机 C++ DLL x86 编译

    我最近将我的工具集从 Win 7 x86 Visual Studio 2010 升级到 Win 8 x64 Visual Studio 2012 但是 现在我的本机 C dll 编译为 x64 而不是 x86 除了将代码移至新操作系统并将其
  • 将 pandas 数据帧拆分为子数据帧列表的最快方法

    我有一个大数据框df我有完整的清单indices中的独特元素df index 我现在想创建一个由元素索引的所有子数据帧的列表indices 具体来说 list df df loc x for x in indices 运行这个命令需要很长时
  • 在单个 mongodb 查询中查找并计数

    我的文档看起来像这样 id ObjectId 572c4bffd073dd581edae045 name What s New in PHP 7 description PHP 7 is the first new major versio
  • linq2sql,存储库模式 - 如何从两个或多个表查询数据?

    我使用存储库模式 和 linq2sql 作为数据访问 并拥有例如 ProductsRep 和 CustomersRep 在非常简单的场景中 数据库有两个表 产品 产品 ID 客户 ID 产品名称 日期 和顾客 客户 ID 名字 姓氏 每个存
  • .pdbs 会减慢发布应用程序的速度吗?

    如果 dll 中包含 pdb 程序调试 文件 则行号将出现在引发的任何异常的堆栈跟踪中 这会影响应用程序的性能吗 这个问题与发布与调试 即优化 无关 这是关于拥有 pdb 文件的性能影响 每次抛出异常时都会读取 pdb 文件吗 加载程序集时
  • 即使在急切加载之后,belongs_to 关联也会单独加载

    我有以下关联 class Picture lt ActiveRecord Base belongs to user end class User lt ActiveRecord Base has many pictures end 在我的
  • 哪些属性有助于运行时 .Net 性能?

    我正在寻找可用于通过向加载器 JIT 编译器或 ngen 提供提示来确保 Net 应用程序获得最佳运行时性能的属性 例如我们有可调试属性 http msdn microsoft com en us library k2wxda47 aspx
  • 过度使用委托对性能来说是一个坏主意吗? [复制]

    这个问题在这里已经有答案了 考虑以下代码 if IsDebuggingEnabled instance Log GetDetailedDebugInfo GetDetailedDebugInfo 可能是一个昂贵的方法 因此我们只想在调试模式
  • 使用 g++ 5.3.1 编译的程序运行速度比使用 g++ 4.8.4 编译的相同程序慢 3 倍,相同的命令

    最近 我开始使用 Ubuntu 16 04 和 g 5 3 1 并检查我的程序是否运行慢3倍 在此之前我使用过 Ubuntu 14 04 g 4 8 4 我用相同的命令构建它 CFLAGS std c 11 Wall O3 我的程序包含循环
  • 为 PostgreSQL 查询选择正确的索引

    简化表 CREATE TABLE products product no integer PRIMARY KEY sales integer status varchar 16 category varchar 16 CREATE INDE
  • R、Rcpp 与 Armadillo 中矩阵 rowSums() 与 colSums() 的效率

    背景 来自 R 编程 我正在扩展到 C C 形式的编译代码Rcpp 作为循环交换 以及一般的 C C 效果的实践练习 我实现了 R 的等效项rowSums and colSums 矩阵的函数Rcpp 我知道它们以 Rcpp 糖的形式存在 并
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 使用 AVX/AVX2 转置 8x8 浮点

    转置 8x8 矩阵可以通过制作四个 4x4 矩阵并对每个矩阵进行转置来实现 这不是我想要的 在另一个问题中 一个答案给出了解决方案 https stackoverflow com a 2518670 4144148x8 矩阵只需要 24 条
  • NHibernate - CreateCriteria 与 CreateAlias

    假设以下场景 class Project public Job Job class Job public Name 假设我想使用 Criteria API 搜索其 Job 名称为 sumthing 的所有项目 我可以使用 CreateAli
  • 为什么X86中没有NAND、NOR和XNOR指令?

    它们是您可以在计算机上执行的最简单的 指令 之一 它们是我亲自实施的第一个指令 执行 NOT AND x y 会使执行时间和依赖链长度和代码大小加倍 BMI1 引入了 andnot 这是一个有意义的补充 是一个独特的操作 为什么不是这个问题
  • 如何检查设备是否“快”足够

    我找不到更好的措辞来回答我的问题 在我的应用程序中的某个时刻 我设置了一些非常密集的动画 事实是 在高端设备上 动画运行流畅且赏心悦目 另一方面 我测试的一款低端设备在制作动画时的性能非常糟糕 为了将用户体验放在第一位 我想在计算能力足够的
  • 模块化算术和 NTT(有限域 DFT)优化

    我想使用 NTT 进行快速平方 参见快速大数平方计算 https stackoverflow com q 18465326 2521214 但即使对于非常大的数字 结果也很慢 超过 12000 位 所以我的问题是 有没有办法优化我的 NTT
  • 汇编器8086将32位数字除以16位数字

    我尝试将 32 位数字除以 16 位数字 例如 10000000h 除以 2000h 根据我尝试做的设计除以 右 4 位数字除以除数 然后左 4 位数字除以除数 这是我的代码 DATA num dd 10000000h divisor dw
  • Grub 和进入实模式(低级汇编语言编程)

    我一直在开发一个玩具操作系统 并一直使用 grub 作为我的引导加载程序 最近尝试使用 VGA 时 我发现无法使用硬件中断 我发现这是因为我被 grub 置于保护模式 有人知道如何在不删除 grub 的情况下回到实模式吗 如果您使用 GRU
  • 当前的 x86 架构是否支持非临时加载(来自“正常”内存)?

    我知道有关此主题的多个问题 但是 我没有看到任何明确的答案或任何基准测量 因此 我创建了一个处理两个整数数组的简单程序 第一个数组a非常大 64 MB 第二个数组b很小 无法放入 L1 缓存 程序迭代a并将其元素添加到相应的元素中b在模块化

随机推荐