Bộ nhớ mặc định được phân bổ cho một số nguyên trong Python là gì?

Python là một ngôn ngữ lập trình tuyệt vời. Nó cũng được biết đến là khá chậm, chủ yếu là do tính linh hoạt và tính năng động rất lớn của nó. Đối với nhiều ứng dụng và miền, đó không phải là vấn đề do yêu cầu của chúng và các kỹ thuật tối ưu hóa khác nhau. Người ta ít biết rằng các biểu đồ đối tượng Python [từ điển lồng nhau của danh sách và bộ dữ liệu và kiểu nguyên thủy] chiếm một lượng bộ nhớ đáng kể. Đây có thể là một yếu tố hạn chế nghiêm trọng hơn nhiều do ảnh hưởng của nó đối với bộ nhớ đệm, bộ nhớ ảo, cho thuê nhiều chương trình khác và nói chung là làm cạn kiệt bộ nhớ khả dụng, vốn là một nguồn tài nguyên khan hiếm và đắt đỏ

Hóa ra không khó để tìm ra lượng bộ nhớ thực sự được sử dụng. Trong bài viết này, tôi sẽ hướng dẫn bạn những điều phức tạp trong quản lý bộ nhớ của đối tượng Python và chỉ ra cách đo chính xác bộ nhớ đã sử dụng

Trong bài viết này, tôi chỉ tập trung vào CPython—việc triển khai chính của ngôn ngữ lập trình Python. Các thử nghiệm và kết luận ở đây không áp dụng cho các triển khai Python khác như IronPython, Jython và PyPy

Tùy thuộc vào phiên bản Python, các con số đôi khi hơi khác một chút [đặc biệt đối với các chuỗi luôn là Unicode], nhưng các khái niệm đều giống nhau. Trong trường hợp của tôi, đang sử dụng Python 3. 10

Kể từ ngày 1 tháng 1 năm 2020, Python 2 không còn được hỗ trợ và bạn nên nâng cấp lên Python 3

Thực hành khám phá cách sử dụng bộ nhớ Python

Trước tiên, hãy khám phá một chút và hiểu cụ thể về việc sử dụng bộ nhớ thực tế của các đối tượng Python

Chức năng tích hợp
3
86

Mô-đun sys của thư viện tiêu chuẩn cung cấp chức năng

3
87. Hàm đó chấp nhận một đối tượng [và mặc định tùy chọn], gọi phương thức
3
88 của đối tượng và trả về kết quả, vì vậy bạn cũng có thể kiểm tra đối tượng của mình

Đo bộ nhớ của các đối tượng Python

Hãy bắt đầu với một số loại số

1
import sys
2
3
sys.getsizeof[5]
4
28

Hấp dẫn. Một số nguyên mất 28 byte

1
3
1____5
import sys
0

Hmm… một float mất 24 byte

1
import sys
2____5______44
3
import sys
6

Ồ. 104 byte. Điều này thực sự khiến bạn phải suy nghĩ xem bạn muốn biểu diễn một số lượng lớn các số thực dưới dạng

3
89 hay
3
90

Hãy chuyển sang chuỗi và bộ sưu tập

1
import sys
8
2
_______50
3
2
2
4
_______54
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6

VÂNG. Một chuỗi trống chiếm 49 byte và mỗi ký tự bổ sung sẽ thêm một byte khác. Điều đó nói lên rất nhiều điều về sự đánh đổi của việc giữ nhiều chuỗi ngắn trong đó bạn sẽ trả 49 byte chi phí cho mỗi chuỗi so với. một chuỗi dài duy nhất mà bạn chỉ trả chi phí chung một lần

Đối tượng

3
91 có tổng phí chỉ 33 byte.  

1
3
8____5
sys.getsizeof[5]
0

Hãy nhìn vào danh sách

1
sys.getsizeof[5]
2
2
sys.getsizeof[5]
4
3
sys.getsizeof[5]
6
4
_______78
2
5
4
0
2
7
4
2
2
9
4
4
3
1
4
6
3
3
4
8
3
5
28
0
28
1
28
2
28
3
28
4
sys.getsizeof[5]
8

