Hướng dẫn what executes python byte code? - cái gì thực thi mã byte python?

Có cách nào để chạy dữ liệu từ tệp PYC trực tiếp không?

Đối tượng mã được biên dịch có thể được lưu bằng cách sử dụng

eggs = marshal.loads(bytes)
exec(eggs)
6

import marshal
bytes = marshal.dumps(eggs)

Các byte có thể được chuyển đổi trở lại thành một đối tượng mã

eggs = marshal.loads(bytes)
exec(eggs)

Tệp

eggs = marshal.loads(bytes)
exec(eggs)
7 là một đối tượng mã thu được với tiêu đề

Đối với Python3, tiêu đề là 16 byte cần được bỏ qua, dữ liệu còn lại có thể được đọc qua

eggs = marshal.loads(bytes)
exec(eggs)
8.


Xem bài đăng trên blog của Ned Batchelder:

Ở cấp độ đơn giản, tệp .pyc là một tệp nhị phân chỉ chứa ba điều:

  • Một số ma thuật bốn byte,
  • Một dấu thời gian sửa đổi bốn byte và
  • Một đối tượng mã Marshalled.

Lưu ý, liên kết tham chiếu Python2, nhưng nó gần như giống nhau trong Python3, kích thước tiêu đề

eggs = marshal.loads(bytes)
exec(eggs)
7 chỉ là 16 thay vì 8 byte.

Chúng tôi đã bắt đầu loạt bài này với một cái nhìn tổng quan về VM Cpython. Chúng tôi đã học được rằng để chạy một chương trình Python, Cpython trước tiên biên dịch nó thành mã byte và chúng tôi đã nghiên cứu cách trình biên dịch hoạt động trong phần hai. Lần trước chúng tôi đã bước qua mã nguồn cpython bắt đầu với hàm

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
0 cho đến khi chúng tôi đạt được vòng lặp đánh giá, một nơi mà mã byte Python được thực thi. Lý do chính tại sao chúng tôi dành thời gian nghiên cứu những điều này là để chuẩn bị cho cuộc thảo luận mà chúng tôi bắt đầu ngày hôm nay. Mục tiêu của cuộc thảo luận này là để hiểu cách CPython làm những gì chúng ta bảo nó làm, đó là cách nó thực thi mã byte mà mã chúng ta viết biên dịch.

Lưu ý: Trong bài đăng này, tôi đang đề cập đến CPython 3.9. Một số chi tiết thực hiện chắc chắn sẽ thay đổi khi CPython phát triển. Tôi sẽ cố gắng theo dõi các thay đổi quan trọng và thêm ghi chú cập nhật.: In this post I'm referring to CPython 3.9. Some implementation details will certainly change as CPython evolves. I'll try to keep track of important changes and add update notes.

Điểm khởi đầu

Hãy nhớ lại ngắn gọn những gì chúng ta đã học được trong các phần trước. Chúng tôi nói với Cpython phải làm gì bằng cách viết mã Python. Tuy nhiên, VM Cpython chỉ hiểu mã Python byte. Đây là công việc của trình biên dịch để dịch mã python sang mã byte. Trình biên dịch lưu trữ mã byte trong một đối tượng mã, là một cấu trúc mô tả đầy đủ những gì một khối mã, như một mô -đun hoặc một hàm, làm. Để thực thi một đối tượng mã, trước tiên, CPython tạo ra trạng thái thực thi cho nó được gọi là đối tượng khung. Sau đó, nó chuyển một đối tượng khung cho chức năng đánh giá khung để thực hiện tính toán thực tế. Hàm đánh giá khung mặc định là

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
1 được xác định trong Python/ceval.c. Hàm này thực hiện lõi của VM Cpython. Cụ thể, nó thực hiện logic để thực hiện mã byte Python. Vì vậy, chức năng này là những gì chúng ta sẽ nghiên cứu ngày hôm nay.

Để hiểu cách thức hoạt động của

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
1, điều quan trọng là phải có ý tưởng về đầu vào của nó, một đối tượng khung, là gì. Một đối tượng khung là một đối tượng Python được xác định bởi cấu trúc C sau:

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

Trường

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
3 của đối tượng khung chỉ vào một đối tượng mã. Một đối tượng mã cũng là một đối tượng Python. Đây là định nghĩa của nó:

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

Trường quan trọng nhất của đối tượng mã là

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
4. Đó là một con trỏ tới đối tượng Byte Python đại diện cho mã byte. Mã byte là một chuỗi các hướng dẫn hai byte: một byte cho một opcode và một byte cho một đối số.

Đừng lo lắng nếu một số thành viên của các cấu trúc trên vẫn là một bí ẩn đối với bạn. Chúng ta sẽ thấy những gì chúng được sử dụng khi chúng ta tiến lên trong nỗ lực của chúng ta để hiểu cách thức VM CPython thực thi mã byte.

Tổng quan về vòng lặp đánh giá

