Apakah Python mengoptimalkan variabel yang hanya digunakan sebagai nilai pengembalian?

106

Apakah ada perbedaan utama antara dua cuplikan kode berikut? Yang pertama memberikan nilai ke variabel dalam fungsi dan kemudian mengembalikan variabel itu. Fungsi kedua hanya mengembalikan nilai secara langsung.

Apakah Python mengubahnya menjadi bytecode yang setara? Apakah salah satunya lebih cepat?

Kasus 1 :

def func():
    a = 42
    return a

Kasus 2 :

def func():
    return 42
Jayesh
sumber
5
Jika Anda menggunakan dis.dis(..)keduanya, Anda melihat bahwa ada perbedaan , jadi ya. Tetapi di sebagian besar aplikasi dunia nyata , biaya overhead ini dibandingkan dengan penundaan pemrosesan dalam fungsi tidak terlalu banyak.
Willem Van Onsem
4
Ada dua kemungkinan: (a) Anda akan memanggil fungsi ini berkali-kali (yaitu setidaknya satu juta) kali dalam loop yang ketat. Dalam hal ini Anda seharusnya tidak memanggil fungsi Python sama sekali, tetapi sebaliknya harus memvektorisasi loop Anda menggunakan sesuatu seperti library numpy. (b) Anda tidak akan memanggil fungsi ini berkali-kali. Dalam hal ini perbedaan kecepatan antara fungsi ini terlalu kecil untuk dikhawatirkan.
Arthur Tacca

Jawaban:

138

Tidak, tidak .

Kompilasi ke kode byte CPython hanya diteruskan melalui pengoptimal lubang intip kecil yang dirancang untuk melakukan hanya pengoptimalan dasar (Lihat test_peepholer.py di rangkaian pengujian untuk mengetahui lebih lanjut tentang pengoptimalan ini).

Untuk melihat apa yang sebenarnya akan terjadi, gunakan dis* untuk melihat instruksi yang dibuat. Untuk fungsi pertama, berisi tugas:

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

Sedangkan untuk fungsi kedua:

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

Dua instruksi lagi (cepat) digunakan di yang pertama: STORE_FASTdan LOAD_FAST. Ini membuat penyimpanan cepat dan mengambil nilai dalam fastlocalslarik bingkai eksekusi saat ini. Kemudian, dalam kedua kasus tersebut, a RETURN_VALUEdilakukan. Jadi, yang kedua sedikit lebih cepat karena lebih sedikit perintah yang diperlukan untuk dieksekusi.

Secara umum, ketahuilah bahwa compiler CPython konservatif dalam pengoptimalan yang dilakukannya. Itu tidak dan tidak mencoba menjadi secerdas kompiler lain (yang, secara umum, juga memiliki lebih banyak informasi untuk dikerjakan). Tujuan desain utama, selain benar-benar benar, adalah untuk a) membuatnya tetap sederhana dan b) secepat mungkin dalam menyusunnya sehingga Anda bahkan tidak menyadari bahwa fase kompilasi ada.

Pada akhirnya, Anda tidak perlu repot dengan masalah kecil seperti ini. Manfaat dalam kecepatan kecil, konstan dan, dikerdilkan oleh overhead yang diperkenalkan oleh fakta bahwa Python ditafsirkan.

* disadalah modul Python kecil yang membongkar kode Anda, Anda dapat menggunakannya untuk melihat bytecode Python yang akan dijalankan oleh VM.

Catatan: Seperti yang juga dinyatakan dalam komentar oleh @Jorn Vernee, ini khusus untuk implementasi CPython dari Python. Implementasi lain mungkin melakukan pengoptimalan yang lebih agresif jika mereka menginginkannya, CPython tidak.

Dimitris Fasarakis Hilliard
sumber
11
Bukan orang python (c ++) jadi saya tidak tahu cara kerjanya di bawah tenda tetapi bukankah seharusnya kasus pertama dioptimalkan untuk kasus kedua? Kompiler C ++ yang layak akan melakukan pengoptimalan itu.
NathanOliver
7
@NathanOliver sebenarnya tidak, Python akan melakukan seperti yang diperintahkan di sini bahkan tanpa mencoba untuk memainkannya dengan cerdas.
Dimitris Fasarakis Hilliard
80
Fakta bahwa tebakan @ NathanOliver yang sangat masuk akal dan cerdas pada jawaban atas pertanyaan ini sepenuhnya salah adalah, bagi saya, bukti bahwa ini bukan pertanyaan yang "menjelaskan sendiri", "tidak masuk akal", "bodoh" yang dapat dijawab dengan "mengambil waktu sejenak untuk berpikir", sebagaimana TigerhawkT3 ingin kita percayai. Ini adalah pertanyaan yang valid dan menarik yang saya tidak yakin jawabannya meskipun telah menjadi programmer Python profesional selama bertahun-tahun.
Mark Amery
Kompiler Python paling-paling 'konservatif', bukan 'sangat konservatif'. Tujuan utama desain bukanlah untuk menjadi "secepat mungkin ... jadi Anda bahkan tidak menyadari bahwa fase kompilasi ada." Itu sekunder, setelah "sederhanakan". Sebuah fungsi dengan konstanta besar seperti "1 << (2 ** 34)" dan "b'x '* (2 ** 32)" membutuhkan waktu beberapa detik untuk dikompilasi, dan menghasilkan konstanta berukuran GB, meskipun fungsinya tidak pernah Lari. String besar bahkan akan dibuang oleh kompilator. Perbaikan yang diusulkan untuk kasus-kasus ini telah ditolak karena akan membuat kompilator menjadi terlalu kompleks.
Andrew Dalke
@AndrewDalke terima kasih atas komentar orang dalam tentang ini, saya menyesuaikan kata-katanya untuk mengatasi masalah yang Anda tunjukkan.
Dimitris Fasarakis Hilliard
3

Keduanya pada dasarnya sama kecuali dalam kasus pertama objek 42hanya ditugaskan ke variabel bernama aatau, dengan kata lain, nama (yaitu a) merujuk ke nilai (yaitu 42). Itu tidak melakukan tugas apa pun secara teknis, dalam arti tidak pernah menyalin data apa pun.

Saat returning, pengikatan bernama aini dikembalikan dalam kasus pertama sedangkan objek 42dikembalikan dalam kasus kedua.

Untuk bacaan lebih lanjut, lihat artikel hebat ini oleh Ned Batchelder

kmario23
sumber