Python giám sát việc sử dụng RAM như thế nào?

Tìm hiểu lý do tại sao các ứng dụng Python của bạn đang sử dụng quá nhiều bộ nhớ và giảm mức sử dụng RAM của chúng bằng các thủ thuật đơn giản và cấu trúc dữ liệu hiệu quả này

Ảnh của The Bored Apeventurer BAYC trên Bapt

Khi nói đến tối ưu hóa hiệu suất, mọi người thường chỉ tập trung vào tốc độ và mức sử dụng CPU. Hiếm khi có ai quan tâm đến mức tiêu thụ bộ nhớ, cho đến khi họ hết RAM. Có nhiều lý do để cố gắng hạn chế sử dụng bộ nhớ, không chỉ tránh việc ứng dụng của bạn bị treo vì lỗi hết bộ nhớ

Trong bài viết này, chúng ta sẽ khám phá các kỹ thuật để tìm phần nào trong ứng dụng Python của bạn đang tiêu tốn quá nhiều bộ nhớ, phân tích lý do và cuối cùng là giảm mức tiêu thụ bộ nhớ và dấu chân bằng cách sử dụng các thủ thuật đơn giản và cấu trúc dữ liệu hiệu quả về bộ nhớ

Tại sao phiền, Dù sao?

Nhưng trước tiên, tại sao bạn phải tiết kiệm RAM?

Một lý do đơn giản là tiền. Tài nguyên — cả CPU và RAM — đều tốn tiền, tại sao lại lãng phí bộ nhớ bằng cách chạy các ứng dụng kém hiệu quả, nếu có nhiều cách để giảm dung lượng bộ nhớ?

Một lý do khác là quan niệm “dữ liệu có khối lượng”, nếu có nhiều thì nó sẽ di chuyển chậm. Nếu dữ liệu phải được lưu trữ trên đĩa thay vì trong RAM hoặc bộ nhớ cache nhanh, thì sẽ mất một lúc để tải và xử lý, ảnh hưởng đến hiệu suất tổng thể. Do đó, tối ưu hóa cho việc sử dụng bộ nhớ có thể có tác dụng phụ tốt là tăng tốc thời gian chạy ứng dụng

Cuối cùng, trong một số trường hợp, hiệu suất có thể được cải thiện bằng cách bổ sung thêm bộ nhớ (nếu hiệu suất của ứng dụng bị giới hạn bởi bộ nhớ), nhưng bạn không thể làm điều đó nếu không còn bộ nhớ nào trên máy

Tìm nút cổ chai

Rõ ràng là có những lý do chính đáng để giảm mức sử dụng bộ nhớ của các ứng dụng Python của chúng ta, tuy nhiên, trước khi làm điều đó, trước tiên chúng ta cần tìm ra các nút thắt cổ chai hoặc các phần mã đang ngốn hết bộ nhớ

Công cụ đầu tiên chúng tôi sẽ giới thiệu là memory_profiler. Công cụ này đo mức sử dụng bộ nhớ của chức năng cụ thể trên cơ sở từng dòng

Để bắt đầu sử dụng, chúng tôi cài đặt nó với gói pip cùng với gói psutil giúp cải thiện đáng kể hiệu suất của trình hồ sơ. Ngoài ra, chúng tôi cũng cần đánh dấu chức năng mà chúng tôi muốn đánh dấu bằng trình trang trí @profile. Cuối cùng, chúng tôi chạy trình lược tả đối với mã của chúng tôi bằng cách sử dụng python -m memory_profiler. Điều này cho thấy việc sử dụng/phân bổ bộ nhớ trên cơ sở từng dòng cho chức năng được trang trí - trong trường hợp này là memory_intensive - cố ý tạo và xóa các danh sách lớn