Vấn đề thực hiện mã Bytepy Python có vẻ không có trí tuệ đối với bạn. Thật vậy, tất cả các VM phải làm là lặp lại các hướng dẫn và hành động theo chúng. Và đây là những gì về cơ bản

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
1 làm. Nó chứa một vòng lặp
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
6 vô hạn mà chúng tôi gọi là vòng đánh giá. Bên trong vòng lặp đó có một tuyên bố khổng lồ
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7 trên tất cả các mã hóa có thể. Mỗi opcode có một khối
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 tương ứng chứa mã để thực thi opcode đó. Mã byte được biểu thị bằng một mảng các số nguyên không dấu 16 bit, một số nguyên cho mỗi hướng dẫn. VM theo dõi hướng dẫn tiếp theo được thực thi bằng biến
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
9, đây là một con trỏ tới mảng hướng dẫn. Khi bắt đầu mỗi lần lặp của vòng lặp đánh giá, VM tính toán opcode tiếp theo và đối số của nó bằng cách sử dụng byte ít có ý nghĩa nhất và quan trọng nhất của lệnh tiếp theo và tăng
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
9. Hàm
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
1 dài gần 3000 dòng, nhưng bản chất của nó có thể được ghi lại bằng phiên bản đơn giản hóa sau:

PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}

Để có được một bức tranh thực tế hơn, hãy thảo luận về một số tác phẩm bị bỏ qua chi tiết hơn.

lý do để đình chỉ vòng lặp

Thỉnh thoảng, luồng hiện đang chạy dừng thực thi mã byte để làm việc khác hoặc không làm gì cả. Điều này có thể xảy ra do một trong bốn lý do:

  • Có tín hiệu để xử lý. Khi bạn đăng ký một chức năng làm trình xử lý tín hiệu bằng cách sử dụng
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    2, CPython sẽ lưu trữ chức năng này trong mảng trình xử lý. Hàm thực sự sẽ được gọi khi một luồng nhận được tín hiệu là
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    3 (nó được chuyển đến chức năng thư viện
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    4 trên các hệ thống giống UNIX). Khi được gọi,
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    3 đặt một biến Boolean nói rằng hàm trong mảng trình xử lý tương ứng với tín hiệu nhận được phải được gọi. Theo định kỳ, luồng chính của trình thông dịch chính gọi trình xử lý vấp.
  • Có những cuộc gọi đang chờ xử lý để gọi. Các cuộc gọi đang chờ xử lý là một cơ chế cho phép lên lịch một chức năng được thực thi từ luồng chính. Cơ chế này được phơi bày bởi API Python/C thông qua hàm
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    6.
  • Ngoại lệ không đồng bộ được nâng lên. Ngoại lệ không đồng bộ là một ngoại lệ được đặt trong một luồng từ một luồng khác. Điều này có thể được thực hiện bằng cách sử dụng hàm
    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest aren't used in either hash or comparisons, except for co_name,
           used in both. This is done to preserve the name and line number
           for tracebacks and debuggers; otherwise, constant de-duplication
           would collapse identical functions/lambdas defined on different lines.
        */
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
        PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                       Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;       /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
        /* Scratch space for extra data relating to the code object.
           Type is a void* to keep the format private in codeobject.c to force
           people to go through the proper APIs. */
        void *co_extra;
    
        /* Per opcodes just-in-time cache
         *
         * To reduce cache size, we use indirect mapping from opcode index to
         * cache object:
         *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
         */
    
        // co_opcache_map is indexed by (next_instr - first_instr).
        //  * 0 means there is no cache for this opcode.
        //  * n > 0 means there is cache in co_opcache[n-1].
        unsigned char *co_opcache_map;
        _PyOpcache *co_opcache;
        int co_opcache_flag;  // used to determine when create a cache.
        unsigned char co_opcache_size;  // length of co_opcache.
    };
    
    7 được cung cấp bởi API Python/C.
  • Chủ đề hiện đang chạy được yêu cầu thả GIL. Khi nó nhìn thấy một yêu cầu như vậy, nó sẽ giảm GIL và đợi cho đến khi nó có được Gil một lần nữa.

CPython có các chỉ số cho mỗi sự kiện này. Biến chỉ ra rằng có những người xử lý để gọi là thành viên của

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
8, đó là một cấu trúc
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};
9:

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters (see
       bpo-40513). Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals(). */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};

Các chỉ số khác là thành viên của

PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
0 là một cấu trúc
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
1:

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};

Kết quả của việc cung cấp tất cả các chỉ số cùng nhau được lưu trữ trong biến

PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
2. Nó cho biết liệu có bất kỳ lý do nào cho chủ đề đang chạy hiện đang dừng thực thi mã byte bình thường của nó hay không. Mỗi lần lặp của vòng đánh giá bắt đầu với việc kiểm tra xem
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
2 có đúng không. Nếu đó là sự thật, luồng kiểm tra các chỉ số để xác định chính xác nó được yêu cầu làm gì, có phải điều đó và tiếp tục thực thi mã byte.

tính toán gotos

Mã cho vòng đánh giá có đầy đủ các macro như

PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
4 và
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
5. Đây không phải là phương tiện để làm cho mã nhỏ gọn hơn. Chúng mở rộng sang mã khác nhau tùy thuộc vào việc tối ưu hóa nhất định, được gọi là "gotos được tính toán" (a.k.a. "Mã luồng"), được sử dụng. Mục tiêu của chúng là tối ưu hóa này là tăng tốc độ thực thi mã byte bằng cách viết mã theo cách đó, để CPU có thể sử dụng cơ chế dự đoán nhánh của nó để dự đoán mã hóa tiếp theo.

