[OPENPPP2] ssea 算法详解(中文)

ssea 算法详解(中文)

语言切换: English Version | README (EN) | README (CN)

本文档覆盖 ppp/cryptography/ssea 中的每一个 算法(在本库中对齐镜像):

标量实现、SIMD 实现(或"为何不适用 SIMD"的形式化论证)、所选择方法的可行性证明、

实测基准、以及边界与错误语义。

所有非标量路径均已验证与标量参考在每个输入长度 1...100000 上逐字节一致

(见 tests/ 测试套件)。


1. shuffle_data / unshuffle_data

算法

复制代码
for i in [0, size):  j = (i ^ key) % size;  swap(data[i], data[j])

unshuffle_data逆序 运行同一循环;swap 序列是自身的逆

(每个 swap 是对合,逆序运行即还原组合)。

标量实现

shuffle_data_scalar / unshuffle_data_scalar --- 1:1 移植。

SIMD 可行性论证(不适用)

  • 目标索引 j = (i ^ key) % size 依赖 i 的 32 位模除(运行时模数) ---
    不存在闭式向量公式。
  • 每次 swap 访问两个数据依赖的随机内存位置 。SIMD 面向连续流;
    随机置换受内存延迟约束,任何 SIMD gather/scatter 原语仍按元素
    逐一加载/存储 --- 相对标量 swap 无吞吐收益。
  • 复杂度 O(n) + 随机访存,无可开发的数据级并行。
    结论:标量是正确选择,SIMD 无法帮助。

优化(标量)

2 的幂 sizej = (i ^ key) & (size - 1) 以 AND 取代 32 位 div

(20-30 周期)。2^k 尺寸实测 1.8-2.9x。

尺寸 标量 优化 加速
256 839 MB/s 1505 MB/s 1.79x
4096 853 MB/s 2468 MB/s 2.89x
65535(非 2 幂) 789 MB/s 834 MB/s 1.06x

2. delta_encode(SSE2 约 8x,AVX2 约 10x)

算法

复制代码
out[0] = in[0] - kf;   out[i] = in[i] - in[i-1]      (mod 256)

每个输出字节只依赖当前与前一个输入字节 --- 数据并行模式。

SSE2 实现(16 字节/轮,约 6 条指令)

复制代码
a      = loadu(in + i)                          // [in[i]..in[i+15]]
prev   = pslldq(a, 1) | prev_last               // [in[i-1], in[i]..in[i+14]]
out    = psubb(a, prev)                          // 模 256 减法
prev_last = srli_si128(a, 15)                   // 下一块需要的 in[i+15]

prev_last 首块仅保留 kf低字节pand 0xFF ---

cvtsi32_si128 写入 4 字节;负值/大 kf 时掩码是必须的)。

证明

pslldq(a,1)[j] = a[j-1],故 prev[j] = in[i+j-1]psubb 为模 256 减法,

与标量 Byte 运算一致。首块以 in[-1] ≡ kf (mod 256) 匹配

out[0] = in[0] - kf

边界语义

空/空指针 → 返回 0。尾(< 16 字节)标量处理,prev_byte = in[i-1]

由主循环延续。


3. delta_decode(SSE2 约 8x,AVX2 约 8.7x)

算法

复制代码
out[0] = in[0] + kf;   out[i] = out[i-1] + in[i]      (mod 256)

这是前缀和 --- 串行依赖链,但可并行化。

SSE2 实现(Hillis-Steele 扫描,16 lane)

复制代码
p = a
p = p + pslldq(p, 1)      // 跨度 1
p = p + pslldq(p, 2)      // 跨度 3
p = p + pslldq(p, 4)      // 跨度 7
p = p + pslldq(p, 8)      // 跨度 15 -> p[j] = sum(in[0..j])
out = p + carry           // carry = 上一块末字节
carry = out[15]           // srli_si128(15) + cvtsi128_si32 提取

方向: 扫描需要"前一个"字节(低地址方向),必须用 pslldq(slli)。

psrldq 会读到"下一个"字节,产生右向和 --- 错误的前缀。

证明

模加法满足结合律:块内前缀 p[j] = Σ in[0..j] 以对数深度

(4 轮,跨度 1+2+4+8=15)精确计算(模 256),跨块进位

out[15] 精确链接各块,与标量递推一致。

边界语义

同 encode:空/空指针 → 0;尾标量,acc 延续。


4. base94_encode(SSSE3+ pshufb:1.4-4.4x)

算法

