Python 程式碼在執行前會先編譯成 Bytecode,直譯器會逐一執行 Bytecode 指令,例如 LOAD_FAST 用於載入區域性變數,LIST_APPEND 則用於將元素新增到列表。執行過程中,堆積疊追蹤機制能幫助開發者快速定位錯誤,堆積疊追蹤報表包含函式名稱、檔案名稱、行號和錯誤訊息等資訊。Python 的 traceback 模組提供了工具來產生和處理堆積疊追蹤報表。Python 的記憶體管理機制根據參照計數和垃圾回收,並使用 pymalloc 作為預設的記憶體分配器。pymalloc 將記憶體分成不同大小的區塊,組織成池,並由 Arena 管理。在 64 位元系統中,記憶體分配的步長為 16 位元組,定義了 32 個大小類別。理解這些機制有助於開發者編寫更高效的 Python 程式。

157 迴圈計算

在 Python 中,每個操作都有一個預定的堆積疊效果,可以透過 stack_effect() 函式計算。這個函式會傳回操作執行後堆積疊中值的變化量。

堆積疊效果

堆積疊效果可以是正數、負數或零。如果操作執行後堆積疊效果不等於堆積疊中值的變化量,會引發一個異常。

158 迴圈計算

在 Python 中,我們可以使用 append() 方法將元素新增到列表中。這個方法會將元素新增到列表的末尾。

新增元素到列表

例如,如果我們定義一個列表 my_list,並且想要將元素 obj 新增到列表中,可以使用以下程式碼:

my_list = []
my_list.append(obj)

在這個例子中,obj 是要新增到列表中的元素。

LOAD_FAST

在這個例子中,LOAD_FAST 操作被用來載入 obj 到堆積疊中。這個操作包括以下步驟:

  1. 載入 obj 的指標到堆積疊中。
  2. 如果 obj 不存在,引發一個未繫結區域性變數異常。
  3. obj 的參照計數增加 1。
  4. obj 的指標放在堆積疊頂部。
  5. 如果追蹤開啟,則執行一次迴圈,並輸出追蹤資訊。如果追蹤關閉,則直接跳轉到下一條指令。

LIST_APPEND

在這個例子中,LIST_APPEND 操作被用來將 obj 新增到列表中。這個操作包括以下步驟:

  1. 載入列表的指標到堆積疊中。
  2. obj 的指標放在堆積疊頂部。
  3. obj 新增到列表中。

FAST_DISPATCH

在這個例子中,FAST_DISPATCH 操作被用來執行下一條指令。如果追蹤開啟,則執行一次迴圈,並輸出追蹤資訊。如果追蹤關閉,則直接跳轉到下一條指令。

Python bytecode 執行過程剖析

Python 的 bytecode 執行過程是 Python 直譯器的核心部分。當 Python 程式碼被編譯成 bytecode 後,直譯器會將其載入記憶體,並由 eval 函式或 exec 函式執行。以下是 bytecode 執行過程的詳細步驟:

1. 載入 bytecode

當 Python 程式碼被編譯成 bytecode 後,直譯器會將其載入記憶體。這個過程稱為 “載入”(loading)。

2. 建立執行環境

在載入 bytecode 後,直譯器會建立一個執行環境(execution environment),包括:

  • 全域變數表(global variable table)
  • 區域性變數表(local variable table)
  • 函式呼叫堆積疊(function call stack)

3. 執行 bytecode

直譯器會從 bytecode 的第一條指令開始執行。每條指令都對應著一個特定的操作,例如:

  • LOAD_FAST: 載入一個區域性變數
  • STORE_FAST: 儲存一個區域性變數
  • CALL_FUNCTION: 呼叫一個函式
  • JUMP_ABSOLUTE: 跳轉到指定的位置

4. 處理例外

如果在執行 bytecode 過程中發生例外,直譯器會捕捉例外並執行相應的處理程式。

5. 結束執行

當 bytecode 的所有指令都被執行完畢後,直譯器會結束執行並傳回結果。

LIST_APPEND 指令

