定点数这玩意儿并不是什么新东西,早年 CPU 浮点性能不够,定点数技巧大量活跃于各类图形图像处理的热点路径中。今天 CPU 浮点上来了,但很多情况下整数仍然快于浮点,因此比如:libcario (gnome/quartz 后端)及 pixman 之类的很多库里你仍然找得到定点数的身影。那么今天我们就来看看使用定点数到底能快多少。

简单用一下的话,下面这几行宏就够了:

#define cfixed_from_int(i)      (((cfixed)(i)) << 16)
#define cfixed_from_float(x)    ((cfixed)((x) * 65536.0f))
#define cfixed_from_double(d)   ((cfixed)((d) * 65536.0))
#define cfixed_to_int(f)        ((f) >> 16)
#define cfixed_to_float(x)      ((float)((x) / 65536.0f))
#define cfixed_to_double(f)     ((double)((f) / 65536.0))
#define cfixed_const_1          (cfixed_from_int(1))
#define cfixed_const_half       (cfixed_const_1 >> 1)
#define cfixed_const_e          ((cfixed)(1))
#define cfixed_const_1_m_e      (cfixed_const_1 - cfixed_const_e)
#define cfixed_frac(f)          ((f) & cfixed_const_1_m_e)
#define cfixed_floor(f)         ((f) & (~cfixed_const_1_m_e))
#define cfixed_ceil(f)          (cfixed_floor((f) + 0xffff))
#define cfixed_mul(x, y)        ((cfixed)((((int64_t)(x)) * (y)) >> 16))
#define cfixed_div(x, y)        ((cfixed)((((int64_t)(x)) << 16) / (y)))
#define cfixed_const_max        ((int64_t)0x7fffffff)
#define cfixed_const_min        (-((((int64_t)1) << 31)))
typedef int32_t cfixed;

类型狂可以写成 inline 函数,封装狂可以封装成一系列 operator xx,如果需要更高的精度,可以将上面用 int32_t 表示的 16.16 定点数改为用 int64_t 表示的 32.32 定点数。

那么我们找个浮点数的例子优化一下吧,比如 libyuv 中的 ARGBAffineRow_C 函数:

void ARGBAffineRow_C(const uint8_t* src_argb,int src_argb_stride,uint8_t* dst_argb,const float* uv_dudv,int width) {int i;// Render a row of pixels from source into a buffer.float uv[2];uv[0] = uv_dudv[0];uv[1] = uv_dudv[1];for (i = 0; i < width; ++i) {int x = (int)(uv[0]);int y = (int)(uv[1]);*(uint32_t*)(dst_argb) = *(const uint32_t*)(src_argb + y * src_argb_stride + x * 4);dst_argb += 4;uv[0] += uv_dudv[2];uv[1] += uv_dudv[3];}
}

这个函数是干什么用的呢?给图像做 仿射变换(affine transformation) 用的,比如 2D 图像库或者 ActionScript 中可以给 Bitmap 设置一个 3x3 的矩阵,然后让 Bitmap 按照该矩阵进行变换绘制:

基本上二维图像所有:缩放,旋转,扭曲都是通过仿射变换完成,这个函数就是从图像的起点(u, v)开始按照步长(du, dv)进行采样,放入临时缓存中,方便下一步一次性整行写入 frame buffer。

这个采样函数有几个特点:

  • 运算简单:没有复杂的运算,计算无越界,不需要求什么 log/exp 之类的复杂函数。
  • 范围可控:大部分图像长宽尺寸都在 32768 范围内,用 16.16 的定点数即可。
  • 转换频繁:每个点的坐标都需要从浮点转换成整数,这个操作很费事。

适合用定点数简单重写一下:

void ARGBAffineRow_Fixed(const uint8_t* src_argb,int src_argb_stride,uint8_t* dst_argb,const float* uv_dudv,int width) {int32_t u = (int32_t)(uv_dudv[0] * 65536);  // 浮点数转定点数int32_t v = (int32_t)(uv_dudv[1] * 65536);int32_t du = (int32_t)(uv_dudv[2] * 65536);int32_t dv = (int32_t)(uv_dudv[3] * 65536);for (; width > 0; width--) {int x = (int)(u >> 16);  // 定点数坐标转整数坐标int y = (int)(v >> 16);*(uint32_t*)(dst_argb) = *(const uint32_t*)(src_argb + y * src_argb_stride + x * 4);dst_argb += 4;u += du;   // 定点数加法v += dv;}
}

局部用一下定点数都不需要定义前面那一堆宏,按相关原理直接写就是了。

我们用 llvm-mca 分析一下,浮点数版本 gcc 9.0 的循环主体部分用 -O3 的代码生成:

