MSVC短长度固定多项式求值的异常代码生成疑问:系数数组非连续及取绝对值的原因探究
MSVC短长度固定多项式求值的异常代码生成疑问:系数数组非连续及取绝对值的原因探究
最近我在测试编译期已知长度和系数的短多项式求值代码时,被MSVC的代码生成行为整懵了——和Intel ICX的规整表现比起来,MSVC的操作实在太诡异,甚至我还怀疑这和遇到的间歇性性能爆炸问题有关,想请教下这背后的原因。
先给大家看个最小复现例子(两家编译器都会自动展开循环):
// 编译期已知的多项式系数数组 const float p10_32[] = { 0.99996600f, 1.007032f, -0.74001284f, 3.3444971f, -21.49531f, 82.639426f, -194.32462f, 283.06584f, -249.3704f, 121.73285f, -25.28842f }; // Horner法则求值,参数p为占位,实际使用上面的p10_32数组 float evalpoly32_horner(double xin, const double* p) { float x = xin; return p10_32[0] + x * (p10_32[1] + x * (p10_32[2] + x * (p10_32[3] + x * (p10_32[4] + x * (p10_32[5] + x * (p10_32[6] + x * (p10_32[7] + x * (p10_32[8] + x * (p10_32[9] + x * (p10_32[10])))))))))); }
先说说Intel ICX的正常表现,完全符合预期:
- 直接复用原数组的连续系数,访问顺序严格对应数组定义
- 全程使用
vfmadd213ss这类FMA指令,高效利用硬件特性 - 没有多余的寄存器拷贝或奇怪的系数处理,生成的代码逻辑和手写的Horner法则完全匹配
再看MSVC这波离谱操作,处处透着不对劲:
- 多此一举生成绝对值系数集:它把原数组中的负系数取了绝对值,生成了一套全新的float系数,明明直接用负系数做加法更直接,结果它非要用
vsubss减法来替代 - 系数存储完全混乱:这些新系数在
.rdata段中不是连续存放的,中间插了一堆无关数据,顺序也被彻底打乱,完全不对应原数组的排序 - 额外的低效指令:还浪费了一些
vmovaps寄存器拷贝指令,连FMA指令都没用到(这点我知道有相关讨论,但不是当前核心疑问)
更头疼的是,我还遇到了间歇性的性能问题:有时候只是修改了程序中某些字符串的总长度,某些求值函数就会突然出现~40倍的性能慢down,我怀疑会不会和这种系数非连续存放导致的缓存命中问题有关,但不确定。
现在我最核心的几个疑问:
- 为什么MSVC要多此一举生成一套取绝对值的新系数?明明原系数都是编译期已知的,直接用负系数做加法难道不是更直接高效吗?
- 为什么要把这些新系数乱序、非连续地插入到其他数据中?对于编译期已知的短多项式,直接复用原数组的连续布局难道不是更利于缓存和访问效率吗?
- 这种诡异的系数处理会不会就是导致我那间歇性40倍性能慢down的罪魁祸首?
内容来源于stack exchange
相关产品推荐
相关产品推荐