LIST_APPEND 指令是 Python bytecode 中的一種指令,用於將一個元素追加到列表的末尾。以下是 LIST_APPEND 指令的執行步驟:

  1. POP: 從堆積疊中彈出一個元素,這個元素就是要被追加到列表的元素。
  2. PEEK: 從堆積疊中取出列表的參照。
  3. PyList_Append: 呼叫 PyList_Append 函式,將元素追加到列表的末尾。
  4. Py_DECREF: 減少元素的參照計數。
  5. PREDICT: 預測下一條指令是 JUMP_ABSOLUTE
  6. DISPATCH: 執行下一條指令。

CALL_FUNCTION 指令

CALL_FUNCTION 指令是 Python bytecode 中的一種指令,用於呼叫一個函式。以下是 CALL_FUNCTION 指令的執行步驟:

  1. POP: 從堆積疊中彈出一個元素,這個元素就是要被呼叫的函式。
  2. PEEK: 從堆積疊中取出函式的引數。
  3. PyEval_CallObject: 呼叫 PyEval_CallObject 函式,執行函式呼叫。
  4. Py_DECREF: 減少函式的參照計數。
  5. PREDICT: 預測下一條指令是 JUMP_ABSOLUTE
  6. DISPATCH: 執行下一條指令。

瞭解 Python 的堆積疊追蹤機制

當 Python 程式發生錯誤時,系統會自動產生一份堆積疊追蹤(stack trace)報表,幫助開發者快速定位錯誤位置。堆積疊追蹤報表包含了錯誤發生的呼叫堆積疊,包括每個函式的名稱、檔案名稱和行號。

堆積疊追蹤報表的結構

堆積疊追蹤報表通常由多行組成,每行代表一個函式呼叫。 報表中包含了以下資訊:

  • 函式名稱
  • 檔案名稱
  • 行號
  • 錯誤訊息

使用 traceback 模組

Python 的 traceback 模組提供了幾個有用的函式,讓你可以自行產生和處理堆積疊追蹤報表。其中,walk_stack() 函式可以用來取得堆積疊中的每個函式呼叫的資訊。

import traceback
import sys

def function1():
    function2()

def function2():
    raise RuntimeError("錯誤訊息")

if __name__ == "__main__":
    try:
        function1()
    except Exception as e:
        # 使用 traceback 模組取得堆積疊追蹤報表
        traceback.print_exc(file=sys.stdout)

在這個範例中,當 function2() 引發 RuntimeError 時,程式會捕捉這個異常,並使用 traceback.print_exc() 函式印出堆積疊追蹤報表。

自定義堆積疊追蹤報表

如果你需要更細膩地控制堆積疊追蹤報表的格式,可以使用 traceback.extract_tb() 函式取得堆積疊中的每個函式呼叫的資訊。

import traceback
import sys

def function1():
    function2()

def function2():
    raise RuntimeError("錯誤訊息")

if __name__ == "__main__":
    try:
        function1()
    except Exception as e:
        # 取得堆積疊追蹤報表
        tb = sys.exc_info()[2]
        # 使用 extract_tb() 函式取得堆積疊中的每個函式呼叫的資訊
        stack_summary = traceback.extract_tb(tb)
        # 自定義印出堆積疊追蹤報表
        for filename, linenum, function, text in stack_summary:
            print(f"檔案:{filename}, 行號:{linenum}, 函式:{function}")

在這個範例中,程式使用 sys.exc_info()[2] 取得目前的異常堆積疊,然後使用 traceback.extract_tb() 函式取得堆積疊中的每個函式呼叫的資訊。最後,程式自行印出堆積疊追蹤報表。

Python 的執行迴圈和記憶體管理

Python 的執行迴圈是指 Python 解譯器執行 Python 程式碼的過程。這個過程涉及到多個階段,包括編譯、最佳化、執行等。在執行階段,Python 解譯器會建立一個執行迴圈,該迴圈負責管理程式碼的執行,包括變數的建立、記憶體的分配等。