Iterations:        100
Instructions:      1300
Total Cycles:      458
Total uOps:        1500Dispatch Width:    6
uOps Per Cycle:    3.28
IPC:               2.84
Block RThroughput: 3.0Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)[1]    [2]    [3]    [4]    [5]    [6]    Instructions:2      6     1.00                        cvttss2si     eax, xmm11      1     0.25                        add   rsi, 41      4     0.50                        addss xmm1, xmm22      6     1.00                        cvttss2si     edx, xmm01      4     0.50                        addss xmm0, xmm31      3     1.00                        imul  eax, r9d1      1     0.50                        shl   edx, 21      1     0.25                        cdqe1      1     0.25                        add   rax, rdi1      5     0.50    *                   mov   eax, dword ptr [rax + rdx]1      1     1.00           *            mov   dword ptr [rsi - 4], eax1      1     0.25                        cmp   rsi, rcx1      1     0.50                        jne   .L3

链接:Compiler Explorer - Analysis (llvm-mca (trunk))

可以看到,虽然编译器自动生成了 sse 代码,但性能消耗的大户,cvttss2si(浮点数转整数指令),虽然只有一条命令,但会生成两个微指令(uOP),延迟 6 个周期,rthroughput 很高 1.0 代表每周期只能同时运行一条该指令,其次是加法指令 addss, 延迟是 4 个周期,吞吐量 0.5 代表每周期可以并行执行 2 条,该代码块模拟运行 100 次,总消耗 458 个周期。

再看定点数版本:

Iterations:        100
Instructions:      1500
Total Cycles:      337
Total uOps:        1500Dispatch Width:    6
uOps Per Cycle:    4.45
IPC:               4.45
Block RThroughput: 2.5Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)[1]    [2]    [3]    [4]    [5]    [6]    Instructions:1      1     0.25                        mov   eax, edi1      1     0.25                        mov   edx, ecx1      1     0.25                        add   rsi, 41      1     0.25                        add   ecx, r11d1      1     0.50                        sar   eax, 161      1     0.50                        sar   edx, 161      1     0.25                        add   edi, ebx1      3     1.00                        imul  eax, r10d1      1     0.50                        shl   edx, 21      1     0.25                        cdqe1      1     0.25                        add   rax, r91      5     0.50    *                   mov   eax, dword ptr [rax + rdx]1      1     1.00           *            mov   dword ptr [rsi - 4], eax1      1     0.25                        cmp   rsi, r81      1     0.50                        jne   .L8

链接:https://godbolt.org/z/Adj9gQ

指令虽然多了两条,但是整数指令 latency 比浮点要低,并且吞吐量(并行性)比浮点更好,大部分指令都是 0.25,代表每周期可以并行执行四条,模拟运行 100 次,总消耗 337 个周期。

这里你可能要问,整数版本,第二列 latency 加起来有 21 个周期啊,为什么平均运行一次才 3.37 个周期呢?这就是多级流水线中很多指令可以并行运行,只要没有运算结果依赖,以及没有执行单元的资源冲突,很多运算都是可以并行的,所以优化里,运算解依赖很有用。

我们给 llmv-mca 加一个 -timeline 参数,能得到流水线分析报表,这里截取两次循环:

[0,0] DeER . . . . . . . . . mov eax, edi
[0,1] DeER . . . . . . . . . mov edx, ecx
[0,2] DeER . . . . . . . . . add rsi, 4
[0,3] DeER . . . . . . . . . add ecx, r11d
[0,4] D=eER. . . . . . . . . sar eax, 16
[0,5] D=eER. . . . . . . . . sar edx, 16
[0,6] .DeER. . . . . . . . . add edi, ebx
[0,7] .D=eeeER . . . . . . . . imul eax, r10d
[0,8] .D=eE--R . . . . . . . . shl edx, 2
[0,9] .D====eER . . . . . . . . cdqe
[0,10] .D=====eER. . . . . . . . add rax, r9
[0,11] .D======eeeeeER. . . . . . . mov eax, dword ptr [rax + rdx]
[0,12] . D==========eER . . . . . . mov dword ptr [rsi - 4], eax
[0,13] . DeE----------R . . . . . . cmp rsi, r8
[0,14] . D=eE---------R . . . . . . jne .L8
[1,0] . DeE----------R . . . . . . mov eax, edi
[1,1] . D=eE---------R . . . . . . mov edx, ecx
[1,2] . D=eE---------R . . . . . . add rsi, 4
[1,3] . DeE---------R . . . . . . add ecx, r11d
[1,4] . D=eE--------R . . . . . . sar eax, 16
[1,5] . D=eE--------R . . . . . . sar edx, 16
[1,6] . D=eE--------R . . . . . . add edi, ebx
[1,7] . D==eeeE-----R . . . . . . imul eax, r10d
[1,8] . D==eE-------R . . . . . . shl edx, 2
[1,9] . D====eE----R . . . . . . cdqe
[1,10] . D=====eE---R . . . . . . add rax, r9
[1,11] . D======eeeeeER . . . . . . mov eax, dword ptr [rax + rdx]
[1,12] . D===========eER . . . . . . mov dword ptr [rsi - 4], eax
[1,13] . DeE-----------R . . . . . . cmp rsi, r8
[1,14] . D==eE---------R . . . . . . jne .L8