Bây giờ chúng ta đã biết cách thu hẹp tiêu điểm và tìm các dòng cụ thể làm tăng mức tiêu thụ bộ nhớ, chúng ta có thể muốn tìm hiểu sâu hơn một chút và xem mỗi biến đang sử dụng bao nhiêu. Bạn có thể đã thấy sys.getsizeof được sử dụng để đo lường điều này trước đây. Tuy nhiên, chức năng này sẽ cung cấp cho bạn thông tin đáng ngờ đối với một số loại cấu trúc dữ liệu. Đối với số nguyên hoặc mảng phụ, bạn sẽ nhận được kích thước thực tính bằng byte, đối với các vùng chứa như danh sách, bạn sẽ chỉ nhận được kích thước của chính vùng chứa đó chứ không phải nội dung của nó

Chúng ta có thể thấy rằng với các số nguyên đơn giản, mỗi khi chúng ta vượt qua một ngưỡng, 4 byte sẽ được thêm vào kích thước. Tương tự, với các chuỗi đơn giản, mỗi khi chúng ta thêm một ký tự khác thì một byte bổ sung sẽ được thêm vào. Tuy nhiên, với các danh sách, điều này không theo kịp — sys.getsizeof không "đi" cấu trúc dữ liệu và chỉ trả về kích thước của đối tượng gốc, trong trường hợp này là list

Cách tiếp cận tốt hơn là sử dụng công cụ cụ thể được thiết kế để phân tích hành vi bộ nhớ. Một trong số đó là công cụ có thể giúp bạn có ý tưởng thực tế hơn về kích thước đối tượng Python

Pympler cung cấp mô-đun asizeof với chức năng cùng tên báo cáo chính xác kích thước của danh sách cũng như tất cả các giá trị mà nó chứa. Ngoài ra, mô-đun này cũng có chức năng pip0, có thể cung cấp cho chúng tôi phân tích kích thước chi tiết hơn của các thành phần riêng lẻ của đối tượng

Tuy nhiên, Pympler có nhiều tính năng hơn, bao gồm hoặc. Trong trường hợp đây là những thứ có thể cần thiết cho ứng dụng của bạn, thì tôi khuyên bạn nên xem các hướng dẫn có sẵn trong tài liệu

Tiết kiệm RAM

Bây giờ chúng ta đã biết cách tìm kiếm tất cả các loại vấn đề tiềm ẩn về bộ nhớ, chúng ta cần tìm cách khắc phục chúng. Giải pháp tiềm năng, nhanh nhất và dễ dàng nhất có thể là chuyển sang các cấu trúc dữ liệu hiệu quả hơn về bộ nhớ

Python pip1 là một trong những tùy chọn ngốn bộ nhớ hơn khi lưu trữ các mảng giá trị

Hàm đơn giản ở trên ( pip2) tạo Python list số bằng cách sử dụng pip4 đã chỉ định. Để đo lượng bộ nhớ nó chiếm, chúng ta có thể sử dụng memory_profiler được hiển thị trước đó, cung cấp cho chúng ta lượng bộ nhớ được sử dụng trong 0. Khoảng thời gian 2 giây trong khi thực hiện chức năng. Chúng ta có thể thấy rằng việc tạo list trong số 10 triệu số cần hơn 350MiB bộ nhớ. Vâng, đó có vẻ là rất nhiều cho một loạt các con số. Chúng ta có thể làm gì tốt hơn không?

Trong ví dụ này, chúng tôi đã sử dụng mô-đun pip7 của Python, mô-đun này có thể lưu trữ các giá trị nguyên thủy, chẳng hạn như số nguyên hoặc ký tự. Chúng ta có thể thấy rằng trong trường hợp này, mức sử dụng bộ nhớ đạt đỉnh chỉ hơn 100MiB. Đó là một sự khác biệt rất lớn so với list. Bạn có thể giảm mức sử dụng bộ nhớ hơn nữa bằng cách chọn độ chính xác phù hợp

Một nhược điểm lớn của việc sử dụng pip7 làm bộ chứa dữ liệu là nó không hỗ trợ nhiều loại