Chuyện gì đang xảy ra vậy? . Một danh sách chứa một chuỗi dài chỉ chiếm 64 byte

Đáp án đơn giản. Danh sách không chứa các đối tượng

3
92. Nó chỉ chứa một con trỏ 8 byte [trên phiên bản 64-bit của CPython] tới đối tượng thực tế
3
92. Điều đó có nghĩa là hàm
3
96 không trả về bộ nhớ thực của danh sách và tất cả các đối tượng mà nó chứa, mà chỉ trả về bộ nhớ của danh sách và các con trỏ tới các đối tượng của nó. Trong phần tiếp theo, tôi sẽ giới thiệu hàm
3
97, giải quyết vấn đề này

1
28
7
2
_______99
3
1
1
4
1
3
2
5
1
5
2
7
sys.getsizeof[5]
4
2
9
1
9
3
1
sys.getsizeof[5]
8
3
3
3
13
3
5
4
2
28
1
3
17
28
2
1
3

Câu chuyện tương tự đối với các bộ dữ liệu. Chi phí hoạt động của một bộ dữ liệu trống là 40 byte so với. 56 của một danh sách. Một lần nữa, sự khác biệt 16 byte trên mỗi chuỗi này là kết quả thấp nếu bạn có cấu trúc dữ liệu với nhiều chuỗi nhỏ, không thay đổi

1
2
1
2
_______53
3
2
5
4
2
3
2
5
2
9
2
7
2
3
2
9
3
1
import sys
04
3
3
sys.getsizeof[5]
8
3
5
import sys
08
28
1
import sys
10
28
2
import sys
12
28
4
import sys
10

Các bộ và từ điển có vẻ như không phát triển chút nào khi bạn thêm các mục, nhưng lưu ý rằng chi phí rất lớn

Điểm mấu chốt là các đối tượng Python có chi phí cố định rất lớn. Nếu cấu trúc dữ liệu của bạn bao gồm một số lượng lớn các đối tượng bộ sưu tập như chuỗi, danh sách và từ điển chứa một số lượng nhỏ các mục, mỗi đối tượng thì bạn phải trả một khoản phí lớn

Hàm
3
97

Bây giờ tôi đã làm bạn sợ chết khiếp và cũng đã chứng minh rằng

3
86 chỉ có thể cho bạn biết một đối tượng nguyên thủy chiếm bao nhiêu bộ nhớ, hãy xem xét một giải pháp phù hợp hơn. Hàm
3
97 đi sâu vào đệ quy và tính toán mức sử dụng bộ nhớ thực tế của biểu đồ đối tượng Python

1
import sys
16
2
import sys
18
3
4
import sys
21
2
5
import sys
23
2
7
2
9
import sys
26
3
1
import sys
28
3
3
import sys
30
3
5
28
1
import sys
33
28
2
import sys
35
28
4
import sys
37
import sys
38
import sys
39
import sys
40
import sys
41
import sys
42
import sys
43
import sys
44
import sys
45
import sys
46
import sys
47
import sys
48
import sys
49
import sys
50
import sys
51
import sys
52
import sys
53
import sys
54
import sys
55
import sys
56
import sys
57
import sys
58
import sys
59
import sys
60
import sys
61
import sys
62
import sys
63
import sys
64
import sys
65
import sys
66
import sys
67
import sys
68
import sys
69
import sys
70
import sys
71
import sys
72
import sys
73
import sys
74
import sys
75

Có một số khía cạnh thú vị cho chức năng này. Nó tính đến các đối tượng được tham chiếu nhiều lần và chỉ đếm chúng một lần bằng cách theo dõi các id đối tượng. Tính năng thú vị khác của việc triển khai là nó tận dụng tối đa các lớp cơ sở trừu tượng của mô-đun bộ sưu tập. Điều đó cho phép hàm xử lý rất chính xác bất kỳ bộ sưu tập nào thực hiện các lớp cơ sở Ánh xạ hoặc Bộ chứa thay vì xử lý trực tiếp với vô số loại bộ sưu tập như.