复制代码
b = (byte - kf) & 0xFF
b <  93 : 输出 1 字符  0x20 + b
b >= 93 : 输出 2 字符  0x20 + (b/93 + 92), 0x20 + (b%93)

说明(免除法 SIMD 形式)

  • b ∈ [93,255]b/93 ∈ {1,2} --- 逃逸首字符恒为 '}' (0x7D) 或
    '~' (0x7E):

    复制代码
    hi = 0x7D + (b >= 186)
    lo = b - 93*(b>=93) - 93*(b>=186) + 0x20      // == b%93 + 0x20
  • 每字节输出顺序为 [hi, lo](首字符在前,余数在后)。

  • 每字节输出长度 L[i] = 1 + (b>=93) 使输出位置数据依赖
    变长展开。

为何需要 SSSE3 pshufb(可行性证明)

展开是字节 gather:out[pos[i]] = lo[i]pos[i] 为 L 的前缀和。

纯 SSE2 没有字节级任意重排指令pshufb 是 SSSE3 引入);替代方案

可证明更差:

  • 逐孔移位链:O(孔数) 次 psrldq + mask + or ≈ 24+ ops/16B 且掩码
    数据依赖 --- 比标量还差;
  • SWAR 乘法压缩:~20+ ops/16B + 掩码相关的魔术常数 --- 复杂且更慢。
    选择: 以 8 位展开掩码索引预计算 256-entry 表 + 一条 pshufb
    为本库采用的方法(计算核保持 SSE2)。

实现(8 输入字节/轮)

复制代码
b8   = loadl(in+i);  b8 = psubb(b8, kf)         // b = byte - kf
biased = pxor(b8, 0x80)                          // 无符号比较偏置
L1 = pcmpgtb(biased, 92^0x80)                    // b >= 93
L2 = pcmpgtb(biased, 185^0x80)                   // b >= 186
lo = b8 - (L1&93) - (L2&93) + 0x20               // b%93 + 0x20
hi = 0x7D + (L2&1)                               // '}' 或 '~'
ex = punpcklbw(hi, lo)                           // [hi0,lo0,hi1,lo1,...]
mask = movemask(L1) & 0xFF                       // 位 i = 第 i 对为 2 字符
out16 = pshufb(ex, ENCODE_TABLE[mask])           // gather
store 16 字节;  op += 8 + popcount(mask)

ENCODE_TABLE[m] = 每对的源索引:2 字符对取 [hi, lo],1 字符对取 [lo]

(其余为垃圾,按长度计数截断)。

边界语义

两遍(先长度后写入)保持缓存友好;尾 < 8 字节标量。

空/空指针 → 错误语义(0/nullptr)。


5. base94_decode(SSSE3+ pshufb:1.3-3.8x)

算法

复制代码
ch >= 0x20, offset = ch - 0x20 <= 94 (否则报错)
offset >= 93  -> 逃逸对: v = (offset-92)*93 + next_offset (校验)
输出字节 = v + kf

说明

  • 逃逸起始 iff ch >= 0x7D;配对值 v = 93 + 93*(ch>=0x7E) + next - 0x20
  • 每个逃逸对只删除其续字符 ;输出位置 = i - (之前的逃逸数) ---
    与 encode 镜像的变长压缩(同一 256-entry 表,索引用 2*i 匹配
    punpcklbw(v,v) 重复布局)。
  • 校验(字符 < 0x20、> 0x7E、续字符偏移 > 93、'~' + 续偏移 > 69 →
    v > 255)以偏置字节 pcmpgtb 向量化;任一违规回退标量参考
    错误语义精确保留。

跨块处理

位置 7 的逃逸消费字节 8(下一块首字节)。删除掩码进位:

del = (esc << 1) | carry;标量尾从 i + carry 开始跳过被消费字节。

边界语义

输入 < 16 字节 → 直接标量参考(SIMD 开销大于收益)。截断逃逸、

字母表外字符、值溢出均与标量一致地返回失败。


6. base94_decimal(整数 <-> Base94 字符串)

算法

  • uint64 -> 字符串:反复 /94%94(≤ 11 位,反转)。
  • 字符串/字节 -> uint64n = n*94 + 数字 链。

SIMD 可行性论证(不适用)

  • 串行依赖: 每一位依赖上一位的余数/累加值 --- 无并行形式。
  • SSE2 无 64 位整数除法%94 所需)。
  • 数据极小: 全部输入/输出仅 8...11 字节;SIMD 初始化
    (加载/广播/查表)超过总工作量。
  • 无批量场景: 每包头部一次转换,无法摊薄。
    结论: 标量是唯一合理选择。已用边界值(0、1、93、94、94²±1、
    UINT64_MAX)、20 万次随机往返、错误路径验证。

