Computer >> Máy Tính >  >> Lập trình >> Ruby

Điều chỉnh thu gom rác thực tế trong Ruby

Chúng tôi đã phát hiện ra rằng bài đăng dưới đây dựa trên một bài báo của Nate Berkopec từ năm 2017 có tên là 'Hiểu về Ruby GC thông qua GC.stat'. Có vẻ như các phần của bài viết này đã bị ăn cắp ý tưởng, điều mà chúng tôi không hề hay biết cho đến khi tác giả gốc đề cập đến nó. Chúng tôi chạy tất cả các bài báo của mình thông qua một công cụ đạo văn trước khi xuất bản, nhưng nó không nhận được điều này. Chúng tôi gửi lời xin lỗi sâu sắc tới Nate và độc giả của chúng tôi vì lỗi vô ý này.

Điều quan trọng là bạn phải hiểu cách thức hoạt động của bộ sưu tập rác trong Ruby để luôn kiểm soát hoàn toàn hiệu suất ứng dụng của bạn.

Trong bài đăng này, chúng ta sẽ đi sâu vào cách triển khai và tùy chỉnh thu thập rác trong Ruby.

Hãy bắt đầu!

Mô-đun thu gom rác của Ruby

Mô-đun Ruby Garbage Collector là một giao diện cho cơ chế thu gom rác đánh dấu và quét của Ruby.

Mặc dù nó tự động chạy trong nền khi cần, nhưng mô-đun GC cho phép bạn gọi GC theo cách thủ công bất cứ khi nào được yêu cầu và hiểu rõ hơn về cách các chu trình thu gom rác đang chạy. Mô-đun cung cấp một số thông số mà bạn có thể thay đổi để kiểm soát hiệu suất.

Một số phương pháp được sử dụng phổ biến nhất của mô-đun này là:

  • start /bage_collect :Phương pháp này bắt đầu chu trình thu gom rác theo cách thủ công.
  • bật / tắt :Các phương pháp này bật hoặc tắt các chu trình thu gom rác tự động. Chúng trả về một giá trị boolean cho biết liệu hoạt động có thành công hay không.
  • thống kê :Phương pháp này cung cấp danh sách các khóa và giá trị mô tả hiệu suất của mô-đun GC. Chúng ta sẽ xem xét chi tiết các chỉ số này trong phần tiếp theo.

Tìm hiểu các thông số của trình thu gom rác của Ruby

Để hiểu cách hoạt động nội bộ của Ruby’s GC, hãy xem các chỉ số của mô-đun GC. Chạy lệnh sau trên irb mới khởi động:

puts GC.stat

Bạn sẽ nhận thấy rằng một loạt các số hiện lên trên màn hình của bạn, trông giống như sau:

