https://www.cnblogs.com/bbqzsl/p/15510377.html

近期在做优化时,对一些函数分别在不同编译平台上进行bench测试。发现了不少问题。

现在拿其中一个问题来分享。

 1 typedef float MAFloat;
2
3 MAFloat sma(const MAFloat* seq, const int cnt, const int N, const int M)
4 {
5 const MAFloat C1 = (MAFloat)M/N;
6 const MAFloat C2 = (MAFloat)(N-M)/N;
7 MAFloat result = 0.f;
8 int total = cnt;
9
10 #pragma nounroll
11 for (int i = 0; i < total; ++i)
12 {
13 result = result * C2 + seq[i] * C1;
14 }
15
16 return result;
17 }

测试代码很简单,只一个循环,循环内只做了算术运算,汇编代码也很容易。

测试平台包括:

win10:平台,vc120,gcc10,clang11

centos8:平台,gcc8,gcc10,clang11

vc:使用选项 /arch:sse2 /O2,并且win32

gcc:使用选项 -ffast-math -O2 -m32

clang:使用选项 -ffast-math -O2 -m32

数组长度为 28884 = 7221 * 4;

cpu 是 core i5,3.5Ghz

测试结果:

win10:平台,vc120 (0.06x ms),gcc10 (0.06x ms),clang11 (0.09x ms)

centos8:平台,gcc8 (0.06x ms),gcc10 (0.06x ms),clang11 (0.09x ms)

不论在win10还是centos8平台上,clang编译的代码的性能居然比vc或gcc编译的代码性能差了50%。

现在我们来对比gcc10与clang11产出汇编代码

## gcc 

.L149:
movss (%edx,%eax,4), %xmm1 # xmm1 = seq[i]
mulss %xmm3, %xmm0      # xmm0 = result * C2
addl $1, %eax         #
mulss %xmm2, %xmm1      # xmm1 = seq[i] * C1 
addss %xmm1, %xmm0      # result = xmm0 + xmm1
cmpl %ecx, %eax
jl .L149            # next loop ## clang LBB7_3: # =>This Inner Loop Header: Depth=1
movss (%eax,%edx,4), %xmm4 # xmm4 = mem[0],zero,zero,zero
mulss %xmm1, %xmm3        #
incl %edx
cmpl %ecx, %edx
mulss %xmm0, %xmm4
addss %xmm4, %xmm3
mulss %xmm2, %xmm3        # xmm2 = 1/N;
jl LBB7_3

gcc生成的汇编代码一共7条指令,clang生成的汇编代码一共8条指令多出了一条mulss。

clang不知什么原因自作聪明将

result * C2 + seq[i] * C1;

优化成

(1/N) * (result * (N-M) + seq[i] * M);

即使多出一条mulss指令,性能也不至于差了50%,就像7条指令与10.5条指令的差距。

现在来分析

我的机器使用i5 3.5Ghz, 1ns可以运行3.5指令周期。

数组长度为28884,即执行循环代码28884次

运行时间为 28884 * (循环体指令周期)/  3.5

我现在粗略地将每条指令周期看作是1,gcc生成的代码运行时间粗略地为 28884 * 7 / 3.5 = 57768ns,与测试结果在0.06ms基本相当。用同样的方法估算,clang生成的代码运行时间粗略地为 28884 * 8 / 3.5 = 66020ns。

但是不同的指令,执行不同数量的微指令(uop),也就是延迟,mulss为4或5,addss为3,上面汇编代码的其它指令各为1。

    mulss    %xmm2, %xmm1         # xmm1 = seq[i] * C1 
addss %xmm1, %xmm0      # result = xmm0 + xmm1

在上面两条指令,addss 依赖 mulss 的结果于 %xmm1,也就是说addss 必须在mulss开始执行后延迟4或5个周期才能执行。由于cpu的乱序机制,这时候延迟的周期数内可以在其他ALU执行其它指令。所以gcc生成的汇编代码的情况可以看作没有指令周期的损失。

再来看clang生成的汇编代码

    mulss    %xmm0, %xmm4
addss %xmm4, %xmm3
mulss %xmm2, %xmm3        # xmm2 = 1/N;

addss 依赖 mulss 的结果于 %xmm4,然后mulss 依赖 addss 的结果于 %xmm3,这里我们将第一个依赖等同于gcc汇编中的那个依赖,那么下一个依赖的3个周期就必须等待,一次循环一共才8条指令,两个依赖的延迟合计就8个指令周期,乱序也就没有指令可以执行,所以就硬生生多出3或4个指令周期等待。

运行时间一下子就变成了 28884 * (8+3) / 3.5 = 90778ns。

估算的结果与测试的结果基本上吻合。

有兴趣的朋友可以到godblot上测试汇编,一旦让clang使用-ffast-math选项,编译发生这一出傻事。

