乘法如何比向左移动位更快?
How can the multiplication be faster than shifting bits to the left?
众所周知,向左移位比乘法更快,因为桶形移位器是直接在硬件中实现的。因此,这个简单的基准应该是错误的:
$start = 1;
$timestart = microtime(1);
for ($i = 0; $i < 10000000; $i++) {
$result2 = $start << 2;
}
echo microtime(1) - $timestart;
$timestart = microtime(1);
for ($i = 0; $i < 10000000; $i++) {
$result1 = $start * 4;
}
echo microtime(1) - $timestart;
echo "\n";
因为我多次执行它并且总是乘法比向左移动位更快。例如:
0.73733711242676
0.71091389656067
因此,或者基准测试错误,或者 PHP 解释器正在此处执行某些操作。测试由PHP 7.0.32 运行 in Ubuntu:
执行
PHP 7.0.32-0ubuntu0.16.04.1 (cli) (NTS)
CPU:英特尔(R) 酷睿(TM) i5-4460 CPU @ 3.20GHz
编辑:
在 Windows 框中执行它,几乎相同 CPU (Intel(R) Core(TM) i5-4460S CPU @2.90GHz) 结果就像预计:
0.24960112571716
0.28080010414124
本案例的 PHP 版本不同:
PHP 7.1.19 (cli)(内置:2018 年 6 月 20 日 23:24:42)(ZTS MSVC14(Visual C++ 2015)x64)
你关于硬件的推理基本上是无关紧要的。您使用的是解释性语言,其中大部分成本是解释器开销。
任一循环的 asm 版本可以 运行 每个时钟 1(假设固定计数移位),因此仅 100k 次迭代将花费(在 3GHz CPU 上)0.033 毫秒,或0.000033 秒,比您的 PHP 时间快约 250 倍。
此外,解释型循环必须使用可变计数移位(因为它无法将移位计数 JIT 编译为机器代码中的立即数),这实际上对吞吐量(3 微指令)来说更昂贵由于 x86 遗留包袱(标志语义),英特尔 CPUs。 AMD CPUs 即使对于可变的移位计数也有单 uop 移位。 (shl reg, cl
对比 shr reg, imm8
)。请参阅 了解为什么 shl reg,cl
在 Sandybridge 系列上是 3 微指令,以及它如何通过标志创建错误依赖)
在 Intel Sandybridge 系列和 AMD Ryzen 上,整数乘法是 1 uop,每时钟 1 个吞吐量,3 个周期延迟。我在 AMD Bulldozer 系列上每 2 个时钟,未完全流水线化。所以是的,乘法有更高的延迟,但它们都是完全流水线化的吞吐量。你的循环丢弃了结果,所以没有循环携带的依赖链,所以延迟是无关紧要的(并且被乱序执行隐藏)。
但是这个微小的差异(2 个额外的微指令)不足以解释测量的差异。实际的移位或乘法只是循环总周期的 1/250需要。你说切换循环的顺序不会改变结果,所以它不仅仅是在你的 CPU 上升到最大时钟速度之前的预热效果。
您没有提到您运行使用的CPU 微体系结构,但答案可能不取决于移位指令与乘法指令的解码方式。
众所周知,向左移位比乘法更快,因为桶形移位器是直接在硬件中实现的。因此,这个简单的基准应该是错误的:
$start = 1;
$timestart = microtime(1);
for ($i = 0; $i < 10000000; $i++) {
$result2 = $start << 2;
}
echo microtime(1) - $timestart;
$timestart = microtime(1);
for ($i = 0; $i < 10000000; $i++) {
$result1 = $start * 4;
}
echo microtime(1) - $timestart;
echo "\n";
因为我多次执行它并且总是乘法比向左移动位更快。例如:
0.73733711242676
0.71091389656067
因此,或者基准测试错误,或者 PHP 解释器正在此处执行某些操作。测试由PHP 7.0.32 运行 in Ubuntu:
执行PHP 7.0.32-0ubuntu0.16.04.1 (cli) (NTS)
CPU:英特尔(R) 酷睿(TM) i5-4460 CPU @ 3.20GHz
编辑:
在 Windows 框中执行它,几乎相同 CPU (Intel(R) Core(TM) i5-4460S CPU @2.90GHz) 结果就像预计:
0.24960112571716
0.28080010414124
本案例的 PHP 版本不同:
PHP 7.1.19 (cli)(内置:2018 年 6 月 20 日 23:24:42)(ZTS MSVC14(Visual C++ 2015)x64)
你关于硬件的推理基本上是无关紧要的。您使用的是解释性语言,其中大部分成本是解释器开销。
任一循环的 asm 版本可以 运行 每个时钟 1(假设固定计数移位),因此仅 100k 次迭代将花费(在 3GHz CPU 上)0.033 毫秒,或0.000033 秒,比您的 PHP 时间快约 250 倍。
此外,解释型循环必须使用可变计数移位(因为它无法将移位计数 JIT 编译为机器代码中的立即数),这实际上对吞吐量(3 微指令)来说更昂贵由于 x86 遗留包袱(标志语义),英特尔 CPUs。 AMD CPUs 即使对于可变的移位计数也有单 uop 移位。 (shl reg, cl
对比 shr reg, imm8
)。请参阅 shl reg,cl
在 Sandybridge 系列上是 3 微指令,以及它如何通过标志创建错误依赖)
在 Intel Sandybridge 系列和 AMD Ryzen 上,整数乘法是 1 uop,每时钟 1 个吞吐量,3 个周期延迟。我在 AMD Bulldozer 系列上每 2 个时钟,未完全流水线化。所以是的,乘法有更高的延迟,但它们都是完全流水线化的吞吐量。你的循环丢弃了结果,所以没有循环携带的依赖链,所以延迟是无关紧要的(并且被乱序执行隐藏)。
但是这个微小的差异(2 个额外的微指令)不足以解释测量的差异。实际的移位或乘法只是循环总周期的 1/250需要。你说切换循环的顺序不会改变结果,所以它不仅仅是在你的 CPU 上升到最大时钟速度之前的预热效果。
您没有提到您运行使用的CPU 微体系结构,但答案可能不取决于移位指令与乘法指令的解码方式。