Giải phóng bộ nhớ Python

Không giống như các ngôn ngữ như C, phần lớn thời gian Python sẽ giải phóng bộ nhớ cho bạn. Nhưng đôi khi, nó sẽ không hoạt động theo cách bạn mong đợi

Hãy xem xét chương trình Python sau—bạn nghĩ nó sẽ sử dụng tối đa bao nhiêu bộ nhớ?

import numpy as np

def load_1GB_of_data[]:
    return np.ones[[2 ** 30], dtype=np.uint8]

def process_data[]:
    data = load_1GB_of_data[]
    return modify2[modify1[data]]

def modify1[data]:
    return data * 2

def modify2[data]:
    return data + 10

process_data[]

Giả sử chúng tôi không thể thay đổi dữ liệu gốc, điều tốt nhất chúng tôi có thể làm là bộ nhớ tối đa 2GB. trong một khoảng thời gian ngắn, cả 1GB dữ liệu gốc và bản sao dữ liệu đã sửa đổi sẽ cần phải có mặt. Trên thực tế, mức sử dụng cao nhất thực tế sẽ là 3GB—ở dưới xuống, bạn sẽ thấy kết quả lập hồ sơ bộ nhớ thực tế chứng minh rằng

Điều tốt nhất chúng tôi có thể làm là 2GB, mức sử dụng thực tế là 3GB. 1GB sử dụng bộ nhớ bổ sung đó đến từ đâu?

Để hiểu tại sao và những gì bạn có thể làm để khắc phục nó, bài viết này sẽ đề cập đến

  1. Tổng quan nhanh về cách Python tự động quản lý bộ nhớ cho bạn
  2. Các chức năng tác động đến việc theo dõi bộ nhớ của Python như thế nào
  3. Bạn có thể làm gì để khắc phục sự cố này

Cách quản lý bộ nhớ tự động của Python giúp cuộc sống của bạn dễ dàng hơn

Trong một số ngôn ngữ lập trình, bạn cần giải phóng rõ ràng bất kỳ bộ nhớ nào bạn đã phân bổ. Ví dụ, một chương trình C có thể làm

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];

Nếu bạn không

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
6 thủ công bộ nhớ được cấp phát bởi
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
0, nó sẽ không bao giờ được giải phóng

Ngược lại, Python theo dõi các đối tượng và tự động giải phóng bộ nhớ của chúng khi chúng không còn được sử dụng nữa. Nhưng đôi khi điều đó không thành công và để hiểu tại sao bạn cần hiểu cách nó theo dõi chúng

Đối với phép tính gần đúng đầu tiên, việc triển khai Python mặc định thực hiện điều này bằng cách sử dụng phép đếm tham chiếu

  1. Mỗi đối tượng có một bộ đếm số lượng địa điểm mà nó đang được sử dụng
  2. Khi một địa điểm/đối tượng mới nhận được tham chiếu đến đối tượng, bộ đếm sẽ tăng thêm 1
  3. Khi một tham chiếu biến mất, bộ đếm sẽ giảm đi 1
  4. Khi bộ đếm chạm 0, bộ nhớ của đối tượng được giải phóng, vì không ai đề cập đến nó

Có một số cơ chế bổ sung [“thu gom rác”] để xử lý các tham chiếu vòng tròn, nhưng những cơ chế này không liên quan đến chủ đề hiện tại

Cách các chức năng tương tác với quản lý bộ nhớ Python

Một cách bạn có thể thêm một tham chiếu đến một đối tượng là thêm nó vào một đối tượng khác. một danh sách, một từ điển, một thuộc tính của một thể hiện của lớp, v.v. Nhưng các tham chiếu cũng được tạo bởi các biến cục bộ trong các hàm

Hãy xem một ví dụ

def f[]:
    obj = {"x": 1}
    g[obj]
    return
    
def g[o]:
    print[o]
    return

Giả sử chúng ta gọi

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1 và xem qua mã từng bước

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory

