Hướng dẫn python memory usage keeps increasing - Việc sử dụng bộ nhớ python tiếp tục tăng

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ó đã thắng được công việc theo cách bạn mong đợi.

Hãy xem xét chương trình Python sau đây, bạn nghĩ nó sẽ sử dụng nhiều như thế nào?

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 ta có thể làm biến đổi dữ liệu gốc, điều tốt nhất chúng ta có thể làm là bộ nhớ cực đại là 2GB: trong một thời điểm ngắn gọn cả 1GB dữ liệu gốc và bản sao đã được sửa đổi của dữ liệu sẽ cần phải có. Trong thực tế, việc sử dụng cực đại thực tế sẽ là 3GB, bạn sẽ thấy một kết quả định hình bộ nhớ thực tế chứng minh điều đó.

Điều tốt nhất chúng ta có thể làm là 2GB, việc sử dụng thực tế là 3GB: cách sử dụng bộ nhớ 1GB đó đến từ đâu? Sự tương tác của các cuộc gọi chức năng với quản lý bộ nhớ Python. The interaction of function calls with Python’s memory management.

Để hiểu lý do tại sao và những gì bạn có thể làm để sửa nó, bài viết này sẽ bao gồm:

  1. Tổng quan nhanh về cách Python tự động quản lý bộ nhớ cho bạn.
  2. Làm thế nào các chức năng tác động đến theo dõi bộ nhớ Python.
  3. Những gì bạn có thể làm để khắc phục vấn đề này.

Làm thế nào Quản lý bộ nhớ tự động 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 phân loại rõ ràng bất kỳ bộ nhớ nào bạn đã phân bổ. Một chương trình C, ví dụ, có thể làm:

uint8_t *arr = malloc(1024 * 1024);
// ... do work with array ...
free(arr);

Nếu bạn không thủ công free() bộ nhớ được phân bổ 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. Nhưng đôi khi điều đó thất bại và để hiểu lý do tại sao bạn cần hiểu cách nó theo dõi họ. But sometimes that fails, and to understand why you need to understand how it tracks them.

Để xấp xỉ đầ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 đếm tham chiếu:

  1. Mỗi đối tượng có một bộ đếm số lượng địa điểm mà nó đượ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 được tăng thêm 1.
  3. Khi một tham chiếu biến mất, bộ đếm bị giảm 1.
  4. Khi bộ đếm đạt 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 (bộ sưu tập rác rưởi) để đối phó với các tài liệu tham khảo tròn, nhưng những cơ chế đó có liên quan đến chủ đề trong tay.

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 vào một đối tượng là bằng cách thêm nó vào một đối tượng khác: danh sách, từ điển, một thuộc tính của một thể hiện lớp, v.v. Nhưng các tài liệu tham khảo cũng được tạo bởi các biến cục bộ trong các chức năng.

Hãy nhìn vào một ví dụ:

def f():
    obj = {"x": 1}
    g(obj)
    return
    
def g(o):
    print(o)
    return

Hãy nói rằng chúng tôi gọi

uint8_t *arr = malloc(1024 * 1024);
// ... do work with array ...
free(arr);
1 và xem từng bước một 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 chúng tôi đã tạo. Biến đó, được tạo bằng cách chạy chức năng, tăng bộ đếm tham chiếu đố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 gọi 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ể hoặc không thể thêm một tài liệu tham khảo, nhưng một khi
    uint8_t *arr = malloc(1024 * 1024);
    // ... do work with array ...
    free(arr);
    
    7 trả về, chúng tôi không có tài liệu tham khảo bổ sung 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 còn 1.
  5. Cuối cùng,
    uint8_t *arr = malloc(1024 * 1024);
    // ... do work with array ...
    free(arr);
    
    1 trả về, biến
    uint8_t *arr = malloc(1024 * 1024);
    // ... do work with array ...
    free(arr);
    
    3 cục bộ biến mất, giảm số lượng tham chiếu trở lại 0.
  6. Số lượng 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, modulo một số tối ưu hóa chuỗi cụ thể và số nguyên mà tôi đã giành được.

Bây giờ, hãy để Lừa nhìn vào mã đó một lần nữa, ở cấp độ ngữ nghĩa. Sau khi từ điển được truyền đến

uint8_t *arr = malloc(1024 * 1024);
// ... do work with array ...
free(arr);
8, nó sẽ không bao giờ được
uint8_t *arr = malloc(1024 * 1024);
// ... do work with array ...
free(arr);
1 sử dụng nữa và vẫn còn một tài liệu tham khảo 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 ra, mặc dù
def f():
    obj = {"x": 1}
    g(obj)
    return
    
def g(o):
    print(o)
    return
9 được thực hiện bằng cách sử dụng nó.The local variable’s reference will never go away until
uint8_t *arr = malloc(1024 * 1024);
// ... do work with array ...
free(arr);
1 exits, even though
def f():
    obj = {"x": 1}
    g(obj)
    return
    
def g(o):
    print(o)
    return
9 is done using it.

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

Hãy để trở lại mã ban đầu của chúng tôi, nơi chúng tôi có thêm 1GB sử dụng bộ nhớ bất ngờ. Tóm lại:

# ...

def process_data():
    data = load_1GB_of_data()
    return modify2(modify1(data))

def modify1(data):
    return data * 2

def modify2(data):
    return data + 10

