Python เพิ่มประสิทธิภาพตัวแปรที่ใช้เป็นค่าส่งคืนหรือไม่?


106

มีความแตกต่างขั้นสูงสุดระหว่างสองข้อมูลโค้ดต่อไปนี้หรือไม่? ขั้นแรกกำหนดค่าให้กับตัวแปรในฟังก์ชันแล้วส่งกลับตัวแปรนั้น ฟังก์ชันที่สองเพียงแค่ส่งกลับค่าโดยตรง

Python เปลี่ยนให้เป็น bytecode ที่เท่ากันหรือไม่? หนึ่งในนั้นเร็วกว่าหรือไม่?

กรณีที่ 1 :

def func():
    a = 42
    return a

กรณีที่ 2 :

def func():
    return 42

5
หากคุณใช้dis.dis(..)ทั้งสองอย่างคุณจะเห็นว่ามีความแตกต่างกันดังนั้นใช่ แต่ในการใช้งานในโลกแห่งความเป็นจริงส่วนใหญ่ค่าใช้จ่ายนี้เมื่อเทียบกับความล่าช้าของการประมวลผลในฟังก์ชันนั้นไม่มากนัก
Willem Van Onsem

4
มีความเป็นไปได้สองประการ: (ก) คุณจะเรียกใช้ฟังก์ชันนี้หลาย ๆ ครั้ง (เช่นอย่างน้อยล้านครั้ง) ในกรณีนี้คุณไม่ควรเรียกใช้ฟังก์ชัน Python เลย แต่ควรทำเวกเตอร์ลูปของคุณโดยใช้บางอย่างเช่นไลบรารี numpy (b) คุณจะไม่เรียกใช้ฟังก์ชันนี้หลายครั้ง ในกรณีนี้ความแตกต่างของความเร็วระหว่างฟังก์ชันเหล่านี้น้อยเกินไปที่จะต้องกังวล
Arthur Tacca

คำตอบ:


138

ไม่มีก็ไม่ได้

การคอมไพล์เป็นโค้ดไบต์ CPython จะถูกส่งผ่านเครื่องมือเพิ่มประสิทธิภาพช่องมองภาพขนาดเล็กที่ออกแบบมาเพื่อทำการเพิ่มประสิทธิภาพพื้นฐานเท่านั้น (ดูtest_peepholer.pyในชุดทดสอบสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพเหล่านี้)

หากต้องการดูสิ่งที่จะเกิดขึ้นจริงให้ใช้dis* เพื่อดูคำแนะนำที่สร้างขึ้น สำหรับฟังก์ชันแรกที่มีการกำหนด:

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

ในขณะที่สำหรับฟังก์ชันที่สอง:

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

คำแนะนำเพิ่มเติม (เร็ว) สองคำสั่งใช้ในคำสั่งแรก: STORE_FASTและLOAD_FAST. สิ่งเหล่านี้ทำให้จัดเก็บอย่างรวดเร็วและคว้าค่าในfastlocalsอาร์เรย์ของกรอบการดำเนินการปัจจุบัน จากนั้นในทั้งสองกรณีRETURN_VALUEจะดำเนินการa ดังนั้นวินาทีนั้นเร็วกว่าเล็กน้อยเนื่องจากคำสั่งที่จำเป็นในการดำเนินการน้อยลง

โดยทั่วไปโปรดทราบว่าคอมไพเลอร์ CPython เป็นแบบอนุรักษ์นิยมในการเพิ่มประสิทธิภาพที่ดำเนินการ ไม่ใช่และไม่พยายามที่จะฉลาดเท่าคอมไพเลอร์อื่น ๆ (ซึ่งโดยทั่วไปแล้วยังมีข้อมูลอีกมากมายที่ต้องทำงานด้วย) เป้าหมายหลักในการออกแบบนอกเหนือจากความถูกต้องชัดเจนคือ a) ทำให้เรียบง่ายและ b) รวบรวมสิ่งเหล่านี้ให้รวดเร็วที่สุดเท่าที่จะเป็นไปได้เพื่อที่คุณจะไม่สังเกตเห็นว่ามีขั้นตอนการรวบรวมอยู่

ในท้ายที่สุดคุณไม่ควรเดือดร้อนกับปัญหาเล็ก ๆ เช่นนี้ ประโยชน์ของความเร็วนั้นมีขนาดเล็กคงที่และแคระแกร็นด้วยค่าใช้จ่ายที่เกิดจากการตีความ Python

