原文出处: Julia Evans   译文出处:开源中国

你好! 我作为一名编写Ruby profiler的先驱,我想对现有的Ruby和Python profiler如何工作进行一次调查。 这也有助于回答很多人的问题:“你怎么写一个profiler?”

在这篇文章中,我们只关注CPUprofiler(而不是内存/堆profiler)。 我将解释一些编写profiler的一般基本方法,给出一些代码示例,以及大量流行的Ruby和Pythonprofiler的例子,并告诉你它们是如何工作的。

在这篇文章中可能会有一些错误(为了研究这篇文章,我阅读了14个不同的分析库的代码部分),请让我们开始吧!

2种不同的profilers

有两种基本CPU profilers类型 – sampling profilers和tracing profilers。

tracingprofilers记录您的程序所调用的每个函数,然后在最后打印出报告。 samplingprofilers采用更加统计化的方法 – 他们每隔几毫秒记录程序的堆栈情况,然后报告结果。

使用sampling profilers而不是tracing profilers的主要原因是sampling profilers的开销较低。 如果每秒只抽取20或200个样本,那不会花费多少时间。 而且它们非常有效率 – 如果您遇到严重的性能问题(比如80%的时间花费在1个慢速函数上),那么每秒200个样本通常就足以确定那个函数的问题所在了!

分析器

下边类出了我们这篇文章要讨论的分析器(来源)。我之后将会解释表格中的术语(setitimer, rb_add_event_hook, ptrace)。这里最有趣的是,所有的分析器都是通过一小部分函数的特性实现的。

python分析器

Name

Kind

How it works

cProfile

Tracing

PyEval_SetProfile

line_profiler

Tracing

PyEval_SetTrace

pyflame (blog post)

Sampling

ptrace + custom timing

stacksampler

Sampling

setitimer

statprof

Sampling

setitimer

vmprof

Sampling

setitimer

pyinstrument

Sampling

PyEval_SetProfile

gprof (greenlet)

Tracing

greenlet.settrace

python-flamegraph

Sampling

profiling thread + custom timing

gdb hacks

Sampling

ptrace

“gbd hacks”并不完全是一个Python分析器:它是一个讲述如何实现用脚本包装gdb来实现hacky分析器的链接。由于新版本的gdb事实上会展开Python堆栈,所以也是和Python有关的。一种简化版的pyflame。

Ruby分析器

Name

Kind

How it works

stackprof by tmm1

Sampling

setitimer

perftools.rb by tmm1

Sampling

setitimer

rblineprof by tmm1

Tracing

rb_add_event_hook

ruby-prof

Tracing

rb_add_event_hook

flamegraph

Sampling

stackprof gem

这些分析器中几乎所有的都存在你的进程里面。

在我们开始详细分析这些分析器之前,有一个非常重要的事情需要说明一下:除fyflame外所有的分析器都运行在你的Python/Ruby进程里面。如果你在一个Python/Ruby程序里面,你通常可以很容易的获取该程序的堆栈。例如下边代码中的简单的Python程序答应出每一个运行线程的堆栈:

1

2

3

4

5

6

7

8

9

10

11

12

import sys

import traceback

def bar():

foo()

def foo():

for _, frame in sys._current_frames().items():

for line in traceback.extract_stack(frame):

print line

bar()

你可以从下边的输出里面看到堆栈的函数名,行号,文件名等你在做分析的时候需要的所有信息。

1

2

3

(‘test2.py’, 12, ‘<module>’, ‘bar()’)

(‘test2.py’, 5, ‘bar’, ‘foo()’)

(‘test2.py’, 9, ‘foo’, ‘for line in traceback.extract_stack(frame):’)

在Ruby程序中,获取堆栈也很容易:你只需要通过caoller来获取堆栈。

这些分析器处于性能考虑都是C扩展所有它们有一点不一样,但是Ruby/Python程序的C扩展也可以很容易的获取调用堆栈。

追踪分析器是如何工作的

我调查过上边表格中所有的追踪分析器:rblineprof、ruby-prof和cProfile。它们工作原理基本相同。它们都记录所有的函数调用并且用C语言编写来降低耗时。