Nếu chúng tôi lập hồ sơ với trình cấu hình bộ nhớ FIL để phân bổ tại thời điểm sử dụng bộ nhớ cao điểm, thì đâ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 phân bổ; Về cơ bản, chúng tôi đang nhìn vào thời điểm 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
0 phân bổ mảng đã sửa đổi của nó:

  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 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; Điều này lảng vảng 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
    
    0 hoàn thành bằng cách sử dụng nó và sau đó được giải quyết.
  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 một lần

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 ra phiên bản sửa đổi. Nhưng do 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à việc sử dụng bộ nhớ cao hơn 1GB so với nó sẽ có.

Giải pháp: Làm cho các chức năng buông ra

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ữ cho mảng ban đầu quá lâu:

def process_data():
    data = load_1GB_of_data() # ← `data` var lives too long
    return modify2(modify1(data))

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ữ cho 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ó tài liệu tham khảo nào, mảng gốc có thể được xóa khỏi bộ nhớ ngay khi nó không được sử dụng:

# ...

def process_data():
    return modify2(modify1(load_1GB_of_data()))
    
# ...

Bây giờ 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 giữ cho 1GB dữ liệu ban đầu tồn tại và việc sử dụng bộ nhớ cực đại 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:

# ...

def process_data():
    data = load_1GB_of_data()
    data = modify1(data)
    data = modify2(data)
    return data
    
# ...

Một lần nữa, chúng tôi kết thúc với bộ nhớ đỉnh 2GB, vì mảng ban đầu có thể được giải quyết ngay khi kết thú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
2.

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

Đây là một mẹo được mượn từ C ++: Chúng tôi có một đối tượng có công việc sở hữu phần dữ liệu 1GB lớn và chúng tôi vượt qua chủ sở hữu thay vì đối tượng gốc.

# ...

class Owner:
    def __init__(self, data):
        self.data = data
        
def process_data():
    data = Owner(load_1GB_of_data())
    return modify2(modify1(data))
    
def modify1(owned_data):
    data = owned_data.data
    # Remove a reference to original data:
    owned_data.data = None
    return data * 2

# ...

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 có tham chiếu đến phần dữ liệu lớn, mà là chủ sở hữu và
# ...

def process_data():
    data = load_1GB_of_data()
    return modify2(modify1(data))

def modify1(data):
    return data * 2

def modify2(data):
    return data + 10
6 sau đó xóa/đặt lại chủ sở hữu sau khi nó trích xuất dữ liệu cần thiết.

Theo dõi tài liệu tham khảo đối tượng

Trong mã thông thường, việc có các đối tượng sống lâu hơn một chút không quan trọng. Nhưng khi một đối tượng sử dụng nhiều gigabyte của ram, sống 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 cho nhiều phần cứng hơn.

Vì vậy, có thói quen theo dõi tinh thần nơi các tài liệu tham khảo đến các đối tượng. Và nếu việc sử dụng bộ nhớ quá cao và trình hồ sơ cho thấy các tài liệu tham khảo ở cấp độ chức năng là vấn đề, hãy thử một trong các kỹ thuật trên.

Tại sao Python lại chiếm nhiều trí nhớ như vậy?

Những con số đó có thể dễ dàng phù hợp với số nguyên 64 bit, vì vậy người ta sẽ hy vọng Python sẽ lưu trữ hàng triệu số nguyên đó không quá ~ 8MB: một triệu đối tượng 8 byte. Trên thực tế, Python sử dụng nhiều RAM hơn 35 MB để lưu trữ các số này. Tại sao? Bởi vì số nguyên python là đối tượng và các đối tượng có nhiều bộ nhớ trên đầu.Because Python integers are objects, and objects have a lot of memory overhead.

Làm cách nào để giảm việc sử dụng bộ nhớ trong Python?

Sử dụng trình tải dữ liệu pytorch.....
Kiểu dữ liệu được tối ưu hóa.....
Tránh sử dụng các biến toàn cầu, thay vào đó sử dụng các đối tượng cục bộ.....
Sử dụng từ khóa năng suất.....
Các phương pháp tối ưu hóa tích hợp của Python.....
Nhập chi phí báo cáo.....
Dữ liệu khối ..

Làm cách nào để sửa lỗi bộ nhớ trong Python?

Chú ý đến các vòng lặp lớn trong những trường hợp này, thực tế tốt nhất thường là chia công việc thành các lô, cho phép bộ nhớ được giải phóng giữa các cuộc gọi.Ví dụ, trong mã bên dưới, chúng tôi đã chia các vòng lặp trước đó thành 3 vòng riêng biệt, mỗi vòng chạy cho 333.333.333 lần lặp.break the work into batches, allowing the memory to be freed in between calls. As an example, in the code below, we have broken out earlier nested loops into 3 separate loops, each running for 333,333,333 iterations.

Bạn có thể bị rò rỉ bộ nhớ trong Python không?

Chương trình Python, giống như các ngôn ngữ lập trình khác, trải nghiệm rò rỉ bộ nhớ.Rò rỉ bộ nhớ trong Python xảy ra nếu người thu gom rác không làm sạch và loại bỏ dữ liệu không được chấp thuận hoặc không sử dụng khỏi Python.. Memory leaks in Python happen if the garbage collector doesn't clean and eliminate the unreferenced or unused data from Python.