Nếu bạn định thực hiện nhiều phép toán trên dữ liệu, thì có lẽ bạn nên sử dụng mảng NumPy để thay thế

Chúng ta có thể thấy rằng các mảng NumPy cũng hoạt động khá tốt khi sử dụng bộ nhớ với kích thước mảng cao nhất là ~123MiB. Đó là nhiều hơn một chút so với pip7 nhưng với NumPy, bạn có thể tận dụng các hàm toán học nhanh cũng như các loại không được pip7 hỗ trợ, chẳng hạn như số phức

Các tối ưu hóa ở trên giúp với kích thước tổng thể của các mảng giá trị, nhưng chúng ta cũng có thể thực hiện một số cải tiến đối với kích thước của các đối tượng riêng lẻ được xác định bởi các lớp Python. Điều này có thể được thực hiện bằng cách sử dụng thuộc tính lớp psutil2 được sử dụng để khai báo rõ ràng các thuộc tính của lớp. Khai báo psutil2 trên một lớp cũng có tác dụng phụ là từ chối việc tạo các thuộc tính psutil4 và psutil5

Ở đây chúng ta có thể thấy thể hiện của lớp psutil6 thực sự nhỏ hơn bao nhiêu. Việc không có psutil4 sẽ loại bỏ toàn bộ 104 byte khỏi mỗi phiên bản, điều này có thể tiết kiệm dung lượng bộ nhớ khổng lồ khi khởi tạo hàng triệu giá trị

Các mẹo và thủ thuật trên sẽ hữu ích trong việc xử lý các giá trị số cũng như đối tượng psutil8. Tuy nhiên, còn dây thì sao? . Nếu bạn định tìm kiếm thông qua một số lượng lớn các giá trị chuỗi, thì - như chúng ta đã thấy - sử dụng list là một ý tưởng rất tồi. @profile0 có thể phù hợp hơn một chút nếu tốc độ thực thi là quan trọng, nhưng có thể sẽ tiêu tốn nhiều RAM hơn. Tùy chọn tốt nhất có thể là sử dụng cấu trúc dữ liệu được tối ưu hóa, chẳng hạn như trie, đặc biệt đối với các tập dữ liệu tĩnh mà bạn sử dụng để truy vấn chẳng hạn. Như thường thấy với Python, đã có một thư viện cho điều đó, cũng như cho nhiều cấu trúc dữ liệu dạng cây khác, bạn sẽ tìm thấy một số trong số đó tại https. //github. com/pytries

Hoàn toàn không sử dụng RAM

Cách dễ nhất để tiết kiệm RAM là không sử dụng nó ngay từ đầu. Rõ ràng là bạn không thể tránh hoàn toàn việc sử dụng RAM, nhưng bạn có thể tránh tải toàn bộ tập dữ liệu cùng một lúc và thay vào đó làm việc với dữ liệu tăng dần nếu có thể. Cách đơn giản nhất để đạt được điều này là sử dụng các trình tạo trả về một trình vòng lặp, tính toán các phần tử theo yêu cầu thay vì tất cả cùng một lúc

Công cụ mạnh hơn mà bạn có thể tận dụng là các tệp ánh xạ bộ nhớ, cho phép chúng tôi chỉ tải các phần dữ liệu từ một tệp. Thư viện chuẩn của Python cung cấp mô-đun @profile1 cho việc này, có thể được sử dụng để tạo các tệp ánh xạ bộ nhớ hoạt động giống như tệp và mảng phụ. Bạn có thể sử dụng cả hai với các thao tác tệp như @profile2, @profile3 hoặc @profile4 cũng như các thao tác chuỗi

Tải/đọc tập tin ánh xạ bộ nhớ rất đơn giản. Trước tiên, chúng tôi mở tệp để đọc như chúng tôi thường làm. Sau đó, chúng tôi sử dụng bộ mô tả tệp của tệp ( @profile5) để tạo tệp ánh xạ bộ nhớ từ nó. Từ đó, chúng ta có thể truy cập dữ liệu của nó bằng cả thao tác tệp như @profile2 hoặc thao tác chuỗi như cắt