一条指令优化引发的血案,性能狂掉50%,clang使用-ffast-math选项后变傻了的更多相关文章

  1. 一条慢SQL引发的血案

    直接切入正题吧: 通常来说,我们看到的慢查询一般还不致于导致挂站,顶多就是应用响应变慢不过这个恰好今天被我撞见了,一个慢查询把整个网站搞挂了先看看这个SQL张撒样子: # Query_time: 70 ...

  2. 转:一个Sqrt函数引发的血案

    转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html 源码下载地址:http://diducoder.com/sotr ...

  3. 一个Sqrt函数引发的血案(转)

    作者: 码农1946  来源: 博客园  发布时间: 2013-10-09 11:37  阅读: 4556 次  推荐: 41   原文链接   [收藏]   好吧,我承认我标题党了,不过既然你来了, ...

  4. 一个无锁消息队列引发的血案(五)——RingQueue(中) 休眠的艺术

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

  5. 【转载】一个Sqrt函数引发的血案

    转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/sotry-about-sqrt.html 源码下载地址:http://diducoder ...

  6. 一个Sqrt函数引发的血案

    源码下载地址:http://diducoder.com/sotry-about-sqrt.html 好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获. 我们平时经常会有一些数据运算 ...

  7. 一个无锁消息队列引发的血案(三)——地:q3.h 与 RingBuffer

    目录 (一)起因 (二)混合自旋锁 (三)q3.h 与 RingBuffer (四)RingQueue(上) 自旋锁 (五)RingQueue(中) 休眠的艺术 (六)RingQueue(中) 休眠的 ...

  8. 一次"内存泄漏"引发的血案

    本文转载自一次"内存泄漏"引发的血案 导语 2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后, ...

  9. 优化Angular应用的性能

    MVVM框架的性能,其实就取决于几个因素: 监控的个数 数据变更检测与绑定的方式 索引的性能 数据的大小 数据的结构 我们要优化Angular项目的性能,也需要从这几个方面入手. 1. 减少监控值的个 ...

随机推荐

  1. TP5 数据保存、更新问题(save、saveAll)

    一.今天写项目的时候,突然发现一个坑爹的问题,使用saveAll新增多条数据,但是一直提示缺少更新条件,然而我发现代码里面并没有更新,而且saveAll我仅仅是去新增多条数据而已 原来源码 模型类中有 ...

  2. (数据科学学习手札128)在matplotlib中添加富文本的最佳方式

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 长久以来,在使用matplotlib进行绘 ...

  3. Selenium多浏览器处理 (Chrome/Firefox/IE)

    测试用例文件:test_selenium/test_hogwarts.py 使用pytest框架 定义一个变量,通过外部传入变量,确定使用哪个浏览器 browser = os.getenv(" ...

  4. 使用Jmeter执行接口自动化测试-如何初始化清空旧数据

    需求分析: 每次执行完自动化测试,我们不会执行删除接口把数据删除,而需要留着手工测试,此时会导致下次执行测试有旧数据 我们手工可能也会新增数据,导致下次执行自动化测试有旧数据 下面介绍两种清空数据的方 ...

  5. requests + 正则表达式 获取 ‘猫眼电影top100’。

    使用 进程池Pool 提高爬取数据的速度. 1 # !/usr/bin/python 2 # -*- coding:utf-8 -*- 3 import requests 4 from request ...

  6. P4332-[SHOI2014]三叉神经树【LCT】

    正题 题目链接:https://www.luogu.com.cn/problem/P4332 题目大意 给出\(n\)个点的一棵有根三叉树,保证每个点的儿子个数为\(3\)或者\(0\),每个叶子有一 ...

  7. P4606-[SDOI2018]战略游戏【圆方树,虚树】

    正题 题目链接:https://www.luogu.com.cn/problem/P4606 题目大意 给出\(n\)个点\(m\)条边的一张图,\(q\)次询问给出一个点集,询问有多少个点割掉后可以 ...

  8. RabbitMQ 3.9.7 镜像模式集群的搭建

    1. 概述 老话说的好:做人脚踏实地,一步一个脚印,便定能战胜一切困难,最终取得成功!!! 言归正传,之前我们聊了 RabbitMQ 单点服务的安装,今天我们来聊聊 RabbitMQ 3.9.7 镜像 ...

  9. CSS 小技巧 | 一行代码实现头像与国旗的融合

    到国庆了,大家都急着给祖国母亲庆生. 每年每到此时,微信朋友圈就会流行起给头像装饰上国旗,而今年又流行这款: emm,很不错. 那么,将一张国旗图片与我们的头像,快速得到想要的头像,使用 CSS 如何 ...

  10. Java面向对象编程(三)

    static关键词 static关键字:可以修饰属性.方法.代码块.内部类. 一.使用static修饰属性:静态变量(或类变量) 1. 属性,按是否使用static修饰,又分为:静态属性 vs 非静态 ...