Sau khi thực hiện bất kỳ lệnh nào đã cho, VM thực hiện một trong ba điều:

  • Nó trở về từ chức năng đánh giá. Điều này xảy ra khi VM thực thi hướng dẫn
    PyObject*
    _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
    {
        // ... declarations and initialization of local variables
        // ... macros definitions
        // ... call depth handling
        // ... code for tracing and profiling
    
        for (;;) {
            // ... check if the bytecode execution must be suspended,
            // e.g. other thread requested the GIL
    
            // NEXTOPARG() macro
            _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
            opcode = _Py_OPCODE(word);
            oparg = _Py_OPARG(word);
            next_instr++;
    
            switch (opcode) {
                case TARGET(NOP) {
                    FAST_DISPATCH(); // more on this later
                }
    
                case TARGET(LOAD_FAST) {
                    // ... code for loading local variable
                }
    
                // ... 117 more cases for every possible opcode
            }
    
            // ... error handling
        }
    
        // ... termination
    }
    
    6,
    PyObject*
    _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
    {
        // ... declarations and initialization of local variables
        // ... macros definitions
        // ... call depth handling
        // ... code for tracing and profiling
    
        for (;;) {
            // ... check if the bytecode execution must be suspended,
            // e.g. other thread requested the GIL
    
            // NEXTOPARG() macro
            _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
            opcode = _Py_OPCODE(word);
            oparg = _Py_OPARG(word);
            next_instr++;
    
            switch (opcode) {
                case TARGET(NOP) {
                    FAST_DISPATCH(); // more on this later
                }
    
                case TARGET(LOAD_FAST) {
                    // ... code for loading local variable
                }
    
                // ... 117 more cases for every possible opcode
            }
    
            // ... error handling
        }
    
        // ... termination
    }
    
    7 hoặc
    PyObject*
    _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
    {
        // ... declarations and initialization of local variables
        // ... macros definitions
        // ... call depth handling
        // ... code for tracing and profiling
    
        for (;;) {
            // ... check if the bytecode execution must be suspended,
            // e.g. other thread requested the GIL
    
            // NEXTOPARG() macro
            _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
            opcode = _Py_OPCODE(word);
            oparg = _Py_OPARG(word);
            next_instr++;
    
            switch (opcode) {
                case TARGET(NOP) {
                    FAST_DISPATCH(); // more on this later
                }
    
                case TARGET(LOAD_FAST) {
                    // ... code for loading local variable
                }
    
                // ... 117 more cases for every possible opcode
            }
    
            // ... error handling
        }
    
        // ... termination
    }
    
    8.
  • Nó xử lý lỗi và tiếp tục thực thi hoặc trả về từ chức năng đánh giá với bộ ngoại lệ. Lỗi có thể xảy ra khi, ví dụ, VM thực hiện lệnh
    PyObject*
    _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
    {
        // ... declarations and initialization of local variables
        // ... macros definitions
        // ... call depth handling
        // ... code for tracing and profiling
    
        for (;;) {
            // ... check if the bytecode execution must be suspended,
            // e.g. other thread requested the GIL
    
            // NEXTOPARG() macro
            _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
            opcode = _Py_OPCODE(word);
            oparg = _Py_OPARG(word);
            next_instr++;
    
            switch (opcode) {
                case TARGET(NOP) {
                    FAST_DISPATCH(); // more on this later
                }
    
                case TARGET(LOAD_FAST) {
                    // ... code for loading local variable
                }
    
                // ... 117 more cases for every possible opcode
            }
    
            // ... error handling
        }
    
        // ... termination
    }
    
    9 và các đối tượng được thêm vào không thực hiện các phương thức
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters (see
           bpo-40513). Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals(). */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    0 và
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters (see
           bpo-40513). Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals(). */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    1.
  • Nó tiếp tục thực hiện. Làm thế nào để thực hiện VM thực hiện lệnh tiếp theo? Giải pháp đơn giản nhất sẽ là kết thúc mỗi khối
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
        PyObject *f_globals;        /* global symbol table (PyDictObject) */
        PyObject *f_locals;         /* local symbol table (any mapping) */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    8 không hoàn nguyên với câu lệnh
    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters (see
           bpo-40513). Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals(). */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    3. Giải pháp thực sự, mặc dù, phức tạp hơn một chút.

Để xem vấn đề với câu lệnh

struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters (see
       bpo-40513). Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals(). */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 đơn giản, chúng ta cần hiểu những gì
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7 biên dịch. Opcode là một số nguyên từ 0 đến 255. Vì phạm vi dày đặc, trình biên dịch có thể tạo một bảng nhảy lưu trữ các địa chỉ của các khối
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 và sử dụng các opcode làm chỉ số vào bảng đó. Các trình biên dịch hiện đại thực sự làm điều đó, vì vậy việc gửi các trường hợp được thực hiện một cách hiệu quả như một bước nhảy gián tiếp. Đây là một cách hiệu quả để thực hiện
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7. Tuy nhiên, việc đặt
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7 bên trong vòng lặp và thêm câu lệnh
struct _ceval_runtime_state {
    /* Request for checking signals. It is shared by all interpreters (see
       bpo-40513). Any thread of any interpreter can receive a signal, but only
       the main thread of the main interpreter can handle signals: see
       _Py_ThreadCanHandleSignals(). */
    _Py_atomic_int signals_pending;
    struct _gil_runtime_state gil;
};
3 tạo ra hai sự thiếu hiệu quả:

  • Câu lệnh

    struct _ceval_runtime_state {
        /* Request for checking signals. It is shared by all interpreters (see
           bpo-40513). Any thread of any interpreter can receive a signal, but only
           the main thread of the main interpreter can handle signals: see
           _Py_ThreadCanHandleSignals(). */
        _Py_atomic_int signals_pending;
        struct _gil_runtime_state gil;
    };
    
    3 vào cuối khối
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
        PyObject *f_globals;        /* global symbol table (PyDictObject) */
        PyObject *f_locals;         /* local symbol table (any mapping) */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    8 thêm một bước nhảy khác. Do đó, để thực thi một opcode, VM phải nhảy hai lần: đến khi bắt đầu vòng lặp và sau đó đến khối
    // typedef struct _frame PyFrameObject; in other place
    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
        PyObject *f_globals;        /* global symbol table (PyDictObject) */
        PyObject *f_locals;         /* local symbol table (any mapping) */
        PyObject **f_valuestack;    /* points after the last local */
        /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
           Frame evaluation usually NULLs it, but a frame that yields sets it
           to the current stack top. */
        PyObject **f_stacktop;
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    
    8 tiếp theo.

  • Vì tất cả các opcode được gửi bởi một bước nhảy, một CPU có một ít cơ hội dự đoán opcode tiếp theo. Điều tốt nhất có thể làm là chọn opcode cuối cùng hoặc, có thể là cái thường xuyên nhất.

Ý tưởng về việc tối ưu hóa là đặt một bước nhảy riêng biệt vào cuối mỗi khối

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 không hoàn trả. Đầu tiên, nó tiết kiệm một bước nhảy. Thứ hai, CPU có thể dự đoán opcode tiếp theo là mã opcode có thể xảy ra nhất sau mã hiện tại.

Việc tối ưu hóa có thể được bật hoặc tắt. Nó phụ thuộc vào việc trình biên dịch có hỗ trợ tiện ích mở rộng GCC C được gọi là "nhãn là giá trị" hay không. Hiệu quả của việc cho phép tối ưu hóa là các macro nhất định, chẳng hạn như

PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
4,
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
5 và
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
6, mở rộng theo cách khác nhau. Các macro này được sử dụng rộng rãi trong toàn bộ mã của vòng đánh giá. Mỗi biểu thức trường hợp có dạng
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
7, trong đó
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
8 là một macro cho số nguyên theo nghĩa đen đại diện cho một opcode. Và mỗi khối
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 không hoàn nguyên kết thúc bằng macro
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
5 hoặc
struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
6. Trước tiên, chúng ta hãy nhìn vào những gì các macro này mở rộng đến khi tối ưu hóa bị vô hiệu hóa:

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}

Macro

struct _ceval_state {
    int recursion_limit;
    /* Records whether tracing is on for any thread.  Counts the number
       of threads for which tstate->c_tracefunc is non-NULL, so if the
       value is 0, we know we don't have to check this thread's
       c_tracefunc.  This speeds up the if statement in
       _PyEval_EvalFrameDefault() after fast_next_opcode. */
    int tracing_possible;
    /* This single variable consolidates all requests to break out of
       the fast path in the eval loop. */
    _Py_atomic_int eval_breaker;
    /* Request for dropping the GIL */
    _Py_atomic_int gil_drop_request;
    struct _pending_calls pending;
};
6 được sử dụng cho một số opcode khi không mong muốn để treo vòng lặp đánh giá sau khi thực hiện opcode đó. Nếu không, việc thực hiện rất đơn giản.

Nếu trình biên dịch hỗ trợ tiện ích mở rộng "Nhãn là giá trị", chúng ta có thể sử dụng toán tử

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
3 Unary trên nhãn để lấy địa chỉ của nó. Nó có giá trị loại
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
4, vì vậy chúng tôi có thể lưu trữ nó trong một con trỏ:

Sau đó chúng ta có thể vào nhãn bằng cách phân tích con trỏ:

Phần mở rộng này cho phép thực hiện một bảng nhảy trong C dưới dạng một mảng các con trỏ nhãn. Và đó là những gì Cpython làm:

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};

Đây là phiên bản tối ưu của vòng lặp đánh giá trông như thế nào:

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}

Phần mở rộng được hỗ trợ bởi các trình biên dịch GCC và Clang. Vì vậy, khi bạn chạy

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
5, bạn có thể đã bật tối ưu hóa. Câu hỏi, tất nhiên, là nó ảnh hưởng đến hiệu suất như thế nào. Ở đây, tôi sẽ dựa vào nhận xét từ mã nguồn:

Tại thời điểm viết bài này, phiên bản "mã luồng" nhanh hơn tới 15-20% so với phiên bản "chuyển đổi" bình thường, tùy thuộc vào trình biên dịch và kiến ​​trúc CPU.

Phần này sẽ cho chúng ta một ý tưởng về cách VM CPython đi từ hướng dẫn này sang hướng dẫn tiếp theo và những gì nó có thể làm ở giữa. Bước hợp lý tiếp theo là nghiên cứu sâu hơn về cách VM thực hiện một hướng dẫn duy nhất. CPython 3.9 có 119 mã hóa khác nhau. Tất nhiên, chúng tôi sẽ không nghiên cứu việc triển khai từng opcode trong bài đăng này. Thay vào đó, chúng tôi sẽ tập trung vào các nguyên tắc chung mà VM sử dụng để thực hiện chúng.

