Bạn có muốn tham quan nhanh nội bộ Ruby không?
Vậy thì bạn sẽ đến để thưởng thức.
Bởi vì …
Chúng ta sẽ cùng nhau khám phá cách một đối tượng Ruby được bố trí trong bộ nhớ và cách bạn có thể thao tác các cấu trúc dữ liệu bên trong để thực hiện một số điều thú vị.
Thắt dây an toàn và sẵn sàng cho hành trình khám phá chiều sâu của trình thông dịch Ruby!
Bố cục bộ nhớ của mảng
Khi bạn tạo một mảng, Ruby phải sao lưu mảng đó bằng một số bộ nhớ hệ thống và một chút siêu dữ liệu.
Siêu dữ liệu bao gồm :
- Kích thước mảng (số lượng mục)
- Dung lượng mảng
- Lớp
- Trạng thái đối tượng (bị đóng băng hoặc không)
- Con trỏ đến nơi dữ liệu được lưu trữ trong bộ nhớ
Vì trình thông dịch Ruby chính (MRI) được viết bằng C nên không có đối tượng nào.
Nhưng có một số thứ khác: structs .
Một cấu trúc trong C giúp bạn lưu trữ dữ liệu liên quan với nhau và cấu trúc này được sử dụng rất nhiều trong mã nguồn của MRI để đại diện cho những thứ như Array
, String
‘S &các loại đối tượng khác.
Bằng cách xem xét một trong những cấu trúc đó, chúng ta có thể suy ra bố cục bộ nhớ của một đối tượng.
Vì vậy, chúng ta hãy xem xét cấu trúc cho Array
, được gọi là RArray
:
struct RArray { struct RBasic basic; union { struct { long len; union { long capa; VALUE shared; } aux; const VALUE *ptr; } heap; const VALUE ary[RARRAY_EMBED_LEN_MAX]; } as; };
Tôi biết điều này có thể hơi đáng sợ nếu bạn không quen với C, nhưng đừng lo lắng! Tôi sẽ giúp bạn chia nhỏ điều này thành các bit dễ hiểu 🙂
Điều đầu tiên chúng tôi có là RBasic
này điều, cũng là một cấu trúc:
struct RBasic { VALUE flags; VALUE klass; }
Đây là thứ mà hầu hết các đối tượng Ruby đều có &nó chứa một số thứ như lớp cho đối tượng này và một số cờ nhị phân cho biết đối tượng này có bị đóng băng hay không (và những thứ khác như thuộc tính ‘tainted’).
Nói cách khác :
RBasic
chứa siêu dữ liệu chung cho đối tượng.
Sau đó, chúng ta có một cấu trúc khác, chứa độ dài của mảng (len
).
Biểu thức liên hợp nói rằng aux
có thể là capa
(cho dung lượng) hoặc shared
. Đây chủ yếu là một thứ tối ưu hóa, được giải thích chi tiết hơn trong bài đăng xuất sắc này của Pat Shaughnessy. Về cấp phát bộ nhớ, trình biên dịch sẽ sử dụng kiểu lớn nhất bên trong một liên minh.
Sau đó, chúng tôi có ptr
, chứa địa chỉ bộ nhớ nơi Array
thực tế dữ liệu được lưu trữ.
Dưới đây là hình ảnh của điều này trông như thế nào (mỗi hộp màu trắng / xám là 4 byte trong hệ thống 32 bit ):
Bạn có thể xem kích thước bộ nhớ của một đối tượng bằng cách sử dụng mô-đun ObjectSpace:
require 'objspace' ObjectSpace.memsize_of([]) # 20
Bây giờ chúng ta đã sẵn sàng để vui chơi!
Fiddle:Một thử nghiệm thú vị
RBasic chính xác là 8 byte trong hệ thống 32 bit và 16 byte trong hệ thống 64 bit. Biết được điều này, chúng ta có thể sử dụng mô-đun Fiddle để truy cập các byte bộ nhớ thô cho một đối tượng và thay đổi chúng cho một số thử nghiệm thú vị.
Ví dụ :
Chúng tôi có thể thay đổi trạng thái cố định bằng cách chuyển đổi một bit.
Về bản chất, đây là những gì phương pháp đóng băng thực hiện, nhưng hãy lưu ý cách không có phương pháp đóng băng.
Hãy triển khai nó chỉ để giải trí!
Đầu tiên, hãy yêu cầu Fiddle
mô-đun (một phần của Thư viện chuẩn Ruby) &tạo một chuỗi cố định.
require 'fiddle' str = 'water'.freeze str.frozen? # true
Tiếp theo:
Chúng tôi cần địa chỉ bộ nhớ cho chuỗi của chúng tôi, có thể lấy được như thế này.
memory_address = str.object_id * 2
Cuối cùng:
Chúng tôi lật bit chính xác mà Ruby kiểm tra để xem liệu một đối tượng có bị đóng băng hay không. Chúng tôi cũng kiểm tra xem điều này có hoạt động hay không bằng cách gọi frozen?
phương pháp.
Fiddle::Pointer.new(memory_address)[1] ^= 8 str.frozen? # false
Lưu ý rằng chỉ mục [1]
đề cập đến byte thứ 2 của cờ giá trị (tổng cộng bao gồm 4 byte).
Sau đó, chúng tôi sử dụng ^=
là toán tử “XOR” (Exclusive OR) để lật bit đó.
Chúng tôi làm điều này vì các bit khác nhau bên trong cờ có ý nghĩa khác nhau và chúng tôi không muốn thay đổi điều gì đó không liên quan.
Nếu bạn đã đọc bài đăng về thủ thuật ruby của tôi, bạn có thể đã thấy nó trước đây, nhưng bây giờ bạn biết nó hoạt động như thế nào 🙂
Một điều khác bạn có thể thử là thay đổi độ dài của mảng và in mảng.
Bạn sẽ thấy cách mảng trở nên ngắn hơn!
Bạn thậm chí có thể thay đổi lớp để tạo một Array
nghĩ rằng đó là một String
…
Kết luận
Bạn đã biết một chút về cách hoạt động của Ruby. Cách bố trí bộ nhớ cho các đối tượng Ruby và cách bạn có thể sử dụng Fiddle
mô-đun để chơi với nó.
Bạn có thể không nên sử dụng Fiddle
như thế này trong một ứng dụng thực, nhưng thật thú vị khi thử nghiệm.
Đừng quên chia sẻ bài đăng này để nhiều người có thể nhìn thấy nó 🙂