7. random_next / lcgmod(PRNG)

算法

三步 LCG L(x) = 1103515245*x + 12345 (mod 2^32) 的 16 位折叠组合:

复制代码
result = ((t1>>16)&0x7FF)<<20 ^ ((t2>>16)&0x3FF)<<10 ^ ((t3>>16)&0x3FF)

SIMD 可行性论证(不适用)

  • 单次调用是单 seed 的 3 步串行折叠 --- 调用内无可并行内容。
  • 批量场景原则上存在(并行 LCG 跳步),但见 §8:生产调用模式
    kf = random_next(&kf) 以返回值覆盖 seed,序列成为非线性 fold 链
    跳步不适用。
    结论: 标量;批量层面的分析属于 §8。

8. masked_xor / masked_xor_random_next

masked_xor(定键)

对每个 32 位字异或常量 kf(尾:16/8 位)。

  • SSE2:16 字节一次 pxor(指令数约降 16 倍)。
  • 实测 0.65-0.96x --- MSVC /O2 已将标量循环自动向量化到同宽度,
    达到内存带宽(约 70 GB/s)。
    手动 SSE2 版本保留作可移植性参考,
    但 MSVC 下推荐标量。结论:编译器已覆盖,保持标量。

masked_xor_random_next(每字 LCG 密钥流)

复制代码
对每个 32 位字:  kf = random_next(&kf);  word ^= kf
尾 (16/8 位):    word ^= kf              (不再更新)

密钥流语义: random_next 将推进后的 seed 写入 *seed返回
折叠值
,随后该值被赋回 kf --- 覆盖了 seed 。因此密钥流是

k_{n+1} = fold(k_n) --- 非线性 fold 链 (fold 含移位/XOR)。

LCG 跳步(L3 幂)可证明无法重现此序列。故密钥流严格串行,无法并行。

实现: 标量密钥流生成 + SSE2 批量 XOR(每轮 4 字)。实测约 1.0x ---

密钥流占主导,XOR 批处理增益有限。

结论:可接受;对该精确语义,并行密钥流数学上不可能。

边界语义

零长度 → true;负长度 → false。尾键使用(末字后不更新)与标量完全一致;

已用 1...100000 全长度 + 多 kf 验证。


汇总表

# 算法 标量 SIMD 实测 选择的方法
1 shuffle/unshuffle ref 不适用(已论证) 2 幂 1.8-2.9x(AND) 标量 + 2 幂 AND
2 delta_encode ref SSE2 及以上(AVX2 最优) 约 8x,AVX2 10x SSE2+
3 delta_decode ref SSE2 及以上(AVX2 最优) 约 8x SSE2+
4 base94_encode ref/LUT SSSE3 及以上(AVX2 最优) 1.4-4.4x SSSE3+
5 base94_decode ref SSSE3 及以上(SSE4.1 最优) 1.3-3.8x SSSE3+
6 base94_decimal ref 不适用(已论证) --- 标量
7 random_next/lcgmod ref 不适用(已论证) --- 标量
8 masked_xor ref AVX2/标量 AVX2 2x;标量优于 128 位 AVX2 否则标量
9 masked_xor_random_next ref 密钥流标量 + XOR SSE2 约 1.0x 混合

正确性保证: 每条 SIMD 路径与标量参考在全部长度 1...100000、边界组合、

错误路径、canary(越界)检测、未对齐偏移、多 kf/key 下逐字节一致 ---

tests/ 测试套件。

相关推荐
碧海银沙音频科技研究院1 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
过期的秋刀鱼!1 小时前
学习曲线-过拟合和欠拟合要做什么以及原因
人工智能·python·深度学习·算法·机器学习·模型评估
m沐沐2 小时前
【自然语言处理】词向量转换与中文文本情感分类——从CountVectorizer到朴素贝叶斯
人工智能·算法·机器学习·自然语言处理·分类·中文分词·词向量转换
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章99-换背景
图像处理·人工智能·opencv·算法·计算机视觉
科学实验家2 小时前
01背包问题
算法
普通攻击往后拉2 小时前
Leetcode 448. 找到所有数组中消失的数字
算法·leetcode·职场和发展
mifengxing2 小时前
LeetCode 189 轮转数组|3种解法拆解,从暴力到O(1)原地最优解
数据结构·算法·leetcode
MIngYaaa5202 小时前
2026暑期牛客多校3 2026-7-24
数据结构·算法·000
geats人山人海3 小时前
算法基础第二讲 基础算法下
算法