Память руби изнутри

1,014 views

Published on

Published in: Education
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,014
On SlideShare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
4
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Память руби изнутри

  1. 1. Память Ruby изнутри Василий Федосеев
  2. 2. Долгоживущие процессыПроблема утечек памяти и стейтовНельзя просто перезапустить процесс, как это делает passenger
  3. 3. Управление памятью
  4. 4. Объекты в MRIВсё - это объектыИх очень многоЖивут в куче (heap) в фиксированных слотах sizeof(RVALUE) = 40 (обычно)
  5. 5. ObjectSpace _id2ref count_objects each_object garbage_collect define_finalizer / undefine_finalizer
  6. 6. Use the Source, Luke!
  7. 7. ObjectSpaceRVALUE ObjectSpace HeapRVALUE ... freelistRVALUE Heap Heap freeRVALUE free freeRVALUE
  8. 8. Параметры GC RUBY_GC_MALLOC_LIMIT По умолчанию 8 Мб RUBY_HEAP_MIN_SLOTS 10k 1.9.2 стартует с 17k объектов, 2.0.0 с 15k RUBY_FREE_MIN 4096
  9. 9. Однако2.0.0dev :001 > ObjectSpace.each_object(Hash){} => 1182.0.0dev :002 > ObjectSpace.each_object(Fixnum){} => 02.0.0dev :003 > ObjectSpace.each_object(Symbol){} => 0
  10. 10. Не всё - объекты
  11. 11. Object ID 2.0.0dev :001 > 0.object_id => 1 2.0.0dev :002 > 1.object_id => 3 2.0.0dev :003 > :a.object_id => 468808 2.0.0dev :004 > "a".object_id => 70199055954380 2.0.0dev :005 > true.object_id => 20 2.0.0dev :006 > false.object_id => 0 2.0.0dev :007 > nil.object_id => 8
  12. 12. Object ID Fixnum flag RUBY_Qfalse = 0x00, 101010101011 1 RUBY_Qtrue = 0x14, RUBY_Qnil = 0x08, RUBY_Qundef = 0x34, Symbol id 1100 RUBY_IMMEDIATE_MASK RUBY_FIXNUM_FLAG = = 0x07, 0x01, RUBY_FLONUM_MASK = 0x03, RUBY_FLONUM_FLAG = 0x02, RUBY_SYMBOL_FLAG = 0x0c, RVALUE ptr 000 RUBY_SPECIAL_SHIFT = 8
  13. 13. enum ruby_value_type {RBasic RUBY_T_NONE = 0x00, RUBY_T_OBJECT = 0x01, RUBY_T_CLASS = 0x02, RUBY_T_MODULE = 0x03, RUBY_T_FLOAT = 0x04, RUBY_T_STRING = 0x05, RUBY_T_REGEXP = 0x06, RUBY_T_ARRAY = 0x07, RUBY_T_HASH = 0x08, RUBY_T_STRUCT = 0x09, RUBY_T_BIGNUM = 0x0a, RUBY_T_FILE = 0x0b, struct RBasic { RUBY_T_DATA = 0x0c, VALUE flags; RUBY_T_MATCH = 0x0d, RUBY_T_COMPLEX = 0x0e, VALUE klass; RUBY_T_RATIONAL = 0x0f, }; RUBY_T_NIL = 0x11, RUBY_T_TRUE = 0x12, RUBY_T_FALSE = 0x13, RUBY_T_SYMBOL = 0x14, RUBY_T_FIXNUM = 0x15, RUBY_T_UNDEF = 0x1b, RUBY_T_NODE = 0x1c, RUBY_T_ICLASS = 0x1d, RUBY_T_ZOMBIE = 0x1e, RUBY_T_MASK = 0x1f };
  14. 14. RObject #define ROBJECT_EMBED_LEN_MAX 3 struct RObject { struct RBasic { VALUE flags; VALUE klass; }; union { ! struct { ! long numiv; ! VALUE *ivptr; struct st_table *iv_index_tbl; ivars[numiv] ! } heap; ! VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as; };
  15. 15. RClass struct RClass { struct RBasic basic; rb_classext_t *ptr; struct st_table *m_tbl; struct st_table *iv_index_tbl; }; struct rb_classext_struct { VALUE super; struct st_table *iv_tbl; struct st_table *const_tbl; VALUE origin; VALUE refined_class; rb_alloc_func_t allocator; };
  16. 16. T_DATA struct RTypedData { struct RBasic basic; const rb_data_type_t *type; VALUE typed_flag; /* 1 or not */ void *data; }; struct rb_data_type_struct { const char *wrap_struct_name; struct { ! void (*dmark)(void*); ! void (*dfree)(void*); ! size_t (*dsize)(const void *); ! void *reserved[2]; } function; const rb_data_type_t *parent; void *data; };
  17. 17. typedef struct RVALUE { union { ! struct { ! VALUE flags; ! struct RVALUE *next;RVALUE ! ! ! } free; struct RBasic basic; struct RObject object; ! struct RClass klass;слоты в куче - это RVALUE ! ! struct RFloat flonum; struct RString string; ! struct RArray array;union от всех возможных ! ! struct RRegexp regexp; struct RHash hash;системных типов ! struct RData data; ! struct RTypedData typeddata; ! struct RStruct rstruct;тип определяется по флагам ! struct RBignum bignum; ! struct RFile file;размер обычно 40 байт ! ! struct RNode node; struct RMatch match; ! struct RRational rational; ! struct RComplex complex; } as; } RVALUE;
  18. 18. Корневые объекты Главный тред и RubyVM Машинный контекст: стек и регистры Глобальные константы и переменные в том числе из нативных гемов Таблица классов Generic ivars Finalizers и at_exit
  19. 19. Корневой объект Корневой объект Obj1 Obj2 Obj4 Obj3 Obj5 Obj7 Obj6
  20. 20. Корневой объект Корневой объект Obj1 Obj2 Obj4 Obj3 Obj5 Obj7 Obj6
  21. 21. Подсчет ссылок Корневой объект 1 Корневой объект 2 Obj1 2 Obj2 1 Obj4 1 Obj3 1 Obj5 1 1 Obj7 Obj6 1
  22. 22. Mark & Sweep: mark Корневой объект Корневой объект Obj1 Obj2 Obj4 Obj3 Obj5 Obj7 Obj6
  23. 23. Mark & Sweep: mark Корневой объект Корневой объект Obj1 Obj2 Obj4 Obj3 Obj5 Obj7 Obj6
  24. 24. Mark & Sweep: sweep Корневой объект Корневой объект Obj1 Obj2 Obj4 Obj3
  25. 25. Виды ссылок Из корневых объектов Переменные класса Переменные экземпляра Содержимое контейнеров Локальные переменные
  26. 26. Задачка class A def a &b; end; def initialize a(&:to_s) end end def closure_method A.new end closure_method GC.start puts ObjectSpace.each_object(A){} что будет выведено на экран?
  27. 27. WTF?
  28. 28. Код - тоже объекты T_CLASS, T_ICLASS T_MODULE T_DATA iseq = instruction sequence method block proc = iseq + VM/env
  29. 29. Виды ссылок Глобальные переменные Переменные класса Переменные экземпляра Содержимое контейнеров Локальные переменные Замыкания
  30. 30. Как найти?
  31. 31. heap_dumphttps://github.com/Vasfed/heap_dump Дампит почти полное дерево ссылок Без патчей в руби Нет оверхеда в простое Удобная считалка объектов
  32. 32. Задачка class A def a &b; end; def initialize a(&:to_s) end end def closure_method A.new end closure_method require heap_dump HeapDump.dump
  33. 33. Расследование$ grep "name":"A" dump.json,{"id":70129858139840,"bt":"T_CLASS","class":70129858139820,"name":"A","methods":{"a":70129857754000,"initialize":70129858140300}}$ grep 70129858139840 dump.json | grep T_OBJECT,{"id":70129858139780,"bt":"T_OBJECT","class":70129858139840}$ grep 70129858139780 dump.json | grep -v id":70129858139780,{"id":70129858139720,"bt":"T_DATA","class":70129857815360,"type_name":"VM/env","size":104,"env":[70129858139780,70129858139720],"local_size":2,...$ grep 70129858139720 dump.json | grep -v id":70129858139720,{"id":70129858139700,"bt":"T_DATA","type_name":"proc","envval":70129858139720,"block":{"iseq":70129858139740,"self":"to_s"}$ grep 70129858139700 dump.json | grep -v id":70129858139700,{"id":70129858139760,"bt":"T_ARRAY","class":null,"val":[null,null,null,null,...,null,null,"to_s",70129858139700,null,null,...]}
  34. 34. Расследование class A object A VM/env Какой-то массив с 134 символами и proc proc Глобальные переменные
  35. 35. Расследование //string.c static VALUE sym_to_proc(VALUE sym) { static VALUE sym_proc_cache = Qfalse; enum {SYM_PROC_CACHE_SIZE = 67}; ... if (!sym_proc_cache) { ! sym_proc_cache = rb_ary_tmp_new(SYM_PROC_CACHE_SIZE * 2); ! rb_gc_register_mark_object(sym_proc_cache); ! ... index = (id % SYM_PROC_CACHE_SIZE) << 1; aryp = RARRAY_PTR(sym_proc_cache); if (aryp[index] == sym) return aryp[index + 1]; else { ! proc = rb_proc_new(sym_call, (VALUE)id); ! aryp[index] = sym; aryp[index + 1] = proc; ! return proc; } }
  36. 36. Правильный ответMRI кеширует результаты Symbol#to_procВ замыкание proc может попасть сам объектОбъект и все, на что он ссылается - останется в памяти довытеснения из кешаЭто баг в rubyНа экран будет выведена единица https://gist.github.com/4273437
  37. 37. Поиск утечек Научиться воспроизводить Понять что именно течет Снять дамп Понять почему течет Дальше по желанию
  38. 38. Пример с рельсами class LeakController < ApplicationController def leak ($leak ||= []).push proc{ "some never-callback" } render text: "ololo" end end
  39. 39. Понять что именно течет class LeakController < ApplicationController def leak ($leak ||= []).push proc{ "some never-callback" } render text: "ololo" end def count GC.start render :json => HeapDump.count_objects([:ApplicationController] + ApplicationController.subclasses.map{|c| c.name.to_sym}) end def dump fork { HeapDump.dump; exit } render :text => "May be Dumped" end end
  40. 40. Счетчик объектов $ curl http://localhost:3000/count { "total_slots": 183152, "free_slots": 53535, "basic_types": { "T_OBJECT": 5064, "T_CLASS": 2723, "T_MODULE": 423, "T_FLOAT": 82, "T_STRING": 74909, "T_REGEXP": 1235, "T_ARRAY": 21019, "T_HASH": 585, "T_STRUCT": 199, "T_BIGNUM": 2, "T_FILE": 8, "T_DATA": 12741, "T_MATCH": 4, "T_COMPLEX": 1, "T_RATIONAL": 69, "T_NODE": 10038, "T_ICLASS": 515 }, "user_types": { "LeakController": 7 } }
  41. 41. Пример с рельсами$ grep name":"LeakController" dump.json | grep T_CLASS,{"id":70281018730340,"bt":"T_CLASS","class":70281018730260,"name":"LeakController","methods":{"_layout":70281018415040,"leak":70281029395580,"count":70281029394940,"dump":70281029394280, ...},"ivs":{"__classpath__":"LeakController","@controller_name":"leak","@visible_actions":70281018559740,"@controller_path":"leak","@_layout":null,"@action_methods":70281004447860,"@parent_name":null,"@parent_prefixes":70281009406480,"@_config":70281007802340,"@view_context_class":70281004125560},"super":70281029346060}
  42. 42. Пример с рельсами$ grep 70211925295180 dump.json ,{"id":70211925285360,"bt":"T_DATA","class":70211923570840,"type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70211925285380,"block":{"iseq":{"id":70211951312640,"name":"block inleak","filename":"/Users/vasfed/work/railsclub/examples/leaky_app/app/controllers/leak_controller.rb","line":5,"type":"block","refs_array_id":70211931351000,"coverage":null,"klass":null,"cref_stack":70211931351600,"defined_method_id":0},"self":70211925295180,"lfp":70211950222292,"dfp":70211950222292}}
  43. 43. Пример с рельсами LeakController LeakController LeakController ... proc proc proc Массив Глобальная $leak global_tbl
  44. 44. Profit!
  45. 45. Популярные утечкиГлобальные переменные и их аналоги@@aa = selfEventMachine.next_tick {...}ЗамыканияSymbol#to_proc aka &:symbolСтеки зависших тредов/файберов
  46. 46. Что это было?Общие сведения об устройстве ObjectSpaceКак работает GC и какие объекты выживаютКак искать утечки
  47. 47. ???https://github.com/Vasfed/heap_dumphttps://gist.github.com/4273437@vasfed

×