每条指令有下面几个生存周期:

D : Instruction dispatched.
e : Instruction executing.
E : Instruction executed.
R : Instruction retired.
= : Instruction already dispatched, waiting to be executed.
- : Instruction executed, waiting to be retired.

可以看到无依赖的头四条指令:

[0,0] DeER . . . . . . . . . mov eax, edi
[0,1] DeER . . . . . . . . . mov edx, ecx
[0,2] DeER . . . . . . . . . add rsi, 4
[0,3] DeER . . . . . . . . . add ecx, r11d

从分发(D),执行(e),完成执行(E)到退休(R),基本都是同时进行的。后面两条指令虽然也是和前面四条一起分发(D),但由于依赖头两条指令的运算结果,所以产生了一个周期的等待(=):

[0,4] D=eER. . . . . . . . . sar eax, 16
[0,5] D=eER. . . . . . . . . sar edx, 16

等到头两个指令执行成功(e结束),他们才能开始执行,第二次迭代 [1,0] 类似,多次迭代虽然用到了同样的寄存器,但是在 CPU 里 eax 只是个名字,CPU 对无关运算的寄存器进行重命名后,其实背后对应到了不同的寄存器地址,第二次迭代又有很多地方可以和第一次迭代并行执行,所以我们会发现两次迭代的最后一条指令 [0,14][1,14] 处理的退休时间 R 都差不多,两次循环几乎是并行执行的,如此多次循环平摊下来每次只要 3.37 个周期。

可以发现比浮点数版本的 4.58 个周期快了 35% 左右,注意一点,实际 I/O 操作会占用更多时间,所以在 mca 的分析里都标注了 MayLoad / MayStore,所以算上 I/O,两边的周期数都会略有增加,但是优势还在那里。

使用 mca 进行分析的时候,你把编译结果贴过去时,只能贴循环的主体部分,因为本身就要进行多次运行的流水线模拟,所以你贴了循环外的初始化部分就会干扰分析结果。

可能有人会说,妈呀,静态性能分析还要计算流水线么?其实大部分时候不需要,没有 llvm-mca 的时候我们做静态性能分析一般就是查指令手册:

大部分时候,看一下 uops 数量(越少越好),看一下周期 latency(越少越好),以及 throughput (决定并行效率,越低越好),心中对各类指令的占用消耗有一个基本概念,再细致一点的话还可以看看占用哪些硬件资源,p0156 代表可以再 0/1/5/6 几个硬件单元里任意一个执行,然后对着纸面代码进行一个大概评估。

后面有了 Intel IACA 以及 llvm-mca 后,自动化静态分析可以更加简单和准确。到这里,我们对仿射纹理映射做了一次性能静态分析,可以看得出定点数版本确实快,于是我们得到了一个性能更好的仿射纹理映射函数:

那么这样的静态分析准不准确呢?我们接着对两个函数进行动态性能评测:

链接:http://quick-bench.com/FqOYuExcXoyHe_r6Bl1oSm0wUPE

在 gcc 9.2 下面,定点数比浮点数快了 30%,比我们之前静态分析的结论类似(4.58 比 3.37),但差距略稍许偏低没有纯周期计算出来的 35% 性能差距那么高,因为两边都平摊了 I/O 操作引入的延迟(这部分 mca 没法计算进去)。

那么我们继续切换编译器,换成 clang 9.0 :

链接:http://quick-bench.com/RoUdH66MayHq6exmQ99mhODUN2w

可以看到性能提升了 2.2 倍,看代码生成,因为在 clang 下进行了矢量展开,定点数和浮点数都进行了循环展开,每轮循环一次性计算两个点,导致了更大的性能提升。

C 版本的定点数还可以继续用 SIMD 一次性算四个点的定点数坐标,性能应该还能提升一级。

定点数除了能在特定地方让你的代码性能提升数倍外,在很多对结果严格要求一致的地方,也会比浮点数更好,比如帧间同步的游戏,需要在 arm/x86 下面不同客户端保证同样的计算结果,这样浮点数就挂了,不同手机运算结果不同,只能用定点数来处理。

(PS:对于一些复杂运算比如 sin/cos 之类的三角函数,定点数一般用查表+插值进行)

最后,定点数是一个值得收藏到你编程百宝箱里的好工具,必要的时候能够帮到你。

--