sys.getsizeof[5]
01,
sys.getsizeof[5]
02,
sys.getsizeof[5]
03,
sys.getsizeof[5]
04,
sys.getsizeof[5]
05,
sys.getsizeof[5]
06,
sys.getsizeof[5]
07,
sys.getsizeof[5]
08,
sys.getsizeof[5]
09,
sys.getsizeof[5]
10, v.v.

Hãy xem nó hoạt động

1
import sys
77____5
import sys
79
3
sys.getsizeof[5]
4

Một chuỗi có độ dài 7 chiếm 56 byte [49 byte trên đầu + 7 byte cho mỗi ký tự]

1
import sys
83____5
sys.getsizeof[5]
4

Một danh sách trống chiếm 56 byte [chỉ tính trên đầu]

1
____487____5
import sys
89

Một danh sách chứa chuỗi "x" chiếm 124 byte [56 + 8 + 56]

1
import sys
91____5
import sys
93

Một danh sách chứa chuỗi "x" năm lần chiếm 156 byte [56 + 5\*8 + 56]

Ví dụ cuối cùng cho thấy rằng

3
97 đếm các tham chiếu đến cùng một đối tượng [chuỗi x] chỉ một lần, nhưng mỗi con trỏ của tham chiếu đều được tính

Điều trị hoặc Tricks

Hóa ra CPython có một số mánh khóe, vì vậy những con số bạn nhận được từ

3
97 không thể hiện đầy đủ mức sử dụng bộ nhớ của chương trình Python

Đếm tham chiếu

Python quản lý bộ nhớ bằng ngữ nghĩa đếm tham chiếu. Khi một đối tượng không còn được tham chiếu nữa, bộ nhớ của nó sẽ bị giải phóng. Nhưng miễn là có một tham chiếu, đối tượng sẽ không bị hủy bỏ. Những thứ như tài liệu tham khảo theo chu kỳ có thể cắn bạn khá khó khăn

Đối tượng nhỏ

CPython quản lý các đối tượng nhỏ [dưới 256 byte] trong nhóm đặc biệt trên ranh giới 8 byte. Có các nhóm cho 1-8 byte, 9-16 byte và tất cả các cách tới 249-256 byte. Khi một đối tượng có kích thước 10 được phân bổ, nó sẽ được phân bổ từ nhóm 16 byte cho các đối tượng có kích thước 9-16 byte. Vì vậy, mặc dù chỉ chứa 10 byte dữ liệu nhưng nó sẽ tốn 16 byte bộ nhớ. Nếu bạn phân bổ 1.000.000 đối tượng có kích thước 10, bạn thực sự sử dụng 16.000.000 byte chứ không phải 10.000.000 byte như bạn có thể giả định. Chi phí phụ thêm 60% này rõ ràng là không nhỏ

số nguyên

CPython giữ một danh sách toàn cầu của tất cả các số nguyên trong phạm vi -5 đến 256. Chiến lược tối ưu hóa này có ý nghĩa vì các số nguyên nhỏ xuất hiện ở khắp mọi nơi và với điều kiện là mỗi số nguyên chiếm 28 byte, nó tiết kiệm rất nhiều bộ nhớ cho một chương trình thông thường

Điều đó cũng có nghĩa là CPython phân bổ trước 266 * 28 = 7448 byte cho tất cả các số nguyên này, ngay cả khi bạn không sử dụng hầu hết chúng. Bạn có thể xác minh nó bằng cách sử dụng hàm

sys.getsizeof[5]
13 cung cấp con trỏ tới đối tượng thực tế. Nếu bạn gọi
sys.getsizeof[5]
14 cho bất kỳ
sys.getsizeof[5]
15 nào trong phạm vi -5 đến 256, bạn sẽ nhận được cùng một kết quả mỗi lần [cho cùng một số nguyên]. Nhưng nếu bạn thử nó với các số nguyên nằm ngoài phạm vi này, mỗi số sẽ khác nhau [mỗi lần một đối tượng mới được tạo nhanh chóng]

Dưới đây là một vài ví dụ trong phạm vi