Giá trị ngăn xếp

Điều quan trọng nhất và may mắn thay, rất đơn giản về VM CPython là dựa trên ngăn xếp. Điều này có nghĩa là để tính toán mọi thứ, các giá trị VM POP (hoặc peek) từ ngăn xếp, thực hiện tính toán trên chúng và đẩy kết quả trở lại. Đây là một số ví dụ:

  • Giá trị Opcode Opcode
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH() macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH() macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    6 từ ngăn xếp, phủ nhận nó và đẩy kết quả.
  • Giá trị Opcode ____77 Opcode từ ngăn xếp, gọi
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP
            case NOP: {
                goto fast_next_opcode; // FAST_DISPATCH() macro
            }
    
            // ...
    
            case BINARY_MULTIPLY: {
                // ... code for binary multiplication
                continue; // DISPATCH() macro
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    8 trên đó và đẩy kết quả.
  • Giá trị Opcode Opcode
    PyObject*
    _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
    {
        // ... declarations and initialization of local variables
        // ... macros definitions
        // ... call depth handling
        // ... code for tracing and profiling
    
        for (;;) {
            // ... check if the bytecode execution must be suspended,
            // e.g. other thread requested the GIL
    
            // NEXTOPARG() macro
            _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
            opcode = _Py_OPCODE(word);
            oparg = _Py_OPARG(word);
            next_instr++;
    
            switch (opcode) {
                case TARGET(NOP) {
                    FAST_DISPATCH(); // more on this later
                }
    
                case TARGET(LOAD_FAST) {
                    // ... code for loading local variable
                }
    
                // ... 117 more cases for every possible opcode
            }
    
            // ... error handling
        }
    
        // ... termination
    }
    
    9 từ ngăn xếp, peek một giá trị khác từ trên cùng, thêm giá trị thứ nhất vào thứ hai và thay thế giá trị trên cùng với kết quả.

Ngăn xếp giá trị nằm trong một đối tượng khung. Nó được triển khai như một phần của mảng được gọi là

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
0. Mảng được chia thành một số phần để lưu trữ những thứ khác nhau, nhưng chỉ phần cuối cùng được sử dụng cho ngăn xếp giá trị. Sự khởi đầu của phần này là đáy của ngăn xếp. Trường
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
1 của một đối tượng khung chỉ vào nó. Để xác định vị trí đỉnh của ngăn xếp, CPython giữ biến cục bộ
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
2, chỉ vào vị trí tiếp theo sau đỉnh của ngăn xếp. Các yếu tố của mảng
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
0 là con trỏ đến các đối tượng Python và con trỏ đến các đối tượng Python là những gì VM Cpython thực sự hoạt động.

Xử lý lỗi và ngăn chặn ngăn chặn

Không phải tất cả các tính toán được thực hiện bởi VM đều thành công. Giả sử chúng ta cố gắng thêm một số vào một chuỗi như

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
4. Trình biên dịch tạo ra opcode
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
9 để thêm hai đối tượng. Khi VM thực thi mã hóa này, nó gọi
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
6 để tính toán kết quả:

eggs = marshal.loads(bytes)
exec(eggs)
0

Điều quan trọng đối với chúng tôi bây giờ không phải là cách

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
6 được thực hiện, mà là cuộc gọi đến nó dẫn đến một lỗi. Lỗi có nghĩa là hai điều:

  • static void *opcode_targets[256] = {
        &&_unknown_opcode,
        &&TARGET_POP_TOP,
        &&TARGET_ROT_TWO,
        &&TARGET_ROT_THREE,
        &&TARGET_DUP_TOP,
        &&TARGET_DUP_TOP_TWO,
        &&TARGET_ROT_FOUR,
        &&_unknown_opcode,
        &&_unknown_opcode,
        &&TARGET_NOP,
        &&TARGET_UNARY_POSITIVE,
        &&TARGET_UNARY_NEGATIVE,
        &&TARGET_UNARY_NOT,
        // ... quite a few more
    };
    
    6 Trả về
    static void *opcode_targets[256] = {
        &&_unknown_opcode,
        &&TARGET_POP_TOP,
        &&TARGET_ROT_TWO,
        &&TARGET_ROT_THREE,
        &&TARGET_DUP_TOP,
        &&TARGET_DUP_TOP_TWO,
        &&TARGET_ROT_FOUR,
        &&_unknown_opcode,
        &&_unknown_opcode,
        &&TARGET_NOP,
        &&TARGET_UNARY_POSITIVE,
        &&TARGET_UNARY_NEGATIVE,
        &&TARGET_UNARY_NOT,
        // ... quite a few more
    };
    
    9.
  • static void *opcode_targets[256] = {
        &&_unknown_opcode,
        &&TARGET_POP_TOP,
        &&TARGET_ROT_TWO,
        &&TARGET_ROT_THREE,
        &&TARGET_DUP_TOP,
        &&TARGET_DUP_TOP_TWO,
        &&TARGET_ROT_FOUR,
        &&_unknown_opcode,
        &&_unknown_opcode,
        &&TARGET_NOP,
        &&TARGET_UNARY_POSITIVE,
        &&TARGET_UNARY_NEGATIVE,
        &&TARGET_UNARY_NOT,
        // ... quite a few more
    };
    
    6 đặt ngoại lệ hiện tại cho ngoại lệ
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP: TARGET_NOP:
            // TARGET_NOP is a label
            case NOP: TARGET_NOP: {
                // FAST_DISPATCH() macro
                // when tracing is disabled
                f->f_lasti = INSTR_OFFSET();
                NEXTOPARG();
                goto *opcode_targets[opcode];
            }
    
            // ...
    
            case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
                // ... code for binary multiplication
                // DISPATCH() macro
                if (!_Py_atomic_load_relaxed(eval_breaker)) {
                  FAST_DISPATCH();
                }
                continue;
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    1. Điều này liên quan đến việc thiết lập
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP: TARGET_NOP:
            // TARGET_NOP is a label
            case NOP: TARGET_NOP: {
                // FAST_DISPATCH() macro
                // when tracing is disabled
                f->f_lasti = INSTR_OFFSET();
                NEXTOPARG();
                goto *opcode_targets[opcode];
            }
    
            // ...
    
            case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
                // ... code for binary multiplication
                // DISPATCH() macro
                if (!_Py_atomic_load_relaxed(eval_breaker)) {
                  FAST_DISPATCH();
                }
                continue;
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    2,
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP: TARGET_NOP:
            // TARGET_NOP is a label
            case NOP: TARGET_NOP: {
                // FAST_DISPATCH() macro
                // when tracing is disabled
                f->f_lasti = INSTR_OFFSET();
                NEXTOPARG();
                goto *opcode_targets[opcode];
            }
    
            // ...
    
            case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
                // ... code for binary multiplication
                // DISPATCH() macro
                if (!_Py_atomic_load_relaxed(eval_breaker)) {
                  FAST_DISPATCH();
                }
                continue;
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    3 và
    for (;;) {
        // ... check if the bytecode execution must be suspended
    
    fast_next_opcode:
        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr;
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;
    
        switch (opcode) {
            // TARGET(NOP) expands to NOP: TARGET_NOP:
            // TARGET_NOP is a label
            case NOP: TARGET_NOP: {
                // FAST_DISPATCH() macro
                // when tracing is disabled
                f->f_lasti = INSTR_OFFSET();
                NEXTOPARG();
                goto *opcode_targets[opcode];
            }
    
            // ...
    
            case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
                // ... code for binary multiplication
                // DISPATCH() macro
                if (!_Py_atomic_load_relaxed(eval_breaker)) {
                  FAST_DISPATCH();
                }
                continue;
            }
    
            // ...
        }
    
        // ... error handling
    }
    
    4.

