Ruby 2.1 – Tracing Object Allocations – ?ras[output]=interactive



Ruby 2.1 – Tracing Object Allocations – ?ras[output]=interactive

0 0


reveal-rubyconf13

Slides for my Tracing Object Allocations talk for RubyConf 2013

On Github srawlins / reveal-rubyconf13

Ruby 2.1

Tracing Object Allocations

Sam Rawlins / @srawlins

Ruby 2.1.0-preview1 is out!

  • $ rbenv install 2.1.0-preview1
  • $ rvm install ruby-2.1.0-preview1

Ruby 2.1 news

= NEWS for Ruby 2.1.0

This document is a list of user visible feature changes made between
releases except for bug fixes.

Note that each entry is kept so brief that no reason behind or
reference information is supplied with.  For a full list of changes
with all sufficient information, see the ChangeLog file.

== Changes since the 2.0.0 release

=== Language changes

* Now the default values of keyword arguments can be omitted.  Those
  "required keyword arguments" need giving explicitly at the call time.

* Added suffixes for integer and float literals: 'r', 'i', and 'ri'.
  * "42r" and "3.14r" are evaluated as Rational(42, 1) and 3.14.rationalize,
    respectively.  But exponential form with 'r' suffix like "6.022e+23r" is
    not accepted because it is misleading.
  * "42i" and "3.14i" are evaluated as Complex(0, 42) and Complex(0, 3.14),
    respectively.
  * "42ri" and "3.14ri" are evaluated as Complex(0, 42r) and Complex(0, 3.14r),
    respectively.

* def-expr now returns the symbol of its name instead of nil.

* Added 'f' suffix for string literals that returns a frozen String object.

=== Core classes updates (outstanding ones only)

* Binding
  * New methods
    * Binding#local_variable_get(symbol)
    * Binding#local_variable_set(symbol, obj)
    * Binding#local_variable_defined?(symbol)

* GC
  * added environment variable:
    * RUBY_HEAP_SLOTS_GROWTH_FACTOR: growth rate of the heap.

* Integer
  * New methods
    * Fixnum#bit_length
    * Bignum#bit_length
  * Bignum performance improvement
    * Use GMP if available.
      GMP is used only for several operations:
      multiplication, division, radix conversion, GCD

* IO
  * extended methods:
    * IO#seek supports SEEK_DATA and SEEK_HOLE as whence.
    * IO#seek accepts symbols (:CUR, :END, :SET, :DATA, :HOLE) for 2nd argument.
    * IO#read_nonblock accepts optional `exception: false` to return symbols
    * IO#write_nonblock accepts optional `exception: false` to return symbols

* Kernel
  * New methods:
    * Kernel#singleton_method

* Module
  * New methods:
    * Module#using, which activates refinements of the specified module only
      in the current class or module definition.
    * Module#singleton_class? returns true if the receiver is a singleton class
      or false if it is an ordinary class or module.
  * extended methods:
    * Module#refine is no longer experimental.
    * Module#include and Module#prepend are now public methods.

* Mutex
  * misc
    * Mutex#owned? is no longer experimental.

* Numeric
  * extended methods:
    * Numeric#step allows the limit argument to be omitted, in which
      case an infinite sequence of numbers is generated.  Keyword
      arguments `to` and `by` are introduced for ease of use.

* Process
  * New methods:
    * alternative methods to $0/$0=:
      * Process.argv0() returns the original value of $0.
      * Process.setproctitle() sets the process title without affecting $0.
    * Process.clock_gettime
    * Process.clock_getres

* RbConfig
  * New constants:
    * RbConfig::SIZEOF is added to provide the size of C types.

* String
  * New methods:
    * String#scrub and String#scrub! verify and fix invalid byte sequence.
  * extended methods:
    * If invalid: :replace is specified for String#encode, replace
      invalid byte sequence even if the destination encoding equals to
      the source encoding.

* Symbol
  * All symbols are now frozen.

* pack/unpack (Array/String)
  * Q! and q! directives for long long type if platform has the type.

* toplevel
  * extended methods:
    * main.using is no longer experimental. The method activates refinements
      in the ancestors of the argument module to support refinement
      inheritance by Module#include.

=== Core classes compatibility issues (excluding feature bug fixes)