在 Python 中,記憶體管理是透過一個稱為「參照計數」(Reference Counting)的機制來實作的。當一個物件被建立時,Python 會為其分配一塊記憶體,並初始化一個參照計數器。每當有一個新的參照指向該物件時,參照計數器就會增加 1;而當有一個參照被刪除時,參照計數器就會減少 1。如果參照計數器為 0,則表示該物件不再被任何參照指向,Python 會自動將其從記憶體中刪除。

除了參照計數機制外,Python 還有一個稱為「垃圾回收」(Garbage Collection)的機制,用於處理那些不再被任何參照指向的物件。垃圾回收機制會定期掃描記憶體中所有的物件,如果發現有一個物件的參照計數器為 0,則會將其從記憶體中刪除。

在 C 語言中,記憶體管理是透過指標(Pointer)來實作的。指標是一個變數,它儲存著另一塊記憶體的地址。透過指標,可以直接存取和操作記憶體中的資料。C 語言提供了三種記憶體分配機制:靜態分配、自動分配和動態分配。

靜態分配是指在編譯時就確定了變數的記憶體位置和大小。自動分配是指在函式呼叫時,系統會自動為區域性變數分配記憶體,並在函式傳回時自動釋放。動態分配是指透過函式(如 mallocfree)來手動分配和釋放記憶體。

Python 的記憶體管理機制與 C 語言不同,Python 使用了一種稱為「物件」(Object)的抽象概念來代表記憶體中的資料。每個物件都有一個唯一的身份(Identity),並且可以被多個參照指向。透過這種方式,Python 實作了自動記憶體管理,減少了程式設計師的負擔。

記憶體管理在C語言中的實踐

在C語言中,記憶體管理是透過靜態、自動和動態三種方式進行的。靜態記憶體管理是指在編譯時就確定記憶體的大小和位置,例如定義一個整數變數int number = 0;,編譯器就會為其分配記憶體空間。

自動記憶體管理是指在函式呼叫時,系統自動分配記憶體空間,例如定義一個函式double celsius(double fahrenheit) {... },系統就會在呼叫該函式時自動分配記憶體空間。

動態記憶體管理是指在程式執行時,透過呼叫API函式來動態分配記憶體空間,例如使用malloc()函式來分配記憶體空間。

Python的記憶體管理

Python是一種動態語言,其記憶體管理機制與C語言不同。Python使用了一種稱為「參照計數」(reference counting)的機制來管理記憶體。當一個物件被建立時,Python就會為其分配一個參照計數器,當該物件被參照時,參照計數器就會加1,當該物件不再被參照時,參照計數器就會減1,如果參照計數器為0,則該物件就會被回收。

Python還使用了一種稱為「垃圾回收」(garbage collection)的機制來回收無用的物件。垃圾回收器會定期掃描記憶體空間,尋找無用的物件,並將其回收。

Python的記憶體分配區域

Python有三種記憶體分配區域:

  1. 原始記憶體區域(raw memory area):用於分配大塊的記憶體空間,或者不用於Python物件的記憶體空間。
  2. 物件記憶體區域(object memory area):用於分配Python物件的記憶體空間。
  3. 其他記憶體區域:用於其他用途的記憶體空間。

這些記憶體分配區域是由Python的記憶體管理機制所管理的,確保了Python程式的正確執行和記憶體的有效利用。

Python 記憶體管理機制

Python 的記憶體管理機制是一種高效且複雜的系統,負責管理 Python 物件的記憶體分配和釋放。這個機制是根據 CPython 的實作,CPython 是 Python 的標準實作。

PyMem 和 pymalloc

Python 有兩種記憶體分配器:PyMem 和 pymalloc。PyMem 是一個較舊的分配器,主要用於與舊有的 API 相容,而 pymalloc 是 CPython 的預設分配器。pymalloc 的設計目的是為了提高 Python 物件的記憶體分配效率,特別是對於小型物件的分配。

記憶體分配演算法

pymalloc 的記憶體分配演算法是根據一個簡單的概念:將記憶體分成小塊(block),每個小塊有一個固定的大小。這些小塊被組織成一個池(pool),每個池包含多個小塊。當需要分配記憶體時,pymalloc 會從池中選擇一個合適的小塊,如果池中沒有可用的小塊,則會從系統中分配新的記憶體。

