本文介绍一下 STL 中的矢量化 MSVC STL 算法,可以理解为算法的优化版,借助 CPU 硬件提高代码效率。
目录
- [1 概述](#1 概述)
- [2 MSVC STL 中的自动矢量化](#2 MSVC STL 中的自动矢量化)
- [3 MSVC STL 中的手动矢量化](#3 MSVC STL 中的手动矢量化)
- [4 浮点类型的手动向量化算法](#4 浮点类型的手动向量化算法)
- [5 备注](#5 备注)
1 概述
在特定条件下,MSVC 标准模板库(STL)中的算法可以在单个 CPU 核心上同时处理多个元素,而不是单独处理每个元素。此优化使用 CPU 提供的单个指令、多个数据(SIMD)指令,这是一种称为矢量化的技术。如果未应用此优化,则实现称为标量实现。
开启矢量化优化需要同时满足以下条件:
容器 / 区间内存连续
- 支持:array、vector、basic_string、span、basic_string_view、原生内置数组;
- 不支持:list、map 等非连续容器;
目标 CPU 支持对应元素类型所需的 SIMD 指令集,数值基础类型、简单运算通常都满足;
必须满足以下条件之一 :
- 编译器自动向量化:编译器把标量循环代码编译生成向量化机器码;
- 手动向量化:算法底层源码直接手写 SIMD 向量化逻辑;
关键说明SIMD 向量化是
CPU 层面并行加速,并非多线程并行,只利用单核运算单元批量处理;
内存连续是硬性门槛,链表、树形容器内存分散,无法打包批量读取,永远只能标量执行;
基础数值(int / float / double)最容易触发向量化,自定义复杂结构体大多无法自动向量化;
2 MSVC STL 中的自动矢量化
自动矢量化规则对 STL 底层实现代码、用户自己编写的代码完全通用。
transform、reduce、accumulate 这类算法能从自动向量化优化中获得巨大性能提升。
补充说明
/arch 编译参数用来指定 CPU 指令集(SSE、AVX、AVX2、AVX512等),编译器根据该参数生成对应 SIMD 向量化指令;- 自动向量化:编译器识别循环标量逻辑,
自动批量打包多个元素并行计算,无需手动写 SIMD intrinsics;- 适用算法特点:
批量遍历、简单算术运算、累加归约类操作,非常契合 SIMD 批量计算特性;- 该优化
仅作用于连续内存区间(vector / array / string 等),list、map 等碎片化容器无法触发;
3 MSVC STL 中的手动矢量化
部分算法内置手动向量化实现。该实现单独编译,依靠运行时 CPU 调度分支,因此仅在适配的 CPU 上才会启用优化逻辑。
- 手动向量化算法通过模板元编程 判断元素类型是否适合向量化,
仅对标准整数这类简单基础类型开启向量化路径。- 开启手动向量化
只会提升程序性能,不会带来负面效果。- 在项目中定义宏
_USE_STD_VECTOR_ALGORITHMS= 0 关闭手动矢量化。默认情况下,在 x64 和 x86 上启用手动矢量化算法,该宏默认值为 1。- 所有参与链接、使用标准算法的编译单元,必须统一 _USE_STD_VECTOR_ALGORITHMS 的取值。为保证一致性,建议在项目属性
预处理器配置中设置(对应编译选项 /D),不要在源码内零散定义。
宏 _USE_STD_VECTOR_ALGORITHMS 控制以下算法的手动矢量化:
| 用途 | 标准算法 |
|---|---|
| 查找类 | contains、contains_subrange、find、find_last、find_first_of、adjacent_find、search、search_n、find_end |
| 统计类 | count |
| 匹配类 | mismatch |
| 交换复制 | swap_ranges |
| 替换 | replace、replace_copy |
| 删除去重 | remove、remove_copy、unique、unique_copy |
| 翻转旋转 | reverse、reverse_copy、rotate |
| 有序校验 | is_sorted、is_sorted_until |
| 集合比较 | includes、lexicographical_compare、lexicographical_compare_three_way |
| 最值 | max、min、minmax、max_element、min_element、minmax_element |
宏 _USE_STD_VECTOR_ALGORITHMS 控制以下项的手动矢量化:
- basic_string 和 basic_string_view 成员:
- find
- rfind
- find_first_of, find_first_not_of
- find_last_of, find_last_not_of
- bitset 字符串中的构造函数和 bitset::to_string
4 浮点类型的手动向量化算法
对浮点类型做向量化存在几点特殊问题:
- 向量化会重新调整运算执行顺序,可能改变浮点计算结果精度;
- 浮点数可能存在 NaN(非数值),NaN 的比较不满足传递性;
- 浮点运算有可能触发硬件浮点异常;
标准库已经安全处理了前两个问题。仅有以下最值、有序校验类算法提供浮点专用手动向量化实现:
- max_element
- min_element
- minmax_element
- max
- min
- minmax
- is_sorted
- is_sorted_until
这类算法有两个特性,规避了浮点向量化风险:
不会生成新浮点值,仅做已有元素的大小比较,运算顺序重排不会影响最终精度;
作为排序 / 最值相关算法,输入序列不允许包含 NaN;
通过宏 _USE_STD_VECTOR_FLOATING_ALGORITHMS 控制浮点向量化开关,设为 0 即可关闭。若 _USE_STD_VECTOR_ALGORITHMS 被设为 0,本宏会完全失效,无论取值如何都不会启用浮点向量化。编译开启 /fp:except(启用浮点异常捕获)时,_USE_STD_FLOATING_ALGORITHMS 默认值为0,默认关闭浮点向量化。
所有链接在一起、使用标准算法的编译单元,该宏取值必须保持统一。为保证全局一致性,建议在项目属性预处理器中统一配置(对应编译选项 / D),不要在源码中零散定义。
5 备注
- 手动向量化 VS 自动向量化
- 自动向量化:编译器识别普通循环,自动生成 SIMD;用户、STL 代码通用;支持浮点、整数。
- 手动向量化:微软工程师手写 SIMD 底层分支,单独编译,运行时检测 CPU 指令集;仅整数类型生效,覆盖大量查找、遍历类算法。
- 运行时 CPU 调度
- 程序启动后检测 CPU 是否支持 AVX/AVX2 等指令集,匹配才走加速路径;
- 老旧 CPU 自动降级为普通标量实现,无需分支判断代码;
- 宏使用强制规范
- 若不同 .cpp 文件一个开、一个关,会出现符号冲突、链接报错,全局统一通过项目 /D 配置最稳妥。
- 生效内存前提
- 即便开启宏,也仅对连续内存容器(vector / array / string)生效;
- list / map 非连续内存无法 SIMD 批量加载,不会触发加速;
- 关闭场景
- 极少数需要极致可复现性能、跨平台对齐测试的场景,才设置 _USE_STD_VECTOR_ALGORITHMS = 0,日常开发建议保持默认开启。