26. Tối ưu hóa mã byte
26. Tối ưu hóa mã byte
Tối ưu hóa mã byte là giai đoạn dọn dẹp và sàng lọc cuối cùng trước khi đối tượng mã sẵn sàng thực thi.
Các giai đoạn biên dịch trước đó quyết định ý nghĩa của chương trình và đưa ra hướng dẫn. Tối ưu hóa cố gắng làm cho các hướng dẫn đó nhỏ hơn, đơn giản hơn hoặc nhanh hơn trong khi vẫn duy trì ngữ nghĩa Python.
Ví dụ:python id="u88pli" x = 1 + 2 có thể biên dịch như thể được viết:```python id="k8v2ef"
x = 3
Nhưng điều này:```python id="lbwhpl"
x = a + b
```không thể gấp lại được vì`a`Và`b`là các giá trị thời gian chạy. Kiểu của chúng có thể xác định hành vi tùy ý cho`+`.
## 26.1 Vị trí trong Đường ống biên dịch
Tối ưu hóa xảy ra sau hoặc trong quá trình tạo mã byte, tùy thuộc vào loại tối ưu hóa.```text id="olp5um"
source
↓
tokenization
↓
parsing
↓
AST
↓
symbol table
↓
instruction generation
↓
optimization
↓
assembly
↓
code object
```Một số tối ưu hóa xảy ra trên AST. Một số xảy ra trên biểu đồ luồng điều khiển hoặc luồng lệnh. Một số xảy ra sau này trong thời gian chạy thông qua chuyên môn hóa.
Đừng coi “tối ưu hóa” chỉ là một bước duy nhất. CPython sử dụng một số cơ chế ở các lớp khác nhau.
## 26.2 Tối ưu hóa phải bảo toàn ngữ nghĩa
Python rất năng động.
Điều này giới hạn những gì CPython có thể tối ưu hóa một cách an toàn.
Ví dụ:```python id="wozly6"
x = 1 + 2
```An toàn để gấp.
Ví dụ:```python id="i4bt05"
x = "a" + "b"
```An toàn để gấp.
Ví dụ:```python id="ue1h3y"
x = a + b
```Không an toàn để gấp lại.
Hoạt động có thể gọi:```python id="fk1geq"
a.__add__(b)
```hoặc:```python id="h80anr"
b.__radd__(a)
```Những phương thức đó có thể chạy mã Python tùy ý.
Quy tắc tối ưu hóa:```text id="o2q4tx"
Only remove or precompute work when the observable behavior remains the same.
```Hành vi có thể quan sát được bao gồm:```text id="g5n65q"
return values
exceptions
side effects
evaluation order
name lookup behavior
attribute lookup behavior
object identity when relevant
traceback positions
debugging behavior where specified
```## 26.3 Gấp liên tục
Việc gấp hằng số tính toán trước các biểu thức chỉ được tạo từ các hằng số.
Ví dụ:```python id="wpyjbl"
1 + 2
2 * 3
"py" + "thon"
(1, 2) + (3, 4)
```Đây có thể trở thành hằng số.
Ví dụ:```python id="8yvbyh"
def f():
return 1 + 2
```Mã byte có thể có:```text id="gjktcx"
LOAD_CONST 3
RETURN_VALUE
```còn hơn là:```text id="rs6njs"
LOAD_CONST 1
LOAD_CONST 2
BINARY_OP +
RETURN_VALUE
```Trình biên dịch lưu trữ giá trị gấp trong`co_consts`.
## 26.4 Gấp bảo thủ
CPython tránh các biểu thức gấp có thể quá lớn hoặc quá đắt tại thời điểm biên dịch.
Ví dụ:```python id="lwt9ja"
x = "a" * 10
```có thể được gấp lại.
Nhưng sự lặp lại quá lớn sẽ không tạo ra một hằng số lớn trong quá trình biên dịch.```python id="k9mjrp"
x = "a" * 10_000_000_000
```Một trình biên dịch thực hiện việc này một cách mù quáng sẽ tiêu tốn bộ nhớ rất lớn trước khi chương trình bắt đầu chạy.
Tối ưu hóa phải được giới hạn.
Trình tối ưu hóa của CPython rất thiết thực. Nó tránh biến việc biên dịch thành một cuộc tấn công tài nguyên không mong muốn.
## 26.5 Gấp Tuple và Container
Các bộ chỉ chứa hằng số có thể được gấp lại.
Ví dụ:```python id="i0us1v"
x = (1, 2, 3)
```có thể tải một hằng số tuple.
Nhưng danh sách và tập hợp có thể thay đổi được nên chúng thường không thể trở thành hằng số.```python id="g3apua"
x = [1, 2, 3]
```phải tạo một danh sách mới khi chạy.
Tại sao?
Mỗi lần thực thi cần một đối tượng có thể thay đổi mới.
Hàm này phải trả về một đối tượng danh sách khác nhau cho mỗi lệnh gọi:```python id="ku6rt2"
def f():
return [1, 2, 3]
```Nếu trình biên dịch lưu trữ một hằng số danh sách chia sẻ, đột biến sẽ rò rỉ qua các lệnh gọi.
## 26.6 Tối ưu hóa kiểm tra tư cách thành viên
Đối với các bài kiểm tra tư cách thành viên, các hằng số có thể thay đổi đôi khi có thể được thay thế bằng các hằng số bất biến.
Ví dụ:```python id="yku3da"
def is_small(x):
return x in {1, 2, 3}
```Chữ đã đặt chỉ được sử dụng để kiểm tra tư cách thành viên. CPython có thể thay thế nó bằng`frozenset`không thay đổi.
Về mặt khái niệm:```text id="nibldo"
LOAD_FAST x
LOAD_CONST frozenset({1, 2, 3})
CONTAINS_OP
RETURN_VALUE
```Điều này tránh việc xây dựng lại tập hợp mỗi khi hàm chạy.
Việc tối ưu hóa là hợp lệ vì bản thân đối tượng đã đặt không được hiển thị với mã người dùng.
## 26.7 Mã chết sau khi thoát vô điều kiện
Một số mã sẽ không thể truy cập được sau khi thoát vô điều kiện.
Ví dụ:```python id="oss0kd"
def f():
return 1
x = 2
```Nhiệm vụ không thể thực hiện được.
Trình biên dịch có thể loại bỏ hoặc bỏ qua các chuỗi lệnh không thể truy cập được.
Lối thoát vô điều kiện bao gồm:```text id="u7yxa5"
return
raise
break
continue
```trong bối cảnh luồng điều khiển phù hợp.
Trình biên dịch vẫn phải lưu giữ thông tin dòng và chẩn đoán khi cần thiết. Tối ưu hóa không thể tự do xóa mọi thứ nếu nó thay đổi hành vi gỡ lỗi hoặc liên quan đến cú pháp theo những cách dễ thấy.
## 26.8 Tối ưu hóa bước nhảy
Luồng điều khiển thường tạo ra các bước nhảy.
Hình dạng ví dụ:```text id="941xx7"
JUMP_FORWARD label_a
label_a:
JUMP_FORWARD label_b
label_b:
...
```Trình tối ưu hóa có thể chuyển hướng bước nhảy đầu tiên trực tiếp đến`label_b`.
```text id="np02i8"
JUMP_FORWARD label_b
label_b:
...
```Điều này làm giảm số lượng lệnh và tránh việc gửi đi không cần thiết.
Tối ưu hóa bước nhảy thường an toàn vì nó duy trì cùng một đích của luồng điều khiển.
## 26.9 Dọn dẹp khối cơ bản
Trình biên dịch xây dựng hoặc sử dụng các khối cơ bản bên trong.
Khối cơ bản là một chuỗi lệnh thẳng có một lối vào và một lối ra.
Tối ưu hóa có thể loại bỏ các khối trống, hợp nhất các khối liền kề hoặc đơn giản hóa các cạnh.
Ví dụ:```text id="o49sgv"
block A:
jump block B
block B:
jump block C
block C:
useful work
```có thể trở thành:```text id="ke9fl3"
block A:
jump block C
block C:
useful work
```Việc dọn dẹp này diễn ra trước khi lắp ráp lần cuối, khi các nhãn vẫn còn mang tính biểu tượng.
## 26.10 Bảo toàn hiệu ứng ngăn xếp
Mọi tối ưu hóa phải duy trì tính chính xác của ngăn xếp.
Ví dụ:```text id="3fooh0"
LOAD_CONST 1
POP_TOP
```Nếu hằng số được nạp không có tác dụng phụ và giá trị của nó bị loại bỏ thì cặp này có thể bị loại bỏ.
Nhưng điều này:```text id="s5gd6m"
LOAD_NAME x
POP_TOP
```nói chung là không thể gỡ bỏ được.
Đang tải`x`có thể nâng cao`NameError`.
Tương tự:```text id="hng0do"
LOAD_ATTR attr
POP_TOP
```có thể gọi tra cứu thuộc tính động và đưa ra các ngoại lệ.
Trình tối ưu hóa phải biết thao tác nào an toàn để loại bỏ.
## 26.11 Ràng buộc thứ tự đánh giá
Thứ tự đánh giá Python có thể quan sát được.
Ví dụ:```python id="bp4w5f"
f() + g()
f()phải chạy trướcg().
Cho dù kết quả củaf()dường như không được sử dụng ở một số dạng chuyển đổi, cuộc gọi không thể bỏ qua hoặc sắp xếp lại.
Ví dụ:```python id="7e0ozh" items[key()] = value()
Tối ưu hóa không thể xử lý các biểu thức Python như các biểu thức toán học thuần túy.
## 26.12 Ràng buộc tra cứu tên
Tra cứu tên là động.
Ví dụ:```python id="pipmww"
len([1, 2, 3])
```CPython thường không thể thay thế điều này bằng`3`.
Tại sao?
Ở cấp độ mô-đun,`len`có thể hồi phục:```python id="sksxhh"
len = lambda x: 999
```Bên trong một hàm,`len`có thể giải quyết thông qua toàn cầu trước nội trang.
Trình biên dịch không cho rằng nội dung`len`không thay đổi.
Đây là điểm khác biệt chính giữa tối ưu hóa Python và tối ưu hóa bằng các ngôn ngữ được liên kết tĩnh.
## 26.13 Ràng buộc tra cứu thuộc tính
Tra cứu thuộc tính là động.
Ví dụ:```python id="o09etj"
obj.x
```Điều này có thể gọi:```text id="jvlw4h"
__getattribute__
__getattr__
descriptors
properties
module-level __getattr__
custom metaclass logic
```Trình biên dịch không thể thay thế quyền truy cập thuộc tính lặp lại bằng một giá trị được lưu trong bộ nhớ cache trừ khi nó có thể chứng minh rằng hành vi đó không thay đổi.
Chuyên môn hóa thời gian chạy có thể tối ưu hóa các trường hợp phổ biến theo cách suy đoán, nhưng điều đó được bảo vệ bằng việc kiểm tra thời gian chạy. Tối ưu hóa thời gian biên dịch vẫn còn thận trọng.
## 26.14`assert`Tối ưu hóa`assert`các câu lệnh bị ảnh hưởng bởi chế độ tối ưu hóa.
Ví dụ:```python id="ixz6ew"
assert x > 0, "x must be positive"
```Thông thường biên dịch đại khái là:```text id="96ihbr"
if not (x > 0):
raise AssertionError("x must be positive")
```Khi Python chạy với tính năng tối ưu hóa được bật, chẳng hạn như`python -O`, các câu lệnh khẳng định sẽ bị xóa.
Điều này thay đổi hành vi có chủ ý. Nó là một phần của chế độ tối ưu hóa tài liệu của Python.
Quy tắc thực tế:```text id="w3wpdm"
Do not use assert for required runtime validation.
```Sử dụng các ngoại lệ rõ ràng cho các kiểm tra phải luôn chạy.
## 26,15`__debug__`Hằng số đặc biệt`__debug__`được gắn với chế độ tối ưu hóa.
Thông thường:```python id="erm4nr"
__debug__ == True
```Dưới`python -O`:
```python id="my7j8j"
__debug__ == False
```Trình biên dịch có thể tối ưu hóa các nhánh tùy thuộc vào`__debug__`.
Ví dụ:```python id="vd8gwm"
if __debug__:
check()
```Trong chế độ tối ưu hóa, nhánh này có thể được bỏ qua.`__debug__`là đặc biệt. Tên thông thường không nhận được sự đối xử này.
## 26.16 Xử lý chuỗi tài liệu
Mức độ tối ưu hóa có thể ảnh hưởng đến chuỗi tài liệu.
Ví dụ:```python id="prb94t"
def f():
"""Function docs."""
return 1
```Thông thường, chuỗi ký tự đầu tiên trong thân hàm sẽ trở thành chuỗi doc của hàm.
Với sự tối ưu hóa cao hơn, chẳng hạn như`python -OO`, các chuỗi tài liệu có thể bị xóa.
Điều này ảnh hưởng đến:```python id="abqsw1"
f.__doc__
```Trình biên dịch và thời gian chạy xử lý các chuỗi tài liệu một cách đặc biệt vì chúng được lưu trữ dưới dạng siêu dữ liệu đối tượng thay vì được thực thi như các câu lệnh biểu thức thông thường.
## 26.17 Tối ưu hóa kiểu nhìn trộm
Các mô tả cũ hơn về CPython thường đề cập đến trình tối ưu hóa lỗ nhìn trộm.
Trình tối ưu hóa lỗ nhìn trộm xem xét một cửa sổ hướng dẫn nhỏ và thay thế các mẫu không hiệu quả.
Ý tưởng ví dụ:```text id="gqvx1f"
LOAD_CONST 1
LOAD_CONST 2
BUILD_TUPLE 2
```có thể trở thành:```text id="ipc5mv"
LOAD_CONST (1, 2)
```CPython hiện đại đã chuyển nhiều tối ưu hóa sang AST và các giai đoạn hướng dẫn/luồng điều khiển thay vì chỉ dựa vào một lỗ nhìn trộm đơn giản.
Khái niệm này vẫn hữu ích: một số tối ưu hóa được viết lại cục bộ trên một mẫu lệnh nhỏ.
## 26.18 Tối ưu hóa cấp độ AST
Một số tối ưu hóa dễ dàng hơn trước khi tồn tại mã byte.
Ví dụ:```python id="qsilq6"
x = 1 + 2
```AST chứa:```text id="nvue0h"
BinOp(Constant(1), Add, Constant(2))
```Trình tối ưu hóa AST có thể thay thế nút đó bằng:```text id="iz4wys"
Constant(3)
```Sau đó, việc tạo mã byte chỉ cần tải`3`.
Tối ưu hóa cấp AST cho thấy cấu trúc cấp cao, có thể dễ dàng hơn việc tái tạo lại ý nghĩa từ mã byte.
## 26.19 Tối ưu hóa cấp độ hướng dẫn
Các tối ưu hóa khác sẽ dễ dàng hơn sau khi tạo lệnh.
Ví dụ:```text id="qz307a"
remove unreachable blocks
redirect jumps
merge blocks
compute final stack sizes
shorten control flow
```Tối ưu hóa cấp độ hướng dẫn cho thấy hiệu ứng ngăn xếp và luồng điều khiển thực tế.
Nó gần với bố cục mã byte cuối cùng hơn.
## 26.20 Chuyên môn hóa thời gian chạy riêng biệt
CPython hiện đại cũng có chuyên môn về thời gian chạy.
Điều này không giống như tối ưu hóa thời gian biên dịch.
Trình biên dịch phát ra mã byte chung. Trong quá trình thực thi, CPython có thể chuyên biệt hóa các hướng dẫn được sử dụng thường xuyên.
Các thao tác ví dụ:```text id="4c4nxd"
LOAD_ATTR
LOAD_GLOBAL
BINARY_OP
CALL
STORE_ATTR
```Quyền truy cập thuộc tính chung có thể trở thành đường dẫn chuyên biệt nhanh hơn sau khi CPython quan sát thấy hành vi thời gian chạy ổn định.
Tối ưu hóa thời gian biên dịch:```text id="p7xt6t"
happens before execution
must be safe without runtime knowledge
```Chuyên môn về thời gian chạy:```text id="qo5hr8"
happens during execution
uses observed runtime types and cache guards
can fall back if assumptions fail
```Sự tách biệt này cho phép CPython tối ưu hóa mã động mà không đưa ra các giả định về thời gian biên dịch không an toàn.
## 26.21 Bộ nhớ đệm nội tuyến
Chuyên môn hóa thời gian chạy sử dụng bộ nhớ đệm nội tuyến gần các hướng dẫn mã byte.
Về mặt khái niệm:```text id="yc3mav"
LOAD_ATTR name
CACHE
CACHE
```Bộ đệm lưu trữ thông tin thời gian chạy như:```text id="ymdvl9"
observed type
dictionary version
descriptor information
resolved offset
specialized handler state
```Trình biên dịch chuẩn bị không gian cho bộ đệm. Trình thông dịch sẽ điền và sử dụng chúng sau này.
Đây là lý do tại sao quá trình tháo gỡ có thể hiển thị các mục trong bộ đệm khi được yêu cầu.
## 26.22 Tối ưu hóa và truy nguyên
Tối ưu hóa phải bảo toàn các vị trí nguồn hữu ích.
Ví dụ:```python id="ett31m"
x = 1 / 0
```Ngay cả khi mã xung quanh được tối ưu hóa,`ZeroDivisionError`nên trỏ đến một vị trí nguồn có ý nghĩa.
Truy nguyên dựa trên ánh xạ mã byte đến nguồn.
Trình tối ưu hóa phải cập nhật hoặc bảo tồn:```text id="wtwgmh"
line information
column information
exception table ranges
instruction offsets
```Siêu dữ liệu bị hỏng tạo ra hành vi gỡ lỗi kém.
## 26.23 Tối ưu hóa và ngoại lệ
Một số biểu thức trông đơn giản nhưng có thể gây ra ngoại lệ.
Ví dụ:```python id="k3r2bn"
1 / 0
```Trình biên dịch không nên biến điều này thành một ngoại lệ tại thời điểm biên dịch trong quá trình biên dịch thông thường.
Ngoại lệ phải xảy ra khi mã thực thi chứ không phải khi mã được biên dịch.
Ví dụ:```python id="9o0rsd"
def f():
return 1 / 0
```Xác định`f`nên thành công. Đang gọi`f()`nên tăng`ZeroDivisionError`.
Do đó, việc gấp liên tục phải tránh các phép biến đổi chuyển các ngoại lệ từ thời gian chạy sang thời gian biên dịch.
## 26.24 Tối ưu hóa và nhận dạng đối tượng
Nhận dạng đối tượng có thể được quan sát thông qua`is`.
Ví dụ:```python id="q46djy"
x is y
```Trình biên dịch phải tránh các phép biến đổi làm thay đổi hành vi nhạy cảm với danh tính.
Đối với các hằng số bất biến, CPython có thể sử dụng lại các đối tượng, chuỗi thực tập hoặc gấp các hằng số theo những cách được hành vi triển khai cho phép. Nhưng mã không nên dựa vào tất cả các chi tiết nhận dạng cố định.
Ví dụ:```python id="7izgy0"
(1, 2) is (1, 2)
```Kết quả có thể phụ thuộc vào các lựa chọn biên dịch và triển khai.
Tối ưu hóa phải duy trì ngữ nghĩa ngôn ngữ, trong khi một số hành vi nhận dạng cho các hằng số vẫn được triển khai cụ thể.
## 26.25 Tối ưu hóa và mặc định có thể thay đổi
Các giá trị mặc định của hàm được đánh giá tại thời điểm định nghĩa hàm.
Ví dụ:```python id="er8lwd"
def f(xs=[]):
xs.append(1)
return xs
```Mặc định danh sách là một đối tượng thời gian chạy được lưu trữ trên đối tượng hàm.
Trình biên dịch không thể thay thế danh sách này bằng danh sách mới cho mỗi cuộc gọi vì điều đó sẽ thay đổi ngữ nghĩa.
Đây không phải là lỗi tối ưu hóa. Đó là hành vi định nghĩa hàm của Python.
Tối ưu hóa phải tôn trọng khi các đối tượng được tạo.
## 26.26 Tối ưu hóa các hằng số có điều kiện
Điều kiện không đổi có thể đơn giản hóa luồng điều khiển.
Ví dụ:```python id="epgm4i"
if False:
x = 1
else:
x = 2
```Trình biên dịch có thể bỏ qua nhánh không thể truy cập được.
Tương tự:```python id="c85x72"
while False:
work()
```Cơ thể không thể thực hiện được.
Nhưng trình tối ưu hóa phải cẩn thận với siêu dữ liệu nguồn và với các cấu trúc ảnh hưởng đến phạm vi hoặc cú pháp.
Ví dụ:```python id="xt3ful"
if False:
import never_loaded
```Quá trình nhập không được thực thi nhưng sự hiện diện của các phép gán trong các khối không thể truy cập vẫn có thể ảnh hưởng đến việc phân loại biến cục bộ vì quá trình phân tích bảng ký hiệu diễn ra trước khi tối ưu hóa.
## 26.27 Phạm vi được quyết định trước khi tối ưu hóa
Phân tích bảng biểu tượng sẽ nhìn thấy toàn bộ AST trước khi tối ưu hóa loại bỏ mã không thể truy cập được.
Ví dụ:```python id="udvaey"
x = 1
def f():
if False:
x = 2
return x
```Điều này vẫn có thể làm`x`địa phương đến`f`, gây ra một hành vi cục bộ không bị ràng buộc khi`return x`thực thi.
Trình tối ưu hóa không thể đơn giản xóa bài tập và sau đó phân loại lại`x`mang tính toàn cục trừ khi toàn bộ mô hình biên dịch cho phép thay đổi ngữ nghĩa như vậy.
Phân loại phạm vi là một phần của ngữ nghĩa Python và xảy ra trước nhiều lần tối ưu hóa.
## 26.28 Mức độ tối ưu hóa
CPython hỗ trợ mức độ tối ưu hóa.
Các chế độ phổ biến:
| Chế độ | Ý nghĩa |
| ------ | ------------------------------------------------------------------- |
| bình thường | Biên dịch mặc định |
|`-O`| Xóa các câu lệnh khẳng định, đặt`__debug__`ĐẾN`False` |
| `-OO`| Đồng thời xóa tài liệu |
Ở cấp độ Python:```python id="nqc269"
import sys
print(sys.flags.optimize)
```API biên dịch cũng có thể nhận được cài đặt tối ưu hóa.
Ví dụ:```python id="ea0hid"
compile("assert False", "<input>", "exec", optimize=1)
```Mức độ tối ưu hóa là một phần của hành vi biên dịch.
## 26,29 Giới hạn tối ưu hóa thời gian biên dịch
CPython cố tình tránh tối ưu hóa thời gian biên dịch mạnh mẽ.
Nó thường không thực hiện:```text id="g0a8os"
function inlining
global constant propagation
type-specialized compilation
loop unrolling
escape analysis
whole-program optimization
automatic vectorization
```Lý do:```text id="kgi5lw"
dynamic name lookup
dynamic attribute access
monkey patching
import-time side effects
debuggability
compile-time cost
implementation simplicity
semantic risk
```CPython thay vào đó dựa vào:```text id="nam80y"
simple safe compile-time optimizations
specialized C implementations of built-in types
runtime adaptive specialization
extension modules
external JIT implementations in other runtimes
```## 26.30 Khu vực nguồn CPython quan trọng
Các tệp nguồn quan trọng bao gồm:```text id="9n21ql"
Python/ast_opt.c
Python/compile.c
Python/flowgraph.c
Python/assemble.c
Python/bytecodes.c
Lib/dis.py
Lib/test/test_peepholer.py
Lib/test/test_compile.py
```Vai trò khái niệm:
| Khu vực | Vai trò |
| ------------- | -------------------------------------- |
|`ast_opt.c`| Tối ưu hóa cấp độ AST |
|`compile.c`| Tạo AST theo hướng dẫn |
|`flowgraph.c`| Công việc biểu đồ luồng điều khiển |
|`assemble.c`| Lắp ráp mã cuối cùng |
|`bytecodes.c`| Định nghĩa Opcode và hỗ trợ thời gian chạy |
|`dis.py`| Kiểm tra mã byte |
| kiểm tra | Bảo vệ hành vi tối ưu hóa |
## 26.31 Mô hình tinh thần tối thiểu
Sử dụng mô hình này:```text id="dz2gpc"
CPython optimization is conservative.
It folds safe constants.
It simplifies some containers and membership tests.
It removes or redirects some unreachable or redundant control flow.
It respects evaluation order, exceptions, dynamic lookup, scope rules, and source positions.
Compile-time optimization differs from runtime specialization.
Runtime specialization can use observed types and guarded inline caches.
```Tối ưu hóa mã byte cải thiện mã được tạo trong khi vẫn nằm trong ngữ nghĩa động của Python.