static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
9 là một chỉ số cho một lỗi. VM nhìn thấy nó và đi đến nhãn
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
6 ở cuối vòng đánh giá. Điều gì xảy ra tiếp theo phụ thuộc vào việc chúng tôi có thiết lập bất kỳ trình xử lý ngoại lệ nào hay không. Nếu chúng ta không có, VM đạt được câu lệnh
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
7 và chức năng đánh giá trả về
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
9 với ngoại lệ được đặt trên trạng thái luồng. Cpython in các chi tiết của ngoại lệ và thoát. Chúng tôi nhận được kết quả dự kiến:

eggs = marshal.loads(bytes)
exec(eggs)
1

Nhưng giả sử rằng chúng ta đặt cùng một mã bên trong mệnh đề

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
9 của câu lệnh
eggs = marshal.loads(bytes)
exec(eggs)
00. Trong trường hợp này, mã bên trong mệnh đề
eggs = marshal.loads(bytes)
exec(eggs)
01 cũng được thực thi:

eggs = marshal.loads(bytes)
exec(eggs)
2

Làm thế nào VM có thể tiếp tục thực thi sau khi lỗi đã xảy ra? Chúng ta hãy xem mã byte được tạo bởi trình biên dịch cho câu lệnh

eggs = marshal.loads(bytes)
exec(eggs)
00:

eggs = marshal.loads(bytes)
exec(eggs)
3

Lưu ý các opcodes

eggs = marshal.loads(bytes)
exec(eggs)
03 và
eggs = marshal.loads(bytes)
exec(eggs)
04. Cái đầu tiên thiết lập trình xử lý ngoại lệ và cái thứ hai loại bỏ nó. Nếu xảy ra lỗi trong khi VM thực hiện các hướng dẫn giữa chúng, việc thực thi tiếp tục với lệnh ở Offset 22, đó là khởi đầu của mệnh đề
eggs = marshal.loads(bytes)
exec(eggs)
01. Mặt khác, mệnh đề
eggs = marshal.loads(bytes)
exec(eggs)
01 được thực thi sau mệnh đề
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
9. Trong cả hai trường hợp, mã byte cho mệnh đề
eggs = marshal.loads(bytes)
exec(eggs)
01 gần như giống hệt nhau. Sự khác biệt duy nhất là người xử lý tái tạo ngoại lệ được đặt trong mệnh đề
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
9.

Một trình xử lý ngoại lệ được triển khai dưới dạng cấu trúc C đơn giản được gọi là khối:

eggs = marshal.loads(bytes)
exec(eggs)
4

VM giữ các khối trong ngăn xếp khối. Để thiết lập một trình xử lý ngoại lệ có nghĩa là đẩy một khối mới vào ngăn xếp khối. Đây là những gì opcodes như