它们是如何工作的呢?Ruby和Python都允许指定一个回调函数,当各种解释事件(例如调用一个函数或者执行一行代码)发生的时候调用。当回调函数被调用的时候,会记录堆栈供以后分析。

我认为确切了解在代码中哪里设置这些回调函数是很有用的,所以我连接了所有在github上边的相关代码。

在Python中,可以通过PyEval_SetTrace或者 PyEval_SetProfile设置回调函数。在Python官方文档的分析和追踪里有说明。文档中说道:除了追踪函数会收到line-number事件外“PyEval_SetTrace和PyEval_SetProfile一样。

代码:

line_profiler 使用PyEval_SetTrace设置回调:看line_profiler.pyx的157行

cProfiles 使用PyEval_SetProfile设置回调:看_lsprof.c的693行(cProfile是用Isprof实现的)

在Ruby里,你可以用rb_add_event_hook来设置回调,我找不到任何关于此处是如何调用的文档

1

2

3

4

rb_add_event_hook(prof_event_hook,

RUBY_EVENT_CALL | RUBY_EVENT_RETURN |

RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |

RUBY_EVENT_LINE, self);

prof_event_hook的类型是

1

2

static void

prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)

这看起来像极了Python的PyEval_SetTrace,但是比Python更灵活——您可以选择你关注的事件类型(就像“函数调用”一样)。

代码:

ruby-prof 调用rb_add_event在:ruby-prof.c line 329

rblineprof调用rb_add_event_hook在:rblineprof.c line 649

追踪分析器的缺点

追踪分析器的主要的缺点是它的实现方式是对于每个函数/行代码都执行固定的次数,这样可能使你做出错误的决定。例如,如果你有某个事物的两个实现:一个通过大量的函数调用实现,另一个没有大量函数调用,两个实现耗时相同,有大量函数调用的相比没有大量函数调用的在分析的时候会变得慢。

为了测试这一点,我做了一个包含下边内容的小文件test.py,并且比较了python -mcProfile test.py和python test.py的耗时。python test.py执行需要大约0.6秒,python -mcProfile test.py执行需要大约1秒。对于这个特定的例子cProfile引入了额外的大约60%的开销。

1

2

3

4

5

6

7

def recur(n):

if n == 0:

return

recur(n–1)

for i in range(5000):

recur(700)

cProfile文档中说:

Python的解释语言的特性往往会增加执行的开销,对于典型的应用确定性分析仅仅会增加很少运行开销。

这似乎是一个合理的说法:上边的示例(执行350万次函数调用)显然不是个典型的Python程序,并且几乎任何其他程序开销都比该示例小。

我没有测试ruby-prof(一个ruby追踪分析器)的开销,但是它的README说:

大多数程序开分析器耗时将会是原来的两倍,并且高度递归程序(斐波那契数列)耗时将会是原来的三倍。

采样分析器都怎么工作的:setitimer

现在讨论第二种分析器:采样分析器。

大多数Ruby和Python的采样分析器都是通过系统调用setitimer实现的。这是怎么回事呢?

好吧,比方说你想要每秒获取一个程序的堆栈50次,一种方法是:

请求Linux内核每20毫秒给你发送一个信号(使用系统调用setitimer)

注册一个信号处理器在每次获得信号的时候记录堆栈。

当结束分析的时候,请求Linux停止发送信号并且打印输出。

如果你想要看一个实际的用setitimer实现采样分析器的例子的话,我认为stacksampler.py是一个最好的例子,stacksampler.py是一个有用的有效的分析器并且代码只有大约100行,好酷啊!

stacksampler.py只有100多行的一个原因是:当你把一个Python函数注册成信号处理器的时候,该函数被传送到你的Python程序的当前堆栈中。所以stacksampler.py信号处理器注册是非常简单的:

1

2

3

4

5

6

7

8

def _sample(self, signum, frame):

stack = []

while frame is not None:

stack.append(self._format_frame(frame))

frame = frame.f_back

stack = ‘;’.join(reversed(stack))

self._stack_counts[stack] += 1

它只是将堆栈从堆栈帧中取出来并且增加堆栈查看计数,非常简单!非常酷!

我们看继续剩下的使用setitimer的分析器并找到它们调用settimer的代码:

stackprof (Ruby): in stackprof.c line 118

perftools.rb (Ruby): in this patch which seems to be applied when the gem is compiled (?)

