Python打印计算结果所需时间比执行计算本身的时间更长。

7

我用Python写了一个脚本,令我惊讶的是脚本的执行速度。 基本上,它会将五个20位数相乘,然后将结果的3000次方。使用timeit模块查找计算所需的时间。当我运行这个脚本时,它告诉我计算只需要3*10^-7秒。 接着,脚本生成一个文件output.txt,但直到15秒之后脚本才结束。

import timeit
outputFile = open("output.txt", "w")
start = timeit.default_timer()
x = (87459837581209463928*23745987364728194857*27385647593847564738*10293769154925693856*12345678901234567891)**3000
stop = timeit.default_timer()
time = stop-start
print "Time taken for the calculation was {} seconds".format(time)
outputFile.writelines(str(x))
outputFile.close()
y = raw_input("Press enter to exit.")

这是否意味着打印一个280kb的文件实际上比执行计算要花费更长的时间?(我觉得这不太可能。)

如果不是这样,那么当调用变量x时,Python会执行计算吗?它会每次计算变量时都执行计算,还是会将实际值存储在变量中?

我刚刚编写了另一个脚本,证实Python需要0.03秒才能将结果写入.txt文件。那么,为什么Python要延迟执行计算呢?


5
输入/输出(例如打印)通常比大多数计算慢。 - prq
2
特别是当你要打印一个大约有300,000位数字的数时。 - BrenBarn
280kb?噗,这不算什么吧?其实是很多的。 - anon582847382
http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html - Thomas
你可以尝试在IDLE控制台中打印同样的内容。如果你注释掉print语句并比较速度,就会很明显地发现问题出在打印上而不是计算上。这在Python世界中是一个相当广为人知的问题。有时候,如果IDLE控制台中有大量文本,它还会减慢计算机的其他任务速度。 - chase
脚本末尾有一个用户提示。你确定你的时间控制正确吗? - Alastair McCormack
3个回答

12

问题不在于计算或写入文件,而是将结果从二进制内部表示转换为十进制表示所消耗的时间。这需要的时间与位数的平方成正比,并且在这里你有很多位。

如果您将输出行替换为:

outputFile.writelines(hex(x))

你会发现它运行得非常快。将其转换为十六进制表示仅需要与位数成线性时间。

如果你确实需要以十进制表示输出巨大的整数,请考虑使用decimal 模块。 它在内部使用与基数10相关的表示进行计算,然后将其转换为十进制字符串只需要与十进制数字的数量成线性时间。但是,你需要事先将十进制上下文的精度设置为“足够大”的值,以避免由于舍入而丢失低阶位。


1
+1 如果提到它可能是“O(ndigits**2)”。你能详细说明一下为什么long_to_decimal_string()是二次的吗?(我在PyLong_FromString()中看到了相应的注释,但它是相反的方向)。 - jfs
2
因为Python longs在内部存储时基本上是以2的15次方为基础,而CPython并不会在2的幂和10的幂之间使用次二次多项式的基础转换算法。相反,它使用了更加明显(且二次时间复杂度)的算法,即“一次输出一个数字”。 - Tim Peters
1
相关:谷歌搜索显示可以实现次二次方无除法算法 - jfs
1
关于使用十进制:请确保您正在使用Python >= 3.3,或者使用来自PyPI的cdecimal包。否则,计算仍然以二进制进行,并且二进制<->十进制转换会占用大量大数字的操作时间。(因此在Python 2.7中,十进制加法所需的时间与数字位数成二次关系!) - Mark Dickinson

2
除了其他答案以外,使用outputFile.write(str(x))而不是writelines。writelines是用于一系列字符串的。在您的情况下,它迭代字符串并逐个写入每个字符。在简单测试中,writelines慢了3.7倍。
>>> timeit("f.writelines(str(s))", setup="f=open('tmp.txt','w');s=range(1000)", number=10000)
4.935087700632465
>>> timeit("f.write(str(s))", setup="f=open('tmp.txt','w');s=range(1000)", number=10000)
1.3468097837871085

在我的电脑上,计算这个数字需要0.0184微秒,将其转换为字符串(str(x))需要3500000微秒,使用.write()将其写入文件需要114微秒,而使用.writelines()则需要10200微秒。换句话说,str(x)占据了所有时间的主导地位。 - jfs
更正:计算这个数字需要50000微秒(似乎Python编译器会计算一些字面量,即如果任何一个数字是变量,则上一个结果是不正确的)。 - jfs
@J.F.Sebastian,这就是“除了...”部分的意思。这不是主要的减速因素,但也应该被修复。 - tdelaney
我知道,这就是为什么上面要加 +1 - jfs

1
"这是将其转换为字符串所花费的时间很长:"
In [68]: %time x = (87459837581209463928*23745987364728194857*27385647593847564738*10293769154925693856*12345678901234567891)**3000
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s

In [69]: %time xs = str(x)
CPU times: user 1.98 s, sys: 0.00 s, total: 1.98 s
Wall time: 1.98 s

In [71]: %time print xs
CPU times: user 0.01 s, sys: 0.00 s, total: 0.01 s
Wall time: 0.04 s

但是对于有数十万位数字的数字来说,这并不奇怪。

编辑

与其他答案相反,将内容写入文件并不需要太多时间:

In [72]: %time with open('tmp.file', 'w') as f: f.write(xs)
CPU times: user 0.00 s, sys: 0.01 s, total: 0.01 s
Wall time: 0.00 s

尝试将幂次方变成一个变量:n=3000; (..)**n - jfs
计算速度当然比str(x)慢得多,但仍然比它快得多。我应该从中得到什么? - m.wasowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接