Python có tối ưu hóa một biến chỉ được sử dụng làm giá trị trả về không?


106

Có sự khác biệt cuối cùng nào giữa hai đoạn mã sau không? Đầu tiên gán giá trị cho một biến trong một hàm và sau đó trả về biến đó. Hàm thứ hai chỉ trả về giá trị trực tiếp.

Python có biến chúng thành mã bytecode tương đương không? Một trong số chúng có nhanh hơn không?

Trường hợp 1 :

def func():
    a = 42
    return a

Trường hợp 2 :

def func():
    return 42

5
Nếu bạn sử dụng dis.dis(..)trên cả hai, bạn thấy rằng có sự khác biệt , vì vậy có. Nhưng trong hầu hết các ứng dụng thế giới thực , chi phí này so với độ trễ của quá trình xử lý trong chức năng không nhiều lắm.
Willem Van Onsem

4
Có hai khả năng: (a) Bạn sẽ gọi hàm này nhiều (tức là ít nhất một triệu) lần trong một vòng lặp chặt chẽ. Trong trường hợp đó, bạn không nên gọi một hàm Python nào cả, mà thay vào đó nên vectơ hóa vòng lặp của bạn bằng cách sử dụng thứ gì đó như thư viện numpy. (b) Bạn sẽ không gọi hàm này nhiều lần. Trong trường hợp đó, sự khác biệt về tốc độ giữa các chức năng này là quá ít để đáng lo ngại.
Arthur Tacca

Câu trả lời:


138

Không, nó không .

Quá trình biên dịch thành mã byte CPython chỉ được chuyển qua một trình tối ưu hóa lỗ nhỏ được thiết kế để chỉ thực hiện các tối ưu hóa cơ bản (Xem test_peepholer.py trong bộ thử nghiệm để biết thêm về các tối ưu hóa này).

Để xem những gì thực sự sẽ xảy ra, hãy sử dụng dis* để xem các hướng dẫn được tạo. Đối với hàm đầu tiên, chứa phép gán:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Trong khi, đối với chức năng thứ hai:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

Hai lệnh nữa (nhanh) được sử dụng trong lệnh đầu tiên: STORE_FASTLOAD_FAST. Chúng giúp lưu trữ nhanh và lấy giá trị trong fastlocalsmảng của khung thực thi hiện tại. Sau đó, trong cả hai trường hợp, a RETURN_VALUEđược thực hiện. Vì vậy, thứ hai nhanh hơn một chút do ít lệnh cần thực thi hơn.

Nói chung, hãy lưu ý rằng trình biên dịch CPython thận trọng trong việc tối ưu hóa nó thực hiện. Nó không và không cố gắng trở nên thông minh như các trình biên dịch khác (nói chung, cũng có nhiều thông tin hơn để làm việc). Mục tiêu thiết kế chính, ngoài việc rõ ràng là chính xác, là a) giữ cho nó đơn giản và b) càng nhanh càng tốt trong việc biên dịch chúng để bạn thậm chí không nhận thấy rằng một giai đoạn biên dịch đang tồn tại.

Cuối cùng, bạn không nên tự rắc rối với những vấn đề nhỏ như thế này. Lợi ích về tốc độ là rất nhỏ, không đổi và bị hạn chế bởi chi phí được giới thiệu bởi thực tế là Python được thông dịch.

* dislà một mô-đun Python nhỏ giúp giải mã của bạn, bạn có thể sử dụng nó để xem mã bytecode Python mà máy ảo sẽ thực thi.

Lưu ý: Như cũng đã nêu trong nhận xét của @Jorn Vernee, điều này dành riêng cho việc triển khai CPython của Python. Các triển khai khác có thể thực hiện tối ưu hóa tích cực hơn nếu họ muốn, CPython thì không.


11
Không phải là một người python (c ++) nên tôi không biết nó hoạt động như thế nào nhưng không phải trường hợp đầu tiên được tối ưu hóa cho trường hợp thứ hai? Một trình biên dịch C ++ tốt sẽ thực hiện tối ưu hóa đó.
NathanOliver 13/04/17

7
@NathanOliver nó thực sự không, Python sẽ làm như đã nói ở đây mà không cần cố gắng chơi nó một cách thông minh.
Dimitris Fasarakis Hilliard

80
Thực tế là suy đoán hoàn toàn hợp lý và thông minh của @ NathanOliver đối với câu trả lời cho câu hỏi này là hoàn toàn sai, đối với tôi, bằng chứng rằng đây không phải là một câu hỏi "tự giải thích", "vô nghĩa", "ngu ngốc" có thể trả lời được bằng cách "dành một chút thời gian để suy nghĩ", như TigerhawkT3 sẽ khiến chúng tôi tin tưởng. Đó là một câu hỏi hợp lệ, thú vị mà tôi không chắc chắn về câu trả lời mặc dù đã là một lập trình viên Python chuyên nghiệp trong nhiều năm.
Mark Amery

Trình biên dịch của Python tốt nhất là 'bảo thủ', không phải 'rất bảo thủ'. Mục tiêu thiết kế chính không phải là "càng nhanh càng tốt ... để bạn thậm chí không nhận thấy rằng giai đoạn biên dịch đang tồn tại." Đó là thứ yếu, sau khi "giữ nó đơn giản". Một hàm có các hằng số lớn như "1 << (2 ** 34)" và "b'x '* (2 ** 32)" mất vài giây để biên dịch và tạo các hằng số có kích thước GB, ngay cả khi hàm không bao giờ chạy. Chuỗi lớn thậm chí sẽ bị trình biên dịch loại bỏ. Các bản sửa lỗi được đề xuất cho những trường hợp này đã bị từ chối vì chúng sẽ làm cho trình biên dịch quá phức tạp.
Andrew Dalke

@AndrewDalke cảm ơn những người trong cuộc đã nhận xét về điều này, tôi đã chỉnh sửa từ ngữ để giải quyết các vấn đề bạn đã chỉ ra.
Dimitris Fasarakis Hilliard

3

Về cơ bản, cả hai đều giống nhau ngoại trừ trường hợp đầu tiên, đối tượng 42chỉ đơn giản là được gán cho một biến có tên ahoặc nói cách khác, tên (tức là a) tham chiếu đến các giá trị (tức là 42). Nó không thực hiện bất kỳ nhiệm vụ nào về mặt kỹ thuật, theo nghĩa là nó không bao giờ sao chép bất kỳ dữ liệu nào.

Trong khi returning, ràng buộc có tên anày được trả về trong trường hợp đầu tiên trong khi đối tượng 42được trả về trong trường hợp thứ hai.

Để đọc thêm, hãy tham khảo bài viết tuyệt vời này của Ned Batchelder

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.