0. 序

本篇分析linux内核中的struct reciprocal_valuestruct reciprocal_value_adv背后的数学原理,参考论文Division by Invariant Integers using Multiplication。

1. basics

核心想法是这样,在除数是常数的情况下,我们希望用乘法和移位指令来代替除法(通常情况下这是更快的)。这可以发生在编译时(作为一种编译优化),或者发生在运行时(软件实现)。整数除法有不同的情况:有符号还是无符号,向0舍入还是向无穷舍入?原论文讨论了许多的情况。我们只分析最常见的一种(linux也只实现了这一种):无符号除法,并向0舍入。

我们假设计算机使用N位bit来表示一个整数,那么可以把问题数学化为:给定除数d,1≤d≤2N−1d, 1 \leq d \leq 2^N - 1d,1≤d≤2N−1,希望找到合适的正整数m,lm, lm,l,使得对∀n,1≤n≤2N−1\forall n, 1\leq n \leq 2^N-1∀n,1≤n≤2N−1,有下式成立
[nd]=[mn2N+l][\frac{n}{d} ]= [\frac{mn}{2^{N +l}}] [dn​]=[2N+lmn​]
这样我们便可以把除法转化为一个乘法和移位操作了。不过注意这里的乘法是数学上的乘法,换到计算机里,实际需要做的是一个宽度为2N的乘法(即不能丢掉高位bit)。

直观上来看,为了使上式成立,我们需要尽可能得使m∗dm*dm∗d接近2N+l2^{N+l}2N+l,取n=dn=dn=d,可知k=m∗d−2N+l≥0k = m*d - 2^{N+l}\geq 0k=m∗d−2N+l≥0,设n=qd+r,0≤r≤d−1n = qd + r, 0\leq r \leq d - 1n=qd+r,0≤r≤d−1。上式成立等价于
kn2N+ld+rd<1\frac{kn}{2^{N+l}d}+\frac{r}{d} < 1 2N+ldkn​+dr​<1
若取k≤2lk \leq 2^lk≤2l,则有
kn2N+ld+rd<2l∗2N2N+l∗d+d−1d=1\frac{kn}{2^{N+l}d}+\frac{r}{d} < \frac{2^l*2^N}{2^{N+l}*d} + \frac{d-1}{d} = 1 2N+ldkn​+dr​<2N+l∗d2l∗2N​+dd−1​=1
因此我们得到了原论文中的定理4.2