* IO
  * incompatible changes:
    * open ignore internal encoding if external encoding is ASCII-8BIT.

* Kernel#eval, Kernel#instance_eval, and Module#module_eval.
  * Copies the scope information of the original environment, which means
    that private, protected, public, and module_function without arguments
    do not affect the environment outside the eval string.
    For example, `class Foo; eval "private"; def foo; end; end' doesn't make
    Foo#foo private.

* Kernel#untrusted?, untrust, and trust
  * These methods are deprecated and their behavior is same as tainted?,
    taint, and untaint, respectively.  If $VERBOSE is true, they show warnings.

* Module#ancestors
  * The ancestors of a singleton class now include singleton classes,
    in particular itself.

* Module#define_method and Object#define_singleton_method
  * Now they return the symbols of the defined methods, not the methods/procs
    themselves.

* Numeric#quo
  * Raises TypeError instead of ArgumentError if the receiver doesn't have
    to_r method.

* Proc
  * Returning from lambda proc now always exits from the Proc, not from the
    method where the lambda is created.  Returing from non-lambda proc exits
    from the method, same as the former behavior.


=== Stdlib updates (outstanding ones only)

* CGI::Util
  * All class methods modulized.

* Digest
  * extended methods:
    * Digest::Class.file takes optional arguments for its constructor

* Matrix
  * Added Vector#cross_product.

* Net::SMTP
  * Added Net::SMTP#rset to implement the RSET command

* Pathname
  * New methods:
    * Pathname#write
    * Pathname#binwrite

* OpenSSL::BN
  * extended methods:
    * OpenSSL::BN.new allows Fixnum/Bignum argument.

* open-uri
  * Support multiple fields with same field name (like Set-Cookie).

* RDoc
  * Updated to 4.1.0.preview.1.  Major enhancements include a modified default
    template and accessibility enhancements.

    For a list of minor enhancements and bug fixes see:
    https://github.com/rdoc/rdoc/blob/v4.1.0.preview.1/History.rdoc

* Resolv
  * New methods:
    * Resolv::DNS.fetch_resource
  * One-shot multicast DNS support
  * Support LOC resources

* REXML::Parsers::SAX2Parser
  * Fixes wrong number of arguments of entitydecl event. Document of the event
    says "an array of the entity declaration" but implemenation passes two
    or more arguments. It is an implementation bug but it breaks backword
    compatibility.

* REXML::Parsers::StreamParser
  * Supports "entity" event.

* REXML::Text
  * REXML::Text#<< supports method chain like 'text << "XXX" << "YYY"'.
  * REXML::Text#<< supports not "raw" mode.

* Rinda::RingServer, Rinda::RingFinger
  * Rinda now supports multicast sockets.  See Rinda::RingServer and
    Rinda::RingFinger for details.

* RubyGems
  * Updated to 2.2.0.preview.1  For a list of enhancements and bug fixes see:
    https://github.com/rubygems/rubygems/blob/v2.2.0.preview.1/History.txt

* Set
  * New methods:
    * Set#intersect?
    * Set#disjoint?

* Socket
  * New methods:
    * Socket.getifaddrs

* StringScanner
  * extended methods:
    * StringScanner#[] supports named captures.

* Syslog::Logger
  * Added facility.

* Tempfile
  * New methods:
    * Tempfile.create


* WEBrick
  * The body of a response may now be a StringIO or other IO-like that responds
    to #readpartial and #read.

* XMLRPC::Client
  * New methods:
    * XMLRPC::Client#http. It returns Net::HTTP for the client. Normally,
      it is not needed. It is useful when you want to change minor HTTP client
      options. You can change major HTTP client options by XMLRPC::Client
      methods. You should use XMLRPC::Client methods for changing major
      HTTP client options instead of XMLRPC::Client#http.


=== Stdlib compatibility issues (excluding feature bug fixes)

* objspace
  * new method:
    * ObjectSpace.trace_object_allocations
    * ObjectSpace.trace_object_allocations_start
    * ObjectSpace.trace_object_allocations_stop
    * ObjectSpace.trace_object_allocations_clear
    * ObjectSpace.allocation_sourcefile
    * ObjectSpace.allocation_sourceline
    * ObjectSpace.allocation_class_path
    * ObjectSpace.allocation_method_id
    * ObjectSpace.allocation_generation

* Set
  * incompatible changes:
    * Set#to_set now returns self instead of generating a copy.

* URI
  * incompatible changes:
    * URI.decode_www_form follows current WHATWG URL Standard.
      It gets encoding argument to specify the character encoding.
      It now allows loose percent encoded strings, but denies ;-separator.
    * URI.encode_www_form follows current WHATWG URL Standard.
      It gets encoding argument to convert before percent encode.
      UTF-16 strings aren't converted to UTF-8 before percent encode by default.


=== Built-in global variables compatibility issues

* $SAFE
  * $SAFE=4 is obsolete.  If $SAFE is set to 4 or larger, an ArgumentError
    is raised.
  
* objspace
  * new method:
    * ObjectSpace.trace_object_allocations
    * ObjectSpace.trace_object_allocations_start
    * ObjectSpace.trace_object_allocations_stop
    * ObjectSpace.trace_object_allocations_clear
    * ObjectSpace.allocation_sourcefile
    * ObjectSpace.allocation_sourceline
    * ObjectSpace.allocation_class_path
    * ObjectSpace.allocation_method_id
    * ObjectSpace.allocation_generation
    

ObjectSpace is not new

Mostly known for

  • ObjectSpace.count_objects
  • ObjectSpace.each_object
  • ObjectSpace.garbage_collect
[rubydoc.info]

ObjectSpace.new_in_ruby_2_1

  • ObjectSpace.trace_object_allocations
  • ObjectSpace.trace_object_allocations_start*
  • ObjectSpace.trace_object_allocations_stop*
  • ObjectSpace.trace_object_allocations_clear*
  • ...

*2.1.0-preview2

Anecdote from GitHub

GitHub.preload_all
GC.start
count = ObjectSpace.count_objects

puts count[:TOTAL] - count[:FREE]
#=> 605183

> 600k Ruby objects!

[github blog]

"Where am I supposedly hogging all this memory?"

Enter ::trace_object_allocations

# example.rb

1  class MyClass
2    def an_array
3      return [2,3,5,7,11,13,17,19,23,29,31]
4    end

5    def a_string
6      return "a String"
7    end
8  end
require 'objspace'

a = s = nil

ObjectSpace.trace_object_allocations do a = MyClass.new.an_array s = MyClass.new.a_string end
ObjectSpace.allocation_sourcefile(a) #=> "example.rb" ObjectSpace.allocation_sourceline(a) #=> 3 ObjectSpace.allocation_class_path(a) #=> "MyClass" ObjectSpace.allocation_method_id(a) #=> "an_array" ObjectSpace.allocation_sourcefile(s) #=> "example.rb" ObjectSpace.allocation_sourceline(s) #=> 6 ObjectSpace.allocation_class_path(s) #=> "MyClass" ObjectSpace.allocation_method_id(s) #=> "a_string"
require 'objspace'



ObjectSpace.trace_object_allocations_start a = MyClass.new.an_array s = MyClass.new.a_string ObjectSpace.trace_object_allocations_stop
ObjectSpace.allocation_sourcefile(a) #=> "example.rb" ObjectSpace.allocation_sourceline(a) #=> 3 ObjectSpace.allocation_class_path(a) #=> "MyClass" ObjectSpace.allocation_method_id(a) #=> "an_array" ObjectSpace.allocation_sourcefile(s) #=> "example.rb" ObjectSpace.allocation_sourceline(s) #=> 6 ObjectSpace.allocation_class_path(s) #=> "MyClass" ObjectSpace.allocation_method_id(s) #=> "a_string"

Why?

  • Reduce memory footprint
  • Reduce garbage collection time
    • (marking and sweeping)

But my application isn't on Ruby 2.1!

That's okay! It's a diagnostic tool!

ObjectSpace.trace_object_allocations

is

Limited Fine-Grained Just the Start

The Next Step:

Aggregation

AllocationStats

[github]

AllocationStats

requires unreleased Ruby 2.1.0-preview2 :(

  • $ rbenv install 2.1.0-dev
  • $ rvm install ruby-trunk

Low-level Example

 1  class MyClass
 2    def my_method
3 @hash = {2 => "foo", 3 => "bar", 5 => "baz"}
4 @string = "quux"
5 end 6 end
 7
 8  require "allocation_stats"
 9  stats = AllocationStats.trace do
10 MyClass.new.my_method
11 end
12
13  puts stats.allocations.to_text
           sourcefile             sourceline  class_path  method_id   class
--------------------------------  ----------  ----------  ---------  -------
./examples/trace_my_class_raw.rb 4 MyClass my_method String
./examples/trace_my_class_raw.rb 3 MyClass my_method Hash ./examples/trace_my_class_raw.rb 3 MyClass my_method String ./examples/trace_my_class_raw.rb 3 MyClass my_method String ./examples/trace_my_class_raw.rb 3 MyClass my_method String
./examples/trace_my_class_raw.rb 10 Class new MyClass

group_by Example

 1  class MyClass
 2    def my_method
3 @hash = {2 => "foo", 3 => "bar", 5 => "baz"}
4 @string = "quux" 5 end 6 end
 7
 8  require "allocation_stats"
 9  stats = AllocationStats.trace do
10    MyClass.new.my_method
11  end
12
13  puts stats.allocations
.group_by(:sourcefile, :sourceline, :class)
.to_text
             sourcefile                sourceline   class   count
-------------------------------------  ----------  -------  -----
./examples/trace_my_class_group_by.rb           4  String       1
./examples/trace_my_class_group_by.rb           3  Hash         1
./examples/trace_my_class_group_by.rb 3 String 3
./examples/trace_my_class_group_by.rb 10 MyClass 1

Psych

require "yaml"
require "allocation_stats"

stats = AllocationStats.trace do
  # lots of objects from Rbconfig::CONFIG["rubylibdir"]
  
y = YAML.dump(["one string", "two string"])
end puts stats.allocations(alias_paths: true)
.group_by(:sourcefile, :class)
.to_text
               sourcefile                             class               count
----------------------------------------  ------------------------------  -----
<RUBYLIBDIR>/psych/visitors/visitor.rb String 38
<RUBYLIBDIR>/psych/visitors/visitor.rb MatchData 5
<RUBYLIBDIR>/psych/visitors/visitor.rb Regexp 1 <RUBYLIBDIR>/psych/visitors/emitter.rb Psych::Emitter 1 <RUBYLIBDIR>/psych/visitors/emitter.rb Array 2 <RUBYLIBDIR>/psych/nodes/node.rb Psych::Visitors::Emitter 1 <RUBYLIBDIR>/psych/nodes/node.rb StringIO 1 <RUBYLIBDIR>/psych/nodes/node.rb String 3 <RUBYLIBDIR>/psych/tree_builder.rb Psych::Nodes::Scalar 2 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb Array 12 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb Method 5 <RUBYLIBDIR>/psych/scalar_scanner.rb String 5 <RUBYLIBDIR>/psych/scalar_scanner.rb MatchData 2 ...

Psych - sorted

require "yaml"
require "allocation_stats"

stats = AllocationStats.trace do
  # lots of objects from Rbconfig::CONFIG["rubylibdir"]
  y = YAML.dump(["one string", "two string"])
end

puts stats.allocations(alias_paths: true).
  
group_by(:sourcefile, :class).
sort_by_count.
to_text
               sourcefile                             class               count
-----------------------------------------  ------------------------------  -----
<RUBYLIBDIR>/psych/visitors/visitor.rb String 38 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb String 21 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb Array 12
<RUBYLIBDIR>/psych/visitors/yaml_tree.rb Method 5 <RUBYLIBDIR>/psych/scalar_scanner.rb String 5 <RUBYLIBDIR>/psych/visitors/visitor.rb MatchData 5 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb Hash 3 <RUBYLIBDIR>/psych/nodes/node.rb String 3 <RUBYLIBDIR>/psych/nodes/node.rb Array 3 <RUBYLIBDIR>/psych/visitors/yaml_tree.rb MatchData 3 <RUBYLIBDIR>/psych/visitors/emitter.rb String 3 ...

class_plus

require 'hike'
require 'allocation_stats'

stats = AllocationStats.trace do
path = $:.detect { |p| p =~ /hike/ } path = File.dirname(path) trail = Hike::Trail.new path trail.append_extension ".rb" trail.append_paths "lib" trail.find("hike")
end puts stats.allocations(alias_paths: true) .group_by(
:sourcefile, :class_plus
) .sort_by_count .to_text
                   sourcefile                              class_plus           count
-------------------------------------------------  ---------------------------  -----
<RUBYLIBDIR>/rubygems/core_ext/kernel_require.rb String 438
<RUBYLIBDIR>/pathname.rb String 152
<RUBYLIBDIR>/rubygems/core_ext/kernel_require.rb Array<Fixnum,FalseClass> 134
<RUBYLIBDIR>/rubygems/core_ext/kernel_require.rb RubyVM::InstructionSequence 105
examples/hike_hike.rb String 81
<RUBYLIBDIR>/pathname.rb Array<String> 67
<RUBYLIBDIR>/pathname.rb String 67
<RUBYLIBDIR>/rubygems/core_ext/kernel_require.rb Array<Array> 58
<RUBYLIBDIR>/rubygems/core_ext/kernel_require.rb Array 47 <RUBYLIBDIR>/rubygems.rb String 27
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb String 21
examples/hike_hike.rb RubyVM::InstructionSequence 20
<RUBYLIBDIR>/pathname.rb String 20 examples/hike_hike.rb Array 16 <GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb String 15 ...

burn

require 'hike'
require 'allocation_stats'

stats = AllocationStats.new
(burn: 1)
.trace do path = $:.detect { |p| p =~ /hike/ } path = File.dirname(path) trail = Hike::Trail.new path trail.append_extension ".rb" trail.append_paths "lib" trail.find("hike") end puts stats.allocations(alias_paths: true) .group_by(:sourcefile, :class_plus) .sort_by_count .to_text
                     sourcefile                          class_plus     count
-----------------------------------------------------  ---------------  -----
<RUBYLIBDIR>/pathname.rb                               String             287
<RUBYLIBDIR>/pathname.rb                               Array<String>       73
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             String              73
<RUBYLIBDIR>/pathname.rb                               MatchData           13
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             Pathname            12
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             Array<Pathname>     11
<GEMDIR>/gems/hike-1.2.3/lib/hike/paths.rb             String               7
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             Array<String>        6
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             MatchData            6
<GEMDIR>/gems/hike-1.2.3/lib/hike/trail.rb             String               6
<GEMDIR>/gems/hike-1.2.3/lib/hike/trail.rb             Array<String>        5
<GEMDIR>/gems/hike-1.2.3/lib/hike/index.rb             Hash                 5
...

Object Churn

Expensive Garbage Collection

per-Rack::Request allocations

Rack AllocationStats

[github]

Rack Middleware

http://my.rack.app:9292
/path?foo=bar&ras[trace]=true

A Simple Sinatra App localhost:9292/erb localhost:9292/erb?ras[trace]=true  

class SinatraTemplatesApp < Sinatra::Base
HELLOS = ["Hello", "Hola", "Bonjour", "Guten Tag", "こんにちは", "привет"]
enable :inline_templates
get "/erb" do erb :erb end
end __END__ @@ erb
<html> <body> <ul id="hi"> <% HELLOS.each do |hello| %> <li class="greeting"><%= hello %> World!</li> <% end %> </ul> </body> </html>

?ras[help]

Rack AllocationStats help

Append ras[trace]=true to your URL to trace all object allocations during the
request. The server will respond with statistics on the allocations, instead of
the application's response.

Options

ras[times]=COUNT
    send the request to the application COUNT times, to reduce statistics from
    initial allocations that don't happen on repeat requests.

ras[help]
    immediately respond with this help text

ras[scope]=SCOPE
    limits the list of allocations in the response to those whose `sourcefile`
    matches SCOPE. `ras[scope]='.'` is a special case which is expanded to be
    the current working directory.

...

?ras[alias_paths]=true

?ras[times]=10

?ras[output]=json

[{"group_key":["/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb",445,"String"],"allocations":[{"memsize":0,"class_path":"String","method_id":"scan","file":"<RUBYLIBDIR>/erb.rb","file (raw)":"/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb","line":445,"class":"String","class_plus":"String"},{"memsize":0,"class_path":"String","method_id":"scan","file":"<RUBYLIBDIR>/erb.rb","file (raw)":"/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb","line":445,"class":"String","class_plus":"String"},{"memsize":0,"class_path":"String","method_id":"scan","file":"<RUBYLIBDIR>/erb.rb","file (raw)":"/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb","line":445,"class":"String","class_plus":"String"},{"memsize":0,"class_path":"String","method_id":"scan","file":"<RUBYLIBDIR>/erb.rb","file (raw)":"/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb","line":445,"class":"String","class_plus":"String"},...
[
  { "group_key": [
      "/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb",
      445,
      "String"
    ],
    "allocations": [
      { "memsize": 0,
        "class_path": "String",
        "method_id": "scan",
        "file": "<RUBYLIBDIR>/erb.rb",
        "file (raw)": "/usr/local/rbenv/versions/2.1.0-dev/lib/ruby/2.1.0/erb.rb",
        "line": 445,
        "class": "String",
        "class_plus": "String" },
      ...
    ]
  },
  ...
]

?ras[output]=interactive

?ras[output]=interactive

?ras[output]=interactive

What do I do???

Allocate less.

How do I allocate less?

That depends :)

Use self-modifying collection methods where possible.

diff --git a/lib/temple/hash.rb b/lib/temple/hash.rb
index a2235c0..f1e5a70 100644
--- a/lib/temple/hash.rb
+++ b/lib/temple/hash.rb
@@ -24,3 +24,3 @@ def each
     def keys
-      @hash.inject([]) {|keys, h| keys += h.keys }.uniq
+      @hash.inject([]) {|keys, h| keys.concat(h.keys) }.uniq
     end
[judofyr/temple #75][ Array#concat ][ Array#+ ]

Memoization

(Especially String-building)

Rails 3.2.15

Rails 3.2.15

Rails 3.2.15

Rails 3.2.15

lib/active_support/callbacks.rb

 80  def run_callbacks(kind, *args, &block)
81 send("_run_#{kind}_callbacks", *args, &block)
82 end
...
413 def __callback_runner_name(key, kind)
414 "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks"
415 end

Rails 4.0.0

lib/active_support/callbacks.rb

383  def __callback_runner_name_cache
384    
@__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
385 end 386 387 def __generate_callback_runner_name(kind) 388 "_run__#{self.name.hash.abs}__#{kind}__callbacks" 389 end 390 391 def __callback_runner_name(kind) 392 __callback_runner_name_cache[kind] 393 end

Rails 3.2.15

active_record/relation.rb

  6 class ActiveRecord::Relation
  7   JoinOperation = Struct.new(:relation, :join_class, :on)
8 ASSOCIATION_METHODS = [:includes, :eager_load, :preload] 9 MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] 10 SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
...
 19 def initialize(klass, table)
...
 26
SINGLE_VALUE_METHODS
.each {|v|
instance_variable_set(:"@#{v}_value", nil)
} 27
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS)
.each {|v|
instance_variable_set(:"@#{v}_values", [])
}
...
30 end

Not Always Easy

active_support/core_ext/string/output_safety.rb:184

 88 class SafeBuffer < String
...
117 def initialize(*) 118 @html_safe = true 119 super 120 end
...
179 end 182 class String 183 def html_safe
184 ActiveSupport::SafeBuffer.new(self)
185 end 186 end

Not Always Easy

action_view/helpers/tag_helper.rb:65

64 def tag(name, options = nil, open = false, escape = true)
65  "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : "/>"}".html_safe
66 end

Not Always Easy

sqlite3/statement.rb:108

106 def each
107   loop do
108 val = step
109 break self if done? 110 yield val 111 end 112 end

Be Excited!

  • Be aware of your object allocations.
  • Be aware of how much garbage collection costs you.
  • Add ::trace_object_allocations to your toolbox.

Watch This Space!

Things to Google

  • github blog hey judy
  • ruby allocation_stats
  • ruby rack-allocation_stats
  • koichi sasada efficient 2.1

Thanks

  • Aman Gupta / @tmm1
  • GitHub
  • Matt Brooks
  • Ruby Central
  • Hakim El Hattab for reveal.js