Ruby代码性能分析

35
除了ruby-prof和核心Benchmark类之外,您使用什么来对您的Ruby代码进行剖析?特别是,您如何找出代码中的瓶颈?似乎我需要开发自己的小工具才能弄清楚代码中花费的所有时间。
我知道ruby-prof可以提供此功能,但其输出实际上非常令人困惑,并且不容易找到实际上造成问题的代码块(它告诉您哪些方法调用花费了最多的时间)。因此,我没有从中获得我想要的收益,也无法真正利用它。
也许我做错了什么?是否有其他选择?谷歌搜索并没有为我带来任何结果。

2
你是否遇到了我在Is it possible to ignore irrelevant methods when profiling ruby applications?中遇到的相同问题?我发现了ruby-prof的方法消除选项。 - Andrew Grimm
4个回答

14

如果您想深入了解代码,可以试用 stackprof

以下是如何使用它的快速解决方案: 安装 gem:gem install stackprof。在您的代码中添加:require 'stackprof' 并将您想要检查的部分用以下方式包围:

StackProf.run(mode: :cpu, out: 'stackprof-output.dump') do   {YOUR_CODE} end

在运行 Ruby 脚本后,可以使用stackprof stackprof.dump在终端中检查输出:

Mode: cpu(1000)
Samples: 9145 (1.25% miss rate)
GC: 448 (4.90%)

 TOTAL    (pct)     SAMPLES    (pct)     FRAME
   236   (2.6%)         231   (2.5%)     String#blank?
   546   (6.0%)         216   (2.4%)     ActiveRecord::ConnectionAdapters::Mysql2Adapter#select
   212   (2.3%)         199   (2.2%)     Mysql2::Client#query_with_timing
   190   (2.1%)         155   (1.7%)     ERB::Util#html_escape``

在这里,您可以看到所有需要大量时间的方法。现在是最棒的部分:要深入了解,请执行stackprof stackprof.dump --method String#blank?,您将获得特定方法的输出:

String#blank? (lib/active_support/core_ext/object/blank.rb:80)
  samples:   231 self (2.5%)  /    236 total (2.6%)
  callers:
    112  (   47.5%)  Object#present?
  code:
                                  |    80  |   def blank?
  187    (2.0%) /   187   (2.0%)  |    81  |     self !~ /[^[:space:]]/
                                  |    82  |   end

您可以很容易地找出代码中哪部分运行时间较长。

如果想要获得可视化输出,请执行 stackprof stackprof.dump --graphviz >> stackprof.dot 并使用 graphviz (brew install graphviz) 执行 dot -T pdf -o stackprof.pdf stackprof.dot,以获得漂亮的 PDF 输出,其中突出显示了运行时间较长的方法。


10
很多性能分析工具都是这样的。 你需要知道的不是程序花费时间的位置,而是原因。有关动态代码分析的任何参考资料吗? 补充: 以下是我如何找到代码中的“瓶颈”(我讨厌那个词)。 这里是为什么的列表。
认为通过大量测量可以找到“瓶颈”是很自然的事情。 几乎所有的性能分析工具都是基于此的。
实际上,查找和测量不是同一个问题。 测量是必须的,以查看您找到的并且修复的内容是否有所改进。 对我来说,发现要修复的内容更像调试而不是测量。
最简单的解释方法是从一个无限或几乎无限的循环开始。 你怎么找到它? 暂停它并查看堆栈,对吧? 因为你知道问题就在堆栈上。 你只需要暂停一次,然后需要研究堆栈上的代码。 如果您想确保找到它,可以暂停几次。
假设代码只需要的时间的两倍,那么当您暂停它时,有50%的机会看到它在执行不必要的操作。如果您暂停并查看10次,则大约会发现5次。实际上,只要您看到它在执行某些可以优化的操作,就可以在尽可能少的样本中找到“瓶颈”。修复它,测量加速度,展示它,并重复。
即使您最大的问题不是很大,这种方法最终也会找到它。此外,还存在放大现象,即在消除更大问题后,小问题变得更容易发现。这使您可以继续进行,直到代码几乎达到最佳状态。
P.S.完成此操作后,仍然可能存在加速的机会。例如,优化算法可能取决于数值稳定性。基于消息的架构可能会使跟踪代码执行原因更加困难。在实时软件中,性能问题可能仅偶尔发生,并且不太容易采样。这需要更多的聪明才智。仅依靠测量是不够的。

1
谢谢,我会尝试这种方法。我刚刚也发现了Perftools,它是一种采样分析器,并生成如下所示的调用图:http://perftools-rb.rubyforge.org/examples/rubygems.gif - 你认为手动操作能提供更多优势吗? - ehsanul
@ehsanul:绝对的,就像在替代Gprof链接的第3点中所解释的那样。但不要只听我的话:https://dev59.com/HnE85IYBdhLWcg3wwWat#2624725 https://dev59.com/2XE95IYBdhLWcg3wEpro#2474118 这就像比较一辆华丽的汽车和一架丑陋的飞机。一个看起来漂亮,但另一个能带你到达目的地。 - Mike Dunlavey
@MikeDunlavey:一个初学者的问题:如何中断 Ruby 应用程序并获取回溯?您可以捕获 SIGINT 进入 Ruby 调试器吗?还是您需要在 gdb 下完成所有操作? - fearless_fool
@fearless:这篇文章说要做“catch Interrupt”。 - Mike Dunlavey
@MikeDunlavey:对我没用——请参见https://dev59.com/Gm_Xa4cB1Zd3GeqP0Ftw - fearless_fool

3

3

1
我承认查看回溯信息很繁琐且不美观,而火焰图则很有视觉吸引力。但相比于火焰图,回溯信息可以找到更多的速度提升。原因在这里 - Mike Dunlavey

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