ARM平台Clang为何选用未启用NEON的aeabi_memcpy而非优化版?
问题背景
我维护的项目原本基于GCC编译、newlib 4.3.0运行时,为缩短链接时间改用Clang+LLD ThinLTO编译。链接时间确实降低,但目标平台运行性能反而下降——排查后发现,Clang选择了libc_a-aeabi_memcpy-armv7a.o版本的memcpy,而GCC选用的是libc_a-memcpy-armv7a.o。
两者的差异很明确:
memcpy-armv7a.S:使用ARM硬浮点协处理器的NEON向量指令,是针对Cortex-A9优化的高性能版本;aeabi_memcpy-armv7a.S:未使用向量指令,性能远不如前者。
我给GCC和Clang设置的编译选项完全一致:-mthumb -mfloat-abi=hard -mcpu=cortex-a9 -mfpu=neon -munaligned-access,且已验证宏定义__ARM_ARCH >= 7 && __ARM_ARCH_PROFILE == 'A' && defined(__ARM_FEATURE_UNALIGNED)为真,但映射文件显示Clang仍在使用aeabi版本的memcpy。
原因分析
- 符号命名与EABI兼容性差异:
ARM EABI标准定义了aeabi_memcpy这类符号作为内存操作函数的标准入口,而memcpy-armv7a是GCC为ARMv7-A架构额外优化的非标准符号。Clang默认会优先匹配符合EABI标准的符号,而GCC在指定-mcpu=cortex-a9等架构选项时,会优先选择针对该架构优化的专属符号版本。 - LLD ThinLTO的符号解析逻辑:
ThinLTO链接阶段的符号解析逻辑与GCC链接器不同,LLD在处理符号冲突时,会更倾向于保留标准EABI符号,而非GCC风格的架构优化符号。
解决方法
方法1:链接时强制指定优化版memcpy
直接在链接命令中添加memcpy-armv7a.o作为输入文件,让链接器优先选择该文件中的memcpy符号:
# 在链接阶段新增该文件路径 clang ... newlib-cygwin/newlib/libc/machine/arm/memcpy-armv7a.o -o target_binary
或者使用链接器的符号重定向选项,将memcpy直接映射到优化版本:
clang ... -Wl,--defsym=memcpy=memcpy-armv7a -o target_binary
方法2:禁止Clang使用内置memcpy
添加编译选项-fno-builtin-memcpy,强制Clang调用newlib中的memcpy实现,而非生成内置调用:
clang -fno-builtin-memcpy -mthumb -mfloat-abi=hard -mcpu=cortex-a9 -mfpu=neon -munaligned-access ...
方法3:修改newlib编译配置(适合自定义编译newlib的场景)
如果是自己编译newlib,调整memcpy-armv7a.S和aeabi_memcpy-armv7a.S的编译条件:
- 确保
memcpy-armv7a.S在Clang编译时导出memcpy符号,而非仅在GCC下导出; - 或者在newlib的编译脚本中,针对Clang环境优先编译
memcpy-armv7a.S并覆盖aeabi_memcpy的符号。
方法4:使用链接wrap机制
通过--wrap=memcpy选项,让链接器将所有memcpy调用重定向到__wrap_memcpy,然后自己实现该函数去调用优化版的memcpy-armv7a:
// 在代码中添加wrap函数 #include <stddef.h> extern void *memcpy_armv7a(void *dest, const void *src, size_t n); void *__wrap_memcpy(void *dest, const void *src, size_t n) { return memcpy_armv7a(dest, src, n); }
编译时添加链接选项:
clang ... -Wl,--wrap=memcpy -o target_binary
内容的提问来源于stack exchange,提问作者Klaus Petersen

