Để trả lời câu hỏi của bạn, chúng tôi có thể kiểm tra bytecode được tạo ra bởi hai chức năng sử dụng dis
mô-đun:
In [5]: def no_unpacking():
...: s = []
...: for item in SEQUENCE:
...: s.append(item[0] + item[1])
...: return s
...:
...:
...: def unpacking():
...: s = []
...: for a,b in SEQUENCE:
...: s.append(a+b)
...: return s
tôi đã mở rộng danh sách-hiểu vì trong python3 nó sẽ là cồng kềnh hơn để kiểm tra thú vị bytecodes. Mã tương đương nên nó không thực sự quan trọng cho mục đích của chúng tôi.
Các bytecode cho chức năng đầu tiên là:
In [6]: dis.dis(no_unpacking)
2 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
3 6 SETUP_LOOP 39 (to 48)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 31 (to 47)
16 STORE_FAST 1 (item)
4 19 LOAD_FAST 0 (s)
22 LOAD_ATTR 1 (append)
25 LOAD_FAST 1 (item)
28 LOAD_CONST 1 (0)
31 BINARY_SUBSCR
32 LOAD_FAST 1 (item)
35 LOAD_CONST 2 (1)
38 BINARY_SUBSCR
39 BINARY_ADD
40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
43 POP_TOP
44 JUMP_ABSOLUTE 13
>> 47 POP_BLOCK
5 >> 48 LOAD_FAST 0 (s)
51 RETURN_VALUE
Lưu ý rằng vòng lặp phải gọi BINARY_SUBSCR
hai lần để truy cập vào hai yếu tố của tuple.
Các bytecode cho chức năng thứ hai là:
In [7]: dis.dis(unpacking)
9 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
10 6 SETUP_LOOP 37 (to 46)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 29 (to 45)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (a)
22 STORE_FAST 2 (b)
11 25 LOAD_FAST 0 (s)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (a)
34 LOAD_FAST 2 (b)
37 BINARY_ADD
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 POP_TOP
42 JUMP_ABSOLUTE 13
>> 45 POP_BLOCK
12 >> 46 LOAD_FAST 0 (s)
49 RETURN_VALUE
Note thế nào không có BINARY_SUBSCR
để thực hiện.
Vì vậy, nó có vẻ như UNPACK_SEQUENCE
cộng với một STORE_FAST
(đó là các hoạt động bổ sung thêm bằng cách giải nén) được nhanh sau đó làm hai BINARY_SUBSCR
. Điều này là hợp lý vì BINARY_SUBSCR
là cuộc gọi phương thức đầy đủ, trong khi UNPACK_SEQUENCE
và STORE_FAST
là các thao tác đơn giản hơn.
Bạn có thể thấy sự khác biệt ngay cả trong trường hợp đơn giản:
In [1]: def iter_with_index(s):
...: for i in range(len(s)):
...: s[i]
...:
In [2]: def iter_without_index(s):
...: for el in s:el
...:
In [3]: %%timeit s = 'a' * 10000
...: iter_with_index(s)
...:
1000 loops, best of 3: 583 us per loop
In [4]: %%timeit s = 'a' * 10000
...: iter_without_index(s)
...:
1000 loops, best of 3: 206 us per loop
Như bạn thấy iterating trên một chuỗi là chậm hơn khoảng 3 lần sử dụng chỉ mục rõ ràng. Đó là tất cả chi phí do cuộc gọi đến BINARY_SUBSCR
.
Về câu hỏi thứ hai của bạn: pypy có JIT có khả năng phân tích mã và tạo phiên bản được tối ưu hóa để tránh chi phí hoạt động lập chỉ mục. Khi nó nhận ra rằng đăng ký được thực hiện trên bộ dữ liệu, nó có thể tạo mã không gọi phương thức tuple nhưng truy cập trực tiếp vào các phần tử, do đó loại bỏ hoàn toàn các hoạt động BINARY_SUBSCR
.
Giải thích tuyệt vời, tốt để biết rằng tôi không quan tâm vì không có lý do gì. – alecxe
Câu trả lời hay. Rất kỹ lưỡng. – fr1tz
Giải thích thực sự thú vị. Tuy nhiên, chỉ bằng cách thay đổi 'dải ô' thành' xrange', tốc độ trên sổ ghi chép của tôi đang thay đổi từ 4.0 xuống còn 3.43 –