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

Làm thế nào để giảm bộ nhớ phồng lên trong Ruby

Vấn đề bộ nhớ cồng kềnh trong các ứng dụng Ruby là một chủ đề được thảo luận thường xuyên. Trong bài đăng này, chúng ta sẽ xem xét cách quản lý bộ nhớ Ruby có thể gặp trục trặc và bạn có thể làm gì để ngăn ứng dụng Ruby của mình bị lỗi.

Trước tiên, chúng ta cần hiểu cồng kềnh có nghĩa là gì trong bối cảnh bộ nhớ của ứng dụng.

Hãy đi sâu vào!

Memory Bloat trong Ruby là gì?

Tăng bộ nhớ là khi mức sử dụng bộ nhớ của ứng dụng của bạn tăng đột ngột mà không có lời giải thích rõ ràng. Sự gia tăng này có thể nhanh chóng hoặc không nhanh chóng, nhưng trong hầu hết các trường hợp, nó trở nên liên tục. Nó khác với rò rỉ bộ nhớ, do nhiều lần thực thi diễn ra trong một thời gian.

Bộ nhớ phình to có thể là một trong những điều tồi tệ nhất xảy ra với ứng dụng Ruby của bạn trong quá trình sản xuất. Tuy nhiên, nó có thể tránh được nếu bạn áp dụng các biện pháp thích hợp. Ví dụ:nếu bạn nhận thấy mức sử dụng bộ nhớ của ứng dụng tăng đột biến, thì tốt nhất là bạn nên kiểm tra các dấu hiệu bộ nhớ bị phình ra trước khi khắc phục các sự cố khác.

Trước khi chúng ta khám phá cách chẩn đoán và khắc phục tình trạng phồng bộ nhớ, hãy cùng xem qua kiến ​​trúc bộ nhớ của Ruby.

Cấu trúc bộ nhớ của Ruby

Việc sử dụng bộ nhớ của Ruby xoay quanh các phần tử cụ thể quản lý việc sử dụng hợp lý các tài nguyên hệ thống có sẵn. Các yếu tố này bao gồm ngôn ngữ Ruby, hệ điều hành máy chủ và hạt nhân hệ thống.

Ngoài ra, quá trình thu gom rác cũng đóng một vai trò quan trọng trong việc xác định cách bộ nhớ Ruby được quản lý và tái sử dụng.

Trang đống Ruby và Khe bộ nhớ

Ngôn ngữ Ruby tổ chức các đối tượng thành các phân đoạn được gọi là các trang heap. Toàn bộ không gian heap (bộ nhớ khả dụng) được chia thành các phần đã sử dụng và phần trống. Các trang heap này được chia nhỏ hơn nữa thành các vị trí có kích thước bằng nhau, cho phép mỗi đối tượng có kích thước đơn vị.

Khi cấp phát bộ nhớ cho một đối tượng mới, Ruby đầu tiên sẽ xem xét vùng trống đã sử dụng trong vùng heap đã sử dụng. Nếu không tìm thấy, nó sẽ phân bổ một trang heap mới từ phần trống.

Khe cắm bộ nhớ là các vị trí bộ nhớ nhỏ, mỗi khe có kích thước gần 40 byte. Dữ liệu tràn ra khỏi các vị trí này được lưu trữ trong một khu vực khác bên ngoài trang heap và mỗi vị trí lưu trữ một con trỏ đến thông tin bên ngoài.

Trình cấp phát bộ nhớ của hệ thống thực hiện tất cả phân bổ trong môi trường thời gian chạy Ruby, bao gồm các trang heap và con trỏ dữ liệu bên ngoài.

Phân bổ bộ nhớ hệ điều hành trong Ruby

Các lệnh gọi cấp phát bộ nhớ do ngôn ngữ Ruby thực hiện sẽ được trình cấp phát bộ nhớ của hệ điều hành máy chủ xử lý và phản hồi.

Thông thường, trình cấp phát bộ nhớ bao gồm một nhóm các hàm C, cụ thể là malloc , calloc , realloc miễn phí . Hãy nhanh chóng xem xét từng:

  • Malloc :Malloc là viết tắt của cấp phát bộ nhớ, và nó được sử dụng để cấp phát bộ nhớ trống cho các đối tượng. Kích thước của bộ nhớ được cấp phát và trả về một con trỏ đến chỉ mục bắt đầu của khối bộ nhớ được cấp phát.
  • Calloc :Calloc là viết tắt của phân bổ liền kề và nó cho phép ngôn ngữ Ruby cấp phát các khối bộ nhớ liên tiếp. Nó có lợi khi phân bổ các mảng đối tượng có độ dài đã biết.
  • Phân bổ lại :Realloc là viết tắt của phân bổ lại và nó cho phép ngôn ngữ phân bổ lại bộ nhớ với kích thước mới.
  • Miễn phí :Miễn phí được sử dụng để xóa các tập hợp vị trí bộ nhớ được cấp phát trước. Nó đưa một con trỏ đến chỉ mục bắt đầu của khối bộ nhớ cần được giải phóng.

Thu gom rác trong Ruby

