记录执行时间:计算机如何快速计算算术?

时间:2014-02-28 11:18:29

标签: python python-3.x time

我的问题起初看起来很简单,但请耐心等待。

我编写了以下代码,以测试python从1到1,000,000计算需要多长时间。

import time

class StopWatch:
    def __init__(self, startTime = time.time()):
        self.__startTime = startTime
        self.__endTime = 0

    def getStartTime(self):
        return self.__startTime

    def getEndTime(self):
        return self.__endTime



    def stop(self):
        self.__endTime = time.time()



    def start(self):
        self.__startTime = time.time()

    def getElapsedTime(self):
        return self.__endTime - self.__startTime


count = 0
Timer = StopWatch()
for i in range(1, 1000001):
    count += i

Timer.stop()
total_Time = Timer.getElapsedTime()
print("Total time elapsed to count to 1,000,000: ",total_Time," milliseconds")

我计算出一个惊人的短时间跨度。它是0.20280098915100098毫秒。我首先要问:这是正确的吗?

我预计执行时间至少为2或3毫秒,但我没想到它能在不到半毫秒的时间内完成计算!

如果这是正确的,那就引出了我的第二个问题:为什么这么快?

我知道CPU本质上是为算术而构建的,但我仍然不会预计它能在十分之二毫秒内计算到一百万!

3 个答案:

答案 0 :(得分:3)

正如@jonrsharpe评论的那样,也许你被时间测量单位欺骗了。

尽管如此,第三代Intel i7能够达到120 + GIPS(即每秒数十亿次基本操作),因此假设所有缓存命中并且没有上下文切换(简单地说,没有意外的等待),它可以轻松地从0开始计数在所述时间到1G甚至更多。可能不是Python,因为它有一些开销,但仍然可能。

解释现代CPU如何实现这样一种......“疯狂”的速度是一个相当广泛的主题,实际上是多种技术的协作:

  1. 动态调度程序将重新排列基本指令以尽可能减少冲突(因此,等待)
  2. 一个精心设计的缓存将及时提供代码和(尽管该基准测试的问题较少)数据。
  3. 动态分支预测器将分析代码并推测分支条件(例如“for循环结束与否?”)以预测具有“获胜”机会的跳跃。
  4. 一个优秀的编译器将通过重新安排指令来提供一些额外的工作,以减少冲突或更快地进行循环(通过展开,合并等)。
  5. 多精度算术可以利用MMX集等提供的矢量运算。
  6. 简而言之,这些小奇迹如此昂贵的原因不仅仅是:)

答案 1 :(得分:2)

首先,正如已经指出的那样,time()输出实际上是以秒为单位,而不是毫秒。

另外,你实际上是在1m ** 2/2总共增加1m,而不是1m,你正在用range初始化一百万长的列表(除非你在python 3上)

我在笔记本电脑上运行了一个更简单的测试:

start = time.time()
i = 0;
while i < 1000000:
   i+=1
print time.time() - start

结果:

0.069179093451

所以,70毫秒。这相当于每秒14次<百万次的操作。

让我们看看Stefano可能提到的表格(http://en.wikipedia.org/wiki/Instructions_per_second)并进行粗略估计。 他们没有像我这样的i5,但最慢的i7将足够接近。它的时钟频率为80 GIPS,4核,每核20 GIPS。

(顺便说一句,如果你的问题是“它如何设法每核心获得20 GIPS?”,那么无法帮助你。这是 maaaagic 纳米技术)

因此,核心每秒能够进行20 <100亿次操作,而我们只能获得14 <100万 - 不同因素 1400

此时正确的问题不是“为什么这么快?”,“为什么这么慢?”。可能是python开销。如果我们在C中尝试这个怎么办?

#include <stdio.h>
#include <unistd.h>
#include <time.h>

int i = 0;
int million = 1000000;
int main() {

    clock_t cstart = clock();
    while (i < million) {
     i += 1;
    }

    clock_t cend = clock();
    printf ("%.3f cpu sec\n", ((double)cend - (double)cstart) / CLOCKS_PER_SEC);
    return 0;
}

结果:

0.003 cpu sec

这比python快23倍,与每秒理论“基本操作”的数量只有60倍不同。我在这里看到两个操作 - 比较和添加,所以30次不同。这是完全合理的,因为基本操作可能比我们的添加和比较小得多(让汇编专家告诉我们),而且我们也没有考虑上下文切换,缓存未命中,时间计算开销以及谁知道还有什么。

这也表明python执行相同操作的操作次数是其23倍。这也是完全合理的,因为python是一种高级语言。这是你在高级语言中得到的惩罚 - 现在你明白为什么速度关键部分通常用C语言编写。

此外,python的整数是不可变的,并且应该为每个新整数分配内存(python运行时对它很聪明,但不过)。

我希望能回答你的问题并教你一些关于如何进行令人难以置信的粗略估计的方法=)

答案 2 :(得分:1)

简短回答:正如jonrsharpe在评论中提到的,它是秒,而不是毫秒。

另外,正如Stefano所说,xxxxxx - &gt;检查他的答案。除了ALU之外,它还有很多细节。

我只是想提一下 - 当你在类或函数中创建默认值时,请确保使用简单的不可变而不是放置函数调用或类似的东西。你的班级实际上是为所有实例设置计时器的开始时间 - 如果你创建一个新的计时器,你会得到一个令人讨厌的惊喜,因为它将使用前一个值作为初始值。试试这个,第二个Timer

计时器不会重置
#...
count = 0
Timer = StopWatch()
time.sleep(1)
Timer - StopWatch()
for i in range(1, 1000001):
    count += i
Timer.stop()
total_Time = Timer.getElapsedTime()
print("Total time elapsed to count to 1,000,000: ",total_Time," milliseconds")

你将得到大约1秒而不是你期望的。