Clang++新增结构体成员后memcpy循环惯用法为何未被优化
问题背景
给出如下测试代码:
#include <cstdint> #include <cstddef> struct Data { uint64_t a; //uint64_t b; }; void foo( void* __restrict data_out, uint64_t* __restrict count_out, std::byte* __restrict data_in, uint64_t count_in) { for(uint64_t i = 0; i < count_in; ++i) { Data value = *reinterpret_cast<Data* __restrict>(data_in + sizeof(Data) * i); static_cast<Data* __restrict>(data_out)[(*count_out)++] = value; } }
当Data仅包含单个uint64_t a成员时,Clang可正常触发循环惯用法优化,将整个循环替换为memcpy调用,优化备注输出如下:
example.cpp:16:59: remark: Formed a call to llvm.memcpy.p0.p0.i64() intrinsic from load and store instruction in _Z3fooPvPmPSt4bytem function [-Rpass=loop-idiom] static_cast<Data* __restrict>(data_out)[(*count_out)++] = value;
取消第二个成员uint64_t b的注释后,上述优化完全失效。测试同时发现:如果将局部变量value改为引用类型Data&,移除临时局部拷贝逻辑,memcpy优化会恢复生效。
简化复现用例
去掉无关的类型转换、计数器指针逻辑后,用如下极简函数即可复现相同现象:
void bar(Data* __restrict data_out, Data* __restrict data_in, uint64_t count_in) { for(uint64_t i = 0; i < count_in; ++i) { Data value = data_in[i]; *data_out++ = value; } }
原因解答
优化未触发的根因
这个现象是LLVM的LoopIdiomRecognize(循环惯用法识别)Pass的现有实现限制导致的,属于明确的优化遗漏:
- 该Pass负责识别循环中重复出现的固定内存操作模式,将其替换为
memcpy/memset等标准库函数调用。目前其memcpy识别逻辑仅能匹配单次整块load+单次整块store构成的连续元素拷贝模式。 - 在x86_64等主流64位平台上,Clang生成IR时,可被单条通用寄存器承载的8字节(单
uint64_t)结构体,其值拷贝会被降级为单条i64 load + 单条i64 store指令,刚好匹配上述识别规则,因此可以正常触发优化。 - 当结构体扩展为16字节(两个
uint64_t)时,中间局部变量Data value的存在会让Clang在早期优化阶段将单元素拷贝拆分为两条独立的i64 load + 两条独立的i64 store(分别对应两个结构体成员),这种多指令拆分后的序列不在LoopIdiomRecognize的匹配范围内,因此无法被识别为可转换为memcpy的拷贝循环。 - 当把
value改为引用类型、或直接写*data_out++ = data_in[i]移除中间临时变量时,Clang不会做字段级的拷贝拆分,会保留整块内存拷贝的语义,生成单块的聚合类型load/store指令,因此可以重新匹配优化规则。
可行的规避/解决方法
如果需要在现有Clang版本下稳定触发该优化,可采用以下方案:
- 移除中间临时变量,直接在数组索引间做赋值,避免编译器将拷贝拆分为字段级操作:
void bar(Data* __restrict data_out, Data* __restrict data_in, uint64_t count_in) { for(uint64_t i = 0; i < count_in; ++i) { data_out[i] = data_in[i]; } } - 单元素拷贝时显式调用
__builtin_memcpy,强制编译器保留整块拷贝语义,循环会被后续优化合并为单次大长度memcpy调用:for(uint64_t i = 0; i < count_in; ++i) { __builtin_memcpy(data_out + i, data_in + i, sizeof(Data)); } - 等待LLVM社区合入相关优化补丁:目前已有多个提案要求扩展
LoopIdiomRecognize的匹配能力,支持识别拆分后的多load/store序列构成的连续拷贝模式。
内容的提问来源于stack exchange,提问作者Richard E
相关产品推荐
相关产品推荐

