帮酷LOGO
0 0 评论
  • 显示原文与译文双语对照的内容
文章标签:Alloc  Allocation  tool  tooling  对象  allocations  alloca  TRAC  
Tooling for tracing object allocations in Ruby 2.1

  • 源代码名称:allocation_stats
  • 源代码网址:http://www.github.com/srawlins/allocation_stats
  • allocation_stats源代码文档
  • allocation_stats源代码下载
  • Git URL:
    git://www.github.com/srawlins/allocation_stats.git
  • Git Clone代码到本地:
    git clone http://www.github.com/srawlins/allocation_stats
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/srawlins/allocation_stats
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
  • AllocationStats Build Status

    简介

    AllocationStats是一个使用 ruby 2.1新功能跟踪 ruby 对象分配( 仅 MRI )的rubygem。 ruby 2.1新版本 ObjectSpace.trace_object_allocations 方法只提供原始信息,并且对于除微分析之外的任何内容都不有用。 必须对数据进行聚合 !

    AllocationStats收集由以下所有配置信息生成的所有分配信息 ObjectSpace.trace_object_allocations 然后,提供过滤,分组和排序分配信息的机制。

    安装

    要安装 AllocationStats,请向项目的一个Gemfile添加以下行,也许是向开发组添加:

    gem 'allocation_stats'

    或者运行以下命令:

    gem install allocation_stats

    表格输出示例

    从AllocationStats中得到一些简单的统计是很容易的。 用 AllocationStats.trace 包装一些代码,并打印出所有新对象分配信息的列表。

    作为一个例子,让我们看看 examples/my_class.rb:

    classMyClassdefmy_method@hash= {1 => "foo", 2 => "bar"}
     endend

    在一些特殊的ruby 中使用这个类:

    
    $ ruby -r./lib/allocation_stats -r./allocation
    
    
    stats = AllocationStats.trace { MyClass.new.my_method }
    
    
    puts stats.allocations(alias_paths: true).to_text
    
    
    ^D
    
    
     sourcefile sourceline class_path method_id memsize class
    
    
    ------------------- ---------- ---------- --------- ------- -------
    
    
    <PWD>/allocation.rb 4 MyClass my_method 0 String
    
    
    <PWD>/allocation.rb 3 MyClass my_method 192 Hash
    
    
    <PWD>/allocation.rb 3 MyClass my_method 0 String
    
    
    <PWD>/allocation.rb 3 MyClass my_method 0 String
    
    
    <PWD>/allocation.rb 3 MyClass my_method 0 String
    
    
    - 1 Class new 0 MyClass
    
    
    
    

    ( 我在上面使用了 alias_paths: true 来提高可读性)。 通过这种方式,sourcefile 列不是疯狂的,使用 文件系统 上的完整文件路径。

    ( 这里完整示例在 examples/trace_my_class_raw.rb 中找到)

    我们还可以通过一个或者多个属性对分配进行分组,并获得聚合计数。 下面,我们按源文件,源代码行和类对分配进行分组:

    
    $ ruby -r./lib/allocation_stats -r./allocation
    
    
    stats = AllocationStats.trace { MyClass.new.my_method }
    
    
    puts stats.allocations(alias_paths: true).group_by(:sourcefile, :sourceline, :class).to_text
    
    
    ^D
    
    
     sourcefile sourceline class count
    
    
    ------------------- ---------- ------- -----
    
    
    <PWD>/allocation.rb 4 String 1
    
    
    <PWD>/allocation.rb 3 Hash 1
    
    
    <PWD>/allocation.rb 3 String 3
    
    
    - 1 MyClass 1
    
    
    
    

    ( 这个完整的例子是在 examples/trace_my_class_group_by.rb )

    更多关于 trace_object_allocations()

    从开始处开始: 将使用一个新特性释放 ruby 2.1,使之能够跟踪对象分配。 通过 ObjectSpace.trace_object_allocations 可以跟踪任何新对象分配的以下属性:

    • 源文件
    • 源文件行
    • 类路径
    • 方法 ID
    • 生成

    让我们来看看这个问题:

    
    $ cat examples/trace_object_allocations.rb
    
    
    require 'objspace'
    
    
    
    ObjectSpace.trace_object_allocations do
    
    
     a = [2,3,5,7,11,13,17,19,23,29,31]
    
    
     puts ObjectSpace.allocation_sourcefile(a)
    
    
     puts ObjectSpace.allocation_sourceline(a)
    
    
    end
    
    
    $ ruby./examples/trace_object_allocations.rb
    
    
    ./examples/trace_object_allocations.rb
    
    
    4
    
    
    
    

    要查看一些详细示例,请查看示例部分。

    API

    AllocationStats API

    可以通过几种不同的方式启动分配跟踪,以提供灵活性:

    块样式

    只需将块传递给 AllocationStats.trace:

    stats =AllocationStats.trace do# code to traceend

    或者初始化 AllocationStats,然后用块调用 #trace:

    stats =AllocationStats.newstats.trace do# code to traceend
    内联

    用对 #trace ( 或者 #start ) 和 #stop的调用来包装代码行:

    stats =AllocationStats.newstats.trace # also stats.start# code to tracestats.stop
    Burn

    如果在 kernel_require.rb 中发现大量的分配,或者大量的RubyVM::InstructuinSequences 分配,那么可以对一个或者多个迭代进行。 用 burn 关键字实例化 AllocationStats 实例,并跟踪代码块样式。 例如: AllocationStats.new(burn: 3).trace{.. . } 将首次调用块 3次,在调用块 4th 时,跟踪分配之前,不要跟踪分配。

    AllocationsProxy API

    下面是 AllocationStats::AllocationsProxy 对象上可用的方法,这些方法由 AllocationStats#allocations 返回:

    • #group_by

    • #from 接受一个字符串参数,该参数将与分配文件名匹配。

    • #not_from#from 相反。

    • #from_pwd 将过滤分配到那些来自 pwd ( 比如 )的分配。 来自"我的项目"的分配)

    • #where 接受伪属性键的散列。 比如,

      allocations.where(class:String)

      它还不接受lambdas作为值,这将使ActiveRecord-4-like调用成为可能,比如

      allocations.where(class:Array, size:->(size) { size >10 }
    • #at_least(n) 选择分配组,每个组至少分配 n 分配。

    • 我认为 #bytes 定义不一致,。 待办事项

    什么是人造属性?

    #group_by#where的有效值包括:

    • 每个 Allocation 上的实例变量。 这些包括 :sourcefile:sourceline 等。
    • 已经分配的对象上的可用方法。 如果你知道你只拥有响应 :size的对象,则这些内容包括 :class,甚至是 :size
    • 分配 helper 方法,这些方法返回有关已经分配对象的特殊内容。 现在这只包括 :class_plus

    我在调用这些东西,你可以 GROUP BY 或者过滤,"人造属性"。"

    什么是 class_plus

    跟踪 RSpec

    你可以通过在 spec_helper.rb的顶部包含这个测试套件来跟踪一个RSpec测试套件:

    require'allocation_stats'AllocationStats.trace_rspec

    这将围绕RSpec测试进行钩子,分别跟踪每个RSpec测试。 当RSpec退出时,顶级 sourcefile/sourceline/class 组合将被打印出来。

    跟踪RSpec为现有库的维护者提供了一个开始寻找它的项目效率低下的地方。 你可以跟踪整个测试套件或者子集,如果任何一个规范从同一行分配数百个对象。

    以下是来自 examples/trace_specs/的示例:

    
     rspec strings_spec.rb
    
    
    ..
    
    
    
    Top 2 slowest examples (0.08615 seconds, 100.0% of total time):
    
    
     Array of Strings allocates Strings and Arrays
    
    
     0.0451 seconds./strings_spec.rb:8
    
    
     Array of Strings allocates more Strings
    
    
     0.04105 seconds./strings_spec.rb:12
    
    
    
    Finished in 0.08669 seconds
    
    
    2 examples, 0 failures
    
    
    
    Randomized with seed 56224
    
    
    
    Top 7 allocation sites:
    
    
     5 allocations of String at <PWD>/strings.rb:2
    
    
     during./strings_spec.rb:8
    
    
     2 allocations of Array at <PWD>/strings_spec.rb:9
    
    
     during./strings_spec.rb:8
    
    
     2 allocations of Array at <PWD>/strings_spec.rb:13
    
    
     during./strings_spec.rb:12
    
    
     1 allocations of Array at <PWD>/strings.rb:2
    
    
     during./strings_spec.rb:8
    
    
     1 allocations of String at <PWD>/strings.rb:6
    
    
     during./strings_spec.rb:8
    
    
     1 allocations of String at <PWD>/strings.rb:14
    
    
     during./strings_spec.rb:12
    
    
     1 allocations of String at <PWD>/strings.rb:10
    
    
     during./strings_spec.rb:12
    
    
    
    

    我们得到通知,在 strings_spec.rb:8的规范中,在 strings.rb:2 中分配了 5x 个。

    #trace_rspec 每次运行一次测试,主要是为了防止 #autoload 出现在分配中。

    在C 中的分配

    如果在C 代码( 例如C 扩展) 中出现分配,那么它们的分配站点会。 这个分配仍然会被记录,但是sourcefile和sourceline将作为最深的ruby 文件( 称为函数),它可能被称为其他C 函数,在某个点分配了。 这将给我们带来下一个问题:

    autoload

    Kernel#autoload 很棘手自动加载可以隐藏简单常量引用中的分配( 在随后的require 中)。 例如在邮件 gem 中,在 rspec spec/mail/body_spec.rb:339 中跟踪对象分配使它看起来像下面的行分配 219个字符串:

    # lib/mail/configuration.rb28deflookup_delivery_method(method)29case method.is_a?(String) ? method.to_sym : method30whennil31Mail::SMTP# <-- 219 Strings alloctated here?

    为此,可以使用 autoload,可以使用类似于邮件/邮件服务器之类的花哨机制,或者依赖于 burn 机制来缓解这种情况。 AllocationStats.trace_rspec 每次运行一次测试。

    示例

    来自规范的示例

    existing_array = [1,2,3,4,5]
    stats =AllocationStats.trace do new_string ="stringy string" another_string ="another string" an_array = [1,1,2,3,5,8,13,21,34,55]
     a_foreign_string = allocate_a_string_from_spec_helperendresults = stats.allocations.group_by(:@sourcefile, :class).to_a

    在分配的资源文件中,我们分组了所有跟踪的分配,以及分配的对象的英镑级别。 可以通过多种方式将分配列表为"已经转换",因此,必须通过调用( 与ActiveRecord关系类似) 来最终解决转换。 结果是以下( 源文件,类) 元组键和 AllocationStats::Allocation 值的哈希值:

    {
     [".../spec/spec_helper.rb", String]
     =>
     [#<AllocationStats::Allocation:0x007f132ac3f160@object="a string from spec_helper",
     @memsize=0,
     @sourcefile=".../spec/spec_helper.rb",
     @sourceline=14,
     @class_path="Object",
     @method_id=:allocate_a_string_from_spec_helper> ],
     [".../spec/allocation_stats_spec.rb", Array]
     =>
     [#<AllocationStats::Allocation:0x007f132ac3e968@object=[1, 1, 2, 3, 5, 8, 13, 21, 34, 55],
     @memsize=80,
     @sourcefile=".../spec/allocation_stats_spec.rb",
     @sourceline=78,
     @class_path=nil,
     @method_id=nil> ],
     [".../spec/allocation_stats_spec.rb", String]
     =>
     [ #<AllocationStats::Allocation:0x007f132ac3e0d0@object="another string",
     @memsize=0,
     @sourcefile=".../spec/allocation_stats_spec.rb",
     @sourceline=77,
     @class_path=nil,
     @method_id=nil>,
     #<AllocationStats::Allocation:0x007f132ac3d838@object="stringy string",
     @memsize=0,
     @sourcefile=".../spec/allocation_stats_spec.rb",
     @sourceline=76,
     @class_path=nil,
     @method_id=nil> ]
    }

    ( 我手动插入了省略号。)

    你可以看到,有三个不同的组:

    • [spec_helper.rb, String]
    • [allocation_stats_spec.rb, Array]
    • [object_space_stats.rb, String]

    只有一个分配属于前两个组,并且两个分配组成第二个组。

    慢一点

    让我们把这个例子稍微放慢一点。 首先,让我们看看如何使用AllocationStats收集对象分配:

    stats =AllocationStats.trace do new_string ="stringy string" another_string ="another string" an_array = [1,1,2,3,5,8,13,21,34,55]
     a_foreign_string = allocate_a_string_from_spec_helperend

    通过 AllocationStats.trace 运行一个块来收集数据。 这基本上只是 trace_object_allocations()的一个小包装。 你将返回你的新 AllocationStats,它实质上只保存了所有的分配信息,通过 #allocations 访问。 让我们看看下一行,看看如何提取有用的信息:

    results = stats.allocations.group_by(:@sourcefile, :class).to_a

    如果你用于链接ActiveRecord关系,其中一些可能对你很熟悉: stats.allocations 将提供一个 {AllocationStats::AllocationsProxy} 对象,设计用来保存你希望通过以下方式运行分配的各种转换。 AllocationsProxy使用 Pattern 命令来存储转换,然后才真正应用它们。 在本示例中,我们只进行一个转换: group_by(:@sourcefile, :class)。这里方法只返回相同的AllocationsProxy对象,因这里可以链接转换。 执行转换的最后一个调用是 #to_a ( 别名为 #all,就像 ActiveRecord )。

    Psych 示例

    让我们看一看具有不同分配的示例,使用 ruby的Psych。 这是在 examples/trace_psych_keys.rb 中找到的:

    stats =AllocationStats.trace do y =YAML.dump(["one string", "two string"]) # lots of objects from Rbconfig::CONFIG["rubylibdir"]endstats.allocations.group_by(:sourcefile, :class).all.keys.each { |key| puts key.inspect }
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Array]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", MatchData]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Method]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/nodes/node.rb", Array]
    ["(eval)", Psych::Nodes::Sequence]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/tree_builder.rb", Psych::Nodes::Document]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/tree_builder.rb", Psych::Nodes::Stream]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Proc]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", RubyVM::Env]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Hash]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Psych::Visitors::YAMLTree::Registrar]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Psych::Visitors::YAMLTree]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/scalar_scanner.rb", Hash]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Psych::ScalarScanner]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/class_loader.rb", Hash]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Psych::ClassLoader]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/tree_builder.rb", Array]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb", Psych::TreeBuilder]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych.rb", Hash]
    ["examples/trace_psych_inspect.rb", Array]
    ["examples/trace_psych_inspect.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/emitter.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/emitter.rb", Array]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/emitter.rb", RubyVM::InstructionSequence]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/visitor.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/visitor.rb", MatchData]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/visitor.rb", Regexp]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/visitors/emitter.rb", Psych::Emitter]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/nodes/node.rb", Psych::Visitors::Emitter]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/nodes/node.rb", StringIO]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/nodes/node.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/tree_builder.rb", Psych::Nodes::Scalar]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/scalar_scanner.rb", String]
    ["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/psych/scalar_scanner.rb", MatchData]

    同样,在不聚集的情况下,从这些结果中找到有用的信息是很困难的。 让我们来做( examples/trace_psych_group_by ):

    stats =AllocationStats.trace do y =YAML.dump(["one string", "two string"]) # lots of objects from Rbconfig::CONFIG["rubylibdir"]endputs stats.allocations(alias_paths:true).group_by(:sourcefile, :class).to_text
     sourcefile class count---------------------------------------------------------------------------------<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Array12<RUBYLIBDIR>/psych/visitors/yaml_tree.rb String20<RUBYLIBDIR>/psych/visitors/yaml_tree.rb MatchData3<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Method5<RUBYLIBDIR>/psych/nodes/node.rb Array3(eval) Psych::Nodes::Sequence1<RUBYLIBDIR>/psych/tree_builder.rb Psych::Nodes::Document1<RUBYLIBDIR>/psych/tree_builder.rb Psych::Nodes::Stream1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Proc1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb RubyVM::Env1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Hash3<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Psych::Visitors::YAMLTree::Registrar1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Psych::Visitors::YAMLTree1<RUBYLIBDIR>/psych/scalar_scanner.rb Hash2<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Psych::ScalarScanner1<RUBYLIBDIR>/psych/class_loader.rb Hash1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Psych::ClassLoader1<RUBYLIBDIR>/psych/tree_builder.rb Array1<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Psych::TreeBuilder1<RUBYLIBDIR>/psych.rb Hash1./examples/trace_psych_raw.rb Array1./examples/trace_psych_raw.rb String2<RUBYLIBDIR>/psych/visitors/emitter.rb String29<RUBYLIBDIR>/psych/visitors/emitter.rb Array3<RUBYLIBDIR>/psych/visitors/emitter.rb RubyVM::InstructionSequence1<RUBYLIBDIR>/psych/visitors/visitor.rb String38<RUBYLIBDIR>/psych/visitors/visitor.rb MatchData5<RUBYLIBDIR>/psych/visitors/visitor.rb Regexp1<RUBYLIBDIR>/psych/visitors/emitter.rb Psych::Emitter1<RUBYLIBDIR>/psych/nodes/node.rb Psych::Visitors::Emitter1<RUBYLIBDIR>/psych/nodes/node.rb StringIO1<RUBYLIBDIR>/psych/nodes/node.rb String3<RUBYLIBDIR>/psych/tree_builder.rb Psych::Nodes::Scalar2<RUBYLIBDIR>/psych/scalar_scanner.rb String5<RUBYLIBDIR>/psych/scalar_scanner.rb MatchData2

    引用

    这个新特性激发了 @tmm1 在GitHub上做的工作,就像在中描述的那样。 方法在 ruby 核中被提出为一个特征,在 ruby 问题 #8107 中使用 @tmm1,而 @ko1 则将它写。 他在 ruby Kaigi 2013演示文稿中介绍了该特性,幻灯片 29至 33 [ pdf ]。



    文章标签:tool  对象  TRAC  Alloc  tooling  alloca  Allocation  allocations  

    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语