* disเป็นโมดูล Python ตัวเล็ก ๆ ที่ไม่รวมโค้ดของคุณคุณสามารถใช้เพื่อดู bytecode Python ที่ VM จะดำเนินการ

หมายเหตุ:ตามที่ระบุไว้ในความคิดเห็นของ @Jorn Vernee สิ่งนี้เฉพาะสำหรับการใช้งาน CPython ของ Python การใช้งานอื่น ๆ อาจทำการเพิ่มประสิทธิภาพเชิงรุกมากขึ้นหากต้องการ CPython ก็ไม่ทำเช่นนั้น


11
ไม่ใช่คน python (c ++) ดังนั้นฉันจึงไม่รู้ว่ามันทำงานอย่างไรภายใต้ประทุน แต่กรณีแรกไม่ควรปรับให้เหมาะกับกรณีที่สองหรือไม่? คอมไพเลอร์ C ++ ที่เหมาะสมจะทำการปรับให้เหมาะสม
NathanOliver

7
@NathanOliver มันไม่ได้จริงๆ Python จะทำตามที่บอกไว้ที่นี่โดยไม่ต้องพยายามเล่นให้ฉลาด
Dimitris Fasarakis Hilliard

80
ความจริงที่ว่าการคาดเดาที่สมเหตุสมผลและชาญฉลาดอย่างสมบูรณ์แบบของ @ NathanOliver สำหรับคำตอบสำหรับคำถามนี้นั้นผิดอย่างสิ้นเชิงในสายตาของฉันการพิสูจน์ว่านี่ไม่ใช่คำถาม "อธิบายตัวเอง" "ไร้สาระ" "โง่" ที่สามารถตอบได้ โดย "ใช้เวลาคิดสักครู่" อย่างที่ TigerhawkT3 จะให้เราเชื่อ เป็นคำถามที่ถูกต้องและน่าสนใจซึ่งฉันไม่แน่ใจในคำตอบแม้ว่าจะเป็นโปรแกรมเมอร์ Python มืออาชีพมาหลายปีแล้วก็ตาม
Mark Amery

คอมไพเลอร์ของ Python เป็น 'อนุรักษ์นิยม' ที่ดีที่สุดไม่ใช่ 'อนุรักษ์นิยมมาก' เป้าหมายหลักในการออกแบบไม่ได้อยู่ที่ "เร็วที่สุดเท่าที่จะเป็นไปได้ ... ดังนั้นคุณจึงไม่ได้สังเกตว่ามีขั้นตอนการรวบรวม" รองจาก "ให้มันง่าย" ฟังก์ชันที่มีค่าคงที่ขนาดใหญ่เช่น "1 << (2 ** 34)" และ "b'x '* (2 ** 32)" ใช้เวลาหลายวินาทีในการคอมไพล์และสร้างค่าคงที่ขนาด GB แม้ว่าฟังก์ชันจะไม่ วิ่ง. สตริงขนาดใหญ่จะถูกคอมไพเลอร์ทิ้งด้วยซ้ำ การแก้ไขที่เสนอสำหรับกรณีเหล่านี้ถูกปฏิเสธเนื่องจากจะทำให้คอมไพเลอร์ซับซ้อนเกินไป
Andrew Dalke

@AndrewDalke ขอบคุณสำหรับความคิดเห็นภายในเกี่ยวกับเรื่องนี้ฉันปรับแต่งถ้อยคำเพื่อแก้ไขปัญหาที่คุณชี้ให้เห็น
Dimitris Fasarakis Hilliard

3

โดยพื้นฐานแล้วทั้งสองจะเหมือนกันยกเว้นว่าในกรณีแรกอ็อบเจ็กต์42ถูกกำหนดให้กับตัวแปรที่ชื่อaหรือกล่าวอีกนัยหนึ่งชื่อ (เช่นa) อ้างถึงค่า (เช่น42) มันไม่ได้ทำการมอบหมายใด ๆ ในทางเทคนิคในแง่ที่ว่าจะไม่คัดลอกข้อมูลใด ๆ

ในขณะที่returning การผูกชื่อนี้aจะถูกส่งกลับในกรณีแรกในขณะที่วัตถุ42ถูกส่งคืนในกรณีที่สอง

สำหรับการอ่านเพิ่มเติมโปรดดูบทความที่ยอดเยี่ยมนี้โดย Ned Batchelder

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.