无符号有符号乘法_【编译笔记】变量除以常量的优化(一)——无符号除法
注:本文中的算法来自于 Division by Invariant Integers using Multiplication [1]。
众所周知,编译器可以把变量除以常量优化为乘法和移位。 例如:
Uint32 f(Uint32 a) { return a / 3; }
会生成下面这样的汇编(x86_64):
f:mov eax, edimov edx, 2863311531imul rax, rdxshr rax, 33ret
你一定很好奇,这个 2863311531
是怎么得到的?在这之前,我们需要先准备一些预备知识。
首先我们需要知道,对于常见的指令集,都提供了N位整数乘以N位整数得到2N位结果的指令。以32位为例,对于 x86 指令集来说:
mul r/m32
:计算eax
和r/m32
进行无符号乘法的结果,将低32位存入eax
,高32位存入edx
。imul r/m32
:计算eax
和r/m32
进行有符号乘法的结果,将低32位存入eax
,高32位存入edx
。
对于 ARM 指令集来说:
umull RdLo, RdHi, Rm, Rs
:计算Rm
和Rs
进行无符号乘法的结果,将低32位存入RdLo
,高32位存入RdHi
。smmul Rd, Rm, Rs
:计算Rm
和Rs
进行有符号乘法的结果,将高32位存入Rd
。
我们可以用下面两个函数代表计算乘法高位的结果:
Uint32 muluh(Uint32 a, Uint32 b) { return (Uint64(a) * b) >> 32; }
Int32 mulsh(Int32 a, Int32 b) { return (Int64(a) * b) >> 32; }
这些指令为我们的优化提供了可能性。 为了充分利用这些指令,我们的目标是将 n / d
优化为 muluh(n, m) >> l
,写成数学表达式就是
定理1 设
证明 设
因为
所以
换而言之,
代码如下(注:本文中均以32位乘除法为例):
constexpr int N = 32;inline int clz(Uint32 x) { return __builtin_clz(x); }struct Multiplier {Uint64 m;int l;
};Multiplier chooseMultiplier(Uint32 d) {assert(d != 0);// l = ceil(log2(d))int l = N - clz(d - 1);Uint64 low = (Uint64(1) << (N + l)) / d;Uint64 high = ((Uint64(1) << (N + l)) + (Uint64(1) << l)) / d;while((low >> 1) < (high >> 1) && l > 0)low >>= 1, high >>= 1, --l;return {high, l};
}
试验一下:
chooseMultiplier(3, 32);
得到
{2863311531, 1}
也就是说,我们可以将 n / 3
优化为
return muluh(n, 2863311531) >> 1;
然而,这个算法是存在问题的。例如,当
{4908534053, 3}
发现了吗?
Uint32
能够表示的范围! 究其原因,是因为原始的范围中就只有一个整数,无法缩小,而
不过不用担心,注意到
muluh
解决,而
Uint32 t = muluh(n, m - (Uint64(1) << 32);
return (n + t) >> l;
但是这样还是有一点问题,如果
Uint32 t = muluh(n, m - (Uint64(1) << 32);
return (((n - t) >> 1) + t) >> (l - 1);
也就是
Uint32 t = muluh(n, 613566757);
return (((n - t) >> 1) + t) >> 2;
另外还有一种情况就是当
Uint32
的范围吗? 实际上,在前一步的
chooseMultiplier
如下:
Multiplier chooseMultiplier(Uint32 d, int p) {assert(d != 0);assert(p >= 1 && p <= N);// l = ceil(log2(d))int l = N - clz(d - 1);Uint64 low = (Uint64(1) << (N + l)) / d;Uint64 high = ((Uint64(1) << (N + l)) + (Uint64(1) << (N + l - p))) / d;while((low >> 1) < (high >> 1) && l > 0)low >>= 1, high >>= 1, --l;return {high, l};
}
(需要注意的是,当
low
和 high
的计算过程中是会产生溢出的,但这种情况下除法的结果只可能是0或1,可以直接用比较解决,所以这里不做考虑。)
调用
chooseMultiplier(7, 31);
的结果是
{2454267027, 2}
所以 n / 14
可以被优化为:
return muluh(n >> 1, 2454267027) >> 2;
当然,众所周知,如果
另外需要注意的是,如果按完整精度计算出的
muluh(n, 2863311531) >> 2
优于先除以二的结果 muluh(n >> 1, 2863311531) >> 1
(后者多了一次移位)。
结合上述几种情况,完整代码如下:
#include <cassert>
#include <initializer_list>
#include <iostream>using Uint32 = unsigned int;
using Uint64 = unsigned long long;
using Int32 = int;
using Int64 = long long;inline int clz(Uint32 x) { return __builtin_clz(x); }
inline int ctz(Uint32 x) { return __builtin_ctz(x); }Uint32 muluh(Uint32 a, Uint32 b) { return (Uint64(a) * b) >> 32; }
Int32 mulsh(Int32 a, Int32 b) { return (Int64(a) * b) >> 32; }constexpr int N = 32;struct Multiplier {Uint64 m;int l;
};Multiplier chooseMultiplier(Uint32 d, int p) {assert(d != 0);assert(p >= 1 && p <= N);// l = ceil(log2(d))int l = N - clz(d - 1);Uint64 low = (Uint64(1) << (N + l)) / d;Uint64 high = ((Uint64(1) << (N + l)) + (Uint64(1) << (N + l - p))) / d;while((low >> 1) < (high >> 1) && l > 0)low >>= 1, high >>= 1, --l;return {high, l};
}void generateUnsignedDivision(Uint32 d) {assert(d != 0);std::cout << "Uint32 div" << d << "(Uint32 n) {n";if(d >= (Uint32(1) << (N - 1))) {std::cout << " return n >= " << d << ";n";} else {int s = ctz(d);if(d == (Uint32(1) << s)) {std::cout << " return n";if(s > 0) std::cout << " >> " << s;std::cout << ";n";} else {Multiplier multiplier = chooseMultiplier(d, N);if(multiplier.m < (Uint64(1) << N)) s = 0;else multiplier = chooseMultiplier(d >> s, N - s);if(multiplier.m < (Uint64(1) << N)) {std::cout << " return muluh(n";if(s > 0) std::cout << " >> " << s;std::cout << ", " << multiplier.m << ")";if(multiplier.l > 0) std::cout << " >> " << multiplier.l;std::cout << ";n";} else {std::cout << " Uint32 t = muluh(n, " << (multiplier.m - (Uint64(1) << N)) << ");n";std::cout << " return (((n - t) >> 1) + t) >> " << (multiplier.l - 1) << ";n";}}}std::cout << "}n";
}int main() {for(Uint32 d : std::initializer_list<Uint32>{1, 3, 6, 7, 14, 31, 32, 641, 0x7FFFFFFF, 0x80000000})generateUnsignedDivision(d);
}
下面展示了该代码生成的各种情况的优化结果:
Uint32 div1(Uint32 n) {return n;
}
Uint32 div3(Uint32 n) {return muluh(n, 2863311531) >> 1;
}
Uint32 div6(Uint32 n) {return muluh(n, 2863311531) >> 2;
}
Uint32 div7(Uint32 n) {Uint32 t = muluh(n, 613566757);return (((n - t) >> 1) + t) >> 2;
}
Uint32 div14(Uint32 n) {return muluh(n >> 1, 2454267027) >> 2;
}
Uint32 div31(Uint32 n) {Uint32 t = muluh(n, 138547333);return (((n - t) >> 1) + t) >> 4;
}
Uint32 div32(Uint32 n) {return n >> 5;
}
Uint32 div641(Uint32 n) {return muluh(n, 6700417);
}
Uint32 div2147483647(Uint32 n) {Uint32 t = muluh(n, 3);return (((n - t) >> 1) + t) >> 30;
}
Uint32 div2147483648(Uint32 n) {return n >= 2147483648;
}
这回讲了无符号除法的优化,关于有符号除法,我们下次再说。
参考
- ^Torbjörn Granlund and Peter L. Montgomery. 1994. Division by invariant integers using multiplication. SIGPLAN Not. 29, 6 (June 1994), 61–72. https://dl.acm.org/doi/10.1145/773473.178249
无符号有符号乘法_【编译笔记】变量除以常量的优化(一)——无符号除法相关推荐
- aardio学习笔记-变量与常量
变 量 定义:在程序运行过程中,用来存储数据值并且其值能被改变的对象称为变量. 要求: 1.变量名开始字符不能为数字. 2.变量名包含中文时,中文字符前面不能有字母或数字. 3.可以使用美元符号 ...
- 位向量 补码与无符号 加法与乘法 CSAPP学习笔记
计算机中用位来表示整数,一种方式只能表示非负数,一种可以表示有符号数. 无符号数编码: 补码编码: 由上面的定义可以知道补码与无符号之间的对应关系(见下式),最高位为0时,补码与无符号表示是一样的,而 ...
- java 常量字符串过长_编译出错:对于常量池来说,字符串表示的UTF过长,那我想知道,JVM的常量池到底有多大?...
输入缓冲说是8000个字符,和这有关吗? String内部是以char数组的形式存储,数组的长度是int类型,那么String允许的最大长度就是Integer.MAX_VALUE了,214748364 ...
- php 双引号 常量,php易错笔记-变量,常量,运算符
变量 基本 $4site = 'not yet'; // 非法变量名:以数字开头 $_4site = '_4site'; // 合法变量名:以下划线开头 $i站点is = 'mansikka'; // ...
- python常量变量和对象_Python学习笔记——变量和常量
一.变量 变量的概念基本上和初中代数的方程变量是一致的,只是在计算机程序中,变量不仅可以是数字,还可以是任意数据类型. 在Python中,不需要事先声明变量名和类型,直接赋值即可创建各种类型的对象变量 ...
- Verilog学习笔记——有符号数的乘法和加法
有符号数的计算在 Verilog 中是一个很重要的问题(也很容易会被忽视),在使用 Verilog 语言编写 FIR 滤波器时,需要涉及到有符号数的加法和乘法,在之前的程序中我把所有的输入输出和中间信 ...
- 负数与正数相乘怎么算_负数乘法_正负符号_两个正负符号的规则_乘法表
做乘法时: 例子 × 正正得正: 3 × 2 = 6 × 负负得正 (−3)× (−2) = 6 × 负正得负: (−3)× 2 = −6 × 正负得负: 3 ×(−2) = −6 是真的,负负得 ...
- 有符号二进制数的乘法
最近在阅读<深入理解计算机系统>讲到补码乘法,书上给了一个例子是三位无符号和补码的乘法表.其中两个负数的例子:3位二进制乘法结果一般需要6为二进制表达 带符号数 x=101=-3 和y=0 ...
- 假设我们在对有符号值使用补码运算的32位机器人运行代码。对于有符号值使用的是算术右移,而对于无符号值使用的是逻辑右移
假设我们在对有符号值使用补码运算的32位机器人运行代码.对于有符号值使用的是算术右移,而对于无符号值使用的是逻辑右移.变量的声明和初始化如下: int x = foo(); //任意值 int y = ...
最新文章
- RunnableException与CheckedException
- Matlab与线性代数 -- 魔方矩阵
- 芯片如何储存信息_十四五规划之:芯片
- 关于Class之深入Class
- html录音并转为音频文件,HTML5音频API Web Audio
- netty worker线程数量_Dubbo线程模型
- jquery 样式获取设置值_[JQuery] jQuery选择器ID、CLASS、标签获取对象值、属性、设置css样式...
- 验证子串(信息学奥赛一本通-T1140)
- CTS ( 9)---CTS 源码分析
- 基于JAVA+Swing+MYSQL的工资管理系统
- 计算机证据的获取,计算机证据获取技术的研究与应用.pdf
- linux防止文件和目录被意外删除或修改
- 基于数字孪生高校可视化的综合运营管理平台
- ps在线版 Photoshop在线精简版-toolfk程序员在线工具网
- Elasticsearch 分布式搜索引擎 速学
- 让用户输入一个数,判断7的倍数
- 如何将PDF转成PPT?为什么转换后不能编辑
- html图片靠右浮动 文字左侧环绕,CSS 模拟float实现center文字左右环绕图片的效果...
- AEJoy —— 彻底搞懂 AE 各种 loop* 表达式【二】
- (一)Activiti 数据库25张表——流程历史记录表25(ACT_EVT_LOG)
热门文章
- matplotlib调整图例的位置
- Excel制作随机抽取名单
- TensorFlow报错run() got multiple values for argument 'feed_dict'
- C++学习之路(一)
- windows 下 Graphviz 安装及入门教程以及 PlantUML
- Struts2源码阅读(四)_DispatcherConfigurationProvider续
- php进程间通信 yoc_PHP 进程间通信各种通信方式间的优劣之分??
- google账号解除游戏绑定_成长守护平台解除实名认证 公众号解绑操作流程
- Python序列类型常用函数练习:enumerate() reversed() sorted() zip()
- 计算机专业性特有的道德要求,什么是通信科学技术人员职业道德的双重性?