eggs = marshal.loads(bytes)
exec(eggs)
03 làm. Nhãn
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
6 trỏ đến một đoạn mã cố gắng xử lý lỗi bằng cách sử dụng các khối trên ngăn xếp khối. VM thư giãn ngăn xếp khối cho đến khi nó tìm thấy khối trên cùng của loại
eggs = marshal.loads(bytes)
exec(eggs)
03. Nó khôi phục mức của ngăn xếp giá trị về mức được chỉ định bởi trường
eggs = marshal.loads(bytes)
exec(eggs)
13 của khối và tiếp tục thực thi mã byte với lệnh tại Offset
eggs = marshal.loads(bytes)
exec(eggs)
14. Đây về cơ bản là cách CPYThon thực hiện các tuyên bố như
eggs = marshal.loads(bytes)
exec(eggs)
15,
eggs = marshal.loads(bytes)
exec(eggs)
00 và
eggs = marshal.loads(bytes)
exec(eggs)
17.

Có một điều nữa để nói về xử lý ngoại lệ. Hãy nghĩ về những gì xảy ra khi xảy ra lỗi trong khi VM xử lý một ngoại lệ:

eggs = marshal.loads(bytes)
exec(eggs)
5

Đúng như dự đoán, Cpython in ngoại lệ ban đầu. Để thực hiện hành vi đó, khi CPython xử lý một ngoại lệ bằng cách sử dụng khối

eggs = marshal.loads(bytes)
exec(eggs)
03, nó sẽ thiết lập một khối khác của loại
eggs = marshal.loads(bytes)
exec(eggs)
19. Nếu xảy ra lỗi khi một khối loại này nằm trên ngăn xếp khối, VM sẽ nhận được ngoại lệ ban đầu từ ngăn xếp giá trị và đặt nó thành hiện tại. Cpython đã từng có các loại khối khác nhau nhưng bây giờ chỉ là
eggs = marshal.loads(bytes)
exec(eggs)
03 và
eggs = marshal.loads(bytes)
exec(eggs)
19.

Ngăn xếp khối được triển khai dưới dạng mảng

eggs = marshal.loads(bytes)
exec(eggs)
22 trong một đối tượng khung. Kích thước của mảng được xác định tĩnh là 20. Vì vậy, nếu bạn làm tổ hơn 20 mệnh đề
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP: TARGET_NOP:
        // TARGET_NOP is a label
        case NOP: TARGET_NOP: {
            // FAST_DISPATCH() macro
            // when tracing is disabled
            f->f_lasti = INSTR_OFFSET();
            NEXTOPARG();
            goto *opcode_targets[opcode];
        }

        // ...

        case BINARY_MULTIPLY: TARGET_BINARY_MULTIPLY: {
            // ... code for binary multiplication
            // DISPATCH() macro
            if (!_Py_atomic_load_relaxed(eval_breaker)) {
              FAST_DISPATCH();
            }
            continue;
        }

        // ...
    }

    // ... error handling
}
9, bạn sẽ nhận được
eggs = marshal.loads(bytes)
exec(eggs)
24.

Bản tóm tắt

Hôm nay, chúng tôi đã học được rằng CPython VM thực thi các hướng dẫn mã byte từng cái một trong một vòng lặp vô hạn. Vòng lặp chứa một câu lệnh

// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
7 trên tất cả các opcodes có thể. Mỗi opcode được thực thi trong khối
// typedef struct _frame PyFrameObject; in other place
struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};
8 tương ứng. Hàm đánh giá chạy trong một luồng và đôi khi luồng đó treo vòng lặp để làm một cái gì đó khác. Ví dụ, một luồng có thể cần phải phát hành GIL, để luồng khác có thể lấy nó và tiếp tục thực thi mã byte của nó. Để tăng tốc độ thực thi mã byte, CPython sử dụng tối ưu hóa cho phép sử dụng cơ chế dự đoán nhánh của CPU. Một nhận xét nói rằng nó làm cho CPython nhanh hơn 15-20%.

Chúng tôi cũng đã xem xét hai cấu trúc dữ liệu quan trọng cho việc thực thi mã byte:

  • Giá trị ngăn xếp mà VM sử dụng để tính toán mọi thứ; và
  • ngăn xếp khối mà VM sử dụng để xử lý các ngoại lệ.

Kết luận quan trọng nhất từ ​​bài viết là: Nếu bạn muốn nghiên cứu việc thực hiện một số khía cạnh của Python, vòng lặp đánh giá là một nơi hoàn hảo để bắt đầu. Bạn muốn biết điều gì xảy ra khi bạn viết

eggs = marshal.loads(bytes)
exec(eggs)
27? Hãy xem mã cho opcode
PyObject*
_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
{
    // ... declarations and initialization of local variables
    // ... macros definitions
    // ... call depth handling
    // ... code for tracing and profiling

    for (;;) {
        // ... check if the bytecode execution must be suspended,
        // e.g. other thread requested the GIL

        // NEXTOPARG() macro
        _Py_CODEUNIT word = *next_instr; // _Py_CODEUNIT is a typedef for uint16_t
        opcode = _Py_OPCODE(word);
        oparg = _Py_OPARG(word);
        next_instr++;

        switch (opcode) {
            case TARGET(NOP) {
                FAST_DISPATCH(); // more on this later
            }

            case TARGET(LOAD_FAST) {
                // ... code for loading local variable
            }

            // ... 117 more cases for every possible opcode
        }

        // ... error handling
    }

    // ... termination
}
9. Bạn muốn biết tuyên bố
eggs = marshal.loads(bytes)
exec(eggs)
17 được thực hiện như thế nào? Xem
eggs = marshal.loads(bytes)
exec(eggs)
30. Quan tâm đến ngữ nghĩa chính xác của một cuộc gọi chức năng? Opcode
eggs = marshal.loads(bytes)
exec(eggs)
31 là những gì bạn đang tìm kiếm. Chúng tôi sẽ áp dụng phương pháp này vào lần tới khi nghiên cứu cách các biến được thực hiện trong CPython.