stacksampler (Python): stacksampler.py line 51

statprof (Python): statprof.py line 239

vmprof (Python): vmprof_unix.c line 294

关于setitimer很重要的一点是,你需要决定如何计算时间。你想要真正的20 ms的“挂钟”时间?你想要20 ms的用户CPU时间?或者20 ms的用户+系统CPU时间?如果你仔细看电话网站上的内容,你就会发现,这些分析器实际上对setitimer做出了不同的选择 — 有时候它是可配置的,有时候却不可。setitimer手册页十分精悍,并且值得去读懂上面所有的观点。

@mgedmin 在推特上指出了一个使用setitimer时出现的有趣的问题,这个问题和这个问题拥有的一系列更多细节。

一个有趣的基于setitimer分析器的问题就是定时器产生的信号!信号有时候能中断系统调用!系统调用有时候需要几毫秒!如果测试太平凡,你会让你的程序永远循环执行系统调用!

不使用setitimer的采样分析器

有些采样分析器不使用setitimer:

pyinstrument使用PyEval_SetProfile(所以它在某种程度上是跟踪分析器),但是当它的跟踪回调函数被调用时,它并不总是收集堆栈样本。下面是选择何时测试堆栈跟踪的代码。更多信息,请看这篇博客文章。 (真相: setitimer带你了解Python中的主线程)

pyflame简要介绍了Python代码在外部调用ptracesystem的过程。根本上来讲,它只是一个抓取样本,睡眠,重复的循环,这里是sleep调用。

python-flamegraph以类似的方式在你的Python操作中开启一个新的线程并且抓取堆栈跟踪,睡眠,和重复。这里是sleep调用。

所有这3个分析器使用挂钟定时采样。

pyflame 博客

有很多关于pyflame是如何工作的。我不打算在这里进行介绍,但是Evan Klitke写了很多关于它的非常好的博客:

Pyflame:超级工程的Ptracing的Python分析器来介绍pyflame

Pyflame双解析器模式关于如何同时支持Python2和Python3

意想不到的python ABI变动增加了Python3.6的支持

释放多线程Python堆栈

Pyflame打包

在Python中一个关于ptrace+syscalls的有趣的问题

使用ptrace的乐趣和好处,ptrace(续)

还有很多在 https://eklitzke.org/。所有有趣的东西,我会更详细地阅读——也许ptrace是比实现一个Ruby分析器process_vm_readv更好的方法!(process_vm_readv开销低,因为它不会阻断进程,但它也可以给你一个不一致的快照,因为它不会阻断进程:))

先讲解到这里了!

在这篇文章中我没有涉及很多重要的细节 – 比如我基本上说vmprof和stacksampler是一样的(但实际上它们不是 – vmprof支持线性分析和用C语言编写的Python函数分析,我相信这在分析器中引入了更多的复杂性)。 但一些基本原理是一样的,所以我认为这项调查是一个很好的起点。

来源:数盟

精彩活动

福利 · 阅读 | 免费申请读大数据新书 第23期

推荐阅读

2017年数据可视化的七大趋势!

全球100款大数据工具汇总(前50款)

论大数据的十大局限

大数据时代的10个重大变革

大数据七大趋势 第一个趋势是物联网

Q: 你在Python学习使用中都有哪些心得?

欢迎留言与大家分享

请把这篇文章分享给你的朋友

转载 / 投稿请联系:hzzy@hzbook.com

更多精彩文章,请在公众号后台点击“历史文章”查看