Hầu hết thời gian, bạn có thể sẽ quan tâm nhiều hơn đến việc đọc tệp như được hiển thị ở trên, nhưng cũng có thể ghi vào tệp ánh xạ bộ nhớ

Sự khác biệt đầu tiên trong mã mà bạn sẽ nhận thấy là sự thay đổi trong chế độ truy cập thành @profile7, biểu thị cả đọc và viết. Để chứng minh rằng chúng tôi thực sự có thể thực hiện cả thao tác đọc và viết, trước tiên chúng tôi đọc từ tệp và sau đó sử dụng RegEx để tìm kiếm tất cả các từ bắt đầu bằng chữ in hoa. Sau đó, chúng tôi chứng minh việc xóa dữ liệu khỏi tệp. Điều này không đơn giản như đọc và tìm kiếm, bởi vì chúng tôi cần điều chỉnh kích thước của tệp khi xóa một số nội dung của tệp. Để làm như vậy, chúng tôi sử dụng phương pháp @profile8 của mô-đun @profile1 sao chép 40 byte dữ liệu từ chỉ mục python -m memory_profiler1 sang chỉ mục python -m memory_profiler2, trong trường hợp này có nghĩa là xóa 10 byte đầu tiên

Nếu bạn đang tính toán trong NumPy, thì bạn có thể thích các tính năng (tài liệu) python -m memory_profiler3 của nó phù hợp với các mảng NumPy được lưu trữ trong các tệp nhị phân

Bớt tư tưởng

Tối ưu ứng dụng là bài toán khó nói chung. Nó cũng phụ thuộc rất nhiều vào nhiệm vụ hiện tại cũng như loại dữ liệu. Trong bài viết này, chúng tôi đã xem xét các cách phổ biến để tìm các vấn đề về sử dụng bộ nhớ và một số tùy chọn để khắc phục chúng. Tuy nhiên, có nhiều cách tiếp cận khác để giảm dung lượng bộ nhớ của một ứng dụng. Điều này bao gồm độ chính xác giao dịch cho không gian lưu trữ bằng cách sử dụng cấu trúc dữ liệu xác suất như bộ lọc nở hoa hoặc HyperLogLog. Một tùy chọn khác là sử dụng các cấu trúc dữ liệu dạng cây như DAWG hoặc Marissa trie rất hiệu quả trong việc lưu trữ dữ liệu chuỗi

Python theo dõi việc sử dụng RAM như thế nào?

Bạn có thể sử dụng nó bằng cách đặt trình trang trí @profile quanh bất kỳ hàm hoặc phương thức nào và chạy python -m memory_profiler myscript . Bạn sẽ thấy mức sử dụng bộ nhớ theo từng dòng sau khi tập lệnh của bạn thoát.

Python quản lý bộ nhớ của nó như thế nào?

Quản lý bộ nhớ trong Python liên quan đến việc quản lý một vùng riêng tư . Heap riêng là một phần bộ nhớ dành riêng cho quy trình Python. Tất cả các đối tượng Python và cấu trúc dữ liệu được lưu trữ trong heap riêng. Hệ điều hành không thể phân bổ phần bộ nhớ này cho một tiến trình khác.

Làm cách nào để kiểm tra kích thước bộ nhớ trong Python?

Trong Python, chức năng cơ bản nhất để đo kích thước của một đối tượng trong bộ nhớ là sys. getsizeof() .

Là một mô-đun Python để theo dõi mức tiêu thụ bộ nhớ của chương trình Python?

Trình cấu hình bộ nhớ là một mô-đun thuần Python sử dụng mô-đun psutil. Nó theo dõi mức tiêu thụ bộ nhớ của quy trình công việc Python. Ngoài ra, nó thực hiện phân tích từng dòng về mức tiêu thụ bộ nhớ của ứng dụng.