Nếu bạn có bất kỳ câu hỏi, nhận xét hoặc đề xuất nào, vui lòng liên hệ với tôi tại

Cập nhật từ ngày 10 tháng 11 năm 2020: Tôi đã được chỉ ra trong các bình luận về HN rằng

for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
6 và
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
7 opcodes nhìn thấy giá trị trên đầu ngăn xếp và thay thế nó bằng kết quả thay vì bật giá trị và đẩy kết quả. Điều này thực sự là như vậy. Về mặt ngữ nghĩa, hai cách tiếp cận là tương đương. Sự khác biệt duy nhất là cách tiếp cận pop/đẩy đầu tiên và sau đó tăng
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
2. Cpython tránh các hoạt động dư thừa này.
: I was pointed out in the comments on HN that the
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
6 and
for (;;) {
    // ... check if the bytecode execution must be suspended

fast_next_opcode:
    // NEXTOPARG() macro
    _Py_CODEUNIT word = *next_instr;
    opcode = _Py_OPCODE(word);
    oparg = _Py_OPARG(word);
    next_instr++;

    switch (opcode) {
        // TARGET(NOP) expands to NOP
        case NOP: {
            goto fast_next_opcode; // FAST_DISPATCH() macro
        }

        // ...

        case BINARY_MULTIPLY: {
            // ... code for binary multiplication
            continue; // DISPATCH() macro
        }

        // ...
    }

    // ... error handling
}
7 opcodes peek the value on top of the stack and replace it with the result instead of popping the value and pushing the result. This is indeed so. Semantically, the two approaches are equivalent. The only difference is that the pop/push approach first decrements and then increments
static void *opcode_targets[256] = {
    &&_unknown_opcode,
    &&TARGET_POP_TOP,
    &&TARGET_ROT_TWO,
    &&TARGET_ROT_THREE,
    &&TARGET_DUP_TOP,
    &&TARGET_DUP_TOP_TWO,
    &&TARGET_ROT_FOUR,
    &&_unknown_opcode,
    &&_unknown_opcode,
    &&TARGET_NOP,
    &&TARGET_UNARY_POSITIVE,
    &&TARGET_UNARY_NEGATIVE,
    &&TARGET_UNARY_NOT,
    // ... quite a few more
};
2. CPython avoids these redundant operations.

Mã byte được thực thi bởi những gì?

Chấp hành. Một chương trình ByteCode có thể được thực thi bằng cách phân tích cú pháp và thực hiện trực tiếp các hướng dẫn, mỗi lần một. Loại thông dịch viên byte này rất di động. Một số hệ thống, được gọi là Trình dịch động hoặc trình biên dịch chỉ trong thời gian (JIT), dịch mã byte thành mã máy khi cần thiết trong thời gian chạy.parsing and directly executing the instructions, one at a time. This kind of bytecode interpreter is very portable. Some systems, called dynamic translators, or just-in-time (JIT) compilers, translate bytecode into machine code as necessary at runtime.

Làm thế nào là mã byte python được tạo ra?

Mã byte được tự động tạo trong cùng một thư mục với tệp .py, khi một mô -đun Python lần đầu tiên được nhập hoặc khi nguồn gần đây hơn tệp được biên dịch hiện tại. Lần tới, khi chương trình được chạy, người thông báo Python sử dụng tệp này để bỏ qua bước biên dịch.automatically created in the same directory as . py file, when a module of python is imported for the first time, or when the source is more recent than the current compiled file. Next time, when the program is run, python interpretator use this file to skip the compilation step.

Tại sao Python sử dụng mã byte?

Python chậm hơn so với ngôn ngữ lập trình khác, nhưng quá trình chuyển đổi mã Python thành mã byte giúp truy cập nhanh hơn mỗi lần sau khi mã được giải thích một lần.Mã byte này được lưu trong tệp có tên giống như tệp nguồn nhưng với một tiện ích mở rộng khác có tên là PYC PYC.makes it faster to access each time after the code is interpreted once. This bytecode is saved in the file named same as the source file but with a different extension named as “pyc”.

Python có được biên dịch cho mã byte không?

Python là một ngôn ngữ được giải thích, có nghĩa là mã nguồn của chương trình Python được chuyển đổi thành mã byte sau đó được thực hiện bởi máy ảo Python.Python khác với các ngôn ngữ được biên dịch chính, chẳng hạn như C và C + +, vì mã Python không bắt buộc phải được xây dựng và liên kết như mã cho các ngôn ngữ này.the source code of a Python program is converted into bytecode that is then executed by the Python virtual machine. Python is different from major compiled languages, such as C and C + +, as Python code is not required to be built and linked like code for these languages.