std::replace实现是否允许向传入数组执行冗余写入?
std::replace的实现可通过向量化优化(通过库特化或编译器优化实现)。向量化实现能同时对比并替换多个元素。
为确保无需替换的元素保留原值,向量化实现需采用以下方式之一:
- 使用掩码向量存储(masked vector stores)
- 对无需替换的元素写入原值
- 仅向量化定位元素,仍逐个替换存储
第一种方式最优,但如果向量指令集不支持对应元素大小的掩码向量存储则无法使用。在x86平台,SSE系列无掩码向量存储¹,AVX/AVX2仅支持32位和64位元素的掩码向量存储,只有部分AVX512子集支持全元素大小的掩码向量存储。
第二种方式假设写入原值的内存是可写的,但这一假设并不绝对成立:内存并非始终可写。在C++中,用户可通过去除const修饰符传入常量数组给std::replace,若无需替换则期望无任何操作;借助操作系统内存管理函数,数组也可被设置为部分可写、部分只读。
第三种方式无需掩码向量存储,也不依赖可写性假设,但无法充分发挥向量化的性能优势。
笔者观察到不同编译器的实现采用了不同方式:启用AVX2时,x86-64 icc 2021.10.0和x86-64 clang 17.0.1均尝试对16位元素的std::replace做向量化优化,但Intel编译器采用写入原值的方式,而clang采用逐个存储的方式。测试代码及-mavx2选项如下:
#include <algorithm> void f(short* i, short* e) { std::replace(i, e, 0, 1); }
这意味着要么x86-64 icc 2021.10.0的实现存在问题,要么x86-64 clang 17.0.1错失了进一步优化的机会。
除向量化场景外,冗余写入还可用于优化小型数组的std::replace或处理向量化尾部:通过无分支代码计算目标值(如x86平台的cmovcc指令)并无条件存储。
据此提出疑问:编译器及标准库实现是否允许通过冗余写入来优化std::replace?
本问题与近似问题的区别在于,本问题针对标准算法std::replace,而非特定实现或手写等效代码。
¹ 非临时掩码向量存储不计入,因其不适用于通用优化
内容的提问来源于stack exchange,提问作者Alex Guteniev

