首页 > 解决方案 > 乘法如何比向左移位更快?

问题描述

众所周知,向左移位比乘法要快,因为桶形移位器直接在硬件中实现。因此,这个简单的基准测试应该是错误的:

$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 解释器在这里做了什么。测试由在 Ubuntu 中运行的 PHP 7.0.32 执行:

PHP 7.0.32-0ubuntu0.16.04.1 (cli) (NTS)

CPU:Intel(R) Core(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) (内置: Jun 20 2018 23:24:42) (ZTS MSVC14 (Visual C++ 2015) x64)

标签: phpbenchmarkingbitwise-operators

解决方案


您对硬件的推理基本上是无关紧要的。您正在使用一种解释语言,其中大部分成本是解释器开销。

任一循环的 asm 版本可以以每时钟 1 次运行(假设固定计数移位),因此(在 3GHz CPU 上)仅 100k 次迭代将花费 0.033 毫秒或 0.000033 秒,比 PHP 时间快约 250 倍。


此外,解释循环必须使用可变计数移位(因为它不能将移位计数 JIT 编译为机器代码中的立即数),这实际上对于 Intel CPU 上的吞吐量(3 uops)来说更昂贵,因为x86 遗留包(标志语义)。即使对于可变移位计数,AMD CPU 也具有单微指令移位。(shl reg, clshr reg, imm8)。请参阅INC 指令与 ADD 1:这有关系吗?更多关于为什么shl reg,clSandybridge-family 是 3 uops,以及它如何通过标志创建错误依赖)

在 Intel Sandybridge 系列和 AMD Ryzen 上,整数乘法是 1 uop,每时钟 1 个吞吐量,3 个周期延迟。我在 AMD Bulldozer 系列上每 2 个时钟,未完全流水线化。所以是的,乘法具有更高的延迟,但它们都是完全流水线的吞吐量。您的循环会丢弃结果,因此没有循环携带的依赖链,因此延迟是无关紧要的(并且被乱序执行隐藏)。

但是那个微小的差异(2个额外的微指令)不足以解释测量的差异。 实际的移位或乘法仅是循环所用总周期的 1/250。您说切换循环的顺序不会改变结果,因此在您的 CPU 加速到最大时钟速度之前,这不仅仅是一种预热效果。

您还没有提到您正在运行的 CPU 微架构,但答案可能并不取决于移位指令与乘法指令的解码方式。


推荐阅读