Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Memory management in MRI

Slide 3

Slide 3 text

About me Arthur Pirogovski arthur@flyintealeaf.com

Slide 4

Slide 4 text

About me Coding for , mostly Ruby.

Slide 5

Slide 5 text

Ruby community

Slide 6

Slide 6 text

Ruby community loves Ruby

Slide 7

Slide 7 text

Ruby community hates Ruby (at times)

Slide 8

Slide 8 text

What’s wrong with Ruby?

Slide 9

Slide 9 text

What’s wrong with Ruby? • not as fast as some languages

Slide 10

Slide 10 text

What’s wrong with Ruby? • not as fast as some languages • concurrency is a swear word

Slide 11

Slide 11 text

What’s wrong with Ruby? • not as fast as some languages • concurrency is a swear word • garbage collector is garbage

Slide 12

Slide 12 text

Garbage Collector is garbage Y?

Slide 13

Slide 13 text

Lifetime of an object

Slide 14

Slide 14 text

Lifetime of an object Object.new

Slide 15

Slide 15 text

Lifetime of an object Object.new rb_class_new_instance()

Slide 16

Slide 16 text

Lifetime of an object Object.new rb_obj_alloc() 㱺 .allocate rb_obj_call_init() 㱺 #initialize

Slide 17

Slide 17 text

Lifetime of an object .allocate rb_newobj() /* gc.c */

Slide 18

Slide 18 text

Lifetime of an object rb_newobj(): 1. try to bite from freelist

Slide 19

Slide 19 text

Lifetime of an object rb_newobj(): 1. try to bite from freelist 2. if freelist is empty invoke GC

Slide 20

Slide 20 text

Lifetime of an object rb_newobj(): 1. try to bite from freelist (except for basic types) 2. if freelist is empty invoke GC

Slide 21

Slide 21 text

Lifetime of an object rb_newobj(): 1. try to bite from freelist (except for basic types) 2. if freelist is empty invoke GC (everything lives on a heap)

Slide 22

Slide 22 text

Native Types • Custom allocators • Embedded values (boxing)

Slide 23

Slide 23 text

Fixnum A 31/63-bit number where the original number is lshifted one bit and LSB is set to “1”

Slide 24

Slide 24 text

true, false, nil C constants: 2, 0, 4

Slide 25

Slide 25 text

String embedded if length < 23 chars (~ 11-12 chars for 32-bit) CoW for #dup

Slide 26

Slide 26 text

Symbol is stored in the symbol table is not GCed

Slide 27

Slide 27 text

1. Stop the World GC in 1.9

Slide 28

Slide 28 text

1. Stop the World 2. Mark all the linked objects GC in 1.9

Slide 29

Slide 29 text

1. Stop the World 2. Mark all the linked objects 3. Sweep until enough GC in 1.9

Slide 30

Slide 30 text

1. Stop the World 2. Mark all the linked objects 3. Sweep until enough 4. malloc() if not enough GC in 1.9

Slide 31

Slide 31 text

1. Stop the World 2. Mark all the linked objects 3. Sweep until enough 4. malloc() if not enough 5. Wake up the World GC in 1.9

Slide 32

Slide 32 text

• Blocks all threads GC problems

Slide 33

Slide 33 text

• Blocks all threads • Is prone to ghost references GC problems

Slide 34

Slide 34 text

• Blocks all threads • Is prone to ghost references • Is slow GC problems

Slide 35

Slide 35 text

the less often it runs, the slower each run is GC slowness

Slide 36

Slide 36 text

the more you parallelize, the more GC impacts you GC slowness

Slide 37

Slide 37 text

the more you parallelize, the more GC impacts you (think EventMachine et al) GC slowness

Slide 38

Slide 38 text

How can we minimize GC impact?

Slide 39

Slide 39 text

How can we minimize GC impact? - Allocate less

Slide 40

Slide 40 text

Hash[something.map] allocates: 1. An array 2. A hash Allocate less

Slide 41

Slide 41 text