場地(Arena)

pymalloc 中的場地(arena)是一個大型的記憶體區塊,用於儲存多個池。場地的大小通常是 256 KB,與系統頁面大小相同。這樣可以減少記憶體碎片化,提高記憶體分配效率。

重要術語

  • 場地(Arena):一塊大型的記憶體區塊,用於儲存多個池。
  • 池(Pool):一組具有相同大小的小塊。
  • 小塊(Block):一塊小型的記憶體區塊,用於儲存 Python 物件。

程式碼例項

以下是一個簡單的程式碼例項,展示瞭如何使用 PyMem 和 pymalloc 分配記憶體:

import ctypes

# 使用 PyMem 分配記憶體
mem = ctypes.PyMem_Malloc(1024)
if mem:
    print("PyMem 分配記憶體成功")
    ctypes.PyMem_Free(mem)

# 使用 pymalloc 分配記憶體
mem = ctypes.pymalloc_malloc(1024)
if mem:
    print("pymalloc 分配記憶體成功")
    ctypes.pymalloc_free(mem)

內容解密:

在上面的程式碼中,我們使用 ctypes 模組來存取 PyMem 和 pymalloc 的 API。PyMem_Mallocpymalloc_malloc 用於分配記憶體,而 PyMem_Freepymalloc_free 用於釋放記憶體。注意,ctypes 模組只提供了一個簡單的介面來存取 PyMem 和 pymalloc 的 API,實際上,pymalloc 的實作遠比這個簡單的程式碼複雜得多。

圖表翻譯:

下面是一個簡單的 Mermaid 圖表,展示了 pymalloc 的記憶體分配過程:

  graph LR
    A[應用程式] -->|請求分配記憶體|> B[pymalloc]
    B -->|查詢可用的小塊|> C[池]
    C -->|如果找到可用的小塊|> D[傳回小塊]
    C -->|如果沒有可用的小塊|> E[從系統中分配新的記憶體]
    E -->|傳回新分配的小塊|> D

在這個圖表中,應用程式請求 pymalloc 分配記憶體,pymalloc 查詢池中是否有可用的小塊,如果找到,則傳回小塊,如果沒有找到,則從系統中分配新的記憶體,並傳回新分配的小塊。

記憶體管理中的 Arena 物件

在記憶體管理中,Arena 物件是一種用於管理記憶體分配的資料結構。以下是 Arena 物件的結構和功能:

Arena 物件結構

Arena 物件包含以下欄位:

  • address: 指向 Arena 在記憶體中的地址,型別為 uintptr_t
  • pool_address: 指向下一個可用記憶體池的指標,型別為 block *
  • nfreepools: 可用記憶體池的數量,型別為 uint
  • ntotalpools: Arena 中的總記憶體池數量,型別為 uint
  • freepools: 可用記憶體池的連結串列,型別為 pool_header*
  • nextarena: 指向下一個 Arena 物件的指標,型別為 arena_object*
  • prevarena: 指向前一個 Arena 物件的指標,型別為 arena_object*

Arena 物件的功能

Arena 物件的主要功能是管理記憶體分配和回收。當需要分配記憶體時,Arena 物件會從可用記憶體池中選擇一個合適的池,並將其分配給應用程式。當應用程式不再需要這塊記憶體時,Arena 物件會將其回收到可用記憶體池中。

記憶體池

在 Arena 物件中,記憶體池是由多個大小相同的區塊組成,每個區塊的大小不超過 512 個位元組。在 32 位元系統中,區塊大小的增長步長為 8 個位元組,因此定義了 64 個大小類別:

位元組數區塊大小類別索引
1-880
9-16161
17-24242
25-32323

這些大小類別用於確定哪個記憶體池可以滿足應用程式的需求。

Arena 物件的連結串列

Arena 物件之間透過 nextarenaprevarena 指標形成一個雙向連結串列。這個連結串列用於管理所有 Arena 物件,並確保記憶體分配和回收的正確性。

64 位系統的記憶體分配

在 64 位系統中,記憶體分配的步長為 16 個位元組,因此定義了 32 個大小類別。以下是大小類別的詳細資訊:

