simd实现条件分支的并行计算及其局限

simd实现条件分支的并行计算及其局限

anchor

anchor

南京大学 理论物理硕士

来自专栏 · 软硬件协同设计

凡是并行计算遇到一层条件嵌套就要打个对折,simd,simt都一样。。以前觉得cpu很弱,但是分支预测真的很强,只会做简单大规模数值计算某种意义上才弱。

目录

收起

引言

SIMD 介绍

简单分支和复杂嵌套分支的simd实现

1. 基础条件分支测试(basic.c)

代码结构

目的

2. 四层嵌套条件测试(four_layer.c)

代码结构

目的

3. 复杂条件与多操作测试(complex.c)

代码结构

目的

4. 关键设计思想

5. 性能差异根源

6. 总结

7. 性能数据汇总表

8. 加速比分析

9. 关键原因总结

10. 性能对比示意图

总结

源码地址

引言

我们在进行数值算法加速的时候经常使用到SIMD(单指令多数据),对于大规模的矩阵,张量计算,simd已经展现了其强大的并行处理能力,诸如在一个cycle内计算8个float的乘累加计算,但是这些算法它们的逻辑其实都是较为简单的,它们只是计算加减乘除等算术运算,没有复杂的分支结构。比如使用位运算来计算float的加减法。我们便思考,对于这些算法simd是否还能有较好的加速效果,本文就探讨这一话题。

SIMD 介绍

  1. 是什么:单指令多数据,一条指令同时处理多个数据(如同时算8个数加法)
  2. 作用:数据并行加速,适合图像/科学计算等批量操作
  3. 条件分支处理
    1. ✅ 可行:通过「掩码」选择不同结果(如条件满足选A,否则选B)
    2. ✅ 常用方法:预计算所有分支结果,最后用掩码混合
  4. 局限
    1. ❗ 深层嵌套会显著增加掩码生成与混合操作
    2. ❗ 分支稀疏(如仅5%数据满足条件)时加速比下降甚至劣化
  5. 适用性
    1. ✔️ 数据量大且条件可合并时有效(加速1.5x+)
    2. ❌ 复杂随机分支时可能比标量更慢(如百万级数据实测慢6%)

简单分支和复杂嵌套分支的simd实现

1. 基础条件分支测试(basic.c)

代码结构

// 标量版本
void scalar_nested_conditions(const float *a, const float *b, float *dst, int n) {
    for (...) {
        if (a[i] > b[i]) dst[i] = a + b; 
        else dst[i] = a - b;
    }
}

// SIMD版本
void simd_nested_conditions(const float *a, const float *b, float *dst, int n) {
    for (...) { // 每次处理8个元素
        __m256 va = _mm256_loadu_ps(a + i);  // 加载向量
        __m256 vb = _mm256_loadu_ps(b + i);
        __m256 mask = _mm256_cmp_ps(va, vb, _CMP_GT_OQ); // 生成条件掩码
        __m256 add = _mm256_add_ps(va, vb);  // 并行加法
        __m256 sub = _mm256_sub_ps(va, vb);  // 并行减法
        __m256 final = _mm256_blendv_ps(sub, add, mask); // 按掩码混合结果
        _mm256_storeu_ps(dst + i, final);    // 存储结果
    }
}

目的

验证 简单条件分支 的SIMD优化效果。通过向量化掩码操作(_mm256_cmp_ps + _mm256_blendv_ps)消除分支预测失败的开销,实现8路并行计算。


2. 四层嵌套条件测试(four_layer.c)

代码结构

// 标量版本(四层条件)
void scalar_nested_conditions(...) {
    for (...) {
        if (a > 10) {
            if (b < 5) {
                if (c == 3) {
                    if (d != 0) final = a + b;
                    else final = a - c;
                } else final = a * b;
            } else final = a / b;
        } else final = sqrt(a);
    }
}

