Python có ngăn xếp bộ nhớ không?

Là nhà khoa học dữ liệu, thông thường, chúng tôi không chú ý đến cách Python và hệ điều hành cơ bản xử lý bộ nhớ cho mã của chúng tôi. Rốt cuộc, Python là ngôn ngữ phổ biến nhất trong số các nhà khoa học dữ liệu, một phần vì nó tự động xử lý những chi tiết đó. Miễn là chúng tôi đang làm việc trên các tập dữ liệu nhỏ, bỏ qua cách Python quản lý bộ nhớ (i. e. , cấp phát và giải phóng bộ nhớ) không ảnh hưởng đến hiệu suất mã của chúng tôi. Nhưng, ngay khi chúng ta chuyển sang các tập dữ liệu lớn (big data) hoặc các dự án xử lý nặng, kiến ​​thức cơ bản về quản lý bộ nhớ trở nên tối quan trọng

Ví dụ, tôi đang thực hiện một dự án khoa học dữ liệu liên quan đến việc lập chỉ mục DNA của con người. Tôi đã sử dụng một đối tượng từ điển python để theo dõi các trình tự (tôi. e. , trình tự nucleotide) và lưu trữ vị trí của chúng trong DNA người tham chiếu. Khoảng 10% trong quá trình, đối tượng từ điển đã lấy hết RAM của tôi và bắt đầu hoán đổi giữa đĩa và RAM. Nó làm cho quá trình trở nên siêu chậm (vì đĩa truyền dữ liệu chậm hơn nhiều). Là một nhà khoa học dữ liệu, nếu tôi biết kiến ​​thức cơ bản về Python và quản lý bộ nhớ, tôi có thể ngăn chặn điều đó và tạo ra nhiều mã hiệu quả hơn về bộ nhớ

Trong bài viết này và một bài viết sắp tới, tôi giải thích một số khái niệm cơ bản về quản lý bộ nhớ trong Python. Đến cuối bài viết này, bạn đã có kiến ​​thức cơ bản tốt về cách Python xử lý cấp phát và giải phóng bộ nhớ. Bắt đầu nào …

Khái niệm cơ bản

Một chương trình python là một tập hợp các

  1. phương pháp
  2. người giới thiệu
  3. các đối tượng

Phương pháp hoặc hoạt động rất dễ dàng. Khi bạn cộng hai số, về cơ bản, bạn đang áp dụng phương pháp cộng (hoặc tính tổng) cho hai giá trị. Tài liệu tham khảo là một chút khó khăn để giải thích. Một tham chiếu là một tên mà chúng tôi sử dụng để truy cập một giá trị dữ liệu (i. e. , một đối tượng). Các tài liệu tham khảo nổi tiếng nhất trong lập trình là các biến. Khi bạn xác định

>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
0 ,
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
1 là biến hoặc tham chiếu và
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
2 là giá trị của nó (chính xác hơn là đối tượng số nguyên). Ngoài biến, thuộc tính và mục là hai tham chiếu phổ biến khác trong lập trình

Bây giờ, hãy đi sâu hơn và giới thiệu các đối tượng. Là một lập trình viên Python, chắc hẳn bạn đã nghe nói rằng “Mọi thứ trong Python đều là một đối tượng. ” Một số nguyên là một đối tượng. Một chuỗi là một đối tượng. Danh sách, từ điển, bộ dữ liệu, khung dữ liệu gấu trúc, mảng NumPy là các đối tượng. Ngay cả một chức năng là một đối tượng. Khi chúng ta tạo một đối tượng, nó sẽ được lưu trữ trong bộ nhớ. Khi chúng ta định nghĩa các tham chiếu trong đoạn trước, lẽ ra tôi nên nói với bạn rằng một tham chiếu không trỏ đến một giá trị trong Python mà trỏ đến địa chỉ bộ nhớ của một đối tượng. Ví dụ: trong ví dụ đơn giản của chúng tôi, ____ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

Bộ nhớ ngăn xếp so với. bộ nhớ đống

Trong thời gian chạy, bộ nhớ máy tính được chia thành các phần khác nhau. Ba phần bộ nhớ quan trọng là

  1. Mã số
  2. Cây rơm
  3. đống