something.reduce({}) allocates: 1. An array 2. A hash Allocate less

Slide 42

Slide 42 text

avoid loading unnecessary libs which lines in Gemfile can be removed? Allocate less

Slide 43

Slide 43 text

know your allocations what’s hidden in the memory of your Ruby app? Allocate less

Slide 44

Slide 44 text

Sometimes it’s really hard to figure that out

Slide 45

Slide 45 text

In Ruby we have:

Slide 46

Slide 46 text

• no working memory profiler In Ruby we have:

Slide 47

Slide 47 text

• no working memory profiler • no memory profiling API In Ruby we have:

Slide 48

Slide 48 text

• no working memory profiler • no memory profiling API • borked finalizers In Ruby we have:

Slide 49

Slide 49 text

• no working memory profiler • no memory profiling API • borked finalizers • ObjectSpace! In Ruby we have:

Slide 50

Slide 50 text

ObjectSpace.count_objects {:TOTAL=>101383, :FREE=>164, :T_OBJECT=>6822, :T_CLASS=>865, :T_FLOAT=>7, :T_STRING=>63301, ...} helps to find leaks in native Ruby types like String ObjectSpace?

Slide 51

Slide 51 text

ObjectSpace.each_object. reduce(Hash.new(0)) { |hash, o| hash[o.class] +=1; hash } helps to find leaks in custom Ruby types like ActiveRecord ObjectSpace?

Slide 52

Slide 52 text

it works, but it’s: • slow • intrusive (allocates objects) • unable to show code location ObjectSpace?

Slide 53

Slide 53 text

Functions have .source_location: ActiveRecord::Base. instance_method(:reload). source_location ... seamless_database_pool_adapter.rb", 80] Showing code location

Slide 54

Slide 54 text

No such thing to reveal where an object was allocated ;( Showing code location

Slide 55

Slide 55 text

No such thing to reveal where an object was allocated But we can hack it up! Showing code location

Slide 56

Slide 56 text

class M attr_reader :allocated_at def initialize @allocated_at = caller.first end end m = M.new puts m.allocated_at.inspect Showing code location

Slide 57

Slide 57 text

class BasicObject attr_reader :allocated_at def initialize_with_allocated_at(*options) unless @allocated_at @allocated_at = caller.first return initialize_without_allocated_at(*options) end end def self.enable_allocated_at self.send :alias_method, :initialize_without_allocated_at, :initialize self.send :alias_method, :initialize, :initialize_with_allocated_at end end Showing code location

Slide 58

Slide 58 text

# Enabling allocation tracking on every object ObjectSpace.each_object(Class) do |klass| klass.enable_allocated_at end # Building a huge allocation map of all the objects in the system class Object def allocation_map ObjectSpace.each_object.reduce({}) { |hash, object| hash[object.allocated_at] ||= [] hash[object.allocated_at] << object if object.respond_to?(:allocated_at) hash } end end Showing code location

Slide 59

Slide 59 text

Ruby 2.0

Slide 60

Slide 60 text

• Enumerable::Lazy [1, 2, 3].lazy.map { ... }.select bypasses intermediate arrays, albeit is currently very slow Ruby 2.0

Slide 61

Slide 61 text

• Bitmap marking helps to avoid pre-2.0 problem where fork()-CoW is useless because of GC marks Ruby 2.0

Slide 62

Slide 62 text

• caller_locations versus caller is orders of magnitude faster and thus can replace caller in the original allocations_map Ruby 2.0

Slide 63

Slide 63 text

• immediate Floats only on 64-bit systems, speeds up floating point calculations Ruby 2.0

Slide 64

Slide 64 text

is coming in December 2013 Ruby 2.1

Slide 65

Slide 65 text

is coming in December 2013 with generational GC: https://bugs.ruby-lang.org/ issues/8339 Ruby 2.1

Slide 66

Slide 66 text

collects younger objects more often Generational GC

Slide 67

Slide 67 text

Generational GC in action

Slide 68

Slide 68 text

Questions? Thanks