Ở dạng văn xuôi

  1. Chúng tôi làm
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    2, có nghĩa là có một biến cục bộ
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    3 trỏ đến từ điển mà chúng tôi đã tạo. Biến đó, được tạo bằng cách chạy hàm, tăng bộ đếm tham chiếu của đối tượng
  2. Tiếp theo, chúng tôi chuyển đối tượng đó cho
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    4. Hiện tại có một biến cục bộ có tên là
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    5 là một tham chiếu bổ sung cho cùng một từ điển, vì vậy tổng số tham chiếu là 2
  3. Tiếp theo, chúng tôi in
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    5, có thể thêm hoặc không thêm tham chiếu, nhưng khi
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    7 trả về, chúng tôi không có tham chiếu bổ sung nào và chúng tôi vẫn ở mức 2
  4. uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    8 trả về, có nghĩa là biến
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    5 cục bộ biến mất, giảm số lượng tham chiếu xuống 1
  5. Cuối cùng,
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    1 trả về, biến cục bộ
    uint8_t *arr = malloc[1024 * 1024];
    // .. do work with array ...
    free[arr];
    
    3 biến mất, giảm số lượng tham chiếu trở lại 0
  6. Số tham chiếu bây giờ là 0 và từ điển có thể được giải phóng. Điều này cũng làm giảm số lượng tham chiếu cho chuỗi
    def f[]:
        obj = {"x": 1}
        g[obj]
        return
        
    def g[o]:
        print[o]
        return
    
    2 và số nguyên
    def f[]:
        obj = {"x": 1}
        g[obj]
        return
        
    def g[o]:
        print[o]
        return
    
    3 mà chúng tôi đã tạo, điều chỉnh một số tối ưu hóa dành riêng cho chuỗi và số nguyên mà tôi sẽ không đi sâu vào

Bây giờ hãy xem lại mã đó, ở cấp độ ngữ nghĩa. Sau khi từ điển được chuyển cho

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
8, nó sẽ không bao giờ được sử dụng bởi
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1 nữa—tuy nhiên, vẫn có một tham chiếu từ
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1 do biến
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
3, đó là lý do tại sao số lượng tham chiếu là 2. Tham chiếu của biến cục bộ sẽ không bao giờ biến mất cho đến khi
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1 thoát, mặc dù
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1 đã sử dụng xong

Giờ đây, việc lưu giữ một từ điển nhỏ trong bộ nhớ lâu hơn một chút không thực sự là vấn đề. Nhưng nếu đối tượng đó sử dụng nhiều bộ nhớ thì sao?

Thêm 1GB

Hãy quay lại mã ban đầu của chúng tôi, nơi chúng tôi có thêm 1GB bộ nhớ sử dụng ngoài dự kiến. Tóm lại

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
4

Nếu chúng tôi cấu hình nó bằng trình cấu hình bộ nhớ Fil để nhận phân bổ tại thời điểm sử dụng bộ nhớ cao nhất, đây là những gì chúng tôi sẽ nhận được


Vào lúc cao điểm, chúng tôi sử dụng 3GB do ba lần phân bổ;

  1. Mảng ban đầu được tạo bởi
    f[]:
        obj = {"x": 1}  # `obj` increments counter to 1
        g[o=obj]:
           # `o` reference increments counter to 2
           print[o]
           return  # `o` goes away, decrements counter to 1
        return # `obj` goes away, decrements counter 0
    # Dictionary is freed from memory
    
    1
  2. Mảng được sửa đổi đầu tiên, được tạo bởi
    f[]:
        obj = {"x": 1}  # `obj` increments counter to 1
        g[o=obj]:
           # `o` reference increments counter to 2
           print[o]
           return  # `o` goes away, decrements counter to 1
        return # `obj` goes away, decrements counter 0
    # Dictionary is freed from memory
    
    2;
  3. Mảng sửa đổi thứ hai, được tạo bởi
    f[]:
        obj = {"x": 1}  # `obj` increments counter to 1
        g[o=obj]:
           # `o` reference increments counter to 2
           print[o]
           return  # `o` goes away, decrements counter to 1
        return # `obj` goes away, decrements counter 0
    # Dictionary is freed from memory
    
    0

Vấn đề là phân bổ đầu tiên. chúng tôi không cần nó nữa khi

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
2 đã tạo phiên bản sửa đổi. Nhưng vì biến cục bộ
f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
6 trong
f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
7, nó không được giải phóng khỏi bộ nhớ cho đến khi
f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
7 trả về. Và điều đó có nghĩa là mức sử dụng bộ nhớ cao hơn 1GB so với mức bình thường

Các giải pháp. Làm cho các chức năng buông bỏ

Vấn đề của chúng tôi là

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
7 đang giữ mảng ban đầu quá lâu

def f[]:
    obj = {"x": 1}
    g[obj]
    return
    
def g[o]:
    print[o]
    return
5

Do đó, các giải pháp liên quan đến việc đảm bảo rằng biến cục bộ

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
6 không giữ mảng ban đầu lâu hơn mức cần thiết

Giải pháp số 1. Không có biến cục bộ nào cả

Nếu không có tham chiếu bổ sung, mảng ban đầu có thể bị xóa khỏi bộ nhớ ngay khi nó không được sử dụng

def f[]:
    obj = {"x": 1}
    g[obj]
    return
    
def g[o]:
    print[o]
    return
7

Hiện tại, không có tham chiếu

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
6 nào giữ nguyên 1GB dữ liệu ban đầu và mức sử dụng bộ nhớ cao nhất sẽ là 2GB