Phần Mã (còn gọi là Văn bản hoặc Hướng dẫn) của bộ nhớ lưu trữ các hướng dẫn mã ở dạng mà máy hiểu được. Máy làm theo hướng dẫn ở phần code. Theo hướng dẫn, trình thông dịch Python tải các hàm và biến cục bộ trong Bộ nhớ ngăn xếp (còn gọi là Ngăn xếp). Bộ nhớ ngăn xếp là tĩnh và tạm thời. Tĩnh có nghĩa là không thể thay đổi kích thước của các giá trị được lưu trữ trong Ngăn xếp. Nghĩa là tạm thời, ngay sau khi hàm được gọi trả về giá trị của nó, hàm và biến liên quan sẽ bị xóa khỏi Stack. Là một nhà khoa học dữ liệu và lập trình viên, bạn không có quyền truy cập vào bộ nhớ Stack. Trình thông dịch Python và quản lý bộ nhớ hệ điều hành cùng nhau chăm sóc phần bộ nhớ này

Như bạn đã biết, biến (hoặc tham chiếu nói chung) chỉ lưu địa chỉ bộ nhớ của đối tượng. Vì vậy, các đối tượng ở đâu? . Để lưu trữ các đối tượng, chúng ta cần bộ nhớ với cấp phát bộ nhớ động (i. e. , kích thước của bộ nhớ và các đối tượng có thể thay đổi). Trình thông dịch Python chủ động phân bổ và giải phóng bộ nhớ trên Heap (điều mà các lập trình viên C/C++ nên thực hiện thủ công. Cảm ơn, Python. ). Python sử dụng thuật toán thu gom rác (gọi là Garbage Collector) để giữ cho bộ nhớ Heap sạch sẽ và loại bỏ các đối tượng không cần thiết nữa

Bạn không cần phải loay hoay với Heap, nhưng tốt hơn hết là bạn nên hiểu cách Python quản lý Heap vì hầu hết dữ liệu của bạn được lưu trữ trong phần này của bộ nhớ

Hãy tìm địa chỉ bộ nhớ trên Heap mà biến

>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
1 trỏ tới. Để tìm ra nó, chúng ta có thể sử dụng một chức năng gọi là
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
6

>>> x = 1
>>> id(x)
140710407579424
>>> hex(id(x))
'0x7ff9b1dc2720'

Khi chúng ta chạy dòng đầu tiên (_______0_______0), Python lưu đối tượng số nguyên

>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
2 trong địa chỉ bộ nhớ
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
9 trên máy tính của tôi (khác với máy tính của bạn). Trong khoa học máy tính, chúng tôi thường hiển thị địa chỉ bộ nhớ ở dạng số thập lục phân; . tiền tố
>>> str1 = "Python"
>>> str2 = "Python"
>>> hex(id(str1))
'0x1e3adfe2830'
>>> hex(id(str2))
'0x1e3adfe2830'
1 được sử dụng trong khoa học máy tính để chỉ ra rằng số đó ở dạng hex). Sau khi lưu trữ đối tượng int
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
2 trong bộ nhớ Heap, Python yêu cầu tham chiếu (hoặc biến)
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
1 ghi nhớ địa chỉ này (
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
9 hoặc
>>> str1 = "Python"
>>> str2 = "Python"
>>> hex(id(str1))
'0x1e3adfe2830'
>>> hex(id(str2))
'0x1e3adfe2830'
5) làm giá trị của nó

Tối ưu hóa bộ nhớ

Hãy xem ví dụ này

>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'

Ở đây, tôi đã xác định hai biến (

>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
1 và
>>> str1 = "Python"
>>> str2 = "Python"
>>> hex(id(str1))
'0x1e3adfe2830'
>>> hex(id(str2))
'0x1e3adfe2830'
7). Tôi đã gán cho họ một đối tượng số nguyên (tôi. e.
>>> x = 1
>>> y = 1
>>> hex(id(x))
'0x7ffdf176a190'
>>> hex(id(y))
'0x7ffdf176a190'
2) cho cả hai. Đáng ngạc nhiên, địa chỉ bộ nhớ mà cả hai biến trỏ đến đều giống nhau. Nhìn vào một ví dụ khác

