python vs bc评估6 ^ 6 ^ 6


29

我正在分别6^6^6使用python和评估表达式bc

python文件的内容为print 6**6**6。当我执行时time python test.py,我得到的输出为

real        0m0.067s
user        0m0.050s
sys         0m0.011s

然后,我运行了命令time echo 6^6^6 | bc,该命令给了我以下输出

real        0m0.205s
user        0m0.197s
sys         0m0.005s

从这些结果可以明显看出,python和bc所花费的sys时间分别为11ms和5ms。在系统时间级别上bc命令的性能优于python,但对于用户和实时python,它的速度几乎是bc的4倍。什么可能去了那里。我没有对此类流程给予任何优先考虑。我试图了解这种情况。


那么,您是说sys组件仅给出加载所需的时间,而运行时将在输出的用户组件中给出吗?
ganessh

我真的不确定,这就是为什么我发表评论。这只是一个猜测。
terdon

7
echo | bc涉及由于管道而启动子外壳-这可能是您多余的用户时间的来源。为了使它成为一个公平的测试,python脚本应该从stdin读取,这样您就可以了time echo 6**6**6 | whatever.py
goldilocks 2014年

1
我宁愿将be命令行放入脚本中并安排执行时间。或使用echo 6^6^6 | time bc
丹尼尔·库尔曼2014年

1
旁注:在python中,6**6**6表达式实际上是在编译时计算的。但是,由于您是直接启动文件而不是从模块导入文件,因此这无关紧要。要看到其中的差别放10**12345678成一个a.py文件,并尝试从交互式解释导入。然后关闭解释器,重新启动它并a再次导入。第一次应该花费很长的时间(因为python正在编译模块),而第二次它加载了.pyc,这应该是瞬时的,
Bakuriu 2014年

Answers:


25

Python在启动时会导入大量文件:

% python -c 'import sys; print len(sys.modules)'
39

由于定义模块的方法有很多,因此每种方法都需要更多尝试打开Python文件的尝试:

% python -vv -c 'pass'
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# trying site.so
# trying sitemodule.so
# trying site.py
# trying site.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sitemodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
import site # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc
# trying os.so
# trying osmodule.so
# trying os.py
# trying os.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/osmodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
import os # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc
    ...

除了内置的那些,每个“ trying”都需要一个os级/系统调用,并且每个“ import”似乎都会触发大约8条“ trying”消息。(有一些方法可以使用zipimport减少这种情况,并且PYTHONPATH中的每个路径可能都需要另一个调用。)

这意味着在我的机器上启动Python之前,有将近200个stat系统调用,并且“时间”将其分配给“ sys”而不是“ user”,因为用户程序正在系统上等待操作。

相比之下,就像Terdon所说的那样,“ bc”没有那么高的启动成本。查看dtruss输出(我有Mac;基于Linux的操作系统为“ strace”),我发现bc不会自己进行任何open()或stat()系统调用,只是加载了一些共享的库是开始,Python当然也可以这样做。此外,在准备处理任何内容之前,Python还需要读取更多文件。

等待磁盘很慢。

通过执行以下操作,您可以了解Python的启动成本:

time python -c pass

在我的机器上为0.032s,而“ print 6 ** 6 ** 6”为0.072s,因此启动成本为总时间的1/2,而计算+转换为小数的时间为另一半。而:

time echo 1 | bc

花费0.005s,“ 6 ^ 6 ^ 6”花费0.184s,因此bc的幂运算速度比Python慢​​4倍以上,尽管入门速度快7倍。


4
你有点把铅埋在那里。您可能需要将结束位移到顶部。
Riking

只是出于我的机器上的兴趣:时间蟒蛇-c“通” 0m0.025s,时间蟒蛇-c“打印6 6 6” 0m0.087s但时间蟒蛇-c“X = 6 6 6” 0m0.028s所以大部分的时间输出大量。
史蒂夫·巴恩斯

是的,转换为以10为底的数字需要二次时间。在极端情况下,请尝试打印较大的Mersenne底漆之一。这是非常快的来计算,但需要较长的时间来打印在基地10
安德鲁Dalke

11

我在SO上找到了一个很好的答案,解释了不同的领域:

  • 实际时间是挂钟时间-从通话开始到结束的时间。这是所有经过的时间,包括其他进程使用的时间片以及该进程花费的时间被阻塞(例如,如果它正在等待I / O完成)。

  • 用户是进程中用户模式代码(内核外部)所花费的CPU时间量。这只是执行过程中使用的实际CPU时间。该进程花费的其他进程和时间不计入该数字。

  • Sys是进程中内核花费的CPU时间量。这意味着在内核内部执行系统调用所花费的CPU时间,与仍在用户空间中运行的库代码相反。像“用户”一样,这只是进程使用的CPU时间。有关内核模式(也称为“管理程序”模式)和系统调用机制的简要说明,请参见下文。

因此,在您的特定示例中,就完成实际时间而言,python版本更快。但是,python方法在内核空间上花费了更多时间,从而调用了内核函数。该bc命令基本上不花时间在内核空间上,而所有时间都花在用户空间上,大概是在运行内部bc代码。

这对您没有影响,您真正关心的唯一信息是real启动命令和获取命令之间的实际时间。

您还应该意识到,这些微小的差异并不是稳定的,它们还取决于系统的负载,并且每次运行命令时都会改变:

$ for i in {1..10}; do ( time python test.py > /dev/null ) 2>&1; done | grep user
user    0m0.056s
user    0m0.052s
user    0m0.052s
user    0m0.052s
user    0m0.060s
user    0m0.052s
user    0m0.052s
user    0m0.056s
user    0m0.048s
user    0m0.056s