verilog 浮点转定点_定点数优化:性能成倍提升相关推荐

  1. verilog 浮点转定点_定点数和浮点数

    定点数 定点数是指,数字在小数点之后和之前具有固定的位数. 可以用Qm.n表示法进行表示. m位为整数部分 n位小数部分 有符号数的总位数N = m + n + 1 当n=0时,则定点数用来存储整数. ...

  2. verilog 浮点转定点_浮点数0.1+0.2为何不等于0.3

    来自公众号:印记中文 本文由扇贝的前端工程师景国凯撰写,跟随作者一起了解浮点数的计算过程,掌握为何会出现精度丢失的根本原因. 之前简单介绍了二进制下整数的加减乘除基本运算,建议没看过的先去了解一下,这 ...

  3. mysql如何优化性能优化_如何优化性能?MySQL实现批量插入以优化性能的实例详解...

    这篇文章主要介绍了MySQL实现批量插入以优化性能的教程,文中给出了运行时间来表示性能优化后的对比,需要的朋友可以参考下 对于一些数据量较大的系统,数据库面临的问题除了查询效率低下,还有就是数据入库时 ...

  4. verilog 浮点数转定点数_定点数转浮点数verilog

    本文目的是记录学习<数字信号处理的FPGA实现>过程中,用verilog语言实现简单的定点数到浮点数转换的经历. 若以f[31:0]表示一个单精度32位浮点数,f[31]是符号位,其为'0 ...

  5. 前端服务器获取js文件偶尔慢_我所认识的前端性能优化

    现象: 用户体验差 网页太卡打不开(卡.慢) 服务器带宽流量(成本) 服务器压力 从哪处理:各处的缓存 地址缓存 减少DNS的解析请求.预解析DNS(不是"解析DNS") TCP缓 ...

  6. 硬件加速下webview切换闪屏_网页渲染性能优化 —— 性能优化下

    博客 有更多精品文章哟. Composite 的优化 终于,我们到了像素管道的末尾.对于这一部分的优化策略,我们可以从为什么需要 Composited Layer(Graphics Layer)来入手 ...

  7. matlab实现浮点转定点,浮点转定点方法总结.doc

    浮点转定点方法总结 浮点转定点方法总结 -孔德琦 目录 定点运算方法3 1.1 数 的 定 标3 1.2c语言:从浮点到定点4 1.2.1 加法4 1.2.2乘法6 1.2.3除法7 1.2.4 三角 ...

  8. 浮点与定点的二进制存储

    1.浮点数和定点数存储 https://blog.csdn.net/niaolianjiulin/article/details/82764511 2.浮点转定点 本篇主要介绍另外一种浮点转定点的方式 ...

  9. 英特尔核芯显卡控制面板没有了_只认性能你就输了!英特尔第十代酷睿处理器最全解析...

    前不久,英特尔公布了第十代酷睿处理器"Ice Lake"的命名规则,AnandTech网站也曝光了"次旗舰"级别酷睿i7-1065G7处理器的实测性能(详见&l ...

最新文章

  1. 刚刚!我被产品小姐姐的笔记本深深吸引了....
  2. 解决eureka注册时使用ip而不是hostname
  3. ORA-01036: 非法的变量名/编号
  4. 一个非常棒的jQuery 评分插件--好东西要分享
  5. tf.matmul()研究【Python】
  6. 【Python】Django生成API 文档
  7. wxWidgets:wxWidgets 辅助功能示例
  8. android 设置PopupWindow透明度
  9. java 多个 panel_java – 在JFrame中组织多个JPanel的好方法是...
  10. 这5种思维模式,大牛产品经理都在用
  11. linux内存布局和地址空间布局随机化(ASLR)下的可分配地址空间
  12. Wireshark 的使用 —— 过滤器(filter)
  13. Win7从VHD中启动 如何扩充虚拟磁盘
  14. 火山PC后台操作第三方窗口案例
  15. [Android] 选项卡组件TabHost
  16. java表格居中_让表格水平垂直居中
  17. 位运算与字母大小写转换
  18. [转载]关于火星坐标系统
  19. ZUCC_BB平台-Quiz B-3-7-答案
  20. YDOOK:CSDN博客自定义模块图片

热门文章

  1. debug assertion failed是什么意思?_MD5是是什么?为什么很多压缩文件上都有这个东西?...
  2. Linux MySQL 忘记root 密码
  3. 华为语音解锁设置_华为手机备忘录的秘密功能
  4. 手把手带你撸一把springsecurity框架源码中的认证流程
  5. 机器学习(三)聚类深度讲解
  6. activiti候选人的多个场景应用
  7. Git——git push 错误[error: src refspec master does not match any]解决方案
  8. html媒体查询怎么把颜色换成图片,为网页中图片src添加媒体查询功能。
  9. mysql的存储覆盖_mysql覆盖存储
  10. mybatis复杂查询环境 多对一的处理 按照结果嵌套处理和按照查询嵌套处理