Quá trình sử dụng bộ nhớ python

Các trang đề cập đến trang này. choom[1],  htop[1],  lsfd[1],  ps[1],  pstree[1],  strace[1],  systemd-nspawn[1],  unshare[1],  chroot[2],  close_range[2 . mount[5], khả năng[7],  cgroup_namespaces[7],  cpuset[7],  thông tin xác thực[7],  epoll[7],  fanotify[7],  phân cấp tệp[7],  phân cấp[7],  inotify[7

Nếu bạn muốn chương trình của mình sử dụng ít bộ nhớ hơn, bạn sẽ cần đo mức sử dụng bộ nhớ. Bạn sẽ muốn đo mức sử dụng hiện tại và sau đó, bạn sẽ cần đảm bảo rằng nó đang sử dụng ít bộ nhớ hơn sau khi bạn thực hiện một số cải tiến

Tuy nhiên, hóa ra việc đo mức sử dụng bộ nhớ không đơn giản như bạn nghĩ. Ngay cả với một mô hình đơn giản hóa cao về cách thức hoạt động của bộ nhớ, các phép đo khác nhau vẫn hữu ích trong các tình huống khác nhau

Trong bài viết này, bạn sẽ học

  • Một mô hình đơn giản nhưng đầy thông tin về cách hoạt động của bộ nhớ
  • Hai phép đo bộ nhớ–bộ nhớ thường trú và bộ nhớ được cấp phát–và cách đo chúng trong Python
  • Sự đánh đổi giữa hai

Một ví dụ [không quá] đơn giản

Hãy xem xét đoạn mã sau

>>> import numpy as np
>>> arr = np.ones[[1024, 1024, 1024, 3], dtype=np.uint8]

Điều này tạo ra một mảng gồm 3GB–gibibyte, cụ thể là–chứa đầy những gibibyte. Ngây thơ, một khi mã này chạy, chúng tôi hy vọng quá trình sẽ sử dụng hơn 3 GB một chút

Một cách để đo bộ nhớ là sử dụng "bộ nhớ thường trú", mà chúng tôi sẽ định nghĩa ở phần sau của bài viết. Chúng tôi có thể lấy thông tin này bằng cách sử dụng thư viện psutil tiện dụng, kiểm tra bộ nhớ thường trú của quy trình hiện tại

>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375

Với phép đo cụ thể này, chúng tôi đang sử dụng 3083 MB hoặc 3. 08GB và sự khác biệt so với kích thước mảng chắc chắn là bộ nhớ được sử dụng bởi trình thông dịch Python và các thư viện chúng tôi đã nhập. Sự thành công

Nhưng bộ nhớ thường trú phức tạp hơn vẻ ngoài của nó. Giả sử tôi truy cập và mở một số trang web trong trình duyệt của mình, để trình thông dịch Python chạy nền. Sau đó, tôi quay lại trình thông dịch và chạy lại chính xác lệnh đó

>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
2902.12109375

Điều gì đang xảy ra?

Để tìm câu trả lời, chúng ta cần tìm hiểu thêm một chút về cách hệ điều hành quản lý bộ nhớ

Một mô hình cấp phát bộ nhớ đơn giản

Một chương trình đang chạy phân bổ một số bộ nhớ, tôi. e. lấy lại một địa chỉ trong bộ nhớ ảo từ hệ điều hành. Bộ nhớ ảo là một không gian địa chỉ dành riêng cho quy trình, về cơ bản là các số từ 0 đến 264

>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
0, trong đó quy trình có thể đọc hoặc ghi byte

Trong chương trình C, bạn có thể sử dụng các API như

>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
1 hoặc
>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
2 để làm như vậy; . Sau đó, quá trình có thể đọc hoặc ghi vào địa chỉ cụ thể đó và các byte liên tiếp

Chúng ta có thể thấy điều này đang hoạt động bằng cách sử dụng tiện ích Linux

>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
5 để theo dõi các cuộc gọi tới
>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
1. Cho chương trình sau

import numpy as np
arr = np.ones[[170_000,], dtype=np.uint8]

Chúng tôi có thể chạy Python dưới

>>> import psutil
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
3083.734375
5

$ ltrace -e malloc python ones.py
...
_multiarray_umath.cpython-39-x86_64-linux-gnu.so->malloc[170000] = 0x5638862a45e0
...

Đây là những gì đang xảy ra

  1. Python tạo một mảng NumPy
  2. Dưới mui xe, các cuộc gọi NumPy
    >>> import psutil
    >>> psutil.Process[].memory_info[].rss / [1024 * 1024]
    3083.734375
    
    1
  3. Kết quả của
    >>> import psutil
    >>> psutil.Process[].memory_info[].rss / [1024 * 1024]
    3083.734375
    
    1 đó là một địa chỉ trong bộ nhớ.
    >>> psutil.Process[].memory_info[].rss / [1024 * 1024]
    2902.12109375
    
    0
  4. Sau đó, mã C được sử dụng để triển khai NumPy có thể đọc và ghi vào địa chỉ đó và 169.999 địa chỉ liên tiếp tiếp theo, mỗi địa chỉ đại diện cho một byte trong bộ nhớ ảo

170.000 byte này được lưu trữ ở đâu?

  • Chúng có thể được lưu trữ trong RAM;
  • Chúng có thể được lưu trữ trên ổ cứng hoặc đĩa của máy tính, trong trường hợp đó chúng được cho là được lưu trữ hoán đổi
  • Một số byte có thể được lưu trữ trong RAM và một số được trao đổi
  • Cuối cùng, chúng có thể được lưu trữ ở bất cứ đâu

Hiện tại, chúng ta sẽ bỏ qua trường hợp khó hiểu cuối cùng đó và chỉ tập trung vào ba tình huống đầu tiên.

Bộ nhớ thường trú là mức sử dụng RAM

RAM nhanh và ổ cứng của bạn chậm… nhưng RAM cũng đắt, vì vậy thông thường bạn sẽ có nhiều dung lượng ổ cứng hơn nhiều so với RAM. Ví dụ: máy tính tôi đang viết bài này có khoảng 500 GB dung lượng ổ cứng, nhưng chỉ có 16 GB RAM

Lý tưởng nhất là tất cả bộ nhớ chương trình của bạn sẽ được lưu trữ trong RAM nhanh, nhưng các quy trình khác nhau đang chạy trên máy tính của bạn có thể đã phân bổ nhiều bộ nhớ hơn mức có sẵn trong RAM. Nếu điều đó xảy ra, hệ điều hành sẽ di chuyển–hoặc “hoán đổi”–một số dữ liệu từ RAM sang ổ cứng. Cụ thể, nó sẽ cố gắng trao đổi dữ liệu không được sử dụng tích cực

Và bây giờ chúng tôi đã sẵn sàng để xác định thước đo sử dụng bộ nhớ đầu tiên của chúng tôi. bộ nhớ thường trú. Bộ nhớ thường trú là lượng bộ nhớ được cấp phát của tiến trình là thường trú – hoặc được lưu trữ – trong RAM

Trong ví dụ đầu tiên của chúng tôi, chúng tôi đã bắt đầu với tất cả 3GB mảng được phân bổ được lưu trữ trong RAM

Mảng 3GB [=bộ nhớ được phân bổ]3GB trong RAM [=bộ nhớ thường trú]

Sau đó, khi tôi mở một loạt tab trình duyệt, việc tải các trang web đó đã sử dụng khá nhiều RAM và vì vậy hệ điều hành đã quyết định hoán đổi một số dữ liệu từ RAM sang đĩa. Và một phần của bộ nhớ đã bị hoán đổi là từ mảng của chúng tôi. Do đó, bộ nhớ lưu trú cho quy trình Python của chúng tôi đã bị hỏng. tất cả dữ liệu vẫn có thể truy cập được, nhưng một số dữ liệu đã được chuyển vào đĩa

Mảng 3GB [=bộ nhớ được phân bổ]2. RAM 8GB [=bộ nhớ thường trú]0. 2GB trên đĩa

một sự thay thế. bộ nhớ được cấp phát

Sẽ rất hữu ích khi đo bộ nhớ được phân bổ, để luôn nhận lại 3 GB bất kể hệ điều hành đưa dữ liệu vào RAM hay hoán đổi nó vào đĩa. Điều này sẽ cho chúng tôi kết quả nhất quán và cũng cho chúng tôi biết chương trình thực sự yêu cầu bao nhiêu bộ nhớ

Trong Python [nếu bạn đang dùng Linux hoặc macOS], bạn có thể đo bộ nhớ được phân bổ bằng cách sử dụng trình cấu hình bộ nhớ Fil, đặc biệt đo bộ nhớ được phân bổ cao nhất. Sciagraph profiler là một giải pháp thay thế khác. không giống như Fil, nó cũng thực hiện hồ sơ hiệu suất và chi phí thấp hơn bằng cách sử dụng lấy mẫu

Đây là đầu ra của Fil cho ví dụ phân bổ 3GB của chúng tôi


Sự đánh đổi giữa bộ nhớ thường trú và bộ nhớ được cấp phát

Là một phương pháp đo bộ nhớ, bộ nhớ thường trú có một số vấn đề

  • Các quy trình khác có thể làm sai lệch kết quả bằng cách sử dụng hết RAM hạn chế và khuyến khích hoán đổi, như chúng đã làm trong ví dụ 3GB của chúng tôi ở trên
  • Bởi vì bộ nhớ lưu trú được giới hạn ở RAM vật lý có sẵn, bạn không bao giờ thực sự biết chương trình yêu cầu bao nhiêu bộ nhớ, một khi bạn đạt đến mức trần đó. Nếu bạn có 16GB RAM, bạn sẽ không thể phân biệt giữa chương trình cần bộ nhớ 17GB và chương trình cần bộ nhớ 30GB. cả hai sẽ báo cáo cùng một bộ nhớ thường trú

Mặt khác, bộ nhớ được phân bổ không bị ảnh hưởng bởi các quy trình khác và cho bạn biết chương trình thực sự yêu cầu gì

Tất nhiên, bộ nhớ lưu trú có một số lợi thế so với bộ nhớ được cấp phát

  • Bộ nhớ hoán đổi đó có thể không bao giờ được sử dụng. hãy tưởng tượng việc tạo một mảng, quên xóa tham chiếu và sau đó không bao giờ thực sự sử dụng lại nó trong phần còn lại của chương trình. Nếu nó bị tráo đổi, thì tốt thôi, và có lẽ chúng ta không nên đo lường nó
  • Nói rộng hơn, bởi vì bộ nhớ lưu trú đo RAM thực sự được sử dụng từ góc độ hệ điều hành, nên nó có thể ghi lại các trường hợp cạnh mà theo dõi bộ nhớ được phân bổ không hiển thị

Hãy xem một ví dụ về một trường hợp cạnh như vậy

bộ nhớ ảo

Trong tất cả các ví dụ của chúng tôi cho đến nay, chúng tôi đã phân bổ các mảng có đầy đủ các mảng. Nếu chúng ta đang đo bộ nhớ được phân bổ, mảng được lấp đầy không có gì khác biệt. chúng ta có thể chuyển sang tạo các mảng đầy số 0 và vẫn nhận được kết quả chính xác như vậy

Nhưng trên Linux, bộ nhớ thường trú kể cho chúng ta một câu chuyện thú vị

>>> import numpy as np
>>> import psutil
>>> arr = np.zeros[[1024, 1024, 1024, 3], dtype=np.uint8]
>>> psutil.Process[].memory_info[].rss / [1024 * 1024]
28.5546875

Một lần nữa, chúng tôi đã phân bổ một mảng 3GB, lần này đầy các số 0. Chúng tôi đo bộ nhớ thường trú và–mảng không được tính, bộ nhớ thường trú chỉ là 29M. Mảng đã đi đâu?

Hóa ra Linux không bận tâm đến việc lưu trữ tất cả các số 0 đó trong RAM. Thay vào đó, nó sẽ chỉ thêm các khối số 0 vào RAM khi dữ liệu thực sự được truy cập – cho đến khi không có RAM nào được sử dụng. Cho đến khi điều đó xảy ra, bộ nhớ được phân bổ sẽ báo cáo 3GB, nhưng bộ nhớ lưu trú sẽ nhận thấy rằng 3GB đó không thực sự sử dụng bất kỳ tài nguyên nào

Bộ nhớ được phân bổ là một điểm khởi đầu tốt

Cần lưu ý rằng đây vẫn là một mô hình sử dụng bộ nhớ khá đơn giản. Nó không bao gồm bộ nhớ cache của tệp hoặc phân mảnh bộ nhớ trong bộ cấp phát hoặc các số liệu có sẵn khác – một số hữu ích, một số ít hơn – bạn có thể nhận được từ Linux

Điều đó đang được nói, đối với nhiều ứng dụng, bộ nhớ được cấp phát có thể là đủ để giúp bạn tối ưu hóa việc sử dụng bộ nhớ của chương trình. Bộ nhớ được phân bổ cho bạn biết ứng dụng của bạn thực sự yêu cầu gì và đó thường là những gì bạn sẽ phải tối ưu hóa

Tìm hiểu thêm các kỹ thuật để giảm mức sử dụng bộ nhớ—đọc phần còn lại của hướng dẫn Bộ dữ liệu lớn hơn bộ nhớ dành cho Python

Bài viết tiếp theo. lọc. một trình biên dịch bộ nhớ Python cho các nhà khoa học và nhà khoa học dữ liệu
bài báo trước. Thủ thuật sao chép khi ghi mmap[]. giảm mức sử dụng bộ nhớ của các bản sao mảng

Tìm các tắc nghẽn về hiệu suất và bộ nhớ trong mã xử lý dữ liệu của bạn với trình cấu hình Sciagraph

Các công việc chạy chậm sẽ lãng phí thời gian của bạn trong quá trình phát triển, cản trở người dùng của bạn và tăng chi phí tính toán của bạn. Tăng tốc mã của bạn và bạn sẽ lặp lại nhanh hơn, có người dùng hài lòng hơn và bám sát ngân sách của mình—nhưng trước tiên, bạn cần xác định nguyên nhân của sự cố

Tìm các nút thắt cổ chai về hiệu suất và bộ nhớ trong khoa học dữ liệu của bạn Công việc Python với trình hồ sơ Sciagraph. Cấu hình trong quá trình phát triển và sản xuất, với sự hỗ trợ đa xử lý và hơn thế nữa

Tìm hiểu các kỹ năng kỹ thuật phần mềm Python thực tế mà bạn có thể sử dụng trong công việc của mình

Đăng ký nhận bản tin của tôi và tham gia cùng hơn 6700 nhà phát triển Python và nhà khoa học dữ liệu học các công cụ và kỹ thuật thực tế, từ hiệu suất Python đến đóng gói Docker, với một bài viết mới miễn phí trong hộp thư đến của bạn mỗi tuần

Chủ Đề