Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành Thức Ruby: Phát Hiện & Sửa Rò Rỉ Bộ Nhớ Hiệu Quả

Rò rỉ bộ nhớ (memory leak) là sự gia tăng không chủ đích, không kiểm soát, và không kết thúc của memory usage. Dù nhỏ đến đâu, cuối cùng leak sẽ khiến process hết memory và crash. Dù bạn định kỳ restart app tránh crash (không phán xét, tôi cũng làm!), bạn vẫn chịu performance implications của memory leak.

Trong bài này, phần đầu của loạt hai phần về memory leaks, chúng ta xem cách Ruby quản lý bộ nhớ, Garbage Collection (GC) hoạt động, và cách tìm leak.

Phần hai, chúng ta đi sâu tracking down leaks.

Hãy bắt đầu!

Ruby Memory Management

Ruby objects lưu trên heap, mỗi object fill một slot trên heap.

Trước Ruby 3.1, tất cả slot trên heap cùng size — 40 bytes chính xác. Object quá lớn fit slot lưu ngoài heap. Mỗi slot include reference đến nơi object moved.

Trong Ruby 3.1, variable width allocation cho object String merged. Sớm, variable width allocation sẽ norm cho all object types.

Variable width allocation nhắm cải thiện performance bằng cải thiện cache locality — tất cả info object lưu một chỗ thay vì hai memory location.

Nó cũng simplify (một phần) memory management. Hiện có hai 'heaps':

  • Ruby heap (hoặc GC heap) lưu Ruby object nhỏ.
  • C heap (hoặc malloc/transient heap) lưu object lớn.

Khi variable width allocation thành norm, không cần heap sau.

Heap bắt đầu size cho trước (10,000 slots default) và object gán free slots khi tạo. Khi Ruby thử tạo object không free slots, Garbage Collection (GC) xảy ra để làm free slots available.

Nếu quá ít free slots sau GC, heap expanded (thêm sau).

Các factor bạn control, cùng environment variables:

  • Initial heap size - RUBY_GC_HEAP_INIT_SLOTS
  • Số free slots nên available sau GC - RUBY_GC_HEAP_FREE_SLOTS
  • Số heap expand - RUBY_GC_HEAP_GROWTH_FACTOR

Garbage Collection Trong Ruby

Garbage Collection Ruby 'stops the world' — không process khác xảy ra khi GC. GC Ruby (từ 2.1) cũng generational, nghĩa là garbage collector hai mode:

  • Minor GC - inspect 'young' objects (object tạo gần đây)
  • Major GC - inspect 'old' objects cũng như 'young' (all objects)

Lưu ý: 'Old' object survive 3 GC runs, major hoặc minor.

Khi heap full, minor GC invoked first. Nếu không free đủ slots dưới limit, major GC invoked. Chỉ thì, nếu vẫn không đủ free slots, heap expanded.

Major GC đắt hơn minor GC vì nhìn nhiều object hơn.

Lý thuyết tại sao generational GC performant hơn: object thường rơi hai category:

  1. Object allocated rồi nhanh out of scope. Rails app, model fetch DB render page out of scope khi request end.
  2. Object allocated và kept around lâu. Classes và caches likely still in use suốt lifetime app.

Major GC cũng chạy sau minor GC nếu số old objects trên threshold, dù đủ free slots. Limit tăng theo heap size, control bởi RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR env var.

Khi bạn có leak, bạn tạo object không clean up — nhiều old objects hơn. Nghĩa là major (đắt) GC chạy thường hơn nên. Vì không gì chạy khi GC, đây thời gian lãng phí.

Tôi để link cuối bài đọc thêm memory layout và garbage collector Ruby.

Memory Leak Trông Như Thế Nào Trong Ruby?

Bạn thấy memory leak dùng simple tools trên Unix system. Lấy code sau làm ví dụ.


Nói code này 'leak' hơi bất công — nó chỉ leak! — nhưng phục vụ mục đích.

Chúng ta observe leak đơn giản từ command line chạy program trong terminal và watch memory increase theo thời gian với ps.


pgrep -f "ruby ./leaky.rb" tìm process ID, restrict ps output chỉ process quan tâm. Giống grep cho process.

Tool watch cho phép poll output command và update in place, cho live dashboard trong terminal.

Output như sau, update vài giây.


Bạn thấy %MEM và RSS tăng. Chúng là:

  • %MEM - Phần trăm memory process dùng so với host machine.
  • RSS (resident set size) - RAM process dùng tính bằng bytes.

Thông tin OS-only cơ bản đủ spot leak — nếu memory cứ tăng, bạn có leak!

Tìm Ruby Leaks Với Garbage Collector Module

Chúng ta cũng detect leaks trong Ruby code với module GC.


Phương thức GC.stat trả hash nhiều info hữu ích. Ở đây, quan tâm :heap_live_slots, số slot heap in use. Đối lập :heap_free_slots. Cuối loop, force major GC và print used slots, ví dụ số object remain sau GC.

Khi chạy program nhỏ, thấy tăng vô tận. Có leak! Cũng dùng GC.stat(:old_objects) hiệu quả tương tự.

Dù GC module thấy có leak và (nếu khéo puts) leak ở đâu, ta thấy type object leaking với module ObjectSpace.


Phương thức ObjectSpace.count_objects trả hash count live objects. T_STRING, ví dụ, số string live in memory. Leaky program, giá trị tăng mỗi loop, sau GC. Ta thấy leak string objects.

Application Performance Monitoring Production Với AppSignal

Dù chơi ps và GC hợp lý cho toy project — cũng vui và informative! — tôi không khuyến khích làm memory leak detection solution production app.

Đây là nơi dùng APM tool. Company rất lớn, tự build. Outfit nhỏ, pick APM off-the-shelf. Cần trả subscription hàng tháng, nhưng info cung cấp worth it.

Để detect memory leaks, muốn tìm server/process memory use (thường gọi RSS) graphs over time. Screenshot từ AppSignal 'process memory usage' dashboard healthy app sau deploy:

Thành Thức Ruby: Phát Hiện & Sửa Rò Rỉ Bộ Nhớ Hiệu Quả

Và unhealthy app sau deploy:

Thành Thức Ruby: Phát Hiện & Sửa Rò Rỉ Bộ Nhớ Hiệu Quả

AppSignal thậm chí surface Ruby VM stats như GC và heap slots, signal rõ hơn memory leak. Nếu live slots tăng liên tục, bạn có leak!

Thành Thức Ruby: Phát Hiện & Sửa Rò Rỉ Bộ Nhớ Hiệu Quả

Đọc thêm AppSignal for Ruby.

Kết Luận & Đọc Thêm

Trong bài, chúng ta tour nhanh Ruby memory management và garbage collector. Sau đó chẩn đoán cách discover memory leak dùng Unix tools và GC module.

Lần sau, xem dùng memory_profiler và derailed_benchmarks tìm fix leaks.

Trong lúc đó, đọc thêm tools dùng:

  • watch
  • ps
  • pgrep

Đọc thêm chi tiết:

  • GC module documentation
  • ObjectSpace module documentation
  • Garbage Collection Deep Dive
  • Variable Width Allocation

Chúc viết mã vui vẻ, hẹn gặp lại!

P.S. Nếu bạn muốn đọc các bài Ruby Magic ngay khi chúng xuất bản, hãy đăng ký bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ bất kỳ bài viết nào!