Quá trình thu thập rác của thời gian chạy ngôn ngữ ảnh hưởng đáng kể đến cách nó sử dụng bộ nhớ có sẵn của nó.

Ruby tình cờ có bộ sưu tập rác khá tiên tiến sử dụng tất cả các phương thức API được mô tả ở trên để tối ưu hóa mức tiêu thụ bộ nhớ ứng dụng mọi lúc.

Một sự thật thú vị về quá trình thu gom rác trong Ruby là nó tạm dừng ứng dụng hoàn chỉnh! Điều này đảm bảo rằng không có sự phân bổ đối tượng mới nào xảy ra trong quá trình thu gom rác. Do đó, quy trình thu gom rác của bạn phải không thường xuyên và càng nhanh càng tốt.

Hai nguyên nhân phổ biến gây ra hiện tượng phồng bộ nhớ trong Ruby

Phần này sẽ thảo luận về hai trong số những lý do quan trọng nhất khiến bộ nhớ bị phồng lên trong Ruby:phân mảnh và giải phóng chậm.

Phân mảnh bộ nhớ

Phân mảnh bộ nhớ là khi phân bổ đối tượng trong bộ nhớ bị phân tán khắp nơi, làm giảm số lượng các phần bộ nhớ trống liền kề. Không thể cấp bộ nhớ cho các đối tượng không có các khối liền kề, ngay cả khi có nhiều bộ nhớ trống trên đĩa. Sự cố này có thể xảy ra trong bất kỳ ngôn ngữ hoặc môi trường lập trình nào và mỗi ngôn ngữ đều có các phương pháp để giải quyết sự cố.

Phân mảnh có thể xảy ra ở hai cấp độ khác nhau:cấp độ ngôn ngữ và cấp độ bộ cấp phát bộ nhớ. Chúng ta hãy xem xét cả hai điều này một cách chi tiết.

Phân mảnh ở cấp độ Ruby

Sự phân mảnh ở cấp độ ngôn ngữ xảy ra do thiết kế của quá trình thu gom rác. Quá trình thu gom rác đánh dấu một khe trang Ruby heap là miễn phí, cho phép sử dụng lại vị trí đó để cấp phát một đối tượng khác trong bộ nhớ. Nếu một trang heap Ruby hoàn chỉnh chỉ bao gồm các khe trống, thì trang heap đó có thể được giải phóng cho bộ cấp phát bộ nhớ để sử dụng lại.

Nhưng điều gì sẽ xảy ra nếu một số lượng nhỏ khe cắm không được đánh dấu trống trên một đống? Nó sẽ không được giải phóng trở lại bộ cấp phát bộ nhớ. Bây giờ, hãy nghĩ về nhiều vị trí trong các trang heap khác nhau được phân bổ và giải phóng đồng thời bằng cách thu gom rác. Toàn bộ trang đống được phát hành cùng một lúc là điều không thể tránh khỏi. Mặc dù quá trình thu gom rác giải phóng bộ nhớ, bộ cấp phát bộ nhớ không thể sử dụng lại nó vì các khối bộ nhớ chiếm một phần bộ nhớ.

Phân mảnh ở cấp phân bổ bộ nhớ

Bản thân trình cấp phát bộ nhớ cũng gặp phải một vấn đề tương tự:nó phải giải phóng đống hệ điều hành khi chúng hoàn toàn miễn phí. Nhưng không thể tránh khỏi việc toàn bộ hệ điều hành heap có thể được giải phóng ngay lập tức khi xem xét tính chất ngẫu nhiên của quá trình thu gom rác.

Trình cấp phát bộ nhớ cũng cung cấp các heap hệ điều hành từ bộ nhớ hệ thống để ứng dụng sử dụng. Nó sẽ chỉ chuyển sang cung cấp các heap hệ điều hành mới, ngay cả khi các heap hiện có có đủ bộ nhớ trống để đáp ứng các yêu cầu về bộ nhớ của ứng dụng. Đây là công thức hoàn hảo giúp chỉ số bộ nhớ của một ứng dụng tăng đột biến.

Bản phát hành chậm

Một nguyên nhân quan trọng khác gây ra hiện tượng phình bộ nhớ trong Ruby là việc giải phóng bộ nhớ đã giải phóng trở lại hệ thống chậm. Trong tình huống này, bộ nhớ được giải phóng chậm hơn nhiều so với tốc độ mà các khối bộ nhớ mới được cấp phát cho các đối tượng. Mặc dù đây không phải là một vấn đề thông thường hay một vấn đề mới cần giải quyết, nhưng nó ảnh hưởng mạnh mẽ đến sự phình ra của bộ nhớ - thậm chí còn hơn cả sự phân mảnh!

Khi điều tra nguồn của trình cấp phát bộ nhớ, hóa ra trình cấp phát được thiết kế để phát hành các trang Hệ điều hành ngay ở cuối đống hệ điều hành và thậm chí sau đó, chỉ rất thỉnh thoảng. Điều này có thể là vì lý do hiệu suất, nhưng nó có thể phản tác dụng và phản tác dụng.

