Tham khảo về memory-profiling và WeakRef trong Ruby
Ruby
104
White

huydx viết ngày 23/05/2015

Gần đây tôi có đọc bài viết về analyize memory leak trên ruby của Sam Saffron. Bài viết có đề cập đến một số kỹ thuật debug khá thú vị như

  • Sử dụng ObjectSpace để snapshot process hiện tại
ObjectSpace.each_object do |o|
 begin
    object_ids << o.object_id
  rescue
    # skip
  end
end

IO.binwrite(filename, Marshal::dump(object_ids))
  • Sử dụng heapdump để dump ra cả quá trình allocate object và destroy object dựa trên các lần GC (mỗi lần GC được gọi là một generation)
require 'objspace'
ObjectSpace.trace_object_allocations_start

io=File.open("/tmp/my_dump", "w")
ObjectSpace.dump_all(output: io); 
io.close

Sử dụng dump_all sẽ giúp chúng ta có thêm một số thông tin rất hữu ích như: sau mỗi lần GC sẽ có những object nào, object đó phát sinh từ dòng code nào.

Thử chạy dump_all trên môi trường cá nhân, tôi thu được một số thông tin rất hữu ích, ví dụ như:

{"address":"0x7f8db8aabe08", "type":"STRING", "class":"0x7f8db0829660", "embedded":true, "bytesize":1, "value":"\n", "encoding":"UTF-8", "flags":{"wb_protected":true, "old":true, "marked":true}}

Từ thông tin trên tôi sẽ thấy có một object String, được lưu ở địa chỉ 0x7f8db8aabe08, class nằm ở địa chỉ 0x7f8db0829660, có size là 1 byte, giá trị là newline, encoding là UTF-8, flag gc là wb_protected, đã được mark để xử lý ở lần GC tiếp theo.

Những bạn nào muốn tham khảo kĩ hơn về các technique đó có thể tham khảo ở bài viết trên. Hơi lan man một chút nhưng tôi sẽ trở về chủ đề chính của bài viết là về WeakRef


Ở bài viết trên của Sam, thì có đề cập đến việc gem therubyracer bị leak weak reference của một số lượng lớn các object:

At the top of our list we can see our JavaScript engine therubyracer is leaking lots of objects, in particular we can see the weak references it uses to maintain Ruby to JavaScript mappings are being kept around for way too long.

WeakRef là một khái niệm khá thú vị, và có ứng dụng trong một số bài toán khá hay. Tài liệu về WeakRef có thể được tìm thấy tại đây.

Weak Reference class that allows a referenced object to be garbage-collected.

Từ định nghĩa trên bạn có thể hình dung Weak Reference dùng trong trường hợp khi bạn có những object mà chỉ tồn tại trong một khoảng thời gian ngắn, cho những mục đích tức thời. Những object này sẽ được GC giải quyết ngay khi có thể.

Hãy thử xem qua ví dụ trên tài liệu của ruby-doc:

foo = Object.new            # create a new object instance
p foo.to_s                  # original's class
foo = WeakRef.new(foo)      # reassign foo with WeakRef instance
p foo.to_s                  # should be same class
GC.start                    # start the garbage collector
p foo.to_s                  # should raise exception (recycled)

Từ ví dụ trên chúng ta có thể thấy 2 điểm:

  • Có thể gán được một object đã tồn tại thành WeakReference
  • WeakRefrence object sẽ được collect ngay khi GC được chạy

Vậy chúng ta nên dùng WeakRef khi nào? Cũng giống như java, WeakRef được sử dụng nhiều cho mục đích caching, khi mà một số quan hệ mapping, hay object mà chúng ta muốn lưu chúng tạm thời nhằm mục đích tăng tốc các xử lý lặp đi lặp lại nhiều lần.

Ví dụ trong ruby-doc cũng thể hiện khá rõ ý tưởng này với implementation của weak-hash:

require 'weakref'

class WeakHash < Hash
  def []= key, obj
    super WeakRef.new(key), WeakRef.new(obj)
  end
end

WeakHash sẽ lưu giữ các cặp key và object, và việc sử dụng WeakRef ở cả key và obj sẽ giúp cho hash object có thể được dọn dẹp bất cứ khi nào hệ thống thiếu bộ nhớ.

