当前位置: 首页 > news >正文

【C++ SIMD】第4篇:条件分支与掩码操作(Windows/VS2022版)——以AVX为例

一、SIMD编程中的条件分支问题

在传统标量代码中,我们习惯使用if-else进行条件判断:

void scalar_conditional(float* arr, int n) {
    for (int i = 0; i < n; ++i) {
        if (arr[i] > 0) {
            arr[i] *= 2;
        }
    }
}

但在AVX向量化编程中,直接条件分支会引发两个关键问题:

  1. ​分支预测失败惩罚:当条件模式不规则时,流水线频繁刷新
  2. SIMD并行性破坏:8个float元素(256位寄存器)可能同时包含满足和不满足条件的元素

二、AVX掩码操作原理

2.1 核心思想

通过向量比较生成掩码(mask),使用位操作混合计算结果:

寄存器A: [ 1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0 ]
掩码   : [ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 ] 
结果   = 原始值 * (掩码 ? 2.0 : 1.0)

2.3 AVX掩码操作指令集

指令类别典型指令操作描述延迟周期
比较指令_mm256_cmp_ps生成全通道掩码3
混合指令_mm256_blendv_ps根据掩码选择通道2
位逻辑运算_mm256_and_ps/_mm256_or_ps掩码逻辑操作1
条件加载_mm256_maskload_ps根据掩码加载内存4
算术运算_mm256_mask_add_ps带掩码的加法(AVX-512)N/A

三、VS2022实现示例

3.1 向量化条件处理

#include <immintrin.h>

void avx_conditional(float* arr, int n) {
    const __m256 zero = _mm256_setzero_ps();
    const __m256 mul = _mm256_set1_ps(2.0f);
    
    for (int i = 0; i < n; i += 8) {
        __m256 data = _mm256_loadu_ps(arr + i);
        
        // 生成比较掩码(arr[i] > 0)
        __m256 mask = _mm256_cmp_ps(data, zero, _CMP_GT_OQ);
        
        // 计算两种可能的结果
        __m256 res_true = _mm256_mul_ps(data, mul);
        __m256 res_false = data;
        
        // 根据掩码混合结果
        __m256 result = _mm256_blendv_ps(res_false, res_true, mask);
        
        _mm256_storeu_ps(arr + i, result);
    }
}

3.2 完整代码

#include <immintrin.h>
#include <chrono>
#include <iostream>

void scalar_conditional(float* arr, int n) {
    for (int i = 0; i < n; ++i) {
        if (arr[i] > 0) {
            arr[i] *= 2;
        }
    }
}

void avx_conditional(float* arr, int n) {
    const __m256 zero = _mm256_setzero_ps();
    const __m256 mul = _mm256_set1_ps(2.0f);

    for (int i = 0; i < n; i += 8) {
        __m256 data = _mm256_loadu_ps(arr + i);

        // 生成比较掩码(arr[i] > 0)
        __m256 mask = _mm256_cmp_ps(data, zero, _CMP_GT_OQ);

        // 计算两种可能的结果
        __m256 res_true = _mm256_mul_ps(data, mul);
        __m256 res_false = data;

        // 根据掩码混合结果
        __m256 result = _mm256_blendv_ps(res_false, res_true, mask);

        _mm256_storeu_ps(arr + i, result);
    }
}

void benchmark() {
    const int SIZE = 10000000;
    float* data = new float[SIZE];

    // 初始化随机数据
    for (int i = 0; i < SIZE; ++i) {
        data[i] = (i % 2) ? i * 0.1f : -i * 0.1f;
    }

    auto t1 = std::chrono::high_resolution_clock::now();
    scalar_conditional(data, SIZE);
    auto t2 = std::chrono::high_resolution_clock::now();
    avx_conditional(data, SIZE);
    auto t3 = std::chrono::high_resolution_clock::now();

    std::cout << "Scalar: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
        << "ms\n";
    std::cout << "AVX:    "
        << std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count()
        << "ms\n";

    delete[] data;
}

int main() {
    benchmark();
    return 0;
}

3.3 测试结果

在这里插入图片描述

相关文章:

  • 利用 Java 爬虫获取淘宝商品 SKU 详细信息
  • PyTorch使用(5)-张量索引操作
  • uniapp小程序生成海报/图片并保存分享
  • 集合学习内容总结
  • Chrome 135 版本新特性
  • YUESAI应急4G网络广播成功应用于绍兴市钱塘江观潮预警提示项目
  • 【9】搭建k8s集群系列(二进制部署)之安装work-node节点组件(kube-proxy)和网络组件calico
  • QT ARM开发板调试
  • 《从零搭建Vue3项目实战》(AI辅助搭建Vue3+ElemntPlus后台管理项目)零基础入门系列第二篇:项目创建和初始化
  • Linux时间函数3-strftime时间格式转换、asctime时间固定格式、asctime_r线程安全、strftime/asctime/ctime区别
  • 组合与括号生成(回溯)
  • 开源模型应用落地-Qwen2.5-Omni-7B模型-Gradio-部署 “光速” 指南(二)
  • 2012年-全国大学生数学建模竞赛(CUMCM)试题速浏、分类及浅析
  • React-04React组件状态(state),构造器初始化state以及数据读取,添加点击事件并更改state状态值
  • 深度学习篇---Prophet时间序列预测工具
  • 使用stm32cubeide stm32f407 lan8720a freertos lwip 实现udp client网络数据转串口数据过程详解
  • Scala相关知识学习总结5
  • 简述Unity对多线程的支持限制和注意事项
  • 【橘子大模型】使用streamlit来构建自己的聊天机器人(下)
  • echarts生成3D立体地图react组件
  • 四大皆空!赛季还没结束,曼城已经吃上“散伙饭”了
  • 爬坡难下坡险,居民出行难题如何解?
  • 全国省市县国土空间总体规划已基本批复完成,进入全面实施阶段
  • 陕西三原高新区违法占用土地,被自然资源局罚款10万元
  • 王东杰评《国家与学术》︱不“国”不“故”的“国学”
  • 证监会强化上市公司募资监管七要点:超募资金不得补流、还贷