>>> str1 = "Python"
>>> str2 = "Python"
>>> hex(id(str1))
'0x1e3adfe2830'
>>> hex(id(str2))
'0x1e3adfe2830'

Tôi đã xác định hai biến (

>>> str1 = "Python"
>>> str2 = "Python"
>>> hex(id(str1))
'0x1e3adfe2830'
>>> hex(id(str2))
'0x1e3adfe2830'
9 và
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
0) và gán một đối tượng chuỗi (
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
1) cho cả hai. Địa chỉ bộ nhớ mà cả hai biến trỏ đến đều giống nhau. Nếu bạn kiểm tra điều tương tự cho các đối tượng boolean, bạn sẽ thấy một quan sát tương tự. Tại sao?

Để tối ưu hóa phân bổ bộ nhớ. Python thực hiện một quá trình gọi là “thực tập. ” Đối với một số đối tượng (sẽ nói sau), Python chỉ lưu trữ một đối tượng trên bộ nhớ Heap và yêu cầu các biến khác nhau trỏ đến địa chỉ vùng nhớ này nếu chúng sử dụng các đối tượng đó. Các đối tượng mà Python thực hiện trên chúng là các số nguyên [-5, 256], boolean và một số chuỗi. Việc thực tập không áp dụng cho các loại đối tượng khác như số nguyên lớn, hầu hết các chuỗi, số float, danh sách, từ điển, bộ dữ liệu

Cấu trúc dữ liệu nâng cao hơn

Cho đến nay, chúng tôi đã chỉ ra các ví dụ về cấu trúc dữ liệu đơn giản như số nguyên, chuỗi hoặc booleans. Còn các cấu trúc dữ liệu phức tạp hơn như danh sách hoặc từ điển thì sao?

>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'

Ví dụ cho thấy rõ ràng rằng địa chỉ bộ nhớ của đối tượng danh sách khác với các mục của nó. Điều này hợp lý vì danh sách là một tập hợp các đối tượng, mỗi mục của nó có danh tính riêng và là một đối tượng riêng biệt có địa chỉ bộ nhớ khác

Nếu mỗi mục trong danh sách là một đối tượng, thì việc thực tập (từ phần trước) có áp dụng cho từng mục trong danh sách không?

>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’

Như bạn thấy, cả

>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
2 và
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
3 đều trỏ đến cùng một địa chỉ bộ nhớ do thực tập số nguyên. Ngoài ra, bạn có thể thấy, khi số nguyên vượt quá 256, cả
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
4 và
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
5 đều trỏ đến các địa chỉ bộ nhớ khác nhau

Điều gì xảy ra khi chúng tôi thêm một mục mới vào danh sách. Địa chỉ bộ nhớ có thay đổi không?

>>> lst = [1, 2, 3]
>>> hex(id(lst))
'0x23633104888'
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]
>>> hex(id(lst))
'0x23633104888'

Thật thú vị, địa chỉ bộ nhớ cho danh sách vẫn giữ nguyên. Lý do là danh sách là một đối tượng có thể thay đổi và nếu bạn thêm các mục vào đó, đối tượng vẫn là cùng một đối tượng với một mục nữa

Một thực tế quan trọng khác về các đối tượng có thể thay đổi là nếu bạn khởi tạo các biến khác nhau từ một đối tượng, thì tất cả chúng sẽ thay đổi nếu bạn thực hiện bất kỳ thay đổi nào đối với đối tượng. Hãy để tôi hiển thị nó với một mã đơn giản

>>> lst1 = [1, 2, 3]
>>> lst2 = lst1
>>> lst1.append(4)
>>> lst2
[1, 2, 3, 4]
>>> lst2.append(5)
>>> lst1
[1, 2, 3, 4, 5]

Trong ví dụ này, cả hai biến

>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
6 và
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
7 đều trỏ đến cùng một đối tượng có thể thay đổi (i. e.
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
8). Nếu bất kỳ biến nào trong số đó thay đổi đối tượng (e. g. , nối thêm một mục mới), giá trị của một biến khác (trỏ đến cùng một đối tượng) cũng sẽ thay đổi. Cách duy nhất để lấy một bản sao riêng của một đối tượng có thể thay đổi là sử dụng phương thức
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
9

