12/08/2018, 16:33

Sử dụng bộ nhớ của object trong Ruby

Khi lập trình Ruby, nhiều người nghĩ rằng việc sử dụng bộ nhớ vượt mức là việc khó có thể tránh khỏi. Tuy nhiên thông qua bài viết này, tôi muốn chỉ cho các bạn thấy có nhiều cách và chiến lược để giữ cho bộ nhớ được sử đụng một cách hợp lý. Những class chính như TrueClass, FalseClass, NilClass, ...

Khi lập trình Ruby, nhiều người nghĩ rằng việc sử dụng bộ nhớ vượt mức là việc khó có thể tránh khỏi. Tuy nhiên thông qua bài viết này, tôi muốn chỉ cho các bạn thấy có nhiều cách và chiến lược để giữ cho bộ nhớ được sử đụng một cách hợp lý.

Những class chính như TrueClass, FalseClass, NilClass, Integer, Float, Symbol, String, Array, Hash và Struct đã được tối ưu về hiệu suất cũng như sử dụng bộ nhớ.

Note: Điều ở trên tôi đang nói về CRuby (MRI) do đó hầu hết mọi thứ có thể không áp dụng cho các Ruby implementation khác.

Mỗi object của các class nói trên được tham chiếu bởi VALUE. Đây là một dạng con trỏ tới cấu trúc C chứa tất cả các thông tin cần thiết.

NIL, TRUE, FALSE AND SOME INTEGERS

Một vài class không cần cấp phát thêm bộ nhớ cho cấu trúc C khi khởi tạo vì các object đó có thể trực tiếp điện diện cho một VALUE ví dụ như TrueClass, FalseClass, NilClass lần lượt đại diện cho các VALUE là true, false, nil. Đó với small integer (-2^62 - 2^62-1) cũng như vậy. Do vậy bạn không cần phải suy nghĩ về việc chiếm dụng bộ nhớ mỗi khi sử dụng các object thuộc các class này.

Chúng tao có thể sử dụng ObjectSpace.memsize_of để trả về bộ nhớ sử dụng của object trong console :

pry(main) > require 'objspace'
 => true
pry(main) > ObjectSpace.memsize_of(nil)
 => 0
pry(main) > ObjectSpace.memsize_of(true)
 => 0
pry(main) > ObjectSpace.memsize_of(false)
 => 0
pry(main) > ObjectSpace.memsize_of(2**62-1)
 => 0
pry(main) > ObjectSpace.memsize_of(2**62)
 => 40

Bạn có thể thấy không có bộ nhớ nào được sử dụng ngoại trừ trường hợp cuối cùng vì số nguyên ở đây quá lớn. Khi một cấu trúc VALUE cần sử dụng bộ nhớ, object đó cần ít nhất là 40 bytes

ARRAYS, STRUCTS, HASHES AND STRINGS

Object cửa 4 class này sử dụng cấu trúc C đặc biệt. Nó cho phép lưu trữ một số giá trị trực tiếp bên trong thay vì cần thêm bộ nhớ.

Đối với Array thì với 3 phần tử bên trong sẽ không cần thêm bộ nhớ. Nếu lớn hơn 3 phần tử thì với mỗi phần tử cần 8 bytes để lưu trữ

pry(main) > ObjectSpace.memsize_of([])
 => 40
pry(main) > ObjectSpace.memsize_of([1])
 => 40
pry(main) > ObjectSpace.memsize_of([1, 2])
 => 40
pry(main) > ObjectSpace.memsize_of([1, 2, 3])
 => 40
pry(main) > ObjectSpace.memsize_of([1, 2, 3, 4])
 => 72

Điều này cũng áp dụng với object của Struct

pry(main) > X = Struct.new(:a, :b, :c)
 => X
pry(main) > Y = Struct.new(:a, :b, :c, :d)
 => Y
pry(main) > ObjectSpace.memsize_of(X.new)
 => 40
pry(main) > ObjectSpace.memsize_of(Y.new)
 => 72

Có một chút khác biệt đối với object của class Hash.

pry(main) > ObjectSpace.memsize_of({})
 => 40
pry(main) > ObjectSpace.memsize_of({a: 1})
 => 192
pry(main) > ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4})
 => 192
pry(main) > ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4, e: 5})
 => 288

Và cuối cùng là object của class String

pry(main) > ObjectSpace.memsize_of("")
 => 40
pry(main) > ObjectSpace.memsize_of("a"*23)
 => 40
pry(main) > ObjectSpace.memsize_of("a"*24)
 => 65

Object thuộc các class khác

Tất cả các object thông thường khác không sử dụng cấu trúc C mà sử dụng cấu trúc RObjeect cũng có một chế độ bộ nhớ hiệu quả (memory efficient).

Giá trị của các instance variable được chứ ở trong object. Tuy nhiên tên của chúng lại được lưu trữ bởi associated class object. Giống như array, một object với 3 instance variable sẽ chỉ sử dụng 40 bytes, sẽ là 80 bytes nếu có 4 hoặc 5 biến.

pry(main) > class X; def initialize(c); c.times {|i| instance_variable_set(:"@i#{i}", i)}; end; end
 => :initialize
pry(main) > ObjectSpace.memsize_of(X.new(0))
 => 40
pry(main) > ObjectSpace.memsize_of(X.new(1))
 => 40
pry(main) > ObjectSpace.memsize_of(X.new(2))
 => 40
pry(main) > ObjectSpace.memsize_of(X.new(3))
 => 40
pry(main) > ObjectSpace.memsize_of(X.new(4))
 => 80
pry(main) > ObjectSpace.memsize_of(X.new(5))
 => 80
pry(main)> ObjectSpace.memsize_of(X.new(6))
 => 96

Chiến lược thiết kế

Nếu project của bạn cần phải tạo nhiều object thì những thông tin tôi đưa ra ở trên rất hữu dụng khi bạn thiết kế các class.

Chúng ta xét một ví dụ: Bạn phải tạo một class đại diện cho các giá trị margin của CSS và nó có tổng cộng 4 giá trị. Bạn sẽ làm thế nào ?

  • Ý tưởng đầu tiên là sử dụng 1 array. Với 4 giá trị thì bộ nhớ cần sử dụng ở đây là 72 bytes.
  • Tuy nhiên thì việc sử dụng array thường không được áp dụng trong thực tế. Array sẽ được gói trong một class và với mỗi object của class này sẽ sử dụng 80 hoặc 112 bytes tuywf thuộc vào kích thước của array.
  • Một cách khác thường được sử dụng hơn là tạo ra một class với 4 biến instance khi khởi tạo. Mỗi object này sẽ sử dụng 80 bytes.
  • Cuối cùng, thay vì tạo một class, bạn sử dụng struct với 4 phần tử sẽ chỉ chiếm 72 bytes.

Qua những ví dụ trên chúng ta rút ra được hai điều :

  1. Sử dụng những class cơ bản chính như TrueClass, FalseClass, NilClass, Integer, Float, Symbol, String, Array, Hash và Struct là cách tốt nhất để tiết kiệm bộ nhớ.
  2. Nếu chúng ta chú ý đến những quy tắc trên khi thiết kế các class chúng ta có thể giảm thiểu được bộ nhớ sử dụng. (Với ví dụ về margin CSS chúng ta tiết kiệm được 10% bộ nhớ cho mỗi đối tượng).
0