Cách sửa lỗi bộ nhớ Ruby Bloat

Giờ chúng ta đã biết nguyên nhân khiến bộ nhớ của Ruby bị phình ra, hãy cùng xem cách bạn có thể khắc phục những vấn đề này và cải thiện hiệu suất ứng dụng của mình thông qua chống phân mảnh và cắt bớt.

Sửa lỗi bộ nhớ Ruby Bloat với chống phân mảnh

Sự phân mảnh xảy ra do thiết kế của bộ sưu tập rác và bạn không thể làm gì nhiều để khắc phục nó. Tuy nhiên, có một số bước mà bạn có thể làm theo để giảm nguy cơ kết thúc với đĩa bộ nhớ bị phân mảnh:

  • Nếu bạn khai báo một tham chiếu đến một đối tượng sử dụng một lượng bộ nhớ đáng kể, hãy đảm bảo rằng bạn giải phóng nó theo cách thủ công khi công việc của nó hoàn thành.
  • Cố gắng khai báo tất cả các phân bổ đối tượng tĩnh của bạn trong một khối lớn. Điều này sẽ đặt tất cả các lớp vĩnh viễn, đối tượng và dữ liệu khác của bạn trên cùng một trang heap. Sau này, khi sử dụng phân bổ động, bạn sẽ không phải lo lắng về các trang heap tĩnh.
  • Nếu có thể, hãy cố gắng thực hiện phân bổ động lớn ở đầu mã của bạn. Điều này sẽ đặt chúng gần với các khối bộ nhớ phân bổ tĩnh lớn hơn của bạn và sẽ giữ cho phần còn lại của bộ nhớ của bạn sạch sẽ.
  • Nếu bạn sử dụng một bộ nhớ cache nhỏ và hiếm khi bị xóa, tốt hơn nên nhóm nó với phân bổ tĩnh vĩnh viễn ngay từ đầu. Bạn thậm chí có thể cân nhắc xóa hoàn toàn ứng dụng để cải thiện khả năng quản lý bộ nhớ của ứng dụng.
  • Sử dụng jemalloc thay vì trình cấp phát bộ nhớ glibc tiêu chuẩn. Chỉnh sửa nhỏ này có thể giảm mức tiêu thụ bộ nhớ Ruby của bạn lên đến bốn lần. Lưu ý duy nhất ở đây là nó có thể không tương thích trong mọi môi trường, vì vậy hãy nhớ kiểm tra kỹ ứng dụng của bạn trước khi đưa vào sản xuất.

Cắt để sửa lỗi bộ nhớ Ruby Bloat

Bạn cần ghi đè quá trình thu gom rác và giải phóng bộ nhớ thường xuyên hơn để khắc phục việc giải phóng bộ nhớ chậm. Có một API có thể thực hiện việc này được gọi là malloc_trim . Tất cả những gì bạn cần làm là sửa đổi Ruby để gọi hàm này trong quá trình thu gom rác.

Đây là mã Ruby 2.6 đã sửa đổi có tên gọi malloc_trim trong hàm gc.c gc_start :

gc_prof_timer_start(objspace);
{
    gc_marks(objspace, do_full_mark);
    // BEGIN MODIFICATION
    if (do_full_mark)
    {
        malloc_trim(0);
    }
    // END MODIFICATION
}
gc_prof_timer_stop(objspace);

Lưu ý: Điều này không được khuyến khích trong các ứng dụng sản xuất vì nó có thể làm cho ứng dụng của bạn không ổn định. Tuy nhiên, điều này rất hữu ích khi việc giải phóng bộ nhớ chậm gây ảnh hưởng lớn đến hiệu suất của bạn và bạn đã sẵn sàng thử bất kỳ giải pháp nào.

Tóm tắt và các bước tiếp theo

Các lỗ hổng bộ nhớ rất khó để xác định và thậm chí còn nhiều thách thức hơn để khắc phục.

Bài viết này đã xem xét hai lý do quan trọng đằng sau việc tăng bộ nhớ trong ứng dụng Ruby - phân mảnh và phát hành chậm - và hai cách khắc phục:chống phân mảnh và cắt bớt.

Bạn phải thường xuyên theo dõi các chỉ số của ứng dụng để xác định một sự cố sắp xảy ra và khắc phục nó trước khi nó đưa ứng dụng của bạn xuống.

Tôi hy vọng rằng tôi đã giúp bạn thực hiện một số bước để sửa lỗi bộ nhớ trong ứng dụng Ruby của bạn.

Tái bút. Nếu bạn muốn đọc các bài đăng của Ruby Magic ngay khi chúng xuất hiện trên báo chí, hãy đăng ký bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ một bài đăng nào!

Tác giả khách mời của chúng tôi, Kumar Harsh là một nhà phát triển phần mềm mới nổi bằng nghề. Anh ấy là một nhà văn có tinh thần tập hợp nội dung xung quanh các công nghệ web phổ biến như Ruby và JavaScript. Bạn có thể tìm hiểu thêm về anh ấy thông qua trang web của anh ấy và theo dõi anh ấy trên Twitter.