Ruby 和 Python 分析器是如何工作的?相关推荐

  1. python可以做什么工作好-Python可以做什么工作?Python有哪些方向?

    Python现在是一种相对流行的语言,可以做很多事情,可以从事很多工作,但是对于某些小白来说,他们不是很了解,但是他们更关心Python的就业情况.那么Python可以做什么工作呢?Python有哪些 ...

  2. php 获取当前url hash,http - 我可以在服务器端应用程序(PHP,Ruby,Python等)上读取URL的哈希部分吗?...

    http - 我可以在服务器端应用程序(PHP,Ruby,Python等)上读取URL的哈希部分吗? 假设URL为: www.example.com/?val=1#part2 PHP可以使用GET数组 ...

  3. Ruby,Python和Java中的Web服务

    今天,我不得不准备一些示例来说明Web服务是可互操作的. 因此,我已经使用Metro使用Java创建了一个简单的Web服务,并在Tomcat上启动了它. 然后尝试使用Python和Ruby消耗它们. ...

  4. 对比Ruby和Python的垃圾回收(2):代式垃圾回收机制

    本文由 伯乐在线 - 熊崽Kevin 翻译自 patshaughnessy.欢迎加入 技术翻译小组.转载请参见文章末尾处的要求. 对比Ruby和Python的垃圾回收(1) 上周,我根据之前在RuPy ...

  5. 对比 Ruby 和 Python 的垃圾回收

    注:这篇文章基于我在布达佩斯的RuPy大会上所作的演讲.我觉得与其直接将幻灯片发布出来,不如在我还有印象的时候将它写成博客来的更有意义.同 样,我会在将来发布RuPy大会的视频链接.我计划将在Ruby ...

  6. python可以做什么工作-Python可以做什么工作?Python有哪些方向?

    Python现在是一种相对流行的语言,可以做很多事情,可以从事很多工作,但是对于某些小白来说,他们不是很了解,但是他们更关心Python的就业情况.那么Python可以做什么工作呢?Python有哪些 ...

  7. python图形化编程更改内部参数_构建FunctionTrace,一个图形化的Python分析器

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. Firefox Profiler用于性能分析 哈拉尔德的介绍 在Project Quantum时代,Fir ...

  8. 参加Python培训班能找到工作吗?

    参加Python培训班能找到工作吗?很多人都比较关心这个问题,尤其是正在参加Python培训的同学,想要了解这个问题,我们必须从Python就业市场.Python就业班优劣以及个人学习效果三个大的方面 ...

  9. Python入门教程:很多人推荐学 Python 入 IT ,如果学完 Python 找不到工作怎么办...

    Python入门教程:很多人推荐学 Python 入 IT ,但是如果学完 Python 找不到工作怎么办,这也是很多人担心的问题. 很多人推荐通过学习 Python 入行 IT 一是因为 Pytho ...

最新文章

  1. python自动测试p-python自动化测试报告(excel篇)
  2. 【leetcode】472. Concatenated Words
  3. Shell多线程操作及线程数控制实例
  4. 看动画学算法之:排序-快速排序
  5. Visual Studio 2017全面上市
  6. JSP + Struts + Hibernate + Spring+MySQL+Myeclipse实现固定资产管理系统
  7. mysql varchar 2000能存_mysql 数据库 varchar 到底可以存多少数据呢,长文慎入
  8. (13)FPGA面试题阻塞赋值与非阻塞赋值
  9. Java备份Oracle数据库
  10. 软考的一些心得分享, 写在信息系统项目管理师通过之后
  11. opencv python安装 centos_在Ubuntu中安装OpenCV-Python
  12. 【plantuml】程序员绘图工具
  13. 目前最值得推荐的几款黑科技APP,快来收藏吧!
  14. 二级域名 免费+免备案
  15. 2020晓庄学院专转本C语言考试试卷,南京晓庄学院五年一贯制专转本模拟考试c语言...
  16. 合唱队形(最大上升子序列)
  17. 逼自己一把,你就优秀了
  18. C语言strlen()函数用法
  19. 如何利用鸟笼效应将消费者留在营销的“鸟笼”中?
  20. 360金融路演PPT曝光:周鸿祎持股14% 预计中旬上市

热门文章

  1. Qt工作笔记-时QLabel具有点击事件(使用EventFilter)
  2. vue-element-xlsx在线读取Excel数据预览
  3. x12arima季节调整方法_深秋是腌洋姜的季节,用老一辈的方法做一坛,比腌萝卜香还爽口...
  4. java字符串剪切函数,java用substring函数截取string中一段字符串,substringstring
  5. js字符串的字典序_27. 字符串的排列
  6. python对应的岗位_隐式相对导入如何在Python中工作?
  7. Python是什么类型的语言?
  8. (王道408考研操作系统)第二章进程管理-第三节3:实现进程互斥的硬件方法
  9. Linux Dynamic Shared Library LD Linker
  10. PyQt5学习笔记02----初探Qt Designer 设计师