{
    :count=>12,
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :minor_gc_count=>10,
    :major_gc_count=>2,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

Điều này nắm giữ tất cả thông tin về cách thu thập rác đã diễn ra trong thời gian chạy. Hãy xem xét chi tiết từng con số này.

Số lượng trong Ruby Garbage Collector

Chúng tôi sẽ bắt đầu bằng cách mô tả các khóa sau:

{
    :count=>12,
    #…
    :minor_gc_count=>10,
    :major_gc_count=>2,
}

Đây là số lượng GC và chúng truyền tải thông tin khá đơn giản. minor_gc_countmajor_gc_count là số lần chạy thu gom rác của từng loại.

Có hai loại bộ sưu tập rác trong Ruby.

GC nhỏ đề cập đến nỗ lực thu gom rác cố gắng thu gom rác chỉ thu thập những đối tượng mới, tức là chúng đã tồn tại từ ba chu kỳ thu gom rác trở xuống.

Mặt khác, GC chính là một nỗ lực thu gom rác cố gắng thu gom rác thải tất cả các đối tượng, ngay cả những đối tượng đã tồn tại hơn ba chu kỳ thu gom rác. count là tổng của minor_gc_countmajor_gc_count .

Theo dõi số lượng GC có thể hữu ích vì một vài lý do. Bạn có thể tìm hiểu xem một công việc hoặc quy trình cụ thể có luôn kích hoạt GC hay không và số lần nó kích hoạt chúng. Nó có thể không chính xác 100% trong các trường hợp như các ứng dụng đa luồng, nhưng đó là một điểm khởi đầu tốt để tìm ra vị trí bộ nhớ của bạn đang chảy.

Số đống:Slots và Pages

Tiếp theo, hãy nói về các khóa này, còn được gọi là số đống :

{
    # page numbers
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
 
    # slots
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
 
    # Eden and Tomb pages
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
}

Heap mà chúng ta đang nói đến ở đây là cấu trúc dữ liệu C. Nó chứa các tham chiếu đến tất cả các đối tượng Ruby đang tồn tại. Một đống trang bao gồm các khe cắm bộ nhớ và mỗi khe cắm chỉ bao gồm thông tin về một đối tượng Ruby trực tiếp:

  • heap_allocated_pages là số lượng trang heap được phân bổ hiện tại. Các trang này có thể trống hoàn toàn, lấp đầy hoàn toàn hoặc lấp đầy một phần.
  • heap_sorted_length là kích thước thực tế mà heap đã chiếm trong bộ nhớ và khác với heap_allocated_pages , vì chiều dài là chiều dài trong số các trang đống được ghép lại với nhau, không phải số lượng của chúng . Nếu ban đầu bạn phân bổ 10 trang và sau đó giải phóng một trang ở giữa tập hợp, thì heap_allocated_pages của bạn sẽ là 9, nhưng heap_sorted_length vẫn sẽ là 10.
  • Cuối cùng, heap_allocatable_pages là số đống mà Ruby hiện sở hữu có thể được sử dụng khi cần thiết.

Bây giờ, đến với các vị trí:

  • heap_available_slots là tổng số vị trí có sẵn trong các trang heap.
  • heap_live_slots là số đối tượng trực tiếp trong bộ nhớ.
  • heap_free_slots là các vị trí trong các trang heap được phân bổ trống.
  • heap_final_slots là số lượng vị trí có đối tượng có kết quả cuối cùng gắn liền với chúng. Trình hoàn thiện là Procs chạy khi một đối tượng được giải phóng, tương tự như trình hủy trong OOPS.
  • heap_marked_slots là số lượng các đối tượng cũ (tức là các đối tượng đã tồn tại hơn 3 chu kỳ GC) và các đối tượng không được bảo vệ bằng rào cản ghi.

Sau đó, chúng tôi có tomb_pageseden_pages .

tomb_pages là số trang không chứa đối tượng trực tiếp. Các trang này cuối cùng đã được Ruby phát hành trở lại hệ điều hành.

Mặt khác, eden_pages là tổng số các trang có chứa ít nhất một đối tượng trực tiếp, vì vậy chúng không thể được giải phóng trở lại hệ điều hành.

Xem xét theo dõi số liệu heap_free_slots nếu bạn gặp phải sự cố phồng bộ nhớ trong ứng dụng của mình.

Số lượng khe trống cao (hơn 250.000) thường chỉ ra rằng bạn có một số tác vụ của bộ điều khiển phân bổ nhiều đối tượng cùng một lúc và sau đó giải phóng chúng. Điều này có thể làm tăng kích thước vĩnh viễn của quy trình Ruby đang chạy của bạn.

Số tích lũy

{
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
}

Những con số này có tính chất tích lũy hoặc cộng gộp cho toàn bộ vòng đời của quy trình. Chúng không bao giờ được đặt lại bởi GC và không thể giảm về mặt kỹ thuật. Tất cả bốn con số này đều có thể tự giải thích được.

Ngưỡng thu gom rác

Để hiểu những con số này, trước tiên bạn cần hiểu khi nào GC được kích hoạt:

{
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

Trái ngược với một giả định phổ biến rằng các lần chạy GC diễn ra theo các khoảng thời gian cố định, các lần chạy GC được kích hoạt khi Ruby bắt đầu hết dung lượng bộ nhớ. GC nhỏ xảy ra khi Ruby hết free_slots .

Nếu Ruby vẫn còn thấp trên free_slots sau một lần chạy GC nhỏ - hoặc ngưỡng oldmalloc, malloc, số lượng đối tượng cũ hoặc râm / ghi-rào cản-không được bảo vệ số lượng vượt quá - một lần chạy GC chính được kích hoạt. Phần trên của gc.stat hiển thị giá trị của các ngưỡng này.

malloc_increase_bytes đề cập đến lượng bộ nhớ được phân bổ bên ngoài heap chúng tôi đã nói về cho đến nay. Khi kích thước của một đối tượng vượt quá kích thước tiêu chuẩn của một khe bộ nhớ - giả sử, 40 byte - Ruby malloc một số không gian ở một nơi khác chỉ dành cho đối tượng đó. Khi tổng không gian được phân bổ bổ sung vượt quá malloc_increase_bytes_limit , một GC chính được kích hoạt.

oldmalloc_increase_bytes là một ngưỡng tương tự cho các đối tượng cũ. old_objects là số lượng các khe đối tượng được đánh dấu là cũ. Khi số lượng vượt quá old_objects_limit , GC chính được kích hoạt.

remembered_wb_unprotected_objects là tổng số đối tượng không được bảo vệ bởi rào cản ghi và là một phần của tập hợp được ghi nhớ .

Rào cản ghi là một giao diện giữa thời gian chạy Ruby và các đối tượng của nó, cho phép trình thông dịch theo dõi các tham chiếu đến và đi từ đối tượng ngay khi chúng được tạo.

Phần mở rộng C có thể tạo các tham chiếu mới đến các đối tượng mà không cần sử dụng rào cản ghi, trong trường hợp đó, các đối tượng được đánh dấu là râm hoặc ghi-rào cản không được bảo vệ . Tập hợp được ghi nhớ chỉ đơn giản là danh sách các đối tượng có ít nhất một tham chiếu đến mới đối tượng.

Tùy chỉnh hiệu suất thu gom rác của Ruby

Bây giờ bạn đã hiểu cách Ruby GC quản lý bộ nhớ ứng dụng của mình, đã đến lúc xem xét các tùy chọn có sẵn để tùy chỉnh hành vi của GC.

Dưới đây là các biến môi trường mà bạn có thể sử dụng để kiểm duyệt hiệu suất của Ruby GC và cải thiện hiệu suất ứng dụng của bạn:

RUBY_GC_HEAP_INIT_SLOTS
RUBY_GC_HEAP_FREE_SLOTS
RUBY_GC_HEAP_GROWTH_FACTOR
RUBY_GC_HEAP_GROWTH_MAX_SLOTS
RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
and other variables

Hãy nói về các thông số quan trọng ở đây, từng cái một:

  • RUBY_GC_HEAP_INIT_SLOTS :xác định số lượng khe ban đầu trên Ruby heap và được đặt thành 10000 theo mặc định. Bạn có thể muốn thay đổi thông số này nếu chắc chắn rằng ban đầu ứng dụng của mình sẽ phân bổ hầu hết các đối tượng của nó.
  • RUBY_GC_HEAP_FREE_SLOTS :kiểm soát số lượng khe trống tối thiểu phải có ngay sau một chu kỳ GC. Giá trị mặc định của nó là 4096. Giá trị này chỉ được sử dụng một lần trong thời gian chạy trong quá trình phát triển heap đầu tiên.
  • RUBY_GC_HEAP_GROWTH_FACTOR :yếu tố mà các đống có sẵn cho trình thông dịch Ruby tăng lên. Giá trị mặc định của nó là 1,8. Thay đổi điều này không có ý nghĩa gì, vì Ruby đã rất tích cực trong việc tăng trưởng đống. Sẽ không có nhiều khác biệt nếu bạn cố gắng giảm bớt vì đống được phân bổ theo yêu cầu trong các trình thông dịch hiện đại.
  • RUBY_GC_HEAP_GROWTH_MAX_SLOTS :số lượng slot tối đa mà Ruby có thể thêm vào heap space cùng một lúc. Giá trị mặc định là 0, không giới hạn số lượng. Nếu ứng dụng của bạn cần phân bổ hàng triệu đối tượng trong thời gian tồn tại, bạn có thể muốn giới hạn thông số này. Tuy nhiên, nó sẽ có ảnh hưởng khá thấp đến thời gian GC của ứng dụng.
  • RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR buộc trình thông dịch thực hiện một chu kỳ GC chính khi tổng số đối tượng cũ trong bộ nhớ =nhiều hơn số này x số đối tượng cũ trong bộ nhớ sau chu kỳ GC cuối cùng. Bạn có thể muốn tăng con số này nếu bạn nghĩ rằng nhiều đối tượng của bạn sẽ trở nên không sử dụng sau khi chúng chuyển sang thế hệ cũ. Tuy nhiên, điều này hiếm khi cần thiết.
  • RUBY_GC_MALLOC_LIMIT là giới hạn tối thiểu của lệnh gọi malloc cho thế hệ mới. Giá trị mặc định của nó là 16 MB. RUBY_GC_MALLOC_LIMIT_MAX là giới hạn tối đa cho các lệnh gọi malloc giống nhau. Giá trị mặc định của nó là 32 MB. Bạn có thể muốn tăng hai giới hạn này nếu ứng dụng của bạn sử dụng bộ nhớ cao hơn mức trung bình. Tuy nhiên, hãy cẩn thận không nâng cao những thứ này quá nhiều. Nếu không, chúng có thể dẫn đến mức tiêu thụ bộ nhớ cao nhất. Luôn tăng dần các giới hạn này - chẳng hạn như 4 hoặc 8 MB.
  • RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR là hệ số tăng trưởng của giới hạn malloc đối với thế hệ mới. Giá trị mặc định của nó là 1,4. Bạn nên cân nhắc việc tăng con số này nếu ứng dụng của bạn phân bổ bộ nhớ theo từng phần thay vì toàn bộ cùng một lúc.
  • Tương tự, RUBY_GC_OLDMALLOC_LIMITRUBY_GC_OLDMALLOC_LIMIT_MAX là giới hạn malloc tối thiểu và tối đa cho thế hệ cũ. Giá trị mặc định của các thông số này là 16 MB và 128 MB.
  • RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR là hệ số tăng trưởng của giới hạn này. Giá trị mặc định của nó là 1,2. Bạn có thể cân nhắc thay đổi những điều này bằng các giới hạn thế hệ mới để có hiệu quả tối đa.

Tinh chỉnh Bộ sưu tập rác trong Ruby

Chúng tôi đã thảo luận về một số cách phổ biến và đơn giản để tùy chỉnh mô-đun GC nhằm giúp bạn cải thiện hiệu suất tổng thể của ứng dụng. Tuy nhiên, những chỉnh sửa này có thể không hoạt động trong mọi trường hợp. Bạn cần tìm ra kiểu sử dụng bộ nhớ của ứng dụng trước khi quyết định tùy chỉnh những gì.

Mặt khác, bạn có thể cân nhắc chạy kiểm tra tự động để tìm giá trị tốt nhất của các thông số này cho bạn. Các công cụ như TuneMyGC khá đơn giản khi tìm ra bộ giá trị tốt nhất cho các biến môi trường của bạn.

Chắc chắn hãy xem xét các thông số GC nếu ứng dụng của bạn đang hoạt động kỳ lạ. Một thay đổi nhỏ ở đây có thể giúp ích rất nhiều để giảm mức tiêu thụ bộ nhớ của ứng dụng và ngăn chặn tình trạng phồng bộ nhớ.

Tôi hy vọng bài viết này đã cung cấp cho bạn một ý tưởng hay về những điều cần lưu ý khi tùy chỉnh mô-đun Ruby Garbage Collection của bạn. Để biết thêm phần giới thiệu, hãy xem phần Giới thiệu về Thu gom rác Phần I và Phần II.

Chúc bạn viết mã vui vẻ!