$ for i in {1..10}; do ( time echo 6^6^6 | bc > /dev/null ) 2>&1; done | grep user
user    0m0.188s
user    0m0.188s
user    0m0.176s
user    0m0.176s
user    0m0.172s
user    0m0.176s
user    0m0.180s
user    0m0.172s
user    0m0.172s
user    0m0.172s

10

我将从另一个角度解释它。

公平地说,bc它具有优势,因为它不必从磁盘读取任何内容,只需要其blob /二进制文件,而python必须导入一系列模块+读取文件。因此您的测试可能偏向bc。要实际测试它,您应该使用bc -q filewhere file包含:

6^6^6
quit

更改只是修改了使用时间echo

bc  0.33s user 0.00s system 80% cpu 0.414 total

要使用文件:

bc -q some  0.33s user 0.00s system 86% cpu 0.385 total

(您将不得不使用terdon的方法来注意到更大的差异,但是至少我们知道它们是)

现在,从python的角度来看,python 每次需要从磁盘读取,编译执行文件,还需要按Andrew的指示加载模块,这会使执行时间变慢。如果编译python脚本的字节码,您会注意到执行代码所需的总时间减少了50%:

python some.py > /dev/null  0.25s user 0.01s system 63% cpu 0.413 total

编译:

./some.pyc  0.22s user 0.00s system 77% cpu 0.282 total

如您所见,有几个因素会影响不同工具之间的时间执行。


3

我受益于阅读其他答案。对于初学者喜欢我的人应该知道为什么我们在这里处理如此庞大的整数是,无论是Pythonbc右关联幂扩张,这意味着这是不是6^36我们正在评估,而是6^46656这是相当大的。1个

使用以下命令的变体,我们可以提取time保留字和命令输出的特定元素的平均值:

for i in {1..1000}; do (time echo 6^6^6 | bc > /dev/null) 2>&1; done | grep 'rea' | sed -e s/.*m// | awk '{sum += $1} END {print sum / NR}'

for i in {1..1000}; do (/usr/bin/time -v sh -c 'echo 6^6^6 | bc > /dev/null') 2>&1; done | grep 'Use' | sed -e s/.*:// | awk '{sum += $1} END {print sum / NR}'

可以走另一条路线,从比较中完全删除文件。另外,我们可以将bc的时序与dc命令进行比较,因为从历史上看,前者是后者的“前端处理器”。定时了以下命令:

echo 6^6^6 | bc
echo 6 6 6 ^ ^ p | dc
echo print 6**6**6 | python2.7

请注意,该dc命令是左关联的以求幂。2

我们用time(bash)获得了1000次迭代(以秒为单位)的结果:

0.229678 real bc
0.228348 user bc
0.000569 sys bc
0.23306  real dc
0.231786 user dc
0.000395 sys dc
0.07 real python
0.065907 user python
0.003141 sys python

bcdc在这种情况下提供可比的性能。

来自GNU 命令的精度较低的3个结果(比例精度在此处无效,但结果相似):/usr/bin/timetime

0.2224 user bc
0 sys bc
0.23 Elapsed bc
0.22998 user dc
0 sys dc
0.23 Elapsed dc
0.06008 user python
0 sys python
0.07 Elapsed python

的优点/usr/bin/time是它提供了-v产生更多信息的选项,这些信息最终可能会有用。

也可以在内部对此进行评估,例如使用timeitPython模块:

python2.7 -m timeit -n 1000 -r 1 'print 6**6**6' | grep 'loops'
1000 loops, best of 1: 55.4 msec per loop

这比我们之前看到的要快一些。让我们尝试解释器本身:

>>> import timeit
>>> import sys
>>> import os
>>> T = timeit.Timer("print 6**6**6")
>>> n = int(1000)
>>> f = open(os.devnull, 'w')
>>> sys.stdout = f
>>> t = t.timeit(n)
>>> sys.stdout = sys.__stdout__
>>> print t/n
0.0553743481636

那是我见过的最快的。


如果我们评估较小的指数,例如6^6,那么time命令会产生令人惊讶的结果-使用与for我们使用的相同的循环命令,我们现在有:

0.001001 bc real
0.000304 user
0.000554 sys
0.014    python real i.e. 10x more than bc??
0.010432 user
0.002606 sys

因此,使用较小的整数bc会突然变得更快吗?从系统重新引导到第二次运行没有区别。但是同时,如果我们将其timeit用于Python,则会得到:

python2.7 -m timeit -n 100000 -r 1 'print 6**6' | grep loops  
100000 loops, best of 1: 0.468 usec per loop

这是微秒,而不是毫秒,所以这与使用for循环的慢得多的结果不匹配。也许还需要其他工具来进行进一步测试,正如其他人所解释的那样,这里不仅仅提供了吸引眼球的工具。在问题的场景中,Python似乎更快,但是尚不清楚是否可以得出结论……


1.不用说,它超出了echo的算术扩展的范围,即echo $((6**6**6))- bash也恰好与该函数正确关联6^6^6 = 6^(6^6)

2.与此比较:6 6 ^ 6 ^ p

3.当在BSD UNIX上运行时,GNU time命令有可能提供更多信息(GNU时间信息文档):“ time”显示的大多数信息均来自“ wait3”系统调用。这些数字仅与“ wait3”返回的数字一样好。许多系统无法衡量“时间”可以报告的所有资源。这些资源被报告为零。衡量大部分或全部资源的系统均基于4.2或4.3BSD。更高的BSD发行版使用了不同的内存管理代码,这些代码可测量更少的资源。-在没有返回状态信息的“ wait3”调用的系统上,将使用“ times”系统调用。它提供的信息比“ wait3”少得多,因此在那些系统上,“时间”将大多数资源报告为零。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.