研究一下 CPU 除法
在 QQ 群 里, 我 让 网友 馥岚过野 出题 来 考 我,
馥岚过野 说 :
“
你昨晚又说做汇编了
我问你几个问题:
目前CPU整数乘法和除法的性能差别在多少左右?
32位乘法和64位乘法有什么差别?
整数乘法时,大数和小数有没有区别?
”
2021-08-11 接着写 。
要 回答 这些问题, 我们先来 研究一下 计算机 的 除法 是 怎么实现 的 。
除法 就是 做 减法, 试商 一位, 除数 乘以 这一位 商, 由 被除数 来 减, 差 为 余数, 再 试商 下一位, 同样, 除数 乘以 这一位 商, 由 余数 来 减, 如此重复 。
这个 过程 用 逻辑电路 可以 实现 出来 , 比如 :
这个 计算过程 的 逻辑电路 可以设计 出来, 最 简单 的 设计 , 每个 时钟周期 作 一次 试商, 比如 先 试商 1, 如果 1 乘以 除数 , 比 被除数 对应 的 前几位 小, 或 比 添位 后 的 余数 小, 则 试商 成功, 否则 试商 0 。
二进制 比较 简单, 每一位 最多 只要 试商 2 次, 0 和 1 。 也就是说, 每一位 最多 需要 2 次 试商, 也就是 2 个 时钟周期 。
对 被除数 的 每一位 最多 要 试商 2 次, 要 2 个 时钟周期, 能不能 在 一个 时钟周期 里, 就 完成 这 2 次 试商 ?
可以 。 这 可以用 组合逻辑电路 实现, 不用 时序逻辑电路, 也就是说, 除法 对 一位 的 (最多两次) 试商 都可以 由 一个 组合逻辑 电路 来 实现, 也就是在 一个 时钟周期 里 完成 。 这里 的 试商 包括了 试商 * 除数 再由 被除数(余数) 来 减 的 整个过程 。
这个 组合逻辑电路 的 模块电路图 可以 这样画 :
这样, 每一位 的 (2 次)试商 可以 在 一个 时钟周期 里 完成 。
一个 8 位 的 二进制数, 除以一个 8 位 以内 的 二进制数, 看起来 要 对 8 个 位 试商 。
一开始, 第 1 次 试商 的 时候, 被除数 和 除数 * 试商 的 最高位 要 对齐, 再相减 。
之后, 余数 和 除数 * 试商 则 是 从 最低位 对齐, 再相减 。
实际上, 余数 要 添位 之后 才会和 除数 * 试商 相减 。 添位, 就是 给 余数 添上 当前 试商 的 被除数 的 那一位 。
有时候, 也 不用 添位, 直接 在 当前 试商 的 位 补 0 就行 。
为了叙述简便, 余数 添位 后 仍然 简称 余数, 大家 自行 理解 即可 。
使用 上面 这个 组合逻辑电路, 对 每一位 的 试商 需要 一个 时钟周期, 对 8 位 试商 需要 8 个 时钟周期, 也就是 做完一次 除法 需要 8 个 时钟周期 。
以此类推, 16 位 二进制数 的 除法 要 16 个 时钟周期, 32 位 的 除法 要 32 个 周期, 64 位 的 除法 要 64 个 周期 。
加法 减法 乘法 都 只要一个 时钟周期 , 看起来 除法 成了 工作量 “大户” 了 。
能不能 缩短一些 时间, 也就是 减少一些 时钟周期 ?
能不能 在 一个 时钟周期 内 做完 8 位 的 试商 ?
可以, 同样, 这也就是用 一个 组合逻辑电路 来 完成 对 8 位 的 试商, 画 一个 模块电路图 :
这个 图 没有 画完, 只 画 了 2 层 试商模块, 1 层 试商模块 负责 对 1 位 试商, 8 位 就要 8 层 试商模块 。
看的出来, 第 1 层 试商模块 是 2 个, 第 2 层 的 试商模块 个数 是 第 1 层 的 2 倍, 是 2 * 2 = 4 个, 下一层 是 上一层 的 2 倍 , 也就是,
第 3 层 的 试商模块 个数 是 2 ³ = 8 个,
第 4 层 的 是 2 ⁴ = 16 个,
第 5 层 的 是 2 ⁵ = 32 个,
第 6 层 的 是 2 ⁶ = 64 个,
第 7 层 的 是 2 ⁷ = 128 个,
第 8 层 的 是 2 ⁸ = 256 个,
全部加起来 就是 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 = 512 个 试商模块 。 512 = 2 * 2 ⁸ , 也就是 2 * 2 ⁸ 个 试商模块 。
也就是, n 位 二进制 整数 的 除法 要在 一个 时钟周期 里 完成, 需要 2 ^ ( n + 1 ) 个 试商模块 。
这个 数量 是 很大的, 16 位 时, 2 * 2^16 = 131072 ≈ 13 万 , 32 位 时, 2 * 2^32 = 8 G ≈ 80 亿 。
13 万 个 试商模块, 这么多 电路元件 只是用于 除法, 这个 电路规模 是 很大 的, 80 亿 那就 更夸张 了 。
可以看到, 要想 在 一个 时钟周期 里 完成 比较多 位 的 除法, 需要 一个 规模 很大 的 组合逻辑电路 , 规模 很大 就是 元件数量 很多 。
这些 与 硬件工艺 硬件资源 硬件成本 相关 。
总之 呢 , 一个 时钟周期 完成 32 位 除法 是 不现实 的, 16 位 可能 也 够呛 , 那么, 能不能 折中一点, 比如 一个 时钟周期 完成 4 位 除法, 这样, 8 位 除法 就是 8/4 = 2 个 周期, 16 位 除法 是 16/4 = 4 个周期, 32 位 除法 是 32/4 = 8 个周期, 64 位 除法 是 64 / 4 = 16 个 周期 。
应该 可以 这样做 , 哈哈 。
上面的 是 基本 的 除法, 也可以说是 整数除法, 再来 看看 浮点数 的 除法 。
比如, 1001 / 0.0011 , 首先 要 对齐 被除数 和 除数 的 小数点 , 就是 把 被除数 和 除数 都 转换成 整数, 1001 / 0.0011 = 10010000 / 11 ,
1001 / 0.0011 会 转换成 10010000 / 11 来 计算 。
1001 是 4 位, 转换成 10010000 , 就成了 8 位, 需要 8 位 的 存储单元 和 运算电路 。
对 二进制 来说, 余数 比 除数 至少 要 少一位, 如果 除数 是 4 位, 则 余数 可能是 3 位 、2 位 、1 位 、0 位 。
当然, 如果 试商 是 直接 补 0 , 试商 * 除数 = 0 * 除数 = 0, 余数 - 试商 * 除数 = 余数 - 0 = 余数, 余数 还是 保持不变, 此时, 余数 也可能 和 除数 的 位数一样, 也就是, 余数 也可能 是 4 位 。
试商模块 处理 余数添位 和 余数 - 试商 * 除数, 余数 最多 4 位, 除数 最多 4 位 ; 试商 是 1 位, 二进制 的 话, 试商 只会 是 1 和 0 , 试商 * 除数 也 只会 等于 除数 或 0, 也就是 试商 * 除数 最多 4 位, 所以 , 试商模块 最多 只要 处理 4 位 数据 就可以, 也就是说, 试商模块 只要 4 位 电路 就可以 。
8 位 电路 应该 主要 是 存储 被除数 和 商 的 电路 。 但 4 位数 的 除法, 商 似乎 也 是 4 位, 所以, 商 似乎 也只要 4 位 的 存储单元 。
如果 是 1001 / 0.00000011 , 那要 转换成 100100000000 / 11 , 被除数 1001 要 变成 100100000000 , 一个 12 位 整数, 需要 12 位 的 存储单元 。
实际 的 CPU 的 电路位数 是 有限 的, 设计 的 时候 会 决定 电路位数, 这 会对 浮点数 的 (大数)除法 精度 产生 相应 的 影响 。
比如, 电路 支持 的 位数 是 4 位, 则 1001 / 0.0011 转换成 10010000 / 11 , 如果 被除数 的 存储单元 只有 4 位, 就 只会 存储 10010000 的 高 4 位 1001 , 于是, 实际 的 除法 只会 做到 前 4 位 1001 除以 11, 商 的 后面位数 可能 直接 补 0 。
补 0 的 部分 和 实际 的 商 之间 就会 有 差, 这个 差 就是 丢失 的 精度 。
当然, 10010000 / 11 刚好 前 4 位 1001 / 11 = 11 就 可以 除尽, 后面 确实 补 0 就可以, 但 这 只是一个 例子, 如果 前 4 位 除不尽, 那 补 0 的 部分 和 实际 的 商 之间 就会 有 差 了 。
所以, 32 位 的 除法电路 的 被除数 存储单元 不一定 只有 32 位, 可能 是 更多位, 这样 可以 在 浮点数 大数除法 时 减少 精度丢失 。
小朋友们, 看了 上面的 计算机 除法 基础知识, 应该可以回答 开头 的 问题 了 吧 ?
等, 我现在 才 发现 , 开头 的 3 个 问题 中, 只有 一个 提到了 除法, 这 ?
我 记得 看了 好几遍 题目 , 这是 怎么回事 ?
我 提倡 用 模块电路图 + 模块规格 来 做 硬件电路 设计, 真的很爽, 爽死了, 无敌 。
模块规格 包括 模块 定义 、接口 、参数 等 。