請求大小(位元組)組態區塊大小大小類別索引
1–16160
17–32321
33–48482
49–64643
480–49649630
496–51251231

所有的記憶體池都有 4096 個位元組(4 KB)的大小,這意味著記憶體池始終包含 64 個池。

記憶體分配的實際應用

在實際應用中,記憶體分配的大小類別可以根據具體的需求進行調整。例如,在某些情況下,可能需要更大的記憶體塊來儲存大型資料結構。在這種情況下,可以使用更大的大小類別來滿足需求。

程式碼例項

以下是使用 C++ 程式語言實作記憶體分配的簡單範例:

#include <iostream>

int main() {
    // 定義大小類別
    enum class SizeClass {
        SIZE_16 = 0,
        SIZE_32 = 1,
        SIZE_48 = 2,
        SIZE_64 = 3,
        //...
        SIZE_496 = 30,
        SIZE_512 = 31
    };

    // 請求記憶體分配
    void* ptr = malloc(32);

    // 判斷大小類別
    SizeClass sizeClass = SizeClass::SIZE_32;

    // 組態記憶體池
    void* pool = malloc(4096);

    // 使用記憶體池
    void* block = pool;

    return 0;
}

圖表翻譯

以下是記憶體分配過程的 Mermaid 圖表:

  flowchart TD
    A[請求記憶體分配] --> B[判斷大小類別]
    B --> C[組態記憶體池]
    C --> D[使用記憶體池]

這個圖表展示了記憶體分配的基本流程,包括請求記憶體分配、判斷大小類別、組態記憶體池和使用記憶體池。

Python記憶體管理機制

Python的記憶體管理機制是一個複雜的系統,負責管理程式執行期間的記憶體分配和釋放。這個機制是根據一個名為「pymalloc」的記憶體分配器,該分配器負責為Python物件分配記憶體。

記憶體分配器

pymalloc是一個高效的記憶體分配器,設計用於滿足Python程式的記憶體需求。它使用了一個名為「arena」的資料結構來管理記憶體,arena是一個大型的記憶體區塊,分割成多個小型的記憶體區塊,稱為「pool」。

Pool

每個pool是一個連續的記憶體區塊,包含多個相同大小的記憶體區塊,稱為「block」。當Python程式需要分配記憶體時,pymalloc會從pool中分配一個block。如果pool中沒有可用的block,pymalloc會從arena中分配一個新的pool。

Arena

arena是一個大型的記憶體區塊,分割成多個pool。每個pool都有一個唯一的索引,稱為「size index」。pymalloc使用size index來管理pool,並確保每個pool只包含相同大小的block。

Block

每個block是一個連續的記憶體區塊,包含一個Python物件的資料。當Python程式需要分配記憶體時,pymalloc會從pool中分配一個block。如果block已經被分配,pymalloc會傳回一個指向該block的指標。

pymalloc_alloc

pymalloc_alloc是一個函式,負責為Python物件分配記憶體。它接受兩個引數:ctx和nbytes。ctx是指向pymalloc的內部資料結構的指標,nbytes是需要分配的記憶體大小。

static inline void*
pymalloc_alloc(void *ctx, size_t nbytes)
{
   ...
}

在這個函式中,pymalloc會先檢查nbytes是否為0,如果是,則傳回NULL。如果nbytes不為0,pymalloc會檢查是否有可用的pool,如果有,則從pool中分配一個block。如果沒有可用的pool,pymalloc會從arena中分配一個新的pool。

物件記憶體組態區域

在 Python 中,記憶體組態是一個重要的過程,尤其是在物件導向的程式設計中。當我們建立一個物件時,Python 需要為其組態記憶體空間,以儲存物件的屬性和方法。

記憶體組態的過程

當我們建立一個物件時,Python 會按照以下步驟組態記憶體:

  1. 計算物件大小:Python 會計算物件的大小,包括其屬性和方法的大小。
  2. 選擇記憶體池:Python 會根據物件的大小選擇一個合適的記憶體池。記憶體池是 Python 中的一個資料結構,用於管理記憶體的組態和釋放。
  3. 組態記憶體:如果選擇的記憶體池中有可用的記憶體空間,Python 會組態記憶體給物件。如果沒有可用的記憶體空間,Python 會建立一個新的記憶體池。
  4. 初始化物件:Python 會初始化物件,包括設定其屬性和方法。