为了保证这样的m存在,我们只需2l+1≥d2^l + 1 \geq d2l+1≥d,因此通常取
l=⌈log2d⌉m=[2N+ld]+1可得范围:2N+1≤m≤2N+1−1l = \lceil log_2d\rceil \\ m = [\frac{2^{N+l}}{d}]+1 \\ 可得范围 : 2^N+1 \leq m \leq 2^{N+1} - 1 l=⌈log2​d⌉m=[d2N+l​]+1可得范围:2N+1≤m≤2N+1−1
可以看到,m是没办法用Nbit来表示的。因此定义m′=m−2Nm' = m - 2^Nm′=m−2N。并定义H(xy)H(xy)H(xy)表示乘法的高N位bit,L(xy)L(xy)L(xy)表示乘法的低N位bit,这样有
[nq]=[mn2N+l]=[m′n2N+l+n2l]=[H(m′n)+n2l+L(m′n)2N+l]=[H(m′n)+n2l]最后一步用到了:L(m′n)2N+l<12l[\frac{n}{q}] = [\frac{mn}{2^{N +l}}] = [\frac{m'n}{2^{N+l}}+\frac{n}{2^l}] = [\frac{H(m'n)+n}{2^l} +\frac{L(m'n)}{2^{N+l}}] = [\frac{H(m'n)+n}{2^l}] \\ 最后一步用到了:\frac{L(m'n)}{2^{N+l}} < \frac{1}{2^l} [qn​]=[2N+lmn​]=[2N+lm′n​+2ln​]=[2lH(m′n)+n​+2N+lL(m′n)​]=[2lH(m′n)+n​]最后一步用到了:2N+lL(m′n)​<2l1​
我们可以提前算好m′,lm',lm′,l,因此每次除法的开销变为一次高N位bit的乘法,一次加法,一次移位操作。但这里有一个麻烦的地方是这里的加法可能会溢出。按照论文中记t1=H(m′n)t_1 = H(m'n)t1​=H(m′n),我们改写结果为
[H(m′n)+n2l]=[t1+[n−t12]2l−1][\frac{H(m'n)+n}{2^l}] = [\frac{t_1+[\frac{n-t_1}{2}]}{2^{l-1}}] [2lH(m′n)+n​]=[2l−1t1​+[2n−t1​​]​]
这样避免了溢出的问题,但开销增加到了两次加减法,两次移位,一次高位乘法。另外还需要额外考虑l=0l=0l=0的情况(此时d=1d=1d=1),这时候这个变换就不合理了。最终得到如下的linux代码(reciprocal_div.c

struct reciprocal_value {u32 m;  // 这就是前面提到的 m'u8 sh1, sh2;  // 当 d != 1时,sh1 = 1, sh2 = l - 1
};struct reciprocal_value reciprocal_value(u32 d) // 预处理函数
{struct reciprocal_value R;u64 m;int l;l = fls(d - 1);m = ((1ULL << 32) * ((1ULL << l) - d));do_div(m, d);++m;R.m = (u32)m;R.sh1 = min(l, 1);R.sh2 = max(l - 1, 0);return R;
}static inline u32 reciprocal_divide(u32 a, struct reciprocal_value R)
{  // 除法替换为乘法,加减法和移位操作u32 t = (u32)(((u64)a * R.m) >> 32);return (t + ((a - t) >> R.sh1)) >> R.sh2;
}

2. improvement

在此基础上,论文中提出了若干优化手段:

  • 若选取的mmm是偶数,那么可以用m/2m/2m/2替换mmm,同时l−1l-1l−1替换lll,结果不变,但此时mmm就可以用Nbit来表示了,这样只需要一次高位乘法,一次移位就可以得到结果。
  • 若ddd是偶数,那么对某个正整数eee,有[nd]=[[n/2e]d/2e][\frac{n}{d}]=[\frac{[n/2^e]}{d/2^e}][dn​]=[d/2e[n/2e]​],这样每次做除法前先将nnn右移eee位,这样在basics中证明定理4.2时的条件可以放宽为1≤n<2N−e1 \leq n < 2^{N-e}1≤n<2N−e,这样便可以把kkk的取值范围放宽为k≤2l+ek \leq 2^{l+e}k≤2l+e,因此m会有更宽的选择范围。因此也更容易将m选到偶数(这样说不太准确,因为此时l=[log2d2e]l = [log_2{\frac{d}{2^e}}]l=[log2​2ed​],因此选择宽度并没有发生变化,但是因为lll变小了,因此选择范围确实发生了变化,总的来说增加了选到mmm是偶数的概率) 。

这对应到linux的代码中为

struct reciprocal_value_adv {u32 m;u8 sh, exp;bool is_wide_m;  // 如果通过优化,使得m能够用32bit表示,那么is_wide_m为false,// 此时上面的u32 m表示原始的m,否则is_wide_m为true,u32 m表示的是m'
};// 第一次调用时传入prec为32,如果返回的is_wide_m为true,那么caller判断d是否为偶数,如果是
// 则再次调用reciprocal_value_adv(d >> e, 32 - e)
struct reciprocal_value_adv reciprocal_value_adv(u32 d, u8 prec)
{struct reciprocal_value_adv R;u32 l, post_shift;u64 mhigh, mlow;/* ceil(log2(d)) */l = fls(d - 1);/* NOTE: mlow/mhigh could overflow u64 when l == 32. This case needs to* be handled before calling "reciprocal_value_adv", please see the* comment at include/linux/reciprocal_div.h.*/WARN(l == 32,"ceil(log2(0x%08x)) == 32, %s doesn't support such divisor",d, __func__);post_shift = l;mlow = 1ULL << (32 + l);do_div(mlow, d);mhigh = (1ULL << (32 + l)) + (1ULL << (32 + l - prec));do_div(mhigh, d); // m的取值范围为(mlow, mhigh]for (; post_shift > 0; post_shift--) {  // 选出范围中包含2的幂次最大的mu64 lo = mlow >> 1, hi = mhigh >> 1;if (lo >= hi)break;mlow = lo;mhigh = hi;}R.m = (u32)mhigh;R.sh = post_shift;R.exp = l;R.is_wide_m = mhigh > U32_MAX;return R;
}

额外的一点是,linux没有实现l=32l=32l=32的情形(此时d>231d > 2^{31}d>231),因为这个时候需要128位的除法来计算mlow,mhigh。这种情况下软件模拟来进行128位除法的开销太大了。

That’s all.

linux内核的reciprocal_value结构体相关推荐

  1. Linux 内核文件系统模块结构体关系图

    导言 很久没有更新csdn博客了,工作两年,学习了不少新东西,很多都没有来的及整理,用过不久很快就忘记了,写到博客中做个记录. 关系图 下图为去年学习文件系统时所画,有参考网上其他博主的资料,也有自己 ...

  2. linux send 失败_Epoll学习服务器的实现-Linux内核原始Epoll结构

    1.Begins~ 有的人学习linux编程很久,只知道网络编程是socket,bind, listen...,然而这些都是网络通信软件最基本的接口.在某网络公司待了y,也了解到公司的基础就是网络转发 ...

  3. Linux内核中sk_buff结构详解

    目录 1.sk_buff结构体 1.1 sk_buff在内核中的结构 1.2 重要的长度len的解析 2. sk_buff数据区 2.1 线性数据区 2.2 非线性数据区 -------------- ...

  4. Linux 网络设备驱动开发(一) —— linux内核网络分层结构

    Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护. ...

  5. Linux 内核源代码的结构

    Linux内核源代码位于/usr/src/linux目录下. /include子目录包含了建立内核代码时所需的大部分包含文件,这个模块利用其他模块重建内核. /init 子目录包含了内核的初始化代码, ...

  6. Linux下网络相关结构体 struct servent

    Linux下网络相关结构体 struct servent 参考书籍:<UNIX环境高级编程> 参考链接: http://www.cnblogs.com/benxintuzi/p/45898 ...

  7. 深入浅出Linux内核网络协议栈|结构sk_buff|Iptables|Netfilter丨内核源码丨驱动开发丨内核开发丨C/C++Linux服务器开发

    深入浅出Linux内核网络协议栈 视频讲解如下,点击观看: 深入浅出Linux内核网络协议栈|结构sk C/C++Linux服务器开发高级架构师知识点精彩内容包括:C/C++,Linux,Nginx, ...

  8. 鸿蒙内核源码分析表,鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 ? | 开篇致敬鸿蒙内核开发者 | v1.10...

    谁是鸿蒙内核最重要的结构体? 答案一定是: LOS_DL_LIST(双向链表),它长这样.typedef struct LOS_DL_LIST {//双向链表,内核最重要结构体 struct LOS_ ...

  9. windows内核开发笔记七:内核开发OVERLAPPED结构体详解

    windows内核开发笔记七:内核开发OVERLAPPED结构体详解 typedef struct _OVERLAPPED {   DWORD Internal;   DWORD InternalHi ...

  10. linux怎么查看内核定义的结构体,Linux如何查找一个结构体的原始定义

    下面以查找结构体FILE的原始定义为例: 1.我们知道,这些定义一般都在 /usr/include下面,所以首先到达目录 /usr/include 下面 2.用grep命令搜索 ,即grep FILE ...

最新文章

  1. CentOS重启启动Apache,VNC
  2. R语言使用lubridate包的tz函数设置和查询日期、时间对象的时区信息( time zone)
  3. HTML5学习笔记(十四):变量作用域
  4. Activity和Task
  5. 程序员下班回家,路上被拦…
  6. android onSaveInstance方法项目中的实践
  7. 用vue手脚架生成的项目直接修改时.vue文件后,保存编译就报一大堆的错,这是咋回事呢?
  8. Linux命令解释之fdisk
  9. 初步搭建RocketMQ环境
  10. C# .Net ListT中Remove()、RemoveAt()、RemoveRange()、RemoveAll()的区别,ListT删除汇总
  11. 洛谷P1141 01迷宫【bfs】
  12. halcon自动对焦
  13. HDU - 4311 Meeting point-1(最小曼哈顿距离和)
  14. FlashFXP,怎么使用flashfxp
  15. 如何预防高层小区电气火灾的发生-Susie 周
  16. 计算机图解教程视频教程,新手怎么制作短视频教程?视频处理的图文步骤
  17. C#毕业设计——基于C#+asp.net+cs的Web Mail邮件收发系统设计与实现(毕业论文+程序源码)——邮件收发系统
  18. c++屏蔽Win10系统快捷键
  19. 念数字 字符串操作系列2
  20. 高校智慧实验室管理系统的实践应用

热门文章

  1. 干货丨荧光定量pcr应用于各个领域的分类疑难问题解答
  2. 计算机打不开硬盘,电脑硬盘打不开的原因 如何解决电脑硬盘问题
  3. 无人机+强化学习开源项目、工具包汇总
  4. excel合并计算机操作,Excel如何快速合并多个单元格的内容?
  5. 如何在BIOS中设置RAID?
  6. xp计算机连接不上网络打印机驱动,windows xp系统打印机共享提示连接失败的解决方法...
  7. JS混入(mix-in)小案例
  8. 第五章-----Java数组及排序
  9. word去除各种下划线
  10. 词干提取(stemming)与词形还原(lemmatization)