Giải pháp số 2. Sử dụng lại biến cục bộ

Chúng ta có thể thay thế rõ ràng

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
6 bằng kết quả của
f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
2

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
1

Một lần nữa, chúng tôi kết thúc với bộ nhớ tối đa 2GB, vì mảng ban đầu có thể được giải phóng ngay sau khi

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
2 kết thúc

Giải pháp số 3. Chuyển quyền sở hữu đối tượng

Đây là một thủ thuật vay mượn từ C++. chúng tôi có một đối tượng có nhiệm vụ sở hữu khối dữ liệu lớn 1GB và chúng tôi chuyển chủ sở hữu thay vì đối tượng ban đầu

uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
3

Bí quyết là

f[]:
    obj = {"x": 1}  # `obj` increments counter to 1
    g[o=obj]:
       # `o` reference increments counter to 2
       print[o]
       return  # `o` goes away, decrements counter to 1
    return # `obj` goes away, decrements counter 0
# Dictionary is freed from memory
7 không còn tham chiếu đến khối dữ liệu lớn nữa mà thay vào đó là chủ sở hữu—và sau đó
uint8_t *arr = malloc[1024 * 1024];
// .. do work with array ...
free[arr];
46 sẽ xóa/đặt lại chủ sở hữu sau khi trích xuất dữ liệu cần thiết

Theo dõi tham chiếu đối tượng

Trong mã bình thường, việc các đối tượng tồn tại lâu hơn một chút không thành vấn đề. Nhưng khi một đối tượng sử dụng nhiều gigabyte RAM, việc tồn tại quá lâu có thể khiến chương trình của bạn hết bộ nhớ hoặc yêu cầu trả tiền để mua thêm phần cứng

Vì vậy, hãy tập thói quen theo dõi trong đầu xem các tham chiếu đến các đối tượng nằm ở đâu. Và nếu mức sử dụng bộ nhớ quá cao và trình lược tả gợi ý các tham chiếu cấp chức năng là vấn đề, hãy thử một trong các kỹ thuật trên

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. Chi phí bộ nhớ lớn. Số trong Python và cách NumPy hỗ trợ
Bài viết trước. Sao chép dữ liệu là lãng phí, thay đổi dữ liệu là nguy hiểm

Xử lý dữ liệu quá chậm?

Bạn có thể nhận được kết quả nhanh hơn từ quy trình khoa học dữ liệu của mình—và cũng nhận lại được một số tiền—nếu bạn có thể tìm ra lý do tại sao mã của mình chạy chậm

Xác định các nút thắt cổ chai hiệu suất và ngốn bộ nhớ trong khoa học dữ liệu sản xuất của bạn Các công việc Python với Sciagraph, trình lược tả luôn bật cho các công việc sản xuất hàng loạt

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 6500 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úng ta có thể giải phóng bộ nhớ trong Python không?

Thu gom rác bằng Python . Điều này sẽ đảm bảo rằng bộ nhớ sẽ bị xóa khi chúng được thu gom rác. Thu gom rác được thực hiện bởi một chương trình để xóa bộ nhớ trước đó cho một đối tượng không được sử dụng. To clear memory, you have to ensure that you don't keep storing the references to the objects. This will ensure that the memory gets cleared when they are garbage-collected. Garbage collection is carried out by a program to clear the previous memory for an object that is not being used.

Python có giải phóng bộ nhớ sau chức năng không?

Quản lý bộ nhớ . Thay vào đó, nó có một bộ cấp phát đối tượng chuyên dụng cho các đối tượng nhỏ hơn 512 byte, giữ lại một số khối bộ nhớ đã được cấp phát để sử dụng thêm trong tương lai. Python does not necessarily release the memory back to the Operating System. Instead, it has a dedicated object allocator for objects smaller than 512 bytes, which keeps some chunks of already allocated memory for further use in the future.

Python có tự động xóa bộ nhớ không?

Lập trình viên phải cấp phát bộ nhớ theo cách thủ công trước khi chương trình có thể sử dụng bộ nhớ và giải phóng bộ nhớ khi chương trình không cần đến bộ nhớ đó nữa. Trong Python, quản lý bộ nhớ là tự động. Python tự động xử lý việc cấp phát và hủy cấp phát bộ nhớ .

Cách chính xác để giải phóng bộ nhớ trong Python là gì?

Phương thức cấp phát và hủy bỏ bộ nhớ của Python là tự động . Người dùng không phải phân bổ trước hoặc phân bổ lại bộ nhớ bằng tay như khi sử dụng phân bổ bộ nhớ động trong các ngôn ngữ như C hoặc C++. Python sử dụng hai chiến lược để đếm tham chiếu cấp phát bộ nhớ và thu gom rác.

Chủ Đề