在这篇文章中,我将介绍一些工具和方法,可以在运行Python脚本时对剖析器进行剖析。
就像在我们之前的文章中一样,CPU分析的含义是一样的,但是现在我们不是针对Python脚本的。相反,我们想知道Python解释器是如何工作的,以及在运行我们的Python脚本时花费多少时间。
接下来我们将看到如何跟踪CPU使用情况,并找到解释器中的热点。
系列索引
一旦帖子发布,下面的链接将会生效:
测量CPU使用率
对于这篇文章,我将主要使用与内存分析和脚本CPU使用相同的脚本,您可以在下面或在这里看到它。
import time
def primes(n):
if n == 2:
return [2]
elif n < 2:
return []
s = []
for i in range(3, n+1):
if i % 2 != 0:
s.append(i)
mroot = n ** 0.5
half = (n + 1) / 2 - 1
i = 0
m = 3
while m <= mroot:
if s[i]:
j = (m * m - 3) / 2
s[j] = 0
while j < half:
s[j] = 0
j += m
i = i + 1
m = 2 * i + 3
l = [2]
for x in s:
if x:
l.append(x)
return l
def benchmark():
start = time.time()
for _ in xrange(40):
count = len(primes(1000000))
end = time.time()
print "Benchmark duration: %r seconds" % (end-start)
benchmark()
优化后的版本是bellow或here:
import time
def primes(n):
if n==2:
return [2]
elif n<2:
return []
s=range(3,n+1,2)
mroot = n ** 0.5
half=(n+1)/2-1
i=0
m=3
while m <= mroot:
if s[i]:
j=(m*m-3)/2
s[j]=0
while j<half:
s[j]=0
j+=m
i=i+1
m=2*i+3
return [2]+[x for x in s if x]
def benchmark():
start = time.time()
for _ in xrange(40):
count = len(primes(1000000))
end = time.time()
print "Benchmark duration: %r seconds" % (end-start)
benchmark()
CPython
CPython是多功能的,完全用C编写,因此更容易测量和/或配置文件。您可以在这里找到在GitHub上托管的CPython的源代码。默认情况下,你会看到最新的分支,在写这个的时候是3.7+,但是所有其他的分支都可以下到2.7。
在我们的帖子中,我们将重点介绍CPython 2,但同样的步骤可以成功应用到最新的3版本。
代码覆盖工具
查看正在运行的C代码部分的最简单方法之一是使用代码覆盖率工具。
我们首先克隆回购:
git clone https://github.com/python/cpython /cd cpython
git checkout 2.7
./configure
复制目录中的脚本并运行以下命令:
make coverage
./python 04.primes-v1.py
make coverage-lcov
第一行将编译GCOV支持的解释器,第二行将运行工作负载并收集.gcda文件中的分析数据,第三行解析包含分析数据的文件,并在名为的文件夹中创建一堆HTML文件lcov-report
。
如果我们index.html
在浏览器中打开,我们可以看到解释器源代码中被执行的位置来运行我们的Python脚本。你会看到像下面的东西:
在顶层,我们可以看到构成源代码的每个目录以及覆盖的代码量。例如,我们打开Objects
目录, listobject.c.gcov.html
文件。虽然我们不会完全解读,但我们分析一部分。看下面的部分。
如何阅读?在黄色的列上,你可以看到来自C文件代码的行号。在下一列中,您可以看到特定行被执行的次数。在最右边的列中,您可以看到实际的C源代码。
在这个例子中,这个方法listiter_next
被称为6000万次。
我们是如何得到这个功能的?如果我们仔细看看我们的Python脚本,我们观察到它使用了很多列表迭代并追加。(另一点可以导致脚本优化首先)
让我们继续其他一些专用工具。
PERF
有关更多信息,我们可以使用perf
Linux系统上提供的工具。官方文档可以在这里阅读。
我们(重新)使用以下内容构建CPython解释器。如果你没有在同一个目录中下载Python脚本,你应该这样做。另外,请确保perf
已安装在您的系统上。
./configure --with-pydebug
make
运行perf
如下。Brendan Gregg写的这个优秀的页面可以看到更多的使用perf的方法。
sudo perf record ./python 04.primes-v1.py
运行脚本之后,您将看到如下所示的内容:
Benchmark duration: 32.03910684585571 seconds
[21868 refs]
perf record: Woken up 20 times to write data ]
[ perf record: Captured and wrote 4.914 MB perf.data (125364 samples) ]
要查看结果,运行`sudo perf report` 以获得度量。
只有最有趣的电话才能被保存。在上面的屏幕中,我们看到花费的时间最多PyEval_EvalFrameEx
。这是主要的解释器循环,我们对这个例子不感兴趣。相反,我们感兴趣的是下一个耗时的功能 - listiter_next
占用10.70%的时间。
运行优化版本后,我们看到以下内容:
经过我们的优化,该listiter_next
功能只消耗了2.11%的时间。读者可以根据口译员的情况做进一步的优化。
Valgrind的/ Callgrind
另一个可以用来查找瓶颈的工具是Valgrind,它的一个叫做callgrind的插件。更多细节可以在这里阅读。
如果尚未完成,我们(重新)使用以下内容构建CPython解释器。如果你没有在同一个目录中下载Python脚本,你应该这样做。另外,请确保valgrind
已安装在您的系统上。
./configure --with-pydebug
make
运行valgrind
如下:
valgrind --tool=callgrind --dump-instr=yes \
--collect-jumps=yes --collect-systime=yes \
--callgrind-out-file=callgrind-%p.out -- ./python 04.primes-v1.py
结果是:
Benchmark duration: 1109.4096319675446 seconds
[21868 refs]
==24152==
==24152== Events : Ir sysCount sysTime
==24152== Collected : 115949791666 942 208
==24152==
==24152== I refs: 115,949,791,666
为了可视化,我们将使用KCacheGrind。
kcachegrind callgrind-2327.out
PyPy
在PyPy上,可以成功使用的配置文件是非常有限的,恢复到vmprof,一个由开发PyPy的人写的工具。
首先,从这里下载PyPy 。在此之后,启用pip
对它的支持
安装vmprof
很简单,只需运行:
bin/pypy -m pip install vmprof
运行工作量为:
bin/pypy -m vmprof --web 04.primes-v1.py
并在浏览器中打开控制台中显示的链接(以http://vmprof.com/#/开头 )
作者:Alecsandru Patrascu,alecsandru.patrascu [at] rinftech [dot] com
原文:https://pythonfiles.wordpress.com/2017/08/24/hunting-performance-in-python-code-part-4/