Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách Python quản lý bộ nhớ hoặc cách Python xử lý nội bộ ngày của chúng ta. Chúng ta sẽ đi sâu vào chủ đề này để hiểu hoạt động bên trong của Python và cách nó xử lý bộ nhớ
Hướng dẫn này sẽ giúp hiểu sâu về quản lý bộ nhớ Python. Khi chúng tôi thực thi tập lệnh Python của mình, có rất nhiều logic chạy phía sau trong bộ nhớ Python để làm cho mã hiệu quả
Giới thiệu
Quản lý bộ nhớ rất quan trọng đối với các nhà phát triển phần mềm để làm việc hiệu quả với bất kỳ ngôn ngữ lập trình nào. Như chúng ta đã biết, Python là một ngôn ngữ lập trình nổi tiếng và được sử dụng rộng rãi. Nó được sử dụng gần như trong mọi lĩnh vực kỹ thuật. Trái ngược với ngôn ngữ lập trình, quản lý bộ nhớ liên quan đến việc viết mã hiệu quả về bộ nhớ. Chúng ta không thể bỏ qua tầm quan trọng của việc quản lý bộ nhớ trong khi triển khai một lượng lớn dữ liệu. Quản lý bộ nhớ không đúng cách dẫn đến ứng dụng và các thành phần phía máy chủ bị chậm. Nó cũng trở thành lý do làm việc không đúng cách. Nếu bộ nhớ không được xử lý tốt, sẽ mất nhiều thời gian trong quá trình tiền xử lý dữ liệu
Trong Python, bộ nhớ được quản lý bởi trình quản lý Python để xác định vị trí đặt dữ liệu ứng dụng vào bộ nhớ. Vì vậy, chúng ta phải có kiến thức về trình quản lý bộ nhớ Python để viết mã hiệu quả và mã có thể bảo trì
Giả sử bộ nhớ giống như một cuốn sách trống và chúng ta muốn viết bất cứ thứ gì trên trang sách. Sau đó, chúng tôi ghi dữ liệu bất kỳ dữ liệu nào người quản lý tìm thấy không gian trống trong cuốn sách và cung cấp cho ứng dụng. Thủ tục cung cấp bộ nhớ cho các đối tượng được gọi là cấp phát
Mặt khác, khi dữ liệu không còn được sử dụng, nó có thể bị xóa bởi trình quản lý bộ nhớ Python. Nhưng câu hỏi là, làm thế nào?
Phân bổ bộ nhớ Python
Cấp phát bộ nhớ là một phần thiết yếu trong quản lý bộ nhớ cho nhà phát triển. Quá trình này về cơ bản phân bổ không gian trống trong bộ nhớ ảo của máy tính và có hai loại bộ nhớ ảo hoạt động trong khi thực thi chương trình
- Cấp phát bộ nhớ tĩnh
- Cấp phát bộ nhớ động
Phân bổ bộ nhớ tĩnh -
Cấp phát bộ nhớ tĩnh xảy ra tại thời điểm biên dịch. Ví dụ - Trong C/C++, chúng ta khai báo một mảng tĩnh với kích thước cố định. Bộ nhớ được cấp phát tại thời điểm biên dịch. Tuy nhiên, chúng ta không thể sử dụng lại bộ nhớ trong chương trình tiếp theo
Phân bổ ngăn xếp
Cấu trúc dữ liệu Stack được sử dụng để lưu trữ bộ nhớ tĩnh. Nó chỉ cần thiết bên trong hàm cụ thể hoặc lệnh gọi phương thức. Hàm được thêm vào ngăn xếp cuộc gọi của chương trình bất cứ khi nào chúng ta gọi nó. Phép gán biến bên trong hàm được lưu trữ tạm thời trong ngăn xếp lệnh gọi hàm; . Trình biên dịch xử lý tất cả các quy trình này, vì vậy chúng tôi không cần phải lo lắng về nó
Ngăn xếp cuộc gọi [cấu trúc dữ liệu ngăn xếp] chứa dữ liệu hoạt động của chương trình như chương trình con hoặc lệnh gọi hàm theo thứ tự chúng được gọi. Các chức năng này được bật lên từ ngăn xếp khi chúng tôi gọi
Cấp phát bộ nhớ động
Không giống như cấp phát bộ nhớ tĩnh, Bộ nhớ động cấp phát bộ nhớ trong thời gian chạy chương trình. Ví dụ - Trong C/C++, có định sẵn kích thước của số nguyên kiểu dữ liệu float nhưng không có định sẵn kích thước của các kiểu dữ liệu. Bộ nhớ được phân bổ cho các đối tượng trong thời gian chạy. Chúng tôi sử dụng Heap để triển khai quản lý bộ nhớ động. Chúng ta có thể sử dụng bộ nhớ trong suốt chương trình
Như chúng ta biết, mọi thứ trong Python là một đối tượng có nghĩa là cấp phát bộ nhớ động truyền cảm hứng cho việc quản lý bộ nhớ Python. Trình quản lý bộ nhớ Python tự động biến mất khi đối tượng không còn được sử dụng
Cấp phát bộ nhớ heap
Cấu trúc dữ liệu heap được sử dụng cho bộ nhớ động không liên quan đến việc đặt tên đối tác. Là loại bộ nhớ sử dụng bên ngoài chương trình ở không gian toàn cục. Một trong những ưu điểm tốt nhất của bộ nhớ heap là nó giải phóng không gian bộ nhớ nếu đối tượng không còn được sử dụng hoặc nút bị xóa
Trong ví dụ dưới đây, chúng tôi xác định cách lưu trữ biến của hàm trong ngăn xếp và một đống
Triển khai Python mặc định
Python là một ngôn ngữ lập trình hướng đối tượng mã nguồn mở được triển khai mặc định trong ngôn ngữ lập trình C. Đó là một thực tế rất thú vị - Một ngôn ngữ phổ biến nhất được viết bằng ngôn ngữ khác?
Về cơ bản, ngôn ngữ Python được viết bằng ngôn ngữ tiếng Anh. Tuy nhiên, nó được định nghĩa trong sách hướng dẫn tham khảo mà bản thân nó không hữu ích. Vì vậy, chúng tôi cần một trình thông dịch mã dựa trên quy tắc trong sách hướng dẫn
Lợi ích của việc triển khai mặc định, nó thực thi mã Python trong máy tính và nó cũng chuyển đổi mã Python của chúng tôi thành hướng dẫn. Vì vậy, chúng ta có thể nói rằng việc triển khai mặc định của Python đáp ứng cả hai yêu cầu
Lưu ý - Máy ảo không phải là máy tính vật lý, nhưng chúng được kích hoạt trong phần mềm
Chương trình mà chúng tôi viết bằng ngôn ngữ Python trước tiên sẽ chuyển đổi thành mã byte hướng dẫn liên quan đến máy tính. Máy ảo diễn giải bytecode này
Bộ thu gom rác Python
Như chúng tôi đã giải thích trước đó, Python loại bỏ những đối tượng không còn được sử dụng hoặc có thể nói rằng nó giải phóng không gian bộ nhớ. Quá trình làm biến mất không gian bộ nhớ của đối tượng không cần thiết này được gọi là Garbage Collector. Trình thu gom rác Python bắt đầu thực thi chương trình và được kích hoạt nếu số lượng tham chiếu giảm xuống 0
Khi chúng tôi gán tên mới hoặc đặt nó trong các vùng chứa như từ điển hoặc bộ dữ liệu, số lượng tham chiếu sẽ tăng giá trị của nó. Nếu chúng ta gán lại tham chiếu cho một đối tượng, số lượng tham chiếu sẽ giảm giá trị của nó nếu. Nó cũng giảm giá trị khi tham chiếu của đối tượng nằm ngoài phạm vi hoặc một đối tượng bị xóa
Như chúng ta đã biết, Python sử dụng cấp phát bộ nhớ động được quản lý bởi cấu trúc dữ liệu Heap. Memory Heap chứa các đối tượng và các cấu trúc dữ liệu khác sẽ được sử dụng trong chương trình. Trình quản lý bộ nhớ Python quản lý việc cấp phát hoặc hủy cấp phát không gian bộ nhớ heap thông qua các hàm API
Các đối tượng Python trong bộ nhớ
Như chúng ta đã biết, mọi thứ trong Python đều là đối tượng. Đối tượng có thể đơn giản [chứa số, chuỗi, v.v. ] hoặc vùng chứa [từ điển, danh sách hoặc lớp do người dùng xác định]. Trong Python, chúng ta không cần khai báo biến hoặc kiểu của chúng trước khi sử dụng chúng trong chương trình
Hãy hiểu ví dụ sau
Ví dụ -
đầu ra
10 Traceback [most recent call last]: File "", line 1, in print[x] NameError : name 'a' is not defined
Như chúng ta có thể thấy trong đầu ra ở trên, chúng ta đã gán giá trị cho đối tượng x và in nó. Khi chúng tôi xóa đối tượng x và cố gắng truy cập vào mã tiếp theo, sẽ có lỗi thông báo rằng biến x không được xác định
Do đó, trình thu gom rác Python hoạt động tự động và các lập trình viên không cần phải lo lắng về điều đó, không giống như C
Đếm tham chiếu trong Python
Đếm tham chiếu cho biết có bao nhiêu lần các đối tượng khác tham chiếu đến một đối tượng. Khi một tham chiếu của đối tượng được chỉ định, số lượng đối tượng được tăng lên một. Khi các tham chiếu của một đối tượng bị xóa hoặc bị xóa, số lượng đối tượng sẽ giảm đi. Trình quản lý bộ nhớ Python thực hiện phân bổ lại khi số lượng tham chiếu trở thành 0. Hãy làm cho nó đơn giản để hiểu
Ví dụ -
Giả sử, có hai hoặc nhiều biến chứa cùng một giá trị, vì vậy máy ảo Python thay vì tạo một đối tượng khác có cùng giá trị trong vùng riêng. Nó thực sự làm cho biến thứ hai trỏ đến giá trị hiện có ban đầu trong vùng riêng
Điều này rất có lợi để bảo vệ bộ nhớ, có thể được sử dụng bởi một biến khác
Khi chúng ta gán giá trị cho x. đối tượng số nguyên 10 được tạo trong bộ nhớ Heap và tham chiếu của nó được gán cho x
Trong đoạn mã trên, chúng ta đã gán y = x, có nghĩa là đối tượng y sẽ tham chiếu đến cùng một đối tượng vì Python đã phân bổ cùng một tham chiếu đối tượng cho biến mới nếu đối tượng đã tồn tại với cùng một giá trị
Bây giờ, hãy xem một ví dụ khác
Ví dụ -
đầu ra
x and y do not refer to the same object
Các biến x và y không tham chiếu đến cùng một đối tượng vì x được tăng thêm một, x tạo đối tượng tham chiếu mới và y vẫn tham chiếu đến 10
Chuyển đổi Garbage Collector
Trình thu gom rác Python đã phân loại các đối tượng bằng cách sử dụng thế hệ của nó. Trình thu gom rác Python có ba thế hệ. Khi chúng ta xác định đối tượng mới trong chương trình, vòng đời của nó được xử lý bởi thế hệ đầu tiên của trình thu gom rác. Nếu đối tượng được sử dụng trong một chương trình khác, nó sẽ được kích thích cho thế hệ tiếp theo. Mỗi thế hệ đều có một ngưỡng
Trình thu gom rác sẽ hoạt động nếu vượt quá ngưỡng của số lượng phân bổ trừ đi số lượng phân bổ lại
Chúng tôi có thể sửa đổi giá trị ngưỡng theo cách thủ công bằng cách sử dụng mô-đun GC. Mô-đun này cung cấp phương thức get_threshold[] để kiểm tra giá trị ngưỡng của một thế hệ thu gom rác khác. Hãy hiểu ví dụ sau
Ví dụ -
đầu ra
Trong đầu ra ở trên, giá trị ngưỡng 700 dành cho thế hệ thứ nhất và các giá trị khác dành cho thế hệ thứ hai và thứ ba
Giá trị ngưỡng để kích hoạt trình thu gom rác có thể được sửa đổi bằng phương thức set_threshold[]
Ví dụ - 2
Trong ví dụ trên, giá trị của ngưỡng tăng cho cả ba thế hệ. Nó sẽ ảnh hưởng đến tần suất chạy bộ thu gom rác. Các lập trình viên không cần lo lắng về trình thu gom rác, nhưng nó đóng vai trò thiết yếu trong việc tối ưu hóa thời gian chạy Python cho hệ thống đích
Trình thu gom rác Python xử lý các chi tiết cấp thấp cho nhà phát triển
Tầm quan trọng của việc thực hiện thu gom rác thủ công
Như chúng ta đã thảo luận trước đó, trình thông dịch Python xử lý tham chiếu đến đối tượng được sử dụng trong chương trình. Nó tự động giải phóng bộ nhớ khi số tham chiếu bằng không. Đây là cách tiếp cận cổ điển để đếm tham chiếu, nếu nó không hoạt động khi chương trình có các chu kỳ tham chiếu. Chu kỳ tham chiếu xảy ra khi một hoặc nhiều đối tượng được tham chiếu với nhau. Do đó, số tham chiếu không bao giờ trở thành số không
Hãy hiểu ví dụ sau -
Chúng tôi đã tạo chu kỳ tham chiếu. Đối tượng list1 đang tham chiếu chính đối tượng list1. Khi hàm trả về đối tượng list1, bộ nhớ cho đối tượng list1 không được giải phóng. Vì vậy, việc đếm tham chiếu không phù hợp để giải chu trình tham chiếu. Tuy nhiên, chúng ta có thể giải quyết nó bằng cách thay đổi bộ thu gom rác hoặc hiệu suất của bộ thu gom rác
Để thực hiện điều đó, chúng tôi sẽ sử dụng gc. hàm coll[] cho mô-đun gc
Đoạn mã trên sẽ cung cấp số lượng đối tượng được thu thập và phân bổ lại
Chúng ta có thể thực hiện thu gom rác thủ công bằng hai phương pháp - thu gom rác dựa trên thời gian hoặc dựa trên sự kiện
các gc. Phương thức coll[] được sử dụng để thực hiện thu gom rác dựa trên thời gian. Phương thức này được gọi sau một khoảng thời gian cố định để thực hiện thu gom rác dựa trên thời gian
Trong bộ sưu tập rác dựa trên chẵn, gc. hàm coll[] gọi sau khi một sự kiện xảy ra. Hãy hiểu ví dụ sau
Ví dụ -
đầu ra
Here, we are creating garbage.. Collecting the object.. Number of unreachable objects collected by GC: 10 Uncollectable garbage: []
Trong đoạn mã trên, chúng ta đã tạo đối tượng list1 được tham chiếu bởi biến danh sách. Phần tử đầu tiên của đối tượng danh sách đề cập đến chính nó. Số lượng tham chiếu của đối tượng danh sách luôn lớn hơn 0, ngay cả khi nó bị xóa hoặc nằm ngoài phạm vi của chương trình
Quản lý bộ nhớ C Python
Trong phần này, chúng ta sẽ thảo luận chi tiết về kiến trúc bộ nhớ C Python
Như chúng ta đã thảo luận trước đó, có một lớp trừu tượng từ phần cứng vật lý đến Python. Nhiều ứng dụng hoặc Python truy cập vào bộ nhớ ảo được tạo bởi hệ điều hành
Python sử dụng một phần bộ nhớ để sử dụng nội bộ và bộ nhớ phi đối tượng. Một phần khác của bộ nhớ được sử dụng cho đối tượng Python như int, dict, list, v.v.
CPython chứa bộ cấp phát đối tượng cấp phát bộ nhớ trong vùng đối tượng. Bộ cấp phát đối tượng nhận được một cuộc gọi mỗi khi đối tượng mới cần không gian. Bộ cấp phát thiết kế chính cho lượng dữ liệu nhỏ vì Python không liên quan đến quá nhiều dữ liệu cùng một lúc. Nó cấp phát bộ nhớ khi thực sự cần thiết
Có ba thành phần chính của chiến lược cấp phát bộ nhớ CPython
Đấu trường - Đó là khối bộ nhớ lớn nhất và được căn chỉnh trên một ranh giới trang trong bộ nhớ. Hệ điều hành sử dụng ranh giới trang là cạnh của mâm cặp bộ nhớ liền kề có độ dài cố định. Python giả sử kích thước trang của hệ thống là 256 kilobyte
Pools - Nó bao gồm một lớp kích thước duy nhất. Một nhóm có cùng kích thước quản lý danh sách liên kết đôi. Một nhóm phải được sử dụng, đầy hoặc trống. Một nhóm đã sử dụng bao gồm các khối bộ nhớ để lưu trữ dữ liệu. Một nhóm đầy đủ có tất cả dữ liệu được phân bổ và chứa. Một nhóm trống không có bất kỳ dữ liệu nào và có thể được chỉ định bất kỳ loại kích thước nào cho khối khi cần
Khối - Nhóm chứa một con trỏ tới khối bộ nhớ "miễn phí" của chúng. Trong nhóm, có một con trỏ, cho biết khối bộ nhớ trống. Bộ cấp phát không chạm vào các khối này cho đến khi nó thực sự cần thiết
Các cách phổ biến để giảm độ phức tạp của không gian
Chúng ta có thể làm theo một số phương pháp hay nhất để giảm độ phức tạp của không gian. Những kỹ thuật này được cho là tiết kiệm khá nhiều không gian và làm cho chương trình hiệu quả. Dưới đây là một số thực hành trong Python dành cho bộ cấp phát bộ nhớ
Chúng tôi xác định một danh sách trong Python; . Giả sử chúng ta cần một danh sách con cho danh sách đã cho, sau đó chúng ta thực hiện cắt danh sách. Đó là một cách đơn giản để lấy danh sách con từ danh sách gốc. Bằng cách nào đó, nó phù hợp với lượng dữ liệu nhỏ nhưng không phù hợp với dữ liệu lớn
Do đó, việc cắt danh sách tạo ra các bản sao của đối tượng trong danh sách. Nó chỉ sao chép tham chiếu đến chúng. Do đó, bộ cấp phát bộ nhớ Python tạo một bản sao của đối tượng và cấp phát nó. Vì vậy, chúng ta cần tránh việc cắt danh sách
Cách tốt nhất để tránh việc nhà phát triển nên cố gắng sử dụng biến riêng biệt để theo dõi các chỉ số thay vì cắt một danh sách
- Sử dụng danh sách lập chỉ mục một cách cẩn thận
Nhà phát triển nên cố gắng sử dụng "đối với mục trong mảng" thay vì "đối với chỉ mục trong phạm vi [len [mảng]]" để tiết kiệm không gian và thời gian. Nếu chương trình của chúng tôi không cần lập chỉ mục cho phần tử danh sách, thì đừng sử dụng nó
Nối chuỗi không phù hợp để tiết kiệm không gian và thời gian phức tạp. Khi có thể, chúng ta nên tránh sử dụng '+' để nối chuỗi vì chuỗi là bất biến. Khi chúng ta thêm chuỗi mới vào chuỗi hiện có, Python sẽ tạo chuỗi mới và phân bổ nó đến một địa chỉ mới
Mỗi chuỗi cần một kích thước bộ nhớ cố định dựa trên ký tự và độ dài của nó. Khi chúng tôi thay đổi chuỗi, nó cần một lượng bộ nhớ khác và yêu cầu phân bổ lại
Hãy chạy ví dụ sau
đầu ra
Nó sẽ tạo biến a để chỉ đối tượng chuỗi, là thông tin giá trị chuỗi
Sau đó, chúng tôi thêm sting mới vào đó bằng toán tử '+'. Python phân bổ lại chuỗi mới trong bộ nhớ dựa trên kích thước và độ dài của nó. Giả sử kích thước bộ nhớ của chuỗi ban đầu là n byte, thì chuỗi mới sẽ là m byte
Thay vì sử dụng nối chuỗi, chúng ta có thể sử dụng". tham gia [iterable_object]" hoặc định dạng hoặc%. Điều này ảnh hưởng rất lớn đến việc tiết kiệm bộ nhớ và thời gian
- Sử dụng Iterator và Generator
Trình lặp rất hữu ích cho cả thời gian và bộ nhớ khi làm việc trên một tập dữ liệu lớn. Làm việc với tập dữ liệu lớn, chúng ta cần xử lý dữ liệu ngay lập tức và có thể đợi chương trình xử lý toàn bộ dữ liệu trước
Trình tạo là các hàm đặc biệt được sử dụng để tạo hàm lặp
Trong ví dụ sau, chúng tôi triển khai một trình vòng lặp gọi hàm tạo đặc biệt. Từ khóa suất trả về giá trị hiện tại, chỉ chuyển sang giá trị tiếp theo trong lần lặp tiếp theo của vòng lặp
Ví dụ -
- Sử dụng Thư viện tích hợp khi có thể
Nếu chúng ta sử dụng các phương thức đã được xác định trước trong thư viện Python, thì hãy nhập thư viện tương ứng. Nó sẽ tiết kiệm rất nhiều không gian và thời gian. Chúng ta cũng có thể tạo một mô-đun để xác định chức năng và nhập nó vào chương trình làm việc hiện tại
Phần kết luận
Trong hướng dẫn này, chúng ta đã thảo luận về hoạt động của bộ nhớ trong Python. Chúng ta đã học cách Python quản lý bộ nhớ và cũng đã thảo luận về cách triển khai Python mặc định. CPython được viết bằng ngôn ngữ lập trình C. Python là một loại ngôn ngữ động sử dụng cấu trúc dữ liệu Heap để lưu trữ bộ nhớ