// SIMD版本
void simd_nested_conditions(...) {
    // 生成四层条件掩码
    __m256 mask1 = _mm256_cmp_ps(va, ten, _CMP_GT_OQ);  // a > 10
    __m256 mask2 = _mm256_cmp_ps(vb, five, _CMP_LT_OQ); // b < 5
    __m256 mask3 = _mm256_cmp_ps(vc, three, _CMP_EQ_OQ);// c == 3
    __m256 mask4 = _mm256_cmp_ps(vd, zero, _CMP_NEQ_OQ);// d != 0

    // 逐层混合结果
    __m256 cond4 = _mm256_and_ps(mask1, _mm256_and_ps(mask2, ...));
    __m256 layer4 = _mm256_blendv_ps(sub, add, cond4);
    // ...(类似处理其他层)
}

目的

测试 多层嵌套条件分支 的优化效果。SIMD版本需通过逻辑与操作(_mm256_and_ps)组合多个掩码,并按层次混合结果。由于需要计算所有分支路径,指令数显著增加。


3. 复杂条件与多操作测试(complex.c)

代码结构

// 标量版本(含else if和复杂操作)
void scalar_nested_conditions(...) {
    for (...) {
        if (a > 4) {
            if (b < 5) {
                if (c == 10) {
                    if (d != 0) final = a + b;
                    else if (e < 7) final = a - e; // else-if分支
                    else final = a - c;
                } else if (f >= -2) final = a * f; // 第三层else-if
                // ...
            } else if (g > 0) final = a / g;      // 第二层else-if
            // ...
        } else if (h != 0) final = sqrt(a + h);    // 第一层else-if
        // ...
    }
}

// SIMD版本(预计算所有可能结果)
void simd_nested_conditions(...) {
    // 预计算所有分支结果
    __m256 add_ab = _mm256_add_ps(va, vb);
    __m256 sub_ae = _mm256_sub_ps(va, ve);
    __m256 sub_ac = _mm256_sub_ps(va, vc);
    // ...(共9种预计算结果)

    // 多层混合逻辑(从最内层开始)
    __m256 layer4_elseif = _mm256_blendv_ps(sub_ac, sub_ae, mask_e_lt_7);
    __m256 layer4 = _mm256_blendv_ps(layer4_elseif, add_ab, mask_d_ne_zero);
    // ...(共4层混合)
}

目的

验证 else-if分支和高延迟操作(除法、平方根) 的极端场景。SIMD版本需处理复杂的掩码组合关系,且预计算所有可能结果,导致指令数爆炸性增长。


4. 关键设计思想

1. SIMD消除分支预测
通过掩码(mask)和混合指令(blendv)替代条件跳转,避免分支预测失败导致的流水线清空。

// 标量分支
if (a > b) x = a + b; else x = a - b;

// SIMD等效
mask = compare(a, b);
x = blend(add(a,b), sub(a,b), mask);

2. 计算与选择分离
强制并行计算所有分支结果,最后通过掩码选择最终值。代价是计算冗余,但对简单条件可显著提升吞吐量。

3. 层次化掩码混合
对多层嵌套条件,从最内层开始逐层混合结果(见four_layer.c):

cond4 = mask1 & mask2 & mask3 & mask4;
layer4 = blend(sub, add, cond4);    // 第四层结果
layer3 = blend(mul, layer4, cond3); // 第三层使用第四层结果

5. 性能差异根源

测试案例SIMD优势场景SIMD劣势场景
basic.c简单条件,无复杂操作-
four_layer.c中等复杂度条件掩码组合开销增加
complex.c-高延迟操作+复杂掩码逻辑
  1. 指令吞吐量差异
    1. SIMD的blendv指令吞吐量为1周期/指令(Skylake架构),而标量的条件跳转在分支预测成功时接近0周期。
    2. 复杂条件导致SIMD需要更多blendv和逻辑运算指令。
  2. 高延迟操作影响
    _mm256_div_ps(除法)和_mm256_sqrt_ps(平方根)的延迟为10-20周期,且SIMD无法乱序执行隐藏延迟。
  3. 内存访问模式
    当数据规模超过L3缓存时,SIMD的8路并行会更快耗尽内存带宽,加剧性能下降。

