Ngoài thời gian lưu trữ biến cục bộ / toàn cầu, dự đoán opcode làm cho chức năng nhanh hơn.
Như các câu trả lời khác giải thích, hàm sử dụng STORE_FAST
opcode trong vòng lặp. Đây là mã byte cho vòng lặp của hàm:
>> 13 FOR_ITER 6 (to 22) # get next value from iterator
16 STORE_FAST 0 (x) # set local variable
19 JUMP_ABSOLUTE 13 # back to FOR_ITER
Thông thường khi một chương trình được chạy, Python thực thi lần lượt từng opcode, theo dõi ngăn xếp và tạo ra các kiểm tra khác trên khung ngăn xếp sau khi mỗi opcode được thực thi. Dự đoán Opcode có nghĩa là trong một số trường hợp, Python có thể nhảy trực tiếp sang opcode tiếp theo, do đó tránh được một số chi phí này.
Trong trường hợp này, mỗi khi Python nhìn thấy FOR_ITER
(đỉnh của vòng lặp), nó sẽ "dự đoán" đó STORE_FAST
là opcode tiếp theo mà nó phải thực thi. Python sau đó nhìn trộm mã op tiếp theo và, nếu dự đoán là chính xác, nó sẽ nhảy thẳng vào STORE_FAST
. Điều này có tác dụng ép hai opcode thành một opcode duy nhất.
Mặt khác, STORE_NAME
opcode được sử dụng trong vòng lặp ở cấp độ toàn cầu. Python không * không * đưa ra dự đoán tương tự khi nhìn thấy opcode này. Thay vào đó, nó phải quay trở lại đầu vòng lặp đánh giá có ý nghĩa rõ ràng đối với tốc độ thực hiện vòng lặp.
Để cung cấp thêm một số chi tiết kỹ thuật về tối ưu hóa này, đây là trích dẫn từ ceval.c
tệp ("công cụ" của máy ảo Python):
Một số opcode có xu hướng đi theo cặp do đó có thể dự đoán mã thứ hai khi mã đầu tiên được chạy. Ví dụ,
GET_ITER
thường được theo sau bởi FOR_ITER
. Và FOR_ITER
thường được theo sau bởiSTORE_FAST
hoặc UNPACK_SEQUENCE
.
Việc xác minh dự đoán sẽ tốn một thử nghiệm tốc độ cao duy nhất của biến đăng ký so với hằng số. Nếu việc ghép nối là tốt, thì việc xác định nhánh bên trong của bộ xử lý có khả năng thành công cao, dẫn đến việc chuyển đổi gần như bằng không sang opcode tiếp theo. Một dự đoán thành công tiết kiệm một chuyến đi thông qua vòng lặp eval bao gồm hai nhánh không thể đoán trước của nó, HAS_ARG
thử nghiệm và trường hợp chuyển đổi. Kết hợp với dự đoán nhánh bên trong của bộ xử lý, một thành công PREDICT
có tác dụng làm cho hai opcode chạy như thể chúng là một opcode mới duy nhất với các phần thân được kết hợp.
Chúng ta có thể thấy trong mã nguồn cho FOR_ITER
opcode chính xác nơi dự đoán STORE_FAST
được thực hiện:
case FOR_ITER: // the FOR_ITER opcode case
v = TOP();
x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
if (x != NULL) {
PUSH(x); // put x on top of the stack
PREDICT(STORE_FAST); // predict STORE_FAST will follow - success!
PREDICT(UNPACK_SEQUENCE); // this and everything below is skipped
continue;
}
// error-checking and more code for when the iterator ends normally
Các PREDICT
chức năng mở rộng để if (*next_instr == op) goto PRED_##op
tức là chúng ta chỉ cần nhảy đến sự bắt đầu của opcode dự đoán. Trong trường hợp này, chúng tôi nhảy vào đây:
PREDICTED_WITH_ARG(STORE_FAST);
case STORE_FAST:
v = POP(); // pop x back off the stack
SETLOCAL(oparg, v); // set it as the new local variable
goto fast_next_opcode;
Biến cục bộ hiện được đặt và opcode tiếp theo sẽ được thực thi. Python tiếp tục lặp đi lặp lại cho đến khi nó kết thúc, đưa ra dự đoán thành công mỗi lần.
Các trang wiki Python có thêm thông tin về làm thế nào máy ảo CPython của hoạt động.