記憶體池的管理

Python 中的記憶體池是由 pymalloc 模組管理的。pymalloc 提供了一個簡單的 API,用於組態和釋放記憶體。當我們建立一個物件時,pymalloc 會根據物件的大小選擇一個合適的記憶體池。

記憶體組態的最佳化

Python 中的記憶體組態可以透過以下方式最佳化:

  • 使用 __slots__:我們可以使用 __slots__ 來減少物件的大小,從而減少記憶體組態的需求。
  • 使用 __dict__:我們可以使用 __dict__ 來儲存物件的屬性,從而減少記憶體組態的需求。
  • 避免建立不必要的物件:我們可以避免建立不必要的物件,從而減少記憶體組態的需求。

程式碼範例

import sys

class MyClass:
    def __init__(self):
        self.attr1 = 1
        self.attr2 = 2

    def __del__(self):
        print("MyClass instance deleted")

obj = MyClass()
print(sys.getsizeof(obj))  # 輸出:56

在這個範例中,我們建立了一個 MyClass 的例項,並使用 sys.getsizeof() 函式來計算其大小。輸出結果為 56,表示 MyClass 例項的大小為 56 bytes。

Mermaid 圖表

  flowchart TD
    A[建立物件] --> B[計算物件大小]
    B --> C[選擇記憶體池]
    C --> D[組態記憶體]
    D --> E[初始化物件]
    E --> F[傳回物件]

這個 Mermaid 圖表展示了 Python 中的記憶體組態過程。從建立物件到傳回物件,每一步驟都被清晰地展示出來。

物件組態器與記憶體管理

在 Python 中,物件組態器(Object Allocator)是一個負責管理物件記憶體組態的機制。它的主要功能是為 Python 物件組態記憶體,例如整數、字串、列表等。除了物件組態器外,Python 還有其他兩個記憶體管理機制:區塊組態器(Block Allocator)和記憶體池(Memory Pool)。

物件組態器的工作原理

物件組態器的工作原理是當需要建立一個新物件時,Python 會呼叫物件組態器的 PyObject_MALLOC 函式來組態記憶體。這個函式會根據物件的大小和型別來決定需要組態多少記憶體。

例如,當建立一個整數物件時,Python 會呼叫 PyLong_New 函式來組態記憶體。這個函式會計算需要組態的記憶體大小,然後呼叫 PyObject_MALLOC 函式來組態記憶體。

記憶體組態的過程

當呼叫 PyObject_MALLOC 函式時,Python 會根據以下步驟來組態記憶體:

  1. 計算需要組態的記憶體大小。
  2. 檢查是否有足夠的記憶體可用。
  3. 如果有足夠的記憶體,則組態記憶體並傳回指標。
  4. 如果沒有足夠的記憶體,則傳回錯誤。

從 Python 記憶體管理的底層機制到實際應用,本文深入剖析了 Python 如何處理記憶體分配、釋放以及最佳化策略。pymalloc 作為預設的記憶體分配器,其利用 arena、pool 和 block 等資料結構,有效地管理不同大小的記憶體區塊,提升了小型物件分配的效率。同時,Python 的垃圾回收機制與參照計數策略相輔相成,自動回收不再使用的物件,避免了記憶體洩漏的風險。然而,pymalloc 的設計並非完美無缺,其針對小型物件的最佳化策略也可能導致大型物件分配的效率下降。對於需要頻繁分配大型物件的應用程式,開發者需要仔細評估 pymalloc 的適用性,並考慮使用其他記憶體管理工具或策略。展望未來,隨著 Python 的不斷發展,其記憶體管理機制也將持續最佳化,以更好地適應日益增長的效能需求。對於開發者而言,深入理解 Python 的記憶體管理機制至關重要,這有助於編寫更高效、更穩定的 Python 程式。