Đào sâu một chút, chúng ta có thể thấy sử dụng WeakHash để lưu cache về view, hay cache về translation trong rails có thể đem lại hiệu quả tốt hơn về bộ nhớ, so với việc sử dụng cache mà phải invalidate trực tiếp như hiện tại.

Trở lại bài viết của Sam, tại sao việc sử dụng WeakRef lại gây ra leak, và trong trường hợp thế nào thì sẽ gây leak?
Bài viết của Sam đề cập đến đoạn code gây leak như ở dưới đây, đoạn code nằm trong source code của therubyracer

class WeakValueMap
   def initialize
      @values = {}
   end

   def [](key)
      if ref = @values[key]
        ref.object
      end
   end

   def []=(key, value)
     @values[key] = V8::Weak::Ref.new(value)
   end
end

Các bạn có thể thấy đoạn code khả nghi

     @values[key] = V8::Weak::Ref.new(value)

đoạn code này sẽ tạo ra một WeakRef mỗi lần có một cặp key-value được gán vào map. Vấn đề là ở chỗ

ngay cả khi key đã tồn tại rồi thì vẫn có một WeakRef mới được tạo ra

Vậy vấn đề ở đây là gì? WeakRef mới được tạo ra nhưng reference đến nó là key, thì lại không được xoá đi, khiến cho WeakRef đó tồn tại mãi mãi với chương trình, và đây chính là nguyên nhân gây ra leak. Rất thú vị phải không :).

Để giải quyết bài toán này, Sam đã làm như sau:

class WeakValueMap
  ...

  def []=(key, value)
    ref = V8::Weak::Ref.new(value)
    ObjectSpace.define_finalizer(value, self.class.ensure_cleanup(@values, key, ref))

    @values[key] = ref
  end

  def self.ensure_cleanup(values,key,ref)
    proc {
      values.delete(key) if values[key] == ref
    }
  end
end

Sam giải quyết bài toán bằng cách

Mỗi khi key của hash được xoá đi, thì sẽ thực hiện clean up value tương ứng với key đó một cách thủ công

Cách giải quyết trên cho chúng ta học được một kĩ thuật mới, đó là sử dụng hàm define_finalizer của ObjectSpace. Hàm này các bạn có thể hiểu nó giống như de-constructor trong C++, sẽ được gọi khi object được clean-up hoặc bị xoá đi, hoặc chương trình kết thúc.

Như vậy chúng ta đã nắm thêm được rất nhiều kĩ thuật mới để profiling memory tren ruby cũng như về khái niệm, và cách sử dụng WeakReference trên ruby.

Tham khảo

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

huydx

115 bài viết.
855 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
135 8
Introduction (Link) là một cuộc thi ở Nhật, và cũng chỉ có riêng ở Nhật. Đây là một cuộc thi khá đặc trưng bởi sự thú vị của cách thi của nó, những...
huydx viết hơn 1 năm trước
135 8
White
109 14
Happy programmer là gì nhỉ, chắc ai đọc xong title của bài post này cũng không hiểu ý mình định nói đến là gì :D. Đầu tiên với cá nhân mình thì hap...
huydx viết gần 3 năm trước
109 14
White
86 10
(Ảnh) Mở đầu Chắc nhiều bạn đã nghe đến khái niệm oauth. Về cơ bản thì oauth là một phương thức chứng thực, mà nhờ đó một web service hay một ap...
huydx viết hơn 2 năm trước
86 10
Bài viết liên quan
White
8 6
Chưa xem phần 2? Xem (Link) Trong bài viết này tôi giới thiệu cho các bạn về khái niệm function arity, một cách gọi mĩ miều của số lượng argument ...
Lơi Rệ viết hơn 2 năm trước
8 6
White
8 1
Tiếp theo (Link) Mình sẽ hướng dẫn cách test căn bản cho API mình tạo. Thật ra mà nói thì mình phải viết test trước khi làm nhưng mà để tránh việc...
My Mai viết hơn 2 năm trước
8 1
White
4 2
__Chú thích__: Đây là bản dịch tiếng Việt của bài viết gốc của tôi. Nếu bạn muốn xem bản tiếng Anh, xin hãy trỏ tới URL (Link) Lời mở (Link) là ...
Lơi Rệ viết gần 3 năm trước
4 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
115 bài viết.
855 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!