我正在阅读Knuth的计算机编程艺术,我注意到他指出DIV命令比他的MIX汇编语言中的ADD命令长6倍。
为了测试与现代架构的相关性,我编写了以下代码片段:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
clock_t start;
unsigned int ia=0,ib=0,ic=0;
int i;
float fa=0.0,fb=0.0,fc=0.0;
int sample_size=100000;
if (argc > 1)
sample_size = atoi(argv[1]);
#define TEST(OP) \
start = clock();\
for (i = 0; i < sample_size; ++i)\
ic += (ia++) OP ((ib--)+1);\
printf("%d,", (int)(clock() - start))
TEST(+);
TEST(*);
TEST(/);
TEST(%);
TEST(>>);
TEST(<<);
TEST(&);
TEST(|);
TEST(^);
#undef TEST
//TEST must be redefined for floating point types
#define TEST(OP) \
start = clock();\
for (i = 0; i < sample_size; ++i)\
fc += (fa+=0.5) OP ((fb-=0.5)+1);\
printf("%d,", (int)(clock() - start))
TEST(+);
TEST(*);
TEST(/);
#undef TEST
printf("\n");
return ic+fc;//to prevent optimization!
}
然后我使用此命令行生成了4000个测试样本(每个样本包含每种类型的100000个操作的样本大小):
for i in {1..4000}; do ./test >> output.csv; done
最后,我用Excel打开了结果并绘制了平均值。我发现的是相当令人惊讶的。以下是结果图:
实际平均值(从左至右):463.36475,437.38475,806.59725,821.70975,419.56525,417.85725,426.35975,425.9445,423.792,549.91975,544.11825,543.11425
总的来说,这就是我的预期(除法和模数都很慢,浮点结果也是如此)。
我的问题是:为什么整数和浮点乘法的执行速度都比其对应的快?这是一个很小的因素,但它在许多测试中都是一致的。在TAOCP中,Knuth将ADD列为2个单位时间,而MUL则为10.从那以后,CPU体系结构发生了什么变化?
答案 0 :(得分:2)
不同的指令在同一个CPU上占用不同的时间;并且相同的指令可能在不同的CPU上花费不同的时间。例如,对于英特尔最初的Pentium 4移位相对昂贵且加法速度相当快,因此向寄存器添加寄存器比将寄存器移位1更快;并且对于英特尔最近的CPU转换和添加大致相同的速度(转换速度比原来的奔腾4更快,并且在“周期”方面加速更慢)。
为了使事情更复杂,不同的CPU可能会同时做更多或更少的事情,并且还有其他影响性能的差异。
理论上(并不一定在实践中):
移位和布尔运算(AND,OR,XOR)应该是最快的(每个位可以并行完成)。加法和减法应该是下一个(相对简单,但结果的所有位不能并行完成,因为从一对位进位到下一位)。
乘法应该慢很多,因为它涉及许多添加,但其中一些添加可以并行完成。对于一个简单的例子(使用十进制数字而不是二进制),像12 * 34(有多个数字)的东西可以分解成“单个数字”形式,变成2 * 4 + 2 * 3 * 10 + 1 * 4 * 10 + 1 * 3 * 100;所有“单位”乘法可以并行完成,然后可以并行完成2次加法,然后可以完成最后一次加法。
分部主要是“比较和减去,如果更大,重复”。它是最慢的,因为它不能并行完成(下一次比较需要减法的结果)。模数是除法的余数,与除法基本相同(对于大多数CPU来说,它实际上是相同的指令 - 例如DIV
指令给出了商和余数。
浮点;每个数字都有2个部分(有效数和指数),因此事情变得复杂一些。浮点移位实际上是增加或减去指数(并且应该与整数加法/减法大致相同)。对于浮点加法,减法和布尔运算,您需要均衡指数,然后单独对有效位数进行操作(并且“均衡”和“执行操作”不能并行完成)。乘法是乘以有效数并添加指数(并调整偏差),其中两个部分可以并行完成,因此总成本是最慢的(乘以有效数);所以它和整数乘法一样快。除法是划分有效数并减去指数(并调整偏差),其中两个部分可以并行完成,总成本是最慢的(划分有效数);所以它和整数除法一样快。
注意:我已经在各个地方进行了简化,以便更容易理解。
答案 1 :(得分:1)
要测试执行时间,请查看汇编列表中生成的指令,并查看处理器的文档以获取这些指令,并注意FPU是否正在执行操作,或者是否直接在代码中执行。
然后,将每条指令的执行时间加起来。
但是,如果cpu是流水线或多线程的,那么操作可能比计算的时间少很多。
答案 2 :(得分:0)
分割和模(分割操作)确实比加法慢。这背后的原因是ALU(算术逻辑单元)的设计。 ALU是并行加法器和逻辑电路的组合。通过重复减法执行除法,因此需要更多级别的减法逻辑使得除法比加法更慢。分裂中涉及的门的传播延迟增加了蛋糕上的樱桃。