>>> lst1 = [1, 2, 3]
>>> lst2 = lst1.copy()
>>> lst1.append(4)
>>> lst2
[1, 2, 3]
>>> hex(id(lst1))
'0x236330dfe08'
>>> hex(id(lst2))
'0x236330e0c88'

Như bạn thấy, sử dụng phương thức

>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
9, chúng ta đang tạo hai đối tượng danh sách riêng biệt với các địa chỉ bộ nhớ khác nhau mà việc thay đổi một trong số chúng không làm thay đổi đối tượng kia

Hầu hết mọi thứ chúng tôi đã nói về các đối tượng danh sách cũng áp dụng cho các đối tượng từ điển. Có một số khác biệt tinh tế nằm ngoài bài viết này. Ví dụ: cách kích thước bộ nhớ của chúng tăng lên sau khi thêm một mục mới sẽ khác

Để kiểm tra xem hai hoặc nhiều biến có trỏ đến cùng một đối tượng hay không, bạn không cần kiểm tra địa chỉ bộ nhớ của chúng. Bạn có thể kiểm tra xem hai biến có trỏ đến cùng một đối tượng hay không bằng cách sử dụng

>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
1. Đây là một ví dụ

>>> lst1 = [1, 2, 3]
>>> lst2 = lst1
>>> lst3 = lst1.copy()
>>> lst2 is lst1
True
>>> lst3 is lst1
False

Hãy nhớ rằng,

>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
1 khác với
>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
3.
>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
1 cho bạn biết liệu hai đối tượng có giống nhau hay không nhưng
>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
3 cho bạn biết nội dung hoặc giá trị của chúng có giống nhau không. Ví dụ: trong đoạn mã trước, nội dung của cả
>>> a = 1
>>> b = 257
>>> hex(id(a))
‘0x7ffdf176a190’
>>> hex(id(b))
‘0x236330dc450’
6 và
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
6 đều giống nhau (i. e.
>>> lst = [1, 2, 3, 257]
>>> hex(id(lst))
'0x236330edf88'
>>> hex(id(lst[0]))
'0x7ffdf176a190'
>>> hex(id(lst[3]))
'0x7ffdf176a1b0'
8), nhưng chúng là hai đối tượng riêng biệt và khác nhau. Đoạn mã sau cho thấy rõ ràng

>>> lst3 == lst1
True
>>> lst3 is lst1
False
Tóm tắt

Bài viết này cung cấp cho bạn kiến ​​thức cơ bản về cách Python (chính xác hơn là triển khai CPython) phân bổ bộ nhớ cho các đối tượng. Khi làm việc với dữ liệu lớn trong Python, bạn cần biết những khái niệm cơ bản này để viết mã hiệu quả hơn về bộ nhớ

Python có sử dụng bộ nhớ ngăn xếp hoặc bộ nhớ heap không?

Quản lý bộ nhớ trong Python bao gồm một đống riêng tư chứa tất cả các đối tượng Python và cấu trúc dữ liệu. Việc quản lý vùng riêng tư này được đảm bảo nội bộ bởi trình quản lý bộ nhớ Python.

Python có mô hình bộ nhớ không?

Tuy nhiên, giờ đây chúng ta đã biết về việc gán lại và thay đổi, nên cần có một mô hình bộ nhớ phức tạp hơn. mô hình bộ nhớ dựa trên đối tượng, chúng ta sẽ gọi đơn giản là mô hình bộ nhớ Python , vì đây là biểu diễn “tiêu chuẩn” của Python lưu trữ dữ liệu.

Python có sử dụng ngăn xếp không?

Danh sách cấu trúc dữ liệu tích hợp sẵn của Python có thể được sử dụng làm ngăn xếp . Thay vì push(), append() được sử dụng để thêm các phần tử vào đầu ngăn xếp trong khi pop() loại bỏ phần tử theo thứ tự LIFO.

Python có quản lý bộ nhớ tốt không?

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 .