1
import sys
95
2
import sys
97
3
4
import sys
95
2
5
2
02
2
7
2
9
import sys
95
3
1
2
02
3
3
3
5
2
10
28
1
2
12
28
2
28
4
2
10
import sys
38
2
12
import sys
39
import sys
41___510
import sys
43
2
1

Dưới đây là một số ví dụ bên ngoài phạm vi

1
2
24
2
_______526
3
4
2
29
2
5
2
31
2
7
2
9
2
29
3
1
2
31
3
3
3
5
2
39
28
1
2
41
28
2
28
4
2
39
import sys
38
2
41

Bộ nhớ Python so với. Bộ nhớ hệ thống

CPython là loại sở hữu. Trong nhiều trường hợp, khi các đối tượng bộ nhớ trong chương trình của bạn không được tham chiếu nữa, chúng sẽ không được trả về hệ thống [e. g. những đồ vật nhỏ]. Điều này tốt cho chương trình của bạn nếu bạn phân bổ và giải phóng nhiều đối tượng thuộc cùng một nhóm 8 byte vì Python không phải làm phiền hệ thống, điều này tương đối tốn kém. Nhưng sẽ không tuyệt lắm nếu chương trình của bạn thường sử dụng X byte và trong một số điều kiện tạm thời, nó sử dụng gấp 100 lần [e. g. phân tích cú pháp và xử lý một tệp cấu hình lớn chỉ khi nó bắt đầu]

Giờ đây, bộ nhớ 100X đó có thể bị giữ lại một cách vô ích trong chương trình của bạn, không bao giờ được sử dụng lại và khiến hệ thống không thể phân bổ bộ nhớ đó cho các chương trình khác. Điều trớ trêu là nếu bạn sử dụng mô-đun xử lý để chạy nhiều phiên bản chương trình của mình, thì bạn sẽ bị giới hạn nghiêm trọng số lượng phiên bản bạn có thể chạy trên một máy cụ thể

Hồ sơ bộ nhớ

Để đánh giá và đo mức sử dụng bộ nhớ thực tế của chương trình, bạn có thể sử dụng mô-đun memory\_profiler. Tôi đã chơi với nó một chút và tôi không chắc mình tin tưởng vào kết quả. Sử dụng nó rất đơn giản. Bạn trang trí một chức năng [có thể là chức năng chính] bằng trình trang trí

sys.getsizeof[5]
16 và khi chương trình thoát, trình cấu hình bộ nhớ in ra đầu ra tiêu chuẩn một báo cáo tiện dụng hiển thị tổng số và các thay đổi trong bộ nhớ cho mỗi dòng. Đây là một chương trình mẫu mà tôi đã chạy dưới profiler

1
2
48
2
3
2
51
4
2
53
2
5
2
55
2
7
2
57
2
9
2
59
3
1
2
61
3
3
2
63
3
5
2
61
28
1
2
67
28
2
2
61
28
4
2
71
import sys
38
2
73
import sys
39
2
75
import sys
41
2
77
import sys
43
import sys
45
2
80
import sys
47
2
82
import sys
49
2
84
import sys
51
2
86

Đây là đầu ra

1
2
88
2
3
2
91
4
2
93
2
5
2
95
2
7
2
97
2
9
2
99
3
1
3
01
3
3
3
03
3
5
3
05
28
1
3
07
28
2
3
09
28
4
3
11
import sys
38
3
13
import sys
39
3
15
import sys
41
3
17
import sys
43
3
19
import sys
45
3
21
import sys
47
3
23
import sys
49
3
25

Như bạn có thể thấy, có 17. Chi phí bộ nhớ 3 MB. Lý do bộ nhớ không tăng khi thêm số nguyên cả bên trong và bên ngoài phạm vi [-5, 256] và cả khi thêm chuỗi là một đối tượng duy nhất được sử dụng trong mọi trường hợp. Không rõ tại sao vòng lặp đầu tiên của phạm vi [100000] trên dòng 9 lại thêm 0. 8 MB trong khi dòng thứ hai trên dòng 11 chỉ thêm 0. 7MB và vòng lặp thứ ba trên dòng 13 thêm 0. 8MB. Cuối cùng, khi xóa danh sách a, b và c, -0. 6 MB được phát hành cho a, -0. 8 MB được phát hành cho b và -0. 8MB được phát hành cho c

Cách theo dõi rò rỉ bộ nhớ trong ứng dụng Python của bạn với tracemalloc

tracemalloc là một mô-đun Python hoạt động như một công cụ gỡ lỗi để theo dõi các khối bộ nhớ được cấp phát bởi Python. Sau khi bật Tracemalloc, bạn có thể lấy thông tin sau

  • xác định nơi đối tượng được phân bổ
  • đưa ra số liệu thống kê về bộ nhớ được phân bổ
  • phát hiện rò rỉ bộ nhớ bằng cách so sánh ảnh chụp nhanh

Hãy xem xét ví dụ dưới đây

1
3
27
2
3
3
30
4
2
5
3
33
2
7
3
35
2
9
3
37
3
1
3
39
3
3
3
41
3
5
3
39
28
1
3
45
28
2
3
39
28
4
3
49
import sys
38
3
51
import sys
39
3
53
import sys
41
3
55
import sys
43
import sys
45
import sys
47
3
59
import sys
49
3
61
import sys
51
3
63
import sys
53
3
65
import sys
54
2
82

Giải trình

  • sys.getsizeof[5]
    
    17—bắt đầu truy tìm ký ức
  • sys.getsizeof[5]
    
    18—chụp nhanh bộ nhớ và trả về đối tượng
    sys.getsizeof[5]
    
    19
  • sys.getsizeof[5]
    
    20—sắp xếp các bản ghi theo dõi và trả về số lượng cũng như kích thước của các đối tượng từ truy nguyên.
    sys.getsizeof[5]
    
    21 chỉ ra rằng việc sắp xếp sẽ được thực hiện theo số dòng trong tệp

Khi bạn chạy mã, đầu ra sẽ là

1
3
69
2
_______071
3
3
73
4
3
75
2
5
3
77
2
7
3
79
2
9
3
81
3
1
3
83
3
3
3
85

Phần kết luận

CPython sử dụng rất nhiều bộ nhớ cho các đối tượng của nó. Nó cũng sử dụng nhiều thủ thuật và tối ưu hóa để quản lý bộ nhớ. Bằng cách theo dõi mức sử dụng bộ nhớ của đối tượng và nhận thức được mô hình quản lý bộ nhớ, bạn có thể giảm đáng kể dung lượng bộ nhớ của chương trình

Bài đăng này đã được cập nhật với sự đóng góp từ Esther Vaati. Esther là nhà phát triển và viết phần mềm cho Envato Tuts+

Bao nhiêu bộ nhớ được phân bổ cho Python?

Python không giới hạn mức sử dụng bộ nhớ trên chương trình của bạn. Nó sẽ phân bổ bộ nhớ nhiều như chương trình của bạn cần cho đến khi máy tính của bạn hết bộ nhớ . Điều bạn có thể làm nhiều nhất là giảm giới hạn xuống giới hạn trên cố định. Điều đó có thể được thực hiện với mô-đun tài nguyên, nhưng đó không phải là thứ bạn đang tìm kiếm.

Cấp phát bộ nhớ của int là gì?

Vì kích thước của int là 4 byte nên câu lệnh này sẽ cấp phát bộ nhớ 400 byte . Và, con trỏ ptr giữ địa chỉ của byte đầu tiên trong bộ nhớ được cấp phát.

Bộ nhớ được phân bổ ở đâu trong Python?

Bộ nhớ được lấy từ đống riêng của Python . miền đối tượng. dành cho việc phân bổ bộ nhớ thuộc về các đối tượng Python. Bộ nhớ được lấy từ đống riêng của Python.

Bộ nhớ được phân bổ cho một biến trong Python như thế nào?

Python tối ưu hóa việc sử dụng bộ nhớ bằng cách phân bổ cùng một tham chiếu đối tượng cho một biến mới nếu đối tượng đã tồn tại với cùng một giá trị . Đó là lý do tại sao python được gọi là bộ nhớ hiệu quả hơn.

Chủ Đề