6. 总结

通过这三个测试案例,可以看出: - SIMD在简单条件分支中优势显著(basic.c加速比8-33x), - 随着条件复杂度和计算操作延迟增加,收益逐渐消失(complex.c在1M数据后性能反降), - 分支预测和乱序执行使标量代码在复杂场景中更具韧性

7. 性能数据汇总表

测试案例数据规模标量版本耗时(ns)SIMD版本耗时(ns)加速比
basic.c1,0001,150.68207.335.55
10,00055,165.341,670.7033.01
100,000356,536.7812,426.5028.70
1,000,0003,818,940.21231,796.2616.47
10,000,00039,662,974.404,546,276.018.72
four_layer.c1,0001,362.54867.951.57
10,00013,559.036,078.752.23
100,00070,421.7836,736.891.92
1,000,000738,778.05489,409.951.51
10,000,0008,475,064.267,287,073.211.16
complex.c1,0002,077.731,763.251.18
10,00021,281.3812,238.881.74
100,000103,256.3566,761.811.55
1,000,0001,049,778.391,090,990.970.96
10,000,00011,033,407.3912,713,688.450.87

8. 加速比分析

  1. basic.c
    1. 最高加速比33.01(10k数据),SIMD优势显著。
    2. 随着数据规模增大,加速比逐渐降低,但仍保持较高水平。
    3. 原因:简单条件分支,SIMD并行度高(8路并行),且无复杂计算。
  2. four_layer.c
    1. 加速比峰值2.23(10k数据),之后逐渐降低。
    2. 在10M数据时加速比仅1.16,接近标量性能。
    3. 原因:四层嵌套条件导致SIMD需计算所有分支,混合操作引入额外开销。
  3. complex.c
    1. 加速比峰值1.74(10k数据),1M数据后SIMD性能反降。
    2. 10M数据时SIMD比标量慢13%(加速比0.87)。
    3. 原因:多层else if分支导致掩码逻辑复杂,SIMD需预计算更多结果,且存在高延迟操作(如除法、平方根)。

9. 关键原因总结

  1. 分支预测与乱序执行
    1. 标量代码可通过分支预测减少分支惩罚,SIMD则完全消除分支但需计算所有路径
    2. 当分支预测成功率较高时(如随机数据),标量性能更优。
  2. SIMD并行度与开销
    1. SIMD理论加速比8x,但实际受限于:
  • 混合操作(blendv)的指令开销。
  • 高延迟操作(如除法)的流水线阻塞。
  • 每个分支都需要预计算,计算冗余较大。
  1. 数据规模影响
    1. 小数据规模:SIMD优势明显(循环展开充分)。
    2. 大数据规模:内存带宽限制和缓存失效导致加速比下降。
  2. 操作复杂度
    1. 复杂条件(如complex.c)导致SIMD需处理更多掩码逻辑,指令数激增,最终性能可能劣于标量版本。

10. 性能对比示意图

basic.c        : ■■■■■■■■ (最高33x加速)
four_layer.c   : ■■■■     (峰值2.23x)
complex.c      : ■■       (后期反降)

总结

综上所述,SIMD在简单条件分支中优势显著,但随着条件复杂度增加,其收益逐渐被额外计算和指令开销抵消,最终可能劣于优化后的标量代码。

可以这样认为,条件分支每嵌套一层,并行度就要打对这,同时因为额外的blend计算,加速比进一步下降,基本上达到4层以上的嵌套,使用avx2来实现条件分支就没有优势了。

实际上可以采用标量和simd混合计算的方式来实现复杂的条件分支。

源码地址

郑安